use super::{
constants::{PARTITION_ENTRY_SIZE, PARTITION_MAP_SIGNATURE},
type_identifiers,
};
use crate::{
Error, Result,
volumes::{VolumeRecord, VolumeSpan},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ApmPartitionMapEntry {
pub total_entry_count: u32,
pub start_block: u32,
pub block_count: u32,
pub name: String,
pub type_identifier: String,
pub data_area_start_block: u32,
pub data_area_block_count: u32,
pub status_flags: u32,
pub boot_code_start_block: u32,
pub boot_code_block_count: u32,
pub boot_code_address: u32,
pub boot_code_entry_point: u32,
pub boot_code_checksum: u32,
pub processor_type: String,
}
impl ApmPartitionMapEntry {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() != PARTITION_ENTRY_SIZE {
return Err(Error::invalid_format(format!(
"apm partition entry must be {PARTITION_ENTRY_SIZE} bytes, got {}",
data.len()
)));
}
if &data[0..2] != PARTITION_MAP_SIGNATURE {
return Err(Error::invalid_format(
"apm partition entry signature is missing".to_string(),
));
}
Ok(Self {
total_entry_count: read_u32_be(data, 4),
start_block: read_u32_be(data, 8),
block_count: read_u32_be(data, 12),
name: read_ascii_string(data, 16, 32),
type_identifier: read_ascii_string(data, 48, 32),
data_area_start_block: read_u32_be(data, 80),
data_area_block_count: read_u32_be(data, 84),
status_flags: read_u32_be(data, 88),
boot_code_start_block: read_u32_be(data, 92),
boot_code_block_count: read_u32_be(data, 96),
boot_code_address: read_u32_be(data, 100),
boot_code_entry_point: read_u32_be(data, 108),
boot_code_checksum: read_u32_be(data, 116),
processor_type: read_ascii_string(data, 120, 16),
})
}
pub fn into_partition_info(self, index: usize, block_size: u16) -> Result<ApmPartitionInfo> {
let block_size = u64::from(block_size);
let byte_offset = u64::from(self.start_block)
.checked_mul(block_size)
.ok_or_else(|| Error::invalid_range("apm partition offset overflow"))?;
let byte_size = u64::from(self.block_count)
.checked_mul(block_size)
.ok_or_else(|| Error::invalid_range("apm partition size overflow"))?;
let mut record = VolumeRecord::new(
index,
VolumeSpan::new(byte_offset, byte_size),
type_identifiers::volume_role_for_type_identifier(&self.type_identifier),
);
if !self.name.is_empty() {
record = record.with_name(self.name.clone());
}
Ok(ApmPartitionInfo {
record,
entry: self,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ApmPartitionInfo {
pub record: VolumeRecord,
pub entry: ApmPartitionMapEntry,
}
fn read_u32_be(data: &[u8], offset: usize) -> u32 {
u32::from_be_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
}
fn read_ascii_string(data: &[u8], offset: usize, len: usize) -> String {
let bytes = &data[offset..offset + len];
let nul = bytes
.iter()
.position(|byte| *byte == 0)
.unwrap_or(bytes.len());
String::from_utf8_lossy(&bytes[..nul]).into_owned()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_partition_entry_fields() {
let mut data = [0u8; PARTITION_ENTRY_SIZE];
data[0..2].copy_from_slice(b"PM");
data[4..8].copy_from_slice(&3u32.to_be_bytes());
data[8..12].copy_from_slice(&64u32.to_be_bytes());
data[12..16].copy_from_slice(&8112u32.to_be_bytes());
data[16..26].copy_from_slice(b"disk image");
data[48..57].copy_from_slice(b"Apple_HFS");
data[80..84].copy_from_slice(&64u32.to_be_bytes());
data[84..88].copy_from_slice(&8112u32.to_be_bytes());
data[88..92].copy_from_slice(&0x4000_0033u32.to_be_bytes());
let entry = ApmPartitionMapEntry::parse(&data).unwrap();
assert_eq!(entry.total_entry_count, 3);
assert_eq!(entry.start_block, 64);
assert_eq!(entry.block_count, 8112);
assert_eq!(entry.name, "disk image");
assert_eq!(entry.type_identifier, "Apple_HFS");
assert_eq!(entry.status_flags, 0x4000_0033);
}
}