use crate::{
block_read::u32_at,
error::{Error, Result, VbrReason},
};
#[derive(Debug, Clone, Copy)]
pub(crate) struct Geometry {
pub bytes_per_sector: u32,
pub sectors_per_cluster: u32,
pub fat_offset: u32,
pub cluster_heap_offset: u32,
pub cluster_count: u32,
pub root_cluster: u32,
pub volume_serial: u32,
}
impl Geometry {
pub(crate) fn cluster_size(&self) -> u64 {
u64::from(self.bytes_per_sector) * u64::from(self.sectors_per_cluster)
}
pub(crate) fn cluster_byte(&self, cluster_id: u32) -> Option<u64> {
let rel = cluster_id.checked_sub(2)?;
if rel >= self.cluster_count {
return None;
}
let sector = u64::from(self.cluster_heap_offset)
+ u64::from(rel) * u64::from(self.sectors_per_cluster);
Some(sector * u64::from(self.bytes_per_sector))
}
pub(crate) fn fat_entry_byte(&self, cluster_id: u32) -> u64 {
u64::from(self.fat_offset) * u64::from(self.bytes_per_sector) + u64::from(cluster_id) * 4
}
}
pub(crate) fn parse(buf: &[u8]) -> Result<Geometry> {
let bad = Error::BadVbr;
if buf.get(3..11) != Some(b"EXFAT ".as_slice()) {
return Err(bad(VbrReason::BadMagic));
}
if buf.get(510..512) != Some([0x55, 0xAA].as_slice()) {
return Err(bad(VbrReason::BadBootSignature));
}
let bps_shift = *buf.get(108).ok_or(bad(VbrReason::BadGeometry))?;
let spc_shift = *buf.get(109).ok_or(bad(VbrReason::BadGeometry))?;
if !(9..=12).contains(&bps_shift) || u16::from(bps_shift) + u16::from(spc_shift) > 25 {
return Err(bad(VbrReason::BadGeometry));
}
let bytes_per_sector = 1u32 << bps_shift;
let sectors_per_cluster = 1u32 << spc_shift;
let fat_offset = u32_at(buf, 80).ok_or(bad(VbrReason::BadGeometry))?;
let fat_length = u32_at(buf, 84).ok_or(bad(VbrReason::BadGeometry))?;
let cluster_heap_offset = u32_at(buf, 88).ok_or(bad(VbrReason::BadGeometry))?;
let cluster_count = u32_at(buf, 92).ok_or(bad(VbrReason::BadGeometry))?;
let root_cluster = u32_at(buf, 96).ok_or(bad(VbrReason::BadGeometry))?;
let volume_serial = u32_at(buf, 100).ok_or(bad(VbrReason::BadGeometry))?;
let number_of_fats = *buf.get(110).ok_or(bad(VbrReason::BadGeometry))?;
if !(1..=2).contains(&number_of_fats)
|| cluster_count == 0
|| root_cluster < 2
|| root_cluster - 2 >= cluster_count
|| fat_offset < 24
|| cluster_heap_offset < fat_offset.saturating_add(fat_length)
{
return Err(bad(VbrReason::BadGeometry));
}
Ok(Geometry {
bytes_per_sector,
sectors_per_cluster,
fat_offset,
cluster_heap_offset,
cluster_count,
root_cluster,
volume_serial,
})
}
pub(crate) fn verify_boot_checksum(region: &[u8], bytes_per_sector: usize) -> Result<()> {
let err = || Error::BadVbr(VbrReason::BadBootChecksum);
let covered = bytes_per_sector
.checked_mul(11)
.and_then(|n| region.get(..n))
.ok_or_else(err)?;
let mut sum: u32 = 0;
for (i, &b) in covered.iter().enumerate() {
if i == 106 || i == 107 || i == 112 {
continue;
}
sum = sum.rotate_right(1).wrapping_add(u32::from(b));
}
let stored = u32_at(region, bytes_per_sector * 11).ok_or_else(err)?;
if sum != stored {
return Err(err());
}
Ok(())
}
#[cfg(test)]
mod tests {
extern crate alloc;
use alloc::{vec, vec::Vec};
use super::*;
fn valid_vbr() -> [u8; 512] {
let mut b = [0u8; 512];
b[3..11].copy_from_slice(b"EXFAT ");
b[80..84].copy_from_slice(&24u32.to_le_bytes()); b[84..88].copy_from_slice(&8u32.to_le_bytes()); b[88..92].copy_from_slice(&32u32.to_le_bytes()); b[92..96].copy_from_slice(&100u32.to_le_bytes()); b[96..100].copy_from_slice(&2u32.to_le_bytes()); b[100..104].copy_from_slice(&0x1234_5678u32.to_le_bytes());
b[108] = 9; b[109] = 3; b[110] = 1; b[510] = 0x55;
b[511] = 0xAA;
b
}
#[test]
fn accepts_valid_and_caches_geometry() {
let g = parse(&valid_vbr()).expect("valid");
assert_eq!(g.bytes_per_sector, 512);
assert_eq!(g.sectors_per_cluster, 8);
assert_eq!(g.cluster_count, 100);
assert_eq!(g.root_cluster, 2);
assert_eq!(g.volume_serial, 0x1234_5678);
assert_eq!(g.cluster_size(), 512 * 8);
assert_eq!(g.cluster_byte(2), Some(32 * 512));
assert_eq!(g.cluster_byte(1), None);
assert_eq!(g.cluster_byte(102), None);
}
#[test]
fn rejects_each_corruption_with_its_reason() {
let reason = |mutate: &dyn Fn(&mut [u8; 512])| {
let mut b = valid_vbr();
mutate(&mut b);
match parse(&b) {
Err(Error::BadVbr(r)) => r,
other => panic!("expected BadVbr, got {other:?}"),
}
};
assert_eq!(reason(&|b| b[5] = b'X'), VbrReason::BadMagic);
assert_eq!(reason(&|b| b[510] = 0), VbrReason::BadBootSignature);
assert_eq!(reason(&|b| b[108] = 8), VbrReason::BadGeometry); assert_eq!(reason(&|b| b[108] = 13), VbrReason::BadGeometry); assert_eq!(reason(&|b| b[109] = 20), VbrReason::BadGeometry); assert_eq!(
reason(&|b| b[96..100].copy_from_slice(&1u32.to_le_bytes())),
VbrReason::BadGeometry );
assert_eq!(reason(&|b| b[110] = 0), VbrReason::BadGeometry); assert_eq!(
reason(&|b| b[88..92].copy_from_slice(&31u32.to_le_bytes())),
VbrReason::BadGeometry );
}
#[test]
fn boot_checksum_excludes_volatile_bytes() {
let bps = 512usize;
let mut region = vec![0u8; bps * 12];
for (i, b) in region.iter_mut().enumerate() {
*b = (i % 251) as u8;
}
let mut sum: u32 = 0;
for (i, &b) in region[..bps * 11].iter().enumerate() {
if i == 106 || i == 107 || i == 112 {
continue;
}
sum = sum.rotate_right(1).wrapping_add(u32::from(b));
}
region[bps * 11..bps * 11 + 4].copy_from_slice(&sum.to_le_bytes());
assert!(verify_boot_checksum(®ion, bps).is_ok());
let mut volatile: Vec<u8> = region.clone();
volatile[106] ^= 0xFF;
volatile[112] ^= 0xFF;
assert!(verify_boot_checksum(&volatile, bps).is_ok());
let mut tampered = region;
tampered[200] ^= 0xFF;
assert_eq!(
verify_boot_checksum(&tampered, bps).unwrap_err(),
Error::BadVbr(VbrReason::BadBootChecksum)
);
}
}