use crate::{
block_read::{read_exact, BlockRead},
error::{Error, Result},
vbr::Geometry,
};
pub(crate) fn next_cluster<R: BlockRead>(
reader: &mut R,
geo: &Geometry,
cluster_id: u32,
) -> Result<Option<u32>> {
let off = geo.fat_entry_byte(cluster_id);
let mut b = [0u8; 4];
read_exact(reader, off, &mut b, "io_fat")?;
let next = u32::from_le_bytes(b);
if (2..0xFFFF_FFF6).contains(&next) {
Ok(Some(next))
} else {
Ok(None)
}
}
fn advance<R: BlockRead>(
reader: &mut R,
geo: &Geometry,
cluster: u32,
contiguous: bool,
) -> Result<u32> {
if contiguous {
let next = cluster.checked_add(1).ok_or(Error::Inconsistent {
token: "cluster_oob",
})?;
if next
.checked_sub(2)
.is_none_or(|rel| rel >= geo.cluster_count)
{
return Err(Error::Inconsistent {
token: "cluster_oob",
});
}
Ok(next)
} else {
next_cluster(reader, geo, cluster)?.ok_or(Error::Inconsistent {
token: "cluster_oob",
})
}
}
pub(crate) fn read_stream<R: BlockRead>(
reader: &mut R,
geo: &Geometry,
first_cluster: u32,
contiguous: bool,
off: u64,
buf: &mut [u8],
) -> Result<()> {
if buf.is_empty() {
return Ok(());
}
let csize = geo.cluster_size();
let max_steps = u64::from(geo.cluster_count);
let mut steps = 0u64;
let mut cluster = first_cluster;
let mut intra = off;
while intra >= csize {
cluster = advance(reader, geo, cluster, contiguous)?;
steps += 1;
if steps > max_steps {
return Err(Error::Inconsistent { token: "fat_cycle" });
}
intra -= csize;
}
let mut filled = 0usize;
while filled < buf.len() {
let base = geo.cluster_byte(cluster).ok_or(Error::Inconsistent {
token: "cluster_oob",
})?;
let in_cluster = (csize - intra) as usize;
let want = core::cmp::min(in_cluster, buf.len() - filled);
read_exact(
reader,
base + intra,
&mut buf[filled..filled + want],
"io_data",
)?;
filled += want;
intra = 0;
if filled < buf.len() {
cluster = advance(reader, geo, cluster, contiguous)?;
steps += 1;
if steps > max_steps {
return Err(Error::Inconsistent { token: "fat_cycle" });
}
}
}
Ok(())
}