pub const FREE: u32 = 0x0000_0000;
pub const BAD: u32 = 0xFFFF_FFF7;
pub const EOC: u32 = 0xFFFF_FFFF;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FatEntry {
Free,
Bad,
Eoc,
Next(u32),
}
pub fn classify(value: u32) -> FatEntry {
match value {
FREE => FatEntry::Free,
BAD => FatEntry::Bad,
EOC => FatEntry::Eoc,
n => FatEntry::Next(n),
}
}
#[derive(Debug, Clone)]
pub struct Fat {
entries: Vec<u32>,
}
impl Fat {
pub fn decode(bytes: &[u8]) -> Self {
let entries = bytes
.chunks_exact(4)
.map(|c| u32::from_le_bytes(c.try_into().unwrap()))
.collect();
Self { entries }
}
pub fn new_blank(capacity: usize) -> Self {
assert!(capacity >= 2, "FAT must hold at least two reserved entries");
let mut entries = vec![FREE; capacity];
entries[0] = 0xFFFF_FFF8;
entries[1] = EOC;
Self { entries }
}
pub fn encode(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(self.entries.len() * 4);
for e in &self.entries {
out.extend_from_slice(&e.to_le_bytes());
}
out
}
pub fn raw(&self, cluster: u32) -> Option<u32> {
self.entries.get(cluster as usize).copied()
}
pub fn set_raw(&mut self, cluster: u32, value: u32) {
if let Some(slot) = self.entries.get_mut(cluster as usize) {
*slot = value;
}
}
pub fn get(&self, cluster: u32) -> Option<FatEntry> {
self.raw(cluster).map(classify)
}
pub fn capacity(&self) -> usize {
self.entries.len()
}
pub fn chain(&self, start: u32) -> crate::Result<Vec<u32>> {
let mut out = Vec::new();
let mut cur = start;
loop {
if cur < 2 || (cur as usize) >= self.entries.len() {
return Err(crate::Error::InvalidImage(format!(
"exfat: cluster {cur} out of range while walking a chain"
)));
}
if out.len() > self.entries.len() {
return Err(crate::Error::InvalidImage(
"exfat: cluster chain loops".into(),
));
}
out.push(cur);
match classify(self.entries[cur as usize]) {
FatEntry::Eoc => return Ok(out),
FatEntry::Free => {
return Err(crate::Error::InvalidImage(
"exfat: cluster chain hits a FREE entry".into(),
));
}
FatEntry::Bad => {
return Err(crate::Error::InvalidImage(
"exfat: cluster chain hits a BAD entry".into(),
));
}
FatEntry::Next(n) => cur = n,
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classify_known_values() {
assert_eq!(classify(0), FatEntry::Free);
assert_eq!(classify(0xFFFF_FFF7), FatEntry::Bad);
assert_eq!(classify(0xFFFF_FFFF), FatEntry::Eoc);
assert_eq!(classify(7), FatEntry::Next(7));
}
fn build(entries: &[u32]) -> Fat {
let mut bytes = Vec::with_capacity(entries.len() * 4);
for e in entries {
bytes.extend_from_slice(&e.to_le_bytes());
}
Fat::decode(&bytes)
}
#[test]
fn chain_walks_to_eoc() {
let fat = build(&[0xFFFF_FFF8, 0xFFFF_FFFF, 3, 5, FREE, EOC]);
assert_eq!(fat.chain(2).unwrap(), vec![2, 3, 5]);
}
#[test]
fn chain_rejects_free_break() {
let fat = build(&[0xFFFF_FFF8, EOC, 3, FREE]);
assert!(fat.chain(2).is_err());
}
#[test]
fn chain_rejects_bad_in_chain() {
let fat = build(&[0xFFFF_FFF8, EOC, 3, BAD]);
assert!(fat.chain(2).is_err());
}
#[test]
fn chain_rejects_out_of_range_start() {
let fat = build(&[0xFFFF_FFF8, EOC, EOC]);
assert!(fat.chain(5).is_err());
assert!(fat.chain(1).is_err());
}
}