pub(crate) const NTFS_OEM: &[u8; 8] = b"NTFS ";
#[derive(Debug, Clone)]
pub struct BootSector {
pub bytes_per_sector: u16,
pub sectors_per_cluster: u8,
pub total_sectors: u64,
pub mft_lcn: u64,
pub mft_mirr_lcn: u64,
pub clusters_per_mft_record: i8,
pub clusters_per_index_record: i8,
pub volume_serial: u64,
}
pub(crate) const MAX_CLUSTER_SIZE: u32 = 2 * 1024 * 1024;
pub(crate) const MIN_RECORD_SIZE: u32 = 256;
pub(crate) const MAX_RECORD_SIZE: u32 = 1024 * 1024;
impl BootSector {
pub fn decode(buf: &[u8]) -> Option<Self> {
if buf.len() < 80 || &buf[3..11] != NTFS_OEM {
return None;
}
let bytes_per_sector = u16::from_le_bytes(buf[11..13].try_into().ok()?);
let sectors_per_cluster = buf[13];
let total_sectors = u64::from_le_bytes(buf[0x28..0x30].try_into().ok()?);
let mft_lcn = u64::from_le_bytes(buf[0x30..0x38].try_into().ok()?);
let mft_mirr_lcn = u64::from_le_bytes(buf[0x38..0x40].try_into().ok()?);
let clusters_per_mft_record = buf[0x40] as i8;
let clusters_per_index_record = buf[0x44] as i8;
let volume_serial = u64::from_le_bytes(buf[0x48..0x50].try_into().ok()?);
if !(256..=4096).contains(&bytes_per_sector) || !bytes_per_sector.is_power_of_two() {
return None;
}
if sectors_per_cluster == 0 || !sectors_per_cluster.is_power_of_two() {
return None;
}
let cluster_size =
u32::from(bytes_per_sector).checked_mul(u32::from(sectors_per_cluster))?;
if cluster_size > MAX_CLUSTER_SIZE {
return None;
}
let mft_rec = Self::record_size(clusters_per_mft_record, cluster_size)?;
let idx_rec = Self::record_size(clusters_per_index_record, cluster_size)?;
if !(MIN_RECORD_SIZE..=MAX_RECORD_SIZE).contains(&mft_rec)
|| !(MIN_RECORD_SIZE..=MAX_RECORD_SIZE).contains(&idx_rec)
{
return None;
}
Some(Self {
bytes_per_sector,
sectors_per_cluster,
total_sectors,
mft_lcn,
mft_mirr_lcn,
clusters_per_mft_record,
clusters_per_index_record,
volume_serial,
})
}
pub fn mft_record_size(&self) -> u32 {
Self::record_size(self.clusters_per_mft_record, self.cluster_size())
.expect("mft_record_size validated in BootSector::decode")
}
pub fn index_record_size(&self) -> u32 {
Self::record_size(self.clusters_per_index_record, self.cluster_size())
.expect("index_record_size validated in BootSector::decode")
}
fn record_size(field: i8, cluster_size: u32) -> Option<u32> {
if field >= 0 {
(field as u32).checked_mul(cluster_size)
} else {
1u32.checked_shl((-(field as i32)) as u32)
}
}
pub fn cluster_size(&self) -> u32 {
u32::from(self.bytes_per_sector) * u32::from(self.sectors_per_cluster)
}
}