use crate::boot::FatVariant;
use crate::bytes::{le_u16, le_u32};
pub fn fat_entry(fat: &[u8], variant: FatVariant, cluster: u32) -> u32 {
match variant {
FatVariant::Fat12 => {
let off = (cluster as usize).saturating_mul(3) / 2;
let raw = u32::from(le_u16(fat, off));
if cluster & 1 == 1 {
raw >> 4
} else {
raw & 0x0FFF
}
}
FatVariant::Fat16 => {
let off = (cluster as usize).saturating_mul(2);
u32::from(le_u16(fat, off))
}
FatVariant::Fat32 | FatVariant::ExFat => {
let off = (cluster as usize).saturating_mul(4);
le_u32(fat, off) & 0x0FFF_FFFF
}
}
}
fn eoc_threshold(variant: FatVariant) -> u32 {
match variant {
FatVariant::Fat12 => 0x0FF8,
FatVariant::Fat16 => 0xFFF8,
FatVariant::Fat32 | FatVariant::ExFat => 0x0FFF_FFF8,
}
}
pub fn is_eoc(variant: FatVariant, value: u32) -> bool {
value >= eoc_threshold(variant)
}
pub fn is_bad(variant: FatVariant, value: u32) -> bool {
value == eoc_threshold(variant) - 1
}
pub fn follow_chain(fat: &[u8], variant: FatVariant, start: u32, max_clusters: usize) -> Vec<u32> {
let mut chain = Vec::new();
let mut cluster = start;
while chain.len() < max_clusters {
if cluster < 2 {
break;
}
chain.push(cluster);
let next = fat_entry(fat, variant, cluster);
if next < 2 || is_bad(variant, next) || is_eoc(variant, next) {
break;
}
cluster = next;
}
chain
}
#[cfg(test)]
mod tests {
use super::{fat_entry, follow_chain, is_bad, is_eoc};
use crate::boot::FatVariant::{Fat12, Fat16, Fat32};
#[test]
fn fat12_packs_two_entries_per_three_bytes() {
let mut fat = vec![0u8; 16];
fat[3] = 0x23;
fat[4] = 0xC1;
fat[5] = 0xAB;
assert_eq!(fat_entry(&fat, Fat12, 2), 0x123);
assert_eq!(fat_entry(&fat, Fat12, 3), 0xABC);
}
#[test]
fn fat16_entries_are_u16() {
let mut fat = vec![0u8; 16];
fat[4..6].copy_from_slice(&0x1234u16.to_le_bytes()); assert_eq!(fat_entry(&fat, Fat16, 2), 0x1234);
}
#[test]
fn fat32_masks_to_28_bits() {
let mut fat = vec![0u8; 32];
fat[8..12].copy_from_slice(&0xF123_4567u32.to_le_bytes()); assert_eq!(fat_entry(&fat, Fat32, 2), 0x0123_4567); }
#[test]
fn out_of_range_cluster_reads_zero() {
assert_eq!(fat_entry(&[], Fat16, 9999), 0);
}
#[test]
fn classifies_eoc_and_bad_markers() {
assert!(is_eoc(Fat12, 0xFF8));
assert!(is_eoc(Fat12, 0xFFF));
assert!(!is_eoc(Fat12, 0xFF7));
assert!(is_bad(Fat12, 0xFF7));
assert!(is_eoc(Fat16, 0xFFF8));
assert!(is_bad(Fat16, 0xFFF7));
assert!(is_eoc(Fat32, 0x0FFF_FFF8));
assert!(is_bad(Fat32, 0x0FFF_FFF7));
assert!(!is_eoc(Fat32, 0x0000_0003));
}
#[test]
fn follows_a_simple_chain_to_eoc() {
let mut fat = vec![0u8; 32];
fat[4..6].copy_from_slice(&3u16.to_le_bytes()); fat[6..8].copy_from_slice(&0xFFFFu16.to_le_bytes()); assert_eq!(follow_chain(&fat, Fat16, 2, 100), vec![2, 3]);
}
#[test]
fn chain_cap_defeats_a_self_cycle() {
let mut fat = vec![0u8; 32];
fat[4..6].copy_from_slice(&2u16.to_le_bytes());
let chain = follow_chain(&fat, Fat16, 2, 5);
assert_eq!(chain.len(), 5); }
#[test]
fn free_or_reserved_terminates_chain() {
let fat = vec![0u8; 32]; assert_eq!(follow_chain(&fat, Fat16, 2, 100), vec![2]);
}
#[test]
fn reserved_start_cluster_yields_empty_chain() {
assert!(follow_chain(&[0u8; 32], Fat16, 1, 100).is_empty());
}
}