an efficient binary archive format
at main 77 lines 2.4 kB view raw
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}