#![allow(dead_code)]
pub const ENTRY_SIZE: usize = 32;
pub const ENTRY_ALLOCATION_BITMAP: u8 = 0x81;
pub const ENTRY_UPCASE_TABLE: u8 = 0x82;
pub const ENTRY_VOLUME_LABEL: u8 = 0x83;
pub const ENTRY_FILE: u8 = 0x85;
pub const ENTRY_STREAM_EXTENSION: u8 = 0xC0;
pub const ENTRY_FILE_NAME: u8 = 0xC1;
pub const ENTRY_INUSE: u8 = 0x80;
pub const ATTR_READ_ONLY: u16 = 0x0001;
pub const ATTR_HIDDEN: u16 = 0x0002;
pub const ATTR_SYSTEM: u16 = 0x0004;
pub const ATTR_DIRECTORY: u16 = 0x0010;
pub const ATTR_ARCHIVE: u16 = 0x0020;
pub const SECFLAG_ALLOC_POSSIBLE: u8 = 0x01;
pub const SECFLAG_NO_FAT_CHAIN: u8 = 0x02;
pub const MAX_NAME_UNITS: usize = 255;
pub const NAME_UNITS_PER_ENTRY: usize = 15;
pub const MAX_CLUSTER_COUNT: u32 = u32::MAX - 10;
pub const FAT_FREE: u32 = 0x0000_0000;
pub const FAT_BAD: u32 = 0xFFFF_FFF7;
pub const FAT_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 {
FAT_FREE => FatEntry::Free,
FAT_BAD => FatEntry::Bad,
FAT_EOC => FatEntry::Eoc,
n => FatEntry::Next(n),
}
}
pub fn set_checksum(set: &[u8]) -> u16 {
let mut sum: u16 = 0;
for (i, &b) in set.iter().enumerate() {
if i == 2 || i == 3 {
continue;
}
sum = sum.rotate_right(1).wrapping_add(b as u16);
}
sum
}
pub fn set_checksum_step(sum: u16, index: usize, entry: &[u8; ENTRY_SIZE]) -> u16 {
let mut sum = sum;
for (i, &b) in entry.iter().enumerate() {
if index == 0 && (i == 2 || i == 3) {
continue;
}
sum = sum.rotate_right(1).wrapping_add(b as u16);
}
sum
}
pub fn name_hash(upcased_le_bytes: &[u8]) -> u16 {
let mut hash: u16 = 0;
for &b in upcased_le_bytes {
hash = hash.rotate_right(1).wrapping_add(b as u16);
}
hash
}
pub fn name_hash_step(hash: u16, unit: u16) -> u16 {
let mut hash = hash;
for b in unit.to_le_bytes() {
hash = hash.rotate_right(1).wrapping_add(b as u16);
}
hash
}
pub fn table_checksum(bytes: &[u8]) -> u32 {
table_checksum_step(0, bytes)
}
pub fn table_checksum_step(sum: u32, bytes: &[u8]) -> u32 {
let mut sum = sum;
for &b in bytes {
sum = sum.rotate_right(1).wrapping_add(b as u32);
}
sum
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Boot {
pub partition_offset: u64,
pub volume_length: u64,
pub fat_offset: u32,
pub fat_length: u32,
pub cluster_heap_offset: u32,
pub cluster_count: u32,
pub first_cluster_of_root_directory: u32,
pub volume_serial_number: u32,
pub fs_revision_major: u8,
pub fs_revision_minor: u8,
pub volume_flags: u16,
pub bytes_per_sector_shift: u8,
pub sectors_per_cluster_shift: u8,
pub number_of_fats: u8,
pub drive_select: u8,
pub percent_in_use: u8,
}
impl Boot {
pub fn bytes_per_sector(&self) -> u32 {
1u32 << self.bytes_per_sector_shift
}
pub fn sectors_per_cluster(&self) -> u32 {
1u32 << self.sectors_per_cluster_shift
}
pub fn bytes_per_cluster(&self) -> u32 {
self.bytes_per_sector() << self.sectors_per_cluster_shift
}
pub fn decode(b: &[u8]) -> Result<Self, &'static str> {
if b.len() < 512 {
return Err("boot sector is shorter than 512 bytes");
}
if &b[3..11] != b"EXFAT " {
return Err("missing \"EXFAT \" signature at offset 3");
}
if b[11..64].iter().any(|&x| x != 0) {
return Err("MustBeZero region is non-zero");
}
if b[510] != 0x55 || b[511] != 0xAA {
return Err("missing 0x55AA boot-sector signature");
}
let bytes_per_sector_shift = b[108];
let sectors_per_cluster_shift = b[109];
if !(9..=12).contains(&bytes_per_sector_shift) {
return Err("invalid BytesPerSectorShift (must be 9..=12)");
}
if bytes_per_sector_shift as u32 + sectors_per_cluster_shift as u32 > 25 {
return Err("BytesPerSectorShift + SectorsPerClusterShift exceeds 25");
}
let number_of_fats = b[110];
if number_of_fats != 1 && number_of_fats != 2 {
return Err("NumberOfFats must be 1 or 2");
}
let cluster_count = le32(b, 92);
if cluster_count > MAX_CLUSTER_COUNT {
return Err("ClusterCount exceeds the specification maximum");
}
let fs_revision = le16(b, 104);
Ok(Self {
partition_offset: le64(b, 64),
volume_length: le64(b, 72),
fat_offset: le32(b, 80),
fat_length: le32(b, 84),
cluster_heap_offset: le32(b, 88),
cluster_count,
first_cluster_of_root_directory: le32(b, 96),
volume_serial_number: le32(b, 100),
fs_revision_major: (fs_revision >> 8) as u8,
fs_revision_minor: (fs_revision & 0xff) as u8,
volume_flags: le16(b, 106),
bytes_per_sector_shift,
sectors_per_cluster_shift,
number_of_fats,
drive_select: b[111],
percent_in_use: b[112],
})
}
}
pub fn le16(b: &[u8], off: usize) -> u16 {
u16::from_le_bytes([b[off], b[off + 1]])
}
pub fn le32(b: &[u8], off: usize) -> u32 {
u32::from_le_bytes([b[off], b[off + 1], b[off + 2], b[off + 3]])
}
pub fn le64(b: &[u8], off: usize) -> u64 {
let mut v = [0u8; 8];
v.copy_from_slice(&b[off..off + 8]);
u64::from_le_bytes(v)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn checksums_match_their_streaming_form() {
let mut set = [0u8; 3 * ENTRY_SIZE];
for (i, b) in set.iter_mut().enumerate() {
*b = (i * 7 + 1) as u8;
}
let one_shot = set_checksum(&set);
let mut streamed = 0u16;
for i in 0..3 {
let entry: &[u8; ENTRY_SIZE] = (&set[i * ENTRY_SIZE..(i + 1) * ENTRY_SIZE])
.try_into()
.unwrap();
streamed = set_checksum_step(streamed, i, entry);
}
assert_eq!(one_shot, streamed);
let units = [0x0041u16, 0x1234, 0xFF21];
let mut bytes = [0u8; 6];
for (i, u) in units.iter().enumerate() {
bytes[i * 2..i * 2 + 2].copy_from_slice(&u.to_le_bytes());
}
let one_shot = name_hash(&bytes);
let streamed = units.iter().fold(0u16, |h, u| name_hash_step(h, *u));
assert_eq!(one_shot, streamed);
}
#[test]
fn table_checksum_known_values() {
assert_eq!(table_checksum(&[]), 0);
assert_eq!(table_checksum(&[0x01]), 1);
assert_eq!(table_checksum(&[0x01, 0x02]), 0x8000_0002);
assert_eq!(
table_checksum_step(table_checksum(&[0x01]), &[0x02]),
table_checksum(&[0x01, 0x02])
);
}
#[test]
fn fat_sentinels_classify() {
assert_eq!(classify(0), FatEntry::Free);
assert_eq!(classify(FAT_BAD), FatEntry::Bad);
assert_eq!(classify(FAT_EOC), FatEntry::Eoc);
assert_eq!(classify(7), FatEntry::Next(7));
assert_eq!(classify(0xFFFF_FFFE), FatEntry::Next(0xFFFF_FFFE));
}
}