an efficient binary archive format
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}