use std::fs::File;
use std::io::{BufReader, Read};
use std::path::{Path, PathBuf};
use flate2::read::MultiGzDecoder;
use crate::error::{OpticaldiscsError, Result};
use crate::sector_reader::{SectorReader, SECTOR_SIZE};
pub const GZ_MAGIC: [u8; 2] = [0x1f, 0x8b];
const SKIP_CHUNK: usize = 64 * 1024;
pub struct GzSectorReader {
path: PathBuf,
decoder: MultiGzDecoder<BufReader<File>>,
pos: u64,
scratch: Vec<u8>,
}
impl GzSectorReader {
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref().to_path_buf();
let mut magic = [0u8; 2];
{
let mut f = File::open(&path).map_err(OpticaldiscsError::Io)?;
f.read_exact(&mut magic).map_err(OpticaldiscsError::Io)?;
}
if magic != GZ_MAGIC {
return Err(OpticaldiscsError::Parse("not a gzip (.gz) image".into()));
}
let decoder = Self::fresh_decoder(&path)?;
Ok(Self {
path,
decoder,
pos: 0,
scratch: vec![0u8; SKIP_CHUNK],
})
}
fn fresh_decoder(path: &Path) -> Result<MultiGzDecoder<BufReader<File>>> {
let file = File::open(path).map_err(OpticaldiscsError::Io)?;
Ok(MultiGzDecoder::new(BufReader::new(file)))
}
fn reset(&mut self) -> Result<()> {
self.decoder = Self::fresh_decoder(&self.path)?;
self.pos = 0;
Ok(())
}
fn skip(&mut self, mut n: u64) -> Result<()> {
while n > 0 {
let want = n.min(self.scratch.len() as u64) as usize;
let got = read_fill(&mut self.decoder, &mut self.scratch[..want])
.map_err(OpticaldiscsError::Io)?;
if got == 0 {
self.pos += n;
return Ok(());
}
self.pos += got as u64;
n -= got as u64;
}
Ok(())
}
fn read_at(&mut self, offset: u64, len: usize) -> Result<Vec<u8>> {
if offset < self.pos {
self.reset()?;
}
if offset > self.pos {
let skip = offset - self.pos;
self.skip(skip)?;
}
let mut buf = vec![0u8; len];
let got = read_fill(&mut self.decoder, &mut buf).map_err(OpticaldiscsError::Io)?;
self.pos += got as u64;
Ok(buf)
}
}
impl SectorReader for GzSectorReader {
fn read_sector(&mut self, lba: u64) -> Result<Vec<u8>> {
self.read_at(lba * SECTOR_SIZE, SECTOR_SIZE as usize)
}
}
fn read_fill(r: &mut impl Read, buf: &mut [u8]) -> std::io::Result<usize> {
let mut filled = 0;
while filled < buf.len() {
match r.read(&mut buf[filled..]) {
Ok(0) => break,
Ok(n) => filled += n,
Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
Ok(filled)
}
#[cfg(test)]
mod tests {
use super::*;
use flate2::write::GzEncoder;
use flate2::Compression;
use std::io::Write;
fn write_gz(payload: &[u8]) -> tempfile::NamedTempFile {
let mut enc = GzEncoder::new(Vec::new(), Compression::default());
enc.write_all(payload).unwrap();
let gz = enc.finish().unwrap();
let mut f = tempfile::Builder::new().suffix(".gz").tempfile().unwrap();
f.write_all(&gz).unwrap();
f.flush().unwrap();
f
}
#[test]
fn reads_sectors_forward_and_backward() {
let mut payload = vec![0u8; 3 * 2048];
payload[..2048].fill(0x11);
payload[2048..4096].fill(0x22);
payload[4096..6144].fill(0x33);
payload[4096..4101].copy_from_slice(b"CD001");
let f = write_gz(&payload);
let mut r = GzSectorReader::open(f.path()).unwrap();
assert_eq!(r.read_sector(0).unwrap()[0], 0x11);
assert_eq!(r.read_sector(1).unwrap()[0], 0x22);
assert_eq!(&r.read_sector(2).unwrap()[..5], b"CD001");
assert_eq!(r.read_sector(0).unwrap()[0], 0x11);
assert_eq!(r.read_sector(50).unwrap(), vec![0u8; 2048]);
}
#[test]
fn read_bytes_spanning_sectors() {
let mut payload = vec![0u8; 2 * 2048];
for (i, b) in payload.iter_mut().enumerate() {
*b = (i % 256) as u8;
}
let f = write_gz(&payload);
let mut r = GzSectorReader::open(f.path()).unwrap();
let out = r.read_bytes(2046, 4).unwrap();
assert_eq!(out, [(2046 % 256) as u8, 255, 0, 1]);
}
#[test]
fn rejects_non_gzip() {
let mut f = tempfile::Builder::new().suffix(".gz").tempfile().unwrap();
f.write_all(b"PK\x03\x04 not gzip").unwrap();
f.flush().unwrap();
assert!(GzSectorReader::open(f.path()).is_err());
}
}