pub const EOC_MIN: u32 = 0x0FFF_FFF8;
pub const EOC: u32 = 0x0FFF_FFFF;
pub const FREE: u32 = 0;
pub const ENTRY_MASK: u32 = 0x0FFF_FFFF;
#[derive(Debug, Clone)]
pub struct Fat {
entries: Vec<u32>,
}
impl Fat {
pub fn new(capacity: usize, media: u8) -> Self {
let mut entries = vec![FREE; capacity];
entries[0] = 0x0FFF_FF00 | media as u32;
entries[1] = EOC;
Self { entries }
}
pub fn capacity(&self) -> usize {
self.entries.len()
}
pub fn get(&self, cluster: u32) -> u32 {
self.entries[cluster as usize] & ENTRY_MASK
}
pub fn set(&mut self, cluster: u32, value: u32) {
self.entries[cluster as usize] = value & ENTRY_MASK;
}
pub fn is_eoc(value: u32) -> bool {
value >= EOC_MIN
}
pub fn encode(&self) -> Vec<u8> {
let mut out = vec![0u8; self.entries.len() * 4];
for (i, &e) in self.entries.iter().enumerate() {
out[i * 4..i * 4 + 4].copy_from_slice(&e.to_le_bytes());
}
out
}
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 chain(&self, start: u32) -> crate::Result<Vec<u32>> {
let mut out = Vec::new();
let mut cur = start;
while !Self::is_eoc(cur) {
if cur < 2 || cur as usize >= self.entries.len() {
return Err(crate::Error::InvalidImage(format!(
"fat32: cluster {cur} out of range while walking a chain"
)));
}
if out.len() > self.entries.len() {
return Err(crate::Error::InvalidImage(
"fat32: cluster chain loops".into(),
));
}
out.push(cur);
cur = self.get(cur);
if cur == FREE {
return Err(crate::Error::InvalidImage(
"fat32: cluster chain hits a free cluster".into(),
));
}
}
Ok(out)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reserved_entries() {
let fat = Fat::new(256, 0xF8);
assert_eq!(fat.get(0), 0x0FFF_FFF8);
assert_eq!(fat.get(1), EOC);
assert_eq!(fat.get(2), FREE);
}
#[test]
fn set_get_roundtrip_via_bytes() {
let mut fat = Fat::new(64, 0xF8);
fat.set(2, 3);
fat.set(3, 4);
fat.set(4, EOC);
let decoded = Fat::decode(&fat.encode());
assert_eq!(decoded.chain(2).unwrap(), vec![2, 3, 4]);
}
#[test]
fn eoc_classification() {
assert!(Fat::is_eoc(EOC));
assert!(Fat::is_eoc(0x0FFF_FFF8));
assert!(!Fat::is_eoc(0x0FFF_FFF7));
assert!(!Fat::is_eoc(5));
}
#[test]
fn chain_detects_free_break() {
let mut fat = Fat::new(64, 0xF8);
fat.set(2, 3); assert!(fat.chain(2).is_err());
}
}