use crate::types::common::Uuid;
use crate::types::object::{ObjectHeader, ObjectType};
use crate::types::{le_u32, le_u64, take};
pub const CONTAINER_SUPERBLOCK_MAGIC: [u8; 4] = *b"NXSB";
pub const CONTAINER_MINIMUM_BLOCK_SIZE_BYTES: u32 = 4096;
pub const CONTAINER_MAXIMUM_BLOCK_SIZE_BYTES: u32 = 65536;
pub const CONTAINER_MAX_FILE_SYSTEMS: usize = 100;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContainerSuperblock {
pub object: ObjectHeader,
pub block_size: u32,
pub block_count: u64,
pub compatible_features: u64,
pub readonly_compatible_features: u64,
pub incompatible_features: u64,
pub uuid: Uuid,
pub next_object_identifier: u64,
pub next_transaction_identifier: u64,
pub checkpoint_descriptor_area_block_count: u32,
pub checkpoint_data_area_block_count: u32,
pub checkpoint_descriptor_area_block: u64,
pub checkpoint_data_area_block: u64,
pub checkpoint_descriptor_area_start_index: u32,
pub checkpoint_descriptor_area_length: u32,
pub space_manager_oid: u64,
pub object_map_oid: u64,
pub reaper_oid: u64,
pub volume_oids: [u64; CONTAINER_MAX_FILE_SYSTEMS],
}
impl ContainerSuperblock {
pub fn parse(data: &[u8]) -> crate::Result<Self> {
let object = ObjectHeader::parse(data)?;
if object.kind() != ObjectType::ContainerSuperblock as u16 {
return Err(crate::ApfsError::InvalidValue("container object type"));
}
let magic = take::<4>(data, 32)?;
if magic != CONTAINER_SUPERBLOCK_MAGIC {
return Err(crate::ApfsError::InvalidMagic {
expected: CONTAINER_SUPERBLOCK_MAGIC,
actual: magic,
});
}
let block_size = le_u32(data, 36)?;
if !(CONTAINER_MINIMUM_BLOCK_SIZE_BYTES..=CONTAINER_MAXIMUM_BLOCK_SIZE_BYTES)
.contains(&block_size)
{
return Err(crate::ApfsError::InvalidValue("container block size"));
}
let mut volume_oids = [0_u64; CONTAINER_MAX_FILE_SYSTEMS];
let mut offset = 184;
for oid in &mut volume_oids {
*oid = le_u64(data, offset)?;
offset += 8;
}
Ok(Self {
object,
block_size,
block_count: le_u64(data, 40)?,
compatible_features: le_u64(data, 48)?,
readonly_compatible_features: le_u64(data, 56)?,
incompatible_features: le_u64(data, 64)?,
uuid: take(data, 72)?,
next_object_identifier: le_u64(data, 88)?,
next_transaction_identifier: le_u64(data, 96)?,
checkpoint_descriptor_area_block_count: le_u32(data, 104)?,
checkpoint_data_area_block_count: le_u32(data, 108)?,
checkpoint_descriptor_area_block: le_u64(data, 112)?,
checkpoint_data_area_block: le_u64(data, 120)?,
checkpoint_descriptor_area_start_index: le_u32(data, 136)?,
checkpoint_descriptor_area_length: le_u32(data, 140)?,
space_manager_oid: le_u64(data, 152)?,
object_map_oid: le_u64(data, 160)?,
reaper_oid: le_u64(data, 168)?,
volume_oids,
})
}
pub fn volumes(&self) -> impl Iterator<Item = u64> + '_ {
self.volume_oids.iter().copied().filter(|oid| *oid != 0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CheckpointMapping {
pub object_type: u32,
pub object_subtype: u32,
pub size: u32,
pub filesystem_identifier: u64,
pub container_identifier: u64,
pub address: u64,
}
impl CheckpointMapping {
pub fn parse(data: &[u8]) -> crate::Result<Self> {
Ok(Self {
object_type: le_u32(data, 0)?,
object_subtype: le_u32(data, 4)?,
size: le_u32(data, 8)?,
filesystem_identifier: le_u64(data, 16)?,
container_identifier: le_u64(data, 24)?,
address: le_u64(data, 32)?,
})
}
pub const fn kind(self) -> u16 {
(self.object_type & 0xffff) as u16
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CheckpointMapBlock {
pub object: ObjectHeader,
pub flags: u32,
#[cfg(any(feature = "alloc", feature = "std"))]
pub mappings: alloc::vec::Vec<CheckpointMapping>,
}
impl CheckpointMapBlock {
#[cfg(any(feature = "alloc", feature = "std"))]
pub fn parse(data: &[u8]) -> crate::Result<Self> {
let object = ObjectHeader::parse(data)?;
if object.kind() != ObjectType::CheckpointMap as u16 {
return Err(crate::ApfsError::InvalidValue("checkpoint map object type"));
}
let flags = le_u32(data, 32)?;
let count = le_u32(data, 36)? as usize;
if count
> data
.len()
.checked_sub(40)
.ok_or(crate::ApfsError::InputTooSmall)?
/ 40
{
return Err(crate::ApfsError::InputTooSmall);
}
let mut mappings = alloc::vec::Vec::with_capacity(count);
let mut offset: usize = 40;
for _ in 0..count {
let end = offset
.checked_add(40)
.ok_or(crate::ApfsError::AddressOverflow)?;
mappings.push(CheckpointMapping::parse(
data.get(offset..end)
.ok_or(crate::ApfsError::InputTooSmall)?,
)?);
offset = end;
}
Ok(Self {
object,
flags,
mappings,
})
}
pub const fn is_last(&self) -> bool {
self.flags & 1 != 0
}
}
#[cfg(all(test, any(feature = "alloc", feature = "std")))]
mod tests {
use super::*;
#[test]
fn oversized_checkpoint_mapping_count_is_rejected() {
let mut block = alloc::vec![0_u8; 4096];
block[24..26].copy_from_slice(&(ObjectType::CheckpointMap as u16).to_le_bytes());
block[36..40].copy_from_slice(&u32::MAX.to_le_bytes());
assert_eq!(
CheckpointMapBlock::parse(&block).unwrap_err(),
crate::ApfsError::InputTooSmall
);
}
}