Skip to main content

rapidgzip_core/indexed/
mod.rs

1//! Random access to decompressed data through a [`DeflateIndex`].
2
3mod window;
4
5use crate::crc32::Crc32;
6use crate::gzip::{SourceCursor, parse_member_header};
7use crate::index::{Checkpoint, CheckpointKind, DeflateIndex, IndexError, IndexKind, WINDOW_SIZE};
8use crate::inflate::RawInflater;
9use crate::zlib::{self, Adler32};
10use crate::{DecodeError, ReadAt};
11use libz_rs_sys as z;
12use std::error::Error;
13use std::fmt::{self, Display, Formatter};
14use std::io::{self, Read, Seek, SeekFrom};
15use std::sync::Arc;
16use window::{DEFAULT_BUDGET, WindowCache};
17
18const INPUT_PAGE: usize = 128 * 1024;
19const OUTPUT_STEP: usize = 128 * 1024;
20
21/// Failure while opening an [`IndexedReader`].
22#[derive(Clone, Debug)]
23#[non_exhaustive]
24pub enum IndexedReaderError {
25    /// The supplied index is internally invalid.
26    Index(IndexError),
27    /// The raw inflate backend could not be initialized.
28    Decode(DecodeError),
29    /// The positional source could not be inspected.
30    Io(Arc<io::Error>),
31}
32
33impl Display for IndexedReaderError {
34    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
35        match self {
36            Self::Index(error) => write!(formatter, "invalid DEFLATE index: {error}"),
37            Self::Decode(error) => {
38                write!(formatter, "could not initialize indexed decode: {error}")
39            }
40            Self::Io(error) => write!(formatter, "could not inspect indexed source: {error}"),
41        }
42    }
43}
44
45impl Error for IndexedReaderError {
46    fn source(&self) -> Option<&(dyn Error + 'static)> {
47        match self {
48            Self::Index(error) => Some(error),
49            Self::Decode(error) => Some(error),
50            Self::Io(error) => Some(error.as_ref()),
51        }
52    }
53}
54
55impl From<IndexError> for IndexedReaderError {
56    fn from(error: IndexError) -> Self {
57        Self::Index(error)
58    }
59}
60
61impl From<DecodeError> for IndexedReaderError {
62    fn from(error: DecodeError) -> Self {
63        Self::Decode(error)
64    }
65}
66
67enum Verification {
68    Gzip { crc: Crc32, output_size: u32 },
69    Zlib(Adler32),
70}
71
72impl Verification {
73    const fn gzip() -> Self {
74        Self::Gzip {
75            crc: Crc32::new(),
76            output_size: 0,
77        }
78    }
79
80    const fn zlib() -> Self {
81        Self::Zlib(Adler32::new())
82    }
83
84    fn update(&mut self, bytes: &[u8]) {
85        match self {
86            Self::Gzip { crc, output_size } => {
87                crc.update(bytes);
88                *output_size = output_size.wrapping_add(bytes.len() as u32);
89            }
90            Self::Zlib(checksum) => checksum.update(bytes),
91        }
92    }
93}
94
95/// A [`Read`] and [`Seek`] view of decompressed bytes described by an index.
96///
97/// Seeking resumes at the nearest preceding checkpoint and discards output up
98/// to the requested byte. Gzip-member and zlib-header checkpoints permit full
99/// checksum verification, including discarded bytes. An interior DEFLATE
100/// checkpoint cannot authenticate the skipped prefix because the index does
101/// not store its checksum state. Raw DEFLATE has no container checksum.
102///
103/// The index is validated and a known compressed size is compared with the
104/// source before construction succeeds. Callers remain responsible for pairing
105/// indexes without a recorded size with the source from which they were built.
106pub struct IndexedReader<R: ReadAt> {
107    source: R,
108    index: DeflateIndex,
109    source_length: u64,
110    inflater: RawInflater,
111    windows: WindowCache,
112    input: Vec<u8>,
113    input_position: usize,
114    next_input: u64,
115    decoded: Vec<u8>,
116    decoded_position: usize,
117    position: u64,
118    state: State,
119    verification: Option<Verification>,
120    window_bits: u8,
121}
122
123#[derive(Clone, Copy, Debug, Eq, PartialEq)]
124enum State {
125    NeedsResume,
126    Running,
127    Ended,
128}
129
130impl<R: ReadAt> IndexedReader<R> {
131    /// Opens `source` for random access through `index`.
132    ///
133    /// # Errors
134    ///
135    /// Returns an error when the index violates its invariants, the source
136    /// length cannot be read or disagrees with a known index size, or the raw
137    /// inflate backend cannot be initialized.
138    pub fn new(source: R, index: DeflateIndex) -> Result<Self, IndexedReaderError> {
139        index.validate()?;
140        let source_length = source
141            .len()
142            .map_err(|error| IndexedReaderError::Io(Arc::new(error)))?;
143        if let Some(index_size) = index.compressed_size() {
144            if index_size != source_length {
145                return Err(IndexedReaderError::Index(IndexError::ArchiveSizeMismatch {
146                    index_size,
147                    archive_size: source_length,
148                }));
149            }
150        }
151        let window_bits = if index.kind() == IndexKind::Zlib {
152            let header = read_exact_from_source::<2, _>(&source, 0)
153                .map_err(|error| IndexedReaderError::Io(Arc::new(error)))?;
154            zlib::parse_header(header, 0)?
155        } else {
156            15
157        };
158        Ok(Self {
159            source,
160            index,
161            source_length,
162            inflater: RawInflater::new_with_window_bits(window_bits)?,
163            windows: WindowCache::new(DEFAULT_BUDGET),
164            input: Vec::new(),
165            input_position: 0,
166            next_input: 0,
167            decoded: Vec::new(),
168            decoded_position: 0,
169            position: 0,
170            state: State::NeedsResume,
171            verification: None,
172            window_bits,
173        })
174    }
175
176    /// Sets the expanded-window cache budget in bytes.
177    #[must_use]
178    pub fn with_window_cache_bytes(mut self, bytes: usize) -> Self {
179        self.windows = WindowCache::new(bytes);
180        self
181    }
182
183    /// Returns the index backing this reader.
184    #[must_use]
185    pub const fn index(&self) -> &DeflateIndex {
186        &self.index
187    }
188
189    /// Returns the decompressed offset of the next byte a read returns.
190    #[must_use]
191    pub const fn position(&self) -> u64 {
192        self.position
193    }
194
195    /// Returns the source and index, discarding decoding state.
196    pub fn into_inner(self) -> (R, DeflateIndex) {
197        (self.source, self.index)
198    }
199
200    /// Seeks to the first byte of zero-based `line` and returns its byte offset.
201    ///
202    /// The reader resumes from the nearest preceding annotated checkpoint and
203    /// scans forward only far enough to find the requested newline. Seeking
204    /// beyond the recorded lines positions the reader at decoded EOF.
205    ///
206    /// # Errors
207    ///
208    /// Returns [`io::ErrorKind::Unsupported`] unless the index carries a total
209    /// line count and a line offset for every checkpoint. Other errors have the
210    /// same meaning as [`Read`] and [`Seek`] errors from this reader.
211    pub fn seek_to_line(&mut self, line: u64) -> io::Result<u64> {
212        let Some(checkpoint) = self.index.checkpoint_at_or_before_line(line) else {
213            return Err(io::Error::new(
214                io::ErrorKind::Unsupported,
215                "the index does not carry complete line metadata",
216            ));
217        };
218        let checkpoint_line = checkpoint
219            .line_offset
220            .expect("line checkpoint lookup requires complete metadata");
221        let mut remaining = line - checkpoint_line;
222        let start = checkpoint.uncompressed_offset_in_bytes;
223        self.seek(SeekFrom::Start(start))?;
224        if remaining == 0 {
225            return Ok(start);
226        }
227
228        let mut scratch = [0_u8; 64 * 1024];
229        loop {
230            let read = self.read(&mut scratch)?;
231            if read == 0 {
232                return Ok(self.position);
233            }
234            let mut consumed = 0_usize;
235            for (index, &byte) in scratch[..read].iter().enumerate() {
236                if byte != b'\n' {
237                    continue;
238                }
239                remaining -= 1;
240                if remaining == 0 {
241                    consumed = index + 1;
242                    break;
243                }
244            }
245            if remaining == 0 {
246                let position = self.position - (read - consumed) as u64;
247                self.seek(SeekFrom::Start(position))?;
248                return Ok(position);
249            }
250        }
251    }
252
253    fn buffered(&self) -> usize {
254        self.decoded.len() - self.decoded_position
255    }
256
257    fn discard_buffers(&mut self) {
258        self.decoded.clear();
259        self.decoded_position = 0;
260        self.input.clear();
261        self.input_position = 0;
262    }
263
264    fn read_input_page(&mut self, offset: u64) -> io::Result<()> {
265        let remaining = self.source_length.saturating_sub(offset);
266        let length = usize::try_from(remaining)
267            .unwrap_or(usize::MAX)
268            .min(INPUT_PAGE);
269        self.input.clear();
270        self.input.resize(length, 0);
271        let mut filled = 0;
272        while filled < length {
273            let read = self
274                .source
275                .read_at(offset + filled as u64, &mut self.input[filled..])?;
276            if read == 0 {
277                return Err(io::Error::new(
278                    io::ErrorKind::UnexpectedEof,
279                    "positional source ended before its reported length",
280                ));
281            }
282            filled += read;
283        }
284        self.input_position = 0;
285        self.next_input = offset + filled as u64;
286        Ok(())
287    }
288
289    fn read_exact_at<const N: usize>(&self, offset: u64) -> io::Result<[u8; N]> {
290        let mut bytes = [0_u8; N];
291        let mut filled = 0;
292        while filled < N {
293            let read = self
294                .source
295                .read_at(offset + filled as u64, &mut bytes[filled..])?;
296            if read == 0 {
297                return Err(io::Error::new(
298                    io::ErrorKind::UnexpectedEof,
299                    "truncated compressed-stream framing",
300                ));
301            }
302            filled += read;
303        }
304        Ok(bytes)
305    }
306
307    fn parse_member_header(&self, offset: u64) -> io::Result<u64> {
308        let mut cursor =
309            SourceCursor::new(&self.source, INPUT_PAGE).map_err(|error| error.to_io_error())?;
310        cursor.seek(offset).map_err(|error| error.to_io_error())?;
311        let header =
312            parse_member_header(&mut cursor, offset == 0).map_err(|error| error.to_io_error())?;
313        Ok(header.deflate_start)
314    }
315
316    fn resume(&mut self) -> io::Result<()> {
317        self.discard_buffers();
318        let checkpoint = self
319            .index
320            .checkpoint_at_or_before(self.position)
321            .copied()
322            .ok_or_else(|| {
323                io::Error::new(
324                    io::ErrorKind::InvalidInput,
325                    "the index has no checkpoint at or before the requested offset",
326                )
327            })?;
328        let bit_offset = checkpoint.compressed_offset_in_bits;
329        let byte_offset = bit_offset / 8;
330        if byte_offset > self.source_length {
331            return Err(io::Error::new(
332                io::ErrorKind::InvalidInput,
333                "checkpoint points past the end of the source",
334            ));
335        }
336
337        self.inflater
338            .reset_with_window_bits(self.window_bits, bit_offset)
339            .map_err(|error| error.to_io_error())?;
340        let mut start = byte_offset;
341        let window = self.expanded_window(&checkpoint)?;
342        self.verification = match checkpoint.kind {
343            CheckpointKind::GzipMemberHeader => {
344                start = self.parse_member_header(byte_offset)?;
345                Some(Verification::gzip())
346            }
347            CheckpointKind::GzipMemberDeflate {
348                header_offset_in_bytes,
349            } => {
350                let parsed_start = self.parse_member_header(header_offset_in_bytes)?;
351                if parsed_start != byte_offset {
352                    return Err(io::Error::new(
353                        io::ErrorKind::InvalidData,
354                        "member checkpoint does not match the parsed gzip header",
355                    ));
356                }
357                Some(Verification::gzip())
358            }
359            CheckpointKind::ZlibHeader => {
360                let header = self.read_exact_at::<2>(byte_offset)?;
361                let parsed_window =
362                    zlib::parse_header(header, byte_offset).map_err(|error| error.to_io_error())?;
363                if parsed_window != self.window_bits {
364                    return Err(io::Error::new(
365                        io::ErrorKind::InvalidData,
366                        "zlib header window changed after reader construction",
367                    ));
368                }
369                start = byte_offset + 2;
370                Some(Verification::zlib())
371            }
372            CheckpointKind::RawDeflateStart => None,
373            CheckpointKind::DeflateBlock => {
374                let remainder = (bit_offset % 8) as u8;
375                if remainder != 0 {
376                    let straddled = self.read_exact_at::<1>(byte_offset)?[0];
377                    self.inflater
378                        .prime(8 - remainder, straddled >> remainder, bit_offset)
379                        .map_err(|error| error.to_io_error())?;
380                    start += 1;
381                }
382                None
383            }
384        };
385        if !window.is_empty() {
386            let allowed = 1_usize << self.window_bits;
387            let window = &window[window.len().saturating_sub(allowed)..];
388            self.inflater
389                .set_dictionary_bytes(window, bit_offset)
390                .map_err(|error| error.to_io_error())?;
391        }
392        self.read_input_page(start)?;
393        self.state = State::Running;
394
395        let mut remaining = self.position - checkpoint.uncompressed_offset_in_bytes;
396        while remaining > 0 {
397            if self.buffered() == 0 && !self.fill()? {
398                return Ok(());
399            }
400            let skipped = remaining.min(self.buffered() as u64);
401            self.decoded_position += skipped as usize;
402            remaining -= skipped;
403        }
404        Ok(())
405    }
406
407    fn expanded_window(&mut self, checkpoint: &Checkpoint) -> io::Result<Vec<u8>> {
408        let key = checkpoint.compressed_offset_in_bits;
409        if let Some(cached) = self.windows.get(key) {
410            return Ok(cached.to_vec());
411        }
412        let Some(stored) = self.index.windows().get(key) else {
413            return Ok(Vec::new());
414        };
415        let expanded = stored
416            .decompressed()
417            .map_err(io::Error::other)?
418            .into_owned();
419        if expanded.len() != WINDOW_SIZE {
420            return Err(io::Error::new(
421                io::ErrorKind::InvalidData,
422                "stored predecessor window is not exactly 32768 bytes",
423            ));
424        }
425        self.windows.insert(key, expanded.clone());
426        Ok(expanded)
427    }
428
429    fn fill(&mut self) -> io::Result<bool> {
430        loop {
431            match self.state {
432                State::Ended => return Ok(false),
433                State::NeedsResume => {
434                    self.resume()?;
435                    if self.buffered() > 0 {
436                        return Ok(true);
437                    }
438                    if self.state == State::Ended {
439                        return Ok(false);
440                    }
441                }
442                State::Running => {}
443            }
444
445            if self.input_position >= self.input.len() {
446                if self.next_input >= self.source_length {
447                    return Err(io::Error::new(
448                        io::ErrorKind::UnexpectedEof,
449                        "truncated DEFLATE stream",
450                    ));
451                }
452                self.read_input_page(self.next_input)?;
453            }
454            if self.decoded_position == self.decoded.len() {
455                self.decoded.clear();
456                self.decoded_position = 0;
457            }
458            let produced = self.inflate_step()?;
459            if produced > 0 {
460                return Ok(true);
461            }
462            if self.state == State::Ended {
463                return Ok(false);
464            }
465        }
466    }
467
468    fn inflate_step(&mut self) -> io::Result<usize> {
469        let output_start = self.decoded.len();
470        self.decoded.reserve(OUTPUT_STEP);
471        let output_capacity = (self.decoded.capacity() - output_start).min(u32::MAX as usize);
472        let input = &self.input[self.input_position..];
473        let input_length = input.len().min(u32::MAX as usize);
474
475        self.inflater.stream.next_in = input.as_ptr();
476        self.inflater.stream.avail_in = input_length as u32;
477        self.inflater.stream.next_out = self.decoded.spare_capacity_mut().as_mut_ptr().cast();
478        self.inflater.stream.avail_out = output_capacity as u32;
479        let input_before = self.inflater.stream.avail_in;
480        let output_before = self.inflater.stream.avail_out;
481        // SAFETY: input and output point to live, non-overlapping allocations
482        // for this call, and the initialized inflater is uniquely borrowed.
483        let status = unsafe { z::inflate(&mut self.inflater.stream, z::Z_NO_FLUSH) };
484        let consumed = (input_before - self.inflater.stream.avail_in) as usize;
485        let produced = (output_before - self.inflater.stream.avail_out) as usize;
486        self.inflater.stream.next_in = std::ptr::null();
487        self.inflater.stream.avail_in = 0;
488        self.inflater.stream.next_out = std::ptr::null_mut();
489        self.inflater.stream.avail_out = 0;
490        self.input_position += consumed;
491        // SAFETY: zlib initialized exactly `produced` bytes in the supplied
492        // spare capacity and cannot report more than its `avail_out` bound.
493        unsafe { self.decoded.set_len(output_start + produced) };
494        if let Some(verification) = self.verification.as_mut() {
495            verification.update(&self.decoded[output_start..]);
496        }
497
498        match status {
499            z::Z_OK if consumed != 0 || produced != 0 => Ok(produced),
500            z::Z_BUF_ERROR if consumed != 0 || produced != 0 => Ok(produced),
501            z::Z_OK | z::Z_BUF_ERROR if self.next_input >= self.source_length => Err(
502                io::Error::new(io::ErrorKind::UnexpectedEof, "truncated DEFLATE stream"),
503            ),
504            z::Z_OK | z::Z_BUF_ERROR => Err(io::Error::new(
505                io::ErrorKind::InvalidData,
506                "DEFLATE decoder made no progress",
507            )),
508            z::Z_STREAM_END => {
509                self.finish_stream()?;
510                Ok(produced)
511            }
512            z::Z_DATA_ERROR => Err(io::Error::new(
513                io::ErrorKind::InvalidData,
514                self.inflater
515                    .message()
516                    .unwrap_or_else(|| "invalid DEFLATE data".to_owned()),
517            )),
518            other => Err(io::Error::other(format!(
519                "unexpected DEFLATE backend status {other}"
520            ))),
521        }
522    }
523
524    fn finish_stream(&mut self) -> io::Result<()> {
525        let trailer_offset = self.next_input - (self.input.len() - self.input_position) as u64;
526        match self.index.kind() {
527            IndexKind::Gzip | IndexKind::Bgzf => self.finish_gzip_member(trailer_offset),
528            IndexKind::Zlib => self.finish_zlib_stream(trailer_offset),
529            IndexKind::RawDeflate => {
530                if trailer_offset != self.source_length {
531                    return Err(io::Error::new(
532                        io::ErrorKind::InvalidData,
533                        "trailing data after raw DEFLATE stream",
534                    ));
535                }
536                self.verification = None;
537                self.state = State::Ended;
538                Ok(())
539            }
540        }
541    }
542
543    fn finish_gzip_member(&mut self, footer_offset: u64) -> io::Result<()> {
544        let footer = self.read_exact_at::<8>(footer_offset)?;
545        if let Some(Verification::Gzip { crc, output_size }) = self.verification.take() {
546            let expected_crc = u32::from_le_bytes(footer[..4].try_into().expect("four bytes"));
547            let expected_size = u32::from_le_bytes(footer[4..].try_into().expect("four bytes"));
548            let actual_crc = crc.finish();
549            if expected_crc != actual_crc {
550                return Err(io::Error::new(
551                    io::ErrorKind::InvalidData,
552                    format!(
553                        "gzip CRC32 mismatch: expected {expected_crc:#010x}, got {actual_crc:#010x}"
554                    ),
555                ));
556            }
557            if expected_size != output_size {
558                return Err(io::Error::new(
559                    io::ErrorKind::InvalidData,
560                    format!(
561                        "gzip ISIZE mismatch: expected {expected_size}, got {}",
562                        output_size
563                    ),
564                ));
565            }
566        }
567
568        let next_member = footer_offset.checked_add(8).ok_or_else(|| {
569            io::Error::new(io::ErrorKind::InvalidData, "gzip footer offset overflow")
570        })?;
571        if next_member == self.source_length {
572            self.state = State::Ended;
573            return Ok(());
574        }
575        if next_member > self.source_length {
576            return Err(io::Error::new(
577                io::ErrorKind::UnexpectedEof,
578                "truncated gzip member footer",
579            ));
580        }
581        let deflate_start = self.parse_member_header(next_member)?;
582        self.inflater
583            .reset(deflate_start.saturating_mul(8))
584            .map_err(|error| error.to_io_error())?;
585        self.verification = Some(Verification::gzip());
586        self.read_input_page(deflate_start)?;
587        self.state = State::Running;
588        Ok(())
589    }
590
591    fn finish_zlib_stream(&mut self, trailer_offset: u64) -> io::Result<()> {
592        let trailer = self.read_exact_at::<4>(trailer_offset)?;
593        if let Some(Verification::Zlib(checksum)) = self.verification.take() {
594            let expected = u32::from_be_bytes(trailer);
595            let actual = checksum.finish();
596            if expected != actual {
597                return Err(io::Error::new(
598                    io::ErrorKind::InvalidData,
599                    format!(
600                        "zlib Adler-32 mismatch: expected {expected:#010x}, got {actual:#010x}"
601                    ),
602                ));
603            }
604        }
605        let end = trailer_offset.checked_add(4).ok_or_else(|| {
606            io::Error::new(io::ErrorKind::InvalidData, "zlib trailer offset overflow")
607        })?;
608        if end != self.source_length {
609            return Err(io::Error::new(
610                io::ErrorKind::InvalidData,
611                "trailing data after zlib stream",
612            ));
613        }
614        self.state = State::Ended;
615        Ok(())
616    }
617}
618
619fn read_exact_from_source<const N: usize, R: ReadAt>(
620    source: &R,
621    offset: u64,
622) -> io::Result<[u8; N]> {
623    let mut bytes = [0_u8; N];
624    let mut filled = 0;
625    while filled < N {
626        let read = source.read_at(offset + filled as u64, &mut bytes[filled..])?;
627        if read == 0 {
628            return Err(io::Error::new(
629                io::ErrorKind::UnexpectedEof,
630                "compressed source ended before the requested framing bytes",
631            ));
632        }
633        filled += read;
634    }
635    Ok(bytes)
636}
637
638impl<R: ReadAt> Read for IndexedReader<R> {
639    fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
640        if output.is_empty() {
641            return Ok(0);
642        }
643        loop {
644            if self.buffered() > 0 {
645                let count = self.buffered().min(output.len());
646                let start = self.decoded_position;
647                output[..count].copy_from_slice(&self.decoded[start..start + count]);
648                self.decoded_position += count;
649                self.position += count as u64;
650                return Ok(count);
651            }
652            if !self.fill()? {
653                return Ok(0);
654            }
655        }
656    }
657}
658
659impl<R: ReadAt> Seek for IndexedReader<R> {
660    fn seek(&mut self, target: SeekFrom) -> io::Result<u64> {
661        let position = match target {
662            SeekFrom::Start(offset) => offset,
663            SeekFrom::Current(delta) => add_offset(self.position, delta)?,
664            SeekFrom::End(delta) => add_offset(
665                self.index.uncompressed_size().ok_or_else(|| {
666                    io::Error::new(
667                        io::ErrorKind::Unsupported,
668                        "the index does not record the decompressed size",
669                    )
670                })?,
671                delta,
672            )?,
673        };
674
675        if position == self.position && self.state != State::NeedsResume {
676            return Ok(position);
677        }
678        if position > self.position && self.state == State::Running {
679            let ahead = position - self.position;
680            if ahead <= self.buffered() as u64 {
681                self.decoded_position += ahead as usize;
682                self.position = position;
683                return Ok(position);
684            }
685        }
686        self.position = position;
687        self.state = State::NeedsResume;
688        self.verification = None;
689        self.discard_buffers();
690        Ok(position)
691    }
692
693    fn stream_position(&mut self) -> io::Result<u64> {
694        Ok(self.position)
695    }
696}
697
698fn add_offset(base: u64, delta: i64) -> io::Result<u64> {
699    let result = if delta >= 0 {
700        base.checked_add(delta as u64)
701    } else {
702        base.checked_sub(delta.unsigned_abs())
703    };
704    result.ok_or_else(|| {
705        io::Error::new(
706            io::ErrorKind::InvalidInput,
707            "seek position is outside the decompressed stream",
708        )
709    })
710}