Skip to main content

bindle_file/
reader.rs

1use crc32fast::Hasher;
2use std::io::{self, BufReader, Read, Seek, SeekFrom};
3
4pub(crate) enum Either<A, B> {
5    Left(A),
6    Right(B),
7}
8
9/// A streaming reader for archive entries.
10///
11/// Created by the archive's `reader()` method. Automatically decompresses compressed entries and tracks CRC32 for integrity verification.
12///
13/// # Example
14///
15/// ```no_run
16/// # use bindle_file::Bindle;
17/// # let archive = Bindle::open("data.bndl")?;
18/// let mut reader = archive.reader("file.txt")?;
19/// std::io::copy(&mut reader, &mut std::io::stdout())?;
20/// reader.verify_crc32()?;
21/// # Ok::<(), std::io::Error>(())
22/// ```
23pub struct Reader<'a> {
24    pub(crate) decoder:
25        Either<zstd::Decoder<'static, BufReader<io::Cursor<&'a [u8]>>>, io::Cursor<&'a [u8]>>,
26    pub(crate) crc32_hasher: Hasher,
27    pub(crate) expected_crc32: u32,
28}
29
30impl<'a> Read for Reader<'a> {
31    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
32        let n = match &mut self.decoder {
33            Either::Left(x) => x.read(buf)?,
34            Either::Right(x) => x.read(buf)?,
35        };
36
37        if n > 0 {
38            self.crc32_hasher.update(&buf[..n]);
39        }
40
41        Ok(n)
42    }
43}
44
45// Note: Seeking is only supported for uncompressed entries in this simple implementation.
46// Seeking in compressed streams requires a frame-aware decoder.
47impl<'a> Seek for Reader<'a> {
48    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
49        match &mut self.decoder {
50            Either::Left(_) => Err(io::Error::new(
51                io::ErrorKind::Unsupported,
52                "Seeking not supported on compressed streams",
53            )),
54            Either::Right(x) => x.seek(pos),
55        }
56    }
57}
58
59impl<'a> Reader<'a> {
60    /// Verifies the CRC32 checksum of the data read so far.
61    ///
62    /// Should be called after reading all data to ensure integrity.
63    /// Returns an error if the computed CRC32 doesn't match the expected value.
64    pub fn verify_crc32(&self) -> io::Result<()> {
65        let computed_crc = self.crc32_hasher.clone().finalize();
66        if computed_crc != self.expected_crc32 {
67            return Err(io::Error::new(
68                io::ErrorKind::InvalidData,
69                format!(
70                    "CRC32 mismatch: expected {:x}, got {:x}",
71                    self.expected_crc32, computed_crc
72                ),
73            ));
74        }
75        Ok(())
76    }
77}