use super::error::{CfbError, Result};
pub const CFB_SIGNATURE: [u8; 8] = [0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1];
pub const FREE_SECT: u32 = 0xFFFFFFFF;
pub const END_OF_CHAIN: u32 = 0xFFFFFFFE;
pub const FAT_SECT: u32 = 0xFFFFFFFD;
pub const DIFAT_SECT: u32 = 0xFFFFFFFC;
pub const MAX_REG_SECT: u32 = 0xFFFFFFFA;
#[derive(Debug, Clone)]
pub struct CfbHeader {
pub major_version: u16,
pub minor_version: u16,
pub sector_size: usize,
pub mini_sector_size: usize,
pub fat_sector_count: u32,
pub first_dir_sector: u32,
pub mini_stream_cutoff: u32,
pub first_mini_fat_sector: u32,
pub mini_fat_sector_count: u32,
pub first_difat_sector: u32,
pub difat_sector_count: u32,
pub header_difat: Vec<u32>,
}
impl CfbHeader {
pub fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < 512 {
return Err(CfbError::InvalidHeader("buffer too short".into()));
}
if buf[0..8] != CFB_SIGNATURE {
return Err(CfbError::InvalidHeader("bad magic signature".into()));
}
let minor_version = u16::from_le_bytes([buf[0x18], buf[0x19]]);
let major_version = u16::from_le_bytes([buf[0x1A], buf[0x1B]]);
if major_version != 3 && major_version != 4 {
return Err(CfbError::InvalidHeader(format!(
"unsupported major version: {major_version}"
)));
}
let byte_order = u16::from_le_bytes([buf[0x1C], buf[0x1D]]);
if byte_order != 0xFFFE {
return Err(CfbError::InvalidHeader(format!(
"unsupported byte order: 0x{byte_order:04X}"
)));
}
let sector_power = u16::from_le_bytes([buf[0x1E], buf[0x1F]]);
let sector_size = 1usize << sector_power;
if major_version == 3 && sector_size != 512 {
return Err(CfbError::InvalidHeader("v3 sector size must be 512".into()));
}
if major_version == 4 && sector_size != 4096 {
return Err(CfbError::InvalidHeader("v4 sector size must be 4096".into()));
}
let mini_sector_power = u16::from_le_bytes([buf[0x20], buf[0x21]]);
let mini_sector_size = 1usize << mini_sector_power;
let fat_sector_count = u32::from_le_bytes([buf[0x2C], buf[0x2D], buf[0x2E], buf[0x2F]]);
let first_dir_sector = u32::from_le_bytes([buf[0x30], buf[0x31], buf[0x32], buf[0x33]]);
let mini_stream_cutoff = u32::from_le_bytes([buf[0x38], buf[0x39], buf[0x3A], buf[0x3B]]);
let first_mini_fat_sector =
u32::from_le_bytes([buf[0x3C], buf[0x3D], buf[0x3E], buf[0x3F]]);
let mini_fat_sector_count =
u32::from_le_bytes([buf[0x40], buf[0x41], buf[0x42], buf[0x43]]);
let first_difat_sector = u32::from_le_bytes([buf[0x44], buf[0x45], buf[0x46], buf[0x47]]);
let difat_sector_count = u32::from_le_bytes([buf[0x48], buf[0x49], buf[0x4A], buf[0x4B]]);
let mut header_difat = Vec::with_capacity(109);
for i in 0..109 {
let off = 0x4C + i * 4;
let val = u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], buf[off + 3]]);
header_difat.push(val);
}
Ok(Self {
major_version,
minor_version,
sector_size,
mini_sector_size,
fat_sector_count,
first_dir_sector,
mini_stream_cutoff,
first_mini_fat_sector,
mini_fat_sector_count,
first_difat_sector,
difat_sector_count,
header_difat,
})
}
#[inline]
pub fn sector_offset(&self, sector: u32) -> u64 {
(sector as u64 + 1) * self.sector_size as u64
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_v3_header() -> Vec<u8> {
let mut buf = vec![0u8; 512];
buf[0..8].copy_from_slice(&CFB_SIGNATURE);
buf[0x18..0x1A].copy_from_slice(&0x003Eu16.to_le_bytes());
buf[0x1A..0x1C].copy_from_slice(&3u16.to_le_bytes());
buf[0x1C..0x1E].copy_from_slice(&0xFFFEu16.to_le_bytes());
buf[0x1E..0x20].copy_from_slice(&9u16.to_le_bytes());
buf[0x20..0x22].copy_from_slice(&6u16.to_le_bytes());
buf[0x2C..0x30].copy_from_slice(&1u32.to_le_bytes());
buf[0x30..0x34].copy_from_slice(&0u32.to_le_bytes());
buf[0x38..0x3C].copy_from_slice(&4096u32.to_le_bytes());
buf[0x3C..0x40].copy_from_slice(&END_OF_CHAIN.to_le_bytes());
for i in 0..109 {
let off = 0x4C + i * 4;
buf[off..off + 4].copy_from_slice(&FREE_SECT.to_le_bytes());
}
buf[0x4C..0x50].copy_from_slice(&1u32.to_le_bytes());
buf
}
#[test]
fn parse_valid_v3_header() {
let buf = build_v3_header();
let header = CfbHeader::parse(&buf).unwrap();
assert_eq!(header.major_version, 3);
assert_eq!(header.sector_size, 512);
assert_eq!(header.mini_sector_size, 64);
assert_eq!(header.fat_sector_count, 1);
assert_eq!(header.first_dir_sector, 0);
assert_eq!(header.mini_stream_cutoff, 4096);
assert_eq!(header.first_mini_fat_sector, END_OF_CHAIN);
}
#[test]
fn bad_signature_rejected() {
let mut buf = build_v3_header();
buf[0] = 0x00;
assert!(CfbHeader::parse(&buf).is_err());
}
#[test]
fn bad_version_rejected() {
let mut buf = build_v3_header();
buf[0x1A..0x1C].copy_from_slice(&5u16.to_le_bytes());
assert!(CfbHeader::parse(&buf).is_err());
}
#[test]
fn bad_byte_order_rejected() {
let mut buf = build_v3_header();
buf[0x1C..0x1E].copy_from_slice(&0xFFFFu16.to_le_bytes());
assert!(CfbHeader::parse(&buf).is_err());
}
#[test]
fn sector_offset_v3() {
let buf = build_v3_header();
let header = CfbHeader::parse(&buf).unwrap();
assert_eq!(header.sector_offset(0), 512);
assert_eq!(header.sector_offset(1), 1024);
assert_eq!(header.sector_offset(5), 3072);
}
#[test]
fn too_short_buffer_rejected() {
let buf = vec![0u8; 100];
assert!(CfbHeader::parse(&buf).is_err());
}
}