#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use super::writer::TOC_MAGIC;
use crate::io::ReadBytesExt;
use crate::sfa::{
Result,
checksum::Checksum,
toc::{Toc, entry::TocEntry},
trailer::reader::ParsedTrailer,
};
#[cfg(not(feature = "std"))]
use crate::io::{Read, Seek, SeekFrom};
#[cfg(feature = "std")]
use std::io::{Read, Seek, SeekFrom};
struct ChecksummedReader<R: crate::io::Read> {
inner: R,
hasher: xxhash_rust::xxh3::Xxh3Default,
}
impl<R: crate::io::Read> ChecksummedReader<R> {
fn new(reader: R) -> Self {
Self {
inner: reader,
hasher: xxhash_rust::xxh3::Xxh3Default::new(),
}
}
fn checksum(&self) -> Checksum {
Checksum::from_raw(self.hasher.digest128())
}
}
#[cfg(feature = "std")]
impl<R: crate::io::Read> std::io::Read for ChecksummedReader<R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let n = self.inner.read(buf)?;
#[expect(clippy::indexing_slicing)]
self.hasher.update(&buf[..n]);
Ok(n)
}
}
#[cfg(not(feature = "std"))]
impl<R: crate::io::Read> crate::io::Read for ChecksummedReader<R> {
fn read(&mut self, buf: &mut [u8]) -> crate::io::Result<usize> {
let n = self.inner.read(buf)?;
#[expect(clippy::indexing_slicing)]
self.hasher.update(&buf[..n]);
Ok(n)
}
}
const TOC_CHUNK: usize = 4_096;
struct ChunkedReader<R> {
inner: R,
chunk: [u8; TOC_CHUNK],
pos: usize,
filled: usize,
}
impl<R> ChunkedReader<R> {
fn new(inner: R) -> Self {
Self {
inner,
chunk: [0; TOC_CHUNK],
pos: 0,
filled: 0,
}
}
#[expect(
clippy::indexing_slicing,
reason = "pos <= filled <= TOC_CHUNK, and n is at most either length"
)]
fn drain_into(&mut self, out: &mut [u8]) -> usize {
let left = &self.chunk[self.pos..self.filled];
let n = out.len().min(left.len());
out[..n].copy_from_slice(&left[..n]);
self.pos += n;
n
}
}
#[cfg(feature = "std")]
impl<R: crate::io::Read> std::io::Read for ChunkedReader<R> {
fn read(&mut self, out: &mut [u8]) -> std::io::Result<usize> {
if self.pos == self.filled {
self.filled = self.inner.read(&mut self.chunk)?;
self.pos = 0;
}
Ok(self.drain_into(out))
}
}
#[cfg(not(feature = "std"))]
impl<R: crate::io::Read> crate::io::Read for ChunkedReader<R> {
fn read(&mut self, out: &mut [u8]) -> crate::io::Result<usize> {
if self.pos == self.filled {
self.filled = self.inner.read(&mut self.chunk)?;
self.pos = 0;
}
Ok(self.drain_into(out))
}
}
pub(crate) struct TocReader;
impl TocReader {
pub(crate) fn from_reader<R: Read + Seek>(
reader: &mut R,
trailer: &ParsedTrailer,
) -> Result<Toc> {
use crate::io::LE;
log::trace!("Reading ToC");
if trailer
.toc_pos
.checked_add(trailer.toc_len)
.is_none_or(|end| end > trailer.trailer_pos)
{
return Err(crate::io::Error::new(
crate::io::ErrorKind::UnexpectedEof,
"the TOC runs past the trailer",
)
.into());
}
reader.seek(SeekFrom::Start(trailer.toc_pos))?;
let mut reader = ChecksummedReader::new(ChunkedReader::new(reader.take(trailer.toc_len)));
{
let mut buf = [0u8; TOC_MAGIC.len()];
reader.read_exact(&mut buf)?;
if buf != TOC_MAGIC {
log::error!("Invalid TOC magic header");
return Err(crate::sfa::Error::InvalidHeader);
}
}
let len = reader.read_u32::<LE>()?;
let mut entries = Vec::new();
for _ in 0..len {
entries.push(TocEntry::read_from_file(&mut reader)?);
}
reader.checksum().check(trailer.toc_checksum)?;
Ok(Toc(entries))
}
}