//! Bounded, read-only GPT parser for 512-byte logical sectors. pub const ENTRY_COUNT: usize = 128; pub const ENTRY_BYTES: usize = 128; pub const ENTRY_ARRAY_BYTES: usize = ENTRY_COUNT * ENTRY_BYTES; pub const MAX_ACTIVE: usize = 16; pub const SECTOR_BYTES: usize = 512; const ARRAY_SECTORS: u64 = (ENTRY_ARRAY_BYTES / SECTOR_BYTES) as u64; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { EmptyDevice, InvalidHeader, BadHeaderCrc, UnsupportedLayout, BadArrayCrc, InconsistentCopies, InvalidEntry, TooManyPartitions, OutsideCapacity, Overlap, Overflow, } #[derive(Debug, PartialEq, Eq)] pub enum StreamError { Parse(Error), Read(E), } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Partition { pub slot: u8, pub type_guid: [u8; 16], pub unique_guid: [u8; 16], pub first_lba: u64, pub last_lba: u64, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Table { pub entries: [Option; MAX_ACTIVE], pub count: u8, pub disk_sectors: u64, pub disk_guid: [u8; 16], } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct MountState { pub table: Option, pub generation: u64, } impl MountState { pub const fn new() -> Self { Self { table: None, generation: 0 } } pub fn mount(&mut self, primary_header: &[u8; SECTOR_BYTES], primary_entries: &[u8; ENTRY_ARRAY_BYTES], backup_header: &[u8; SECTOR_BYTES], backup_entries: &[u8; ENTRY_ARRAY_BYTES], disk_sectors: u64) -> Result { let table = parse(primary_header, primary_entries, backup_header, backup_entries, disk_sectors)?; let generation = self.generation.checked_add(1).ok_or(Error::Overflow)?; self.table = Some(table); self.generation = generation; Ok(table) } /// The reader supplies one physical sector at a time. No partial table /// is published if a read or parse fails. pub fn mount_stream(&mut self, primary_header: &[u8; SECTOR_BYTES], backup_header: &[u8; SECTOR_BYTES], disk_sectors: u64, read: F) -> Result> where F: FnMut(u64, &mut [u8; SECTOR_BYTES]) -> Result<(), E> { let table = parse_stream(primary_header, backup_header, disk_sectors, read)?; let generation = self.generation.checked_add(1) .ok_or(StreamError::Parse(Error::Overflow))?; self.table = Some(table); self.generation = generation; Ok(table) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct Header { first_usable: u64, last_usable: u64, disk_guid: [u8; 16], array_lba: u64, array_crc: u32, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Layout { pub primary_array_lba: u64, pub backup_array_lba: u64, pub first_usable: u64, pub last_usable: u64, pub disk_guid: [u8; 16], pub array_crc: u32, } fn le32(bytes: &[u8], offset: usize) -> u32 { u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap()) } fn le64(bytes: &[u8], offset: usize) -> u64 { u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap()) } fn guid(bytes: &[u8], offset: usize) -> [u8; 16] { bytes[offset..offset + 16].try_into().unwrap() } /// IEEE CRC-32, with the caller zeroing the GPT header's CRC field. pub fn crc32(bytes: &[u8]) -> u32 { !crc32_update(!0u32, bytes) } fn crc32_update(mut crc: u32, bytes: &[u8]) -> u32 { for &byte in bytes { crc ^= u32::from(byte); for _ in 0..8 { let mask = 0u32.wrapping_sub(crc & 1); crc = (crc >> 1) ^ (0xedb8_8320 & mask); } } crc } fn header(raw: &[u8; SECTOR_BYTES], here: u64, there: u64, disk_sectors: u64) -> Result { if &raw[0..8] != b"EFI PART" || le32(raw, 8) != 0x0001_0000 { return Err(Error::InvalidHeader); } let size = usize::try_from(le32(raw, 12)).map_err(|_| Error::InvalidHeader)?; if !(92..=SECTOR_BYTES).contains(&size) || le32(raw, 20) != 0 || raw[size..].iter().any(|&byte| byte != 0) { return Err(Error::InvalidHeader); } let stored_crc = le32(raw, 16); let mut copy = *raw; copy[16..20].fill(0); if crc32(©[..size]) != stored_crc { return Err(Error::BadHeaderCrc); } if le64(raw, 24) != here || le64(raw, 32) != there { return Err(Error::InvalidHeader); } let first_usable = le64(raw, 40); let last_usable = le64(raw, 48); if first_usable < 34 || first_usable > last_usable || last_usable >= disk_sectors.saturating_sub(1) { return Err(Error::OutsideCapacity); } if le32(raw, 80) != ENTRY_COUNT as u32 || le32(raw, 84) != ENTRY_BYTES as u32 { return Err(Error::UnsupportedLayout); } let array_lba = le64(raw, 72); let array_end = array_lba.checked_add(ARRAY_SECTORS).ok_or(Error::Overflow)?; if here == 1 { if array_lba < 2 || array_end > first_usable { return Err(Error::OutsideCapacity); } } else if array_lba <= last_usable || array_end > here { return Err(Error::OutsideCapacity); } Ok(Header { first_usable, last_usable, disk_guid: guid(raw, 56), array_lba, array_crc: le32(raw, 88) }) } /// Validate both headers and bounded array positions before any array I/O. pub fn inspect_headers(primary_header: &[u8; SECTOR_BYTES], backup_header: &[u8; SECTOR_BYTES], disk_sectors: u64) -> Result { let last_lba = disk_sectors.checked_sub(1).ok_or(Error::EmptyDevice)?; if last_lba < 67 { return Err(Error::OutsideCapacity); } let primary = header(primary_header, 1, last_lba, disk_sectors)?; let backup = header(backup_header, last_lba, 1, disk_sectors)?; if primary.first_usable != backup.first_usable || primary.last_usable != backup.last_usable || primary.disk_guid != backup.disk_guid || primary.array_crc != backup.array_crc { return Err(Error::InconsistentCopies); } Ok(Layout { primary_array_lba: primary.array_lba, backup_array_lba: backup.array_lba, first_usable: primary.first_usable, last_usable: primary.last_usable, disk_guid: primary.disk_guid, array_crc: primary.array_crc }) } pub fn parse(primary_header: &[u8; SECTOR_BYTES], primary_entries: &[u8; ENTRY_ARRAY_BYTES], backup_header: &[u8; SECTOR_BYTES], backup_entries: &[u8; ENTRY_ARRAY_BYTES], disk_sectors: u64) -> Result { let layout = inspect_headers(primary_header, backup_header, disk_sectors)?; if false || crc32(backup_entries) != layout.array_crc { return Err(Error::BadArrayCrc); } if primary_entries != backup_entries { return Err(Error::InconsistentCopies); } let mut table = empty_table(disk_sectors, layout.disk_guid); for slot in 0..ENTRY_COUNT { let entry = &primary_entries[slot * ENTRY_BYTES..(slot + 1) * ENTRY_BYTES]; insert_entry(&mut table, layout, slot, entry)?; } Ok(table) } fn empty_table(disk_sectors: u64, disk_guid: [u8; 16]) -> Table { Table { entries: [None; MAX_ACTIVE], count: 0, disk_sectors, disk_guid } } fn insert_entry(table: &mut Table, layout: Layout, slot: usize, entry: &[u8]) -> Result<(), Error> { let type_guid = guid(entry, 0); if type_guid == [0; 16] { if entry.iter().any(|&byte| byte != 0) { return Err(Error::InvalidEntry); } return Ok(()); } let unique_guid = guid(entry, 16); if unique_guid == [0; 16] { return Err(Error::InvalidEntry); } let first_lba = le64(entry, 32); let last_lba = le64(entry, 40); if first_lba > last_lba { return Err(Error::InvalidEntry); } if first_lba < layout.first_usable || last_lba > layout.last_usable { return Err(Error::OutsideCapacity); } let partition = Partition { slot: slot as u8, type_guid, unique_guid, first_lba, last_lba }; for prior in table.entries.iter().flatten() { if prior.unique_guid == unique_guid { return Err(Error::InvalidEntry); } if first_lba <= prior.last_lba && prior.first_lba <= last_lba { return Err(Error::Overlap); } } let next = usize::from(table.count); if next == MAX_ACTIVE { return Err(Error::TooManyPartitions); } table.entries[next] = Some(partition); table.count += 1; Ok(()) } /// Read and compare paired GPT arrays one sector at a time. The reader owns /// block/DMA buffers and must not write to the device. pub fn parse_stream(primary_header: &[u8; SECTOR_BYTES], backup_header: &[u8; SECTOR_BYTES], disk_sectors: u64, mut read: F) -> Result> where F: FnMut(u64, &mut [u8; SECTOR_BYTES]) -> Result<(), E> { let layout = inspect_headers(primary_header, backup_header, disk_sectors) .map_err(StreamError::Parse)?; let mut table = empty_table(disk_sectors, layout.disk_guid); let mut primary_sector = [0u8; SECTOR_BYTES]; let mut backup_sector = [0u8; SECTOR_BYTES]; let mut crc = !0u32; for sector in 0..ARRAY_SECTORS { read(layout.primary_array_lba + sector, &mut primary_sector) .map_err(StreamError::Read)?; read(layout.backup_array_lba + sector, &mut backup_sector) .map_err(StreamError::Read)?; if primary_sector != backup_sector { return Err(StreamError::Parse(Error::InconsistentCopies)); } crc = crc32_update(crc, &primary_sector); for offset in 0..SECTOR_BYTES / ENTRY_BYTES { let slot = sector as usize * (SECTOR_BYTES / ENTRY_BYTES) + offset; let entry = &primary_sector[offset * ENTRY_BYTES..(offset + 1) * ENTRY_BYTES]; insert_entry(&mut table, layout, slot, entry) .map_err(StreamError::Parse)?; } } if !crc != layout.array_crc { return Err(StreamError::Parse(Error::BadArrayCrc)); } Ok(table) }