use crate::Result;
use crate::block::BlockDevice;
use super::volume_header::{ExtentDescriptor, ForkData};
pub const KIND_LEAF: i8 = -1;
pub const KIND_INDEX: i8 = 0;
pub const KIND_HEADER: i8 = 1;
pub const NODE_DESCRIPTOR_SIZE: usize = 14;
pub const HEADER_REC_SIZE: usize = 106;
#[derive(Debug, Clone, Copy)]
pub struct NodeDescriptor {
pub f_link: u32,
pub b_link: u32,
pub kind: i8,
pub height: u8,
pub num_records: u16,
}
impl NodeDescriptor {
pub fn decode(buf: &[u8]) -> Result<Self> {
if buf.len() < NODE_DESCRIPTOR_SIZE {
return Err(crate::Error::InvalidImage(
"hfs+: short node descriptor".into(),
));
}
Ok(Self {
f_link: u32::from_be_bytes(buf[0..4].try_into().unwrap()),
b_link: u32::from_be_bytes(buf[4..8].try_into().unwrap()),
kind: buf[8] as i8,
height: buf[9],
num_records: u16::from_be_bytes(buf[10..12].try_into().unwrap()),
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct BTreeHeader {
pub tree_depth: u16,
pub root_node: u32,
pub leaf_records: u32,
pub first_leaf_node: u32,
pub last_leaf_node: u32,
pub node_size: u16,
pub max_key_length: u16,
pub total_nodes: u32,
pub free_nodes: u32,
pub clump_size: u32,
pub btree_type: u8,
pub key_compare_type: u8,
pub attributes: u32,
}
impl BTreeHeader {
pub fn decode(buf: &[u8]) -> Result<Self> {
if buf.len() < HEADER_REC_SIZE {
return Err(crate::Error::InvalidImage("hfs+: short BTHeaderRec".into()));
}
let h = Self {
tree_depth: u16::from_be_bytes(buf[0..2].try_into().unwrap()),
root_node: u32::from_be_bytes(buf[2..6].try_into().unwrap()),
leaf_records: u32::from_be_bytes(buf[6..10].try_into().unwrap()),
first_leaf_node: u32::from_be_bytes(buf[10..14].try_into().unwrap()),
last_leaf_node: u32::from_be_bytes(buf[14..18].try_into().unwrap()),
node_size: u16::from_be_bytes(buf[18..20].try_into().unwrap()),
max_key_length: u16::from_be_bytes(buf[20..22].try_into().unwrap()),
total_nodes: u32::from_be_bytes(buf[22..26].try_into().unwrap()),
free_nodes: u32::from_be_bytes(buf[26..30].try_into().unwrap()),
clump_size: u32::from_be_bytes(buf[32..36].try_into().unwrap()),
btree_type: buf[36],
key_compare_type: buf[37],
attributes: u32::from_be_bytes(buf[38..42].try_into().unwrap()),
};
if h.node_size == 0 || !h.node_size.is_power_of_two() {
return Err(crate::Error::InvalidImage(format!(
"hfs+: BTHeaderRec node_size {} is not a positive power of two",
h.node_size
)));
}
Ok(h)
}
}
pub struct ForkReader {
pub base_offset: u64,
pub block_size: u32,
pub extents: Vec<ExtentDescriptor>,
pub logical_size: u64,
}
impl ForkReader {
pub fn from_inline(fork: &ForkData, block_size: u32, what: &str) -> Result<Self> {
if u64::from(fork.total_blocks) > fork.inline_blocks() {
return Err(crate::Error::Unsupported(format!(
"hfs+: {what} fork has {} allocation blocks but only \
{} inline; extents-overflow file is not supported in v1",
fork.total_blocks,
fork.inline_blocks()
)));
}
let extents: Vec<ExtentDescriptor> = fork
.extents
.iter()
.copied()
.filter(|e| e.block_count != 0)
.collect();
Ok(Self {
base_offset: 0,
block_size,
extents,
logical_size: fork.logical_size,
})
}
pub fn from_inline_plus_overflow(
fork: &ForkData,
overflow: &[ExtentDescriptor],
block_size: u32,
what: &str,
) -> Result<Self> {
let mut extents: Vec<ExtentDescriptor> = fork
.extents
.iter()
.copied()
.filter(|e| e.block_count != 0)
.collect();
extents.extend(overflow.iter().copied().filter(|e| e.block_count != 0));
let covered: u64 = extents.iter().map(|e| u64::from(e.block_count)).sum();
if covered < u64::from(fork.total_blocks) {
return Err(crate::Error::InvalidImage(format!(
"hfs+: {what} fork still missing extents after overflow walk \
(covered {covered} blocks, expected {})",
fork.total_blocks
)));
}
Ok(Self {
base_offset: 0,
block_size,
extents,
logical_size: fork.logical_size,
})
}
fn translate(&self, offset: u64) -> Option<(u64, u64)> {
let mut walked: u64 = 0;
for ext in &self.extents {
let len = u64::from(ext.block_count) * u64::from(self.block_size);
if offset < walked + len {
let within = offset - walked;
let device = self.base_offset
+ u64::from(ext.start_block) * u64::from(self.block_size)
+ within;
let avail = len - within;
return Some((device, avail));
}
walked += len;
}
None
}
pub fn read(&self, dev: &mut dyn BlockDevice, offset: u64, buf: &mut [u8]) -> Result<()> {
let mut pos = offset;
let mut written = 0usize;
while written < buf.len() {
let (dev_off, avail) = self.translate(pos).ok_or_else(|| {
crate::Error::InvalidImage(format!(
"hfs+: fork read past mapped extents (offset {pos})"
))
})?;
let want = (buf.len() - written).min(avail as usize);
dev.read_at(dev_off, &mut buf[written..written + want])?;
pos += want as u64;
written += want;
}
Ok(())
}
}
pub fn read_node(
dev: &mut dyn BlockDevice,
fork: &ForkReader,
node_idx: u32,
node_size: u32,
) -> Result<Vec<u8>> {
let off = u64::from(node_idx) * u64::from(node_size);
let mut buf = vec![0u8; node_size as usize];
fork.read(dev, off, &mut buf)?;
Ok(buf)
}
pub fn record_offsets(node: &[u8], num_records: u16) -> Result<Vec<u16>> {
let n = num_records as usize;
if node.len() < (n + 1) * 2 {
return Err(crate::Error::InvalidImage(
"hfs+: node too small to hold its record-offset table".into(),
));
}
let total = node.len();
let mut offs = Vec::with_capacity(n + 1);
for i in 0..=n {
let off_at = total - 2 * (i + 1);
let val = u16::from_be_bytes([node[off_at], node[off_at + 1]]);
offs.push(val);
}
let max_data = total - 2 * (n + 1);
let mut prev: u16 = NODE_DESCRIPTOR_SIZE as u16;
for (i, &o) in offs.iter().enumerate() {
if (o as usize) < NODE_DESCRIPTOR_SIZE || (o as usize) > total {
return Err(crate::Error::InvalidImage(format!(
"hfs+: record offset[{i}] = {o} is out of range (node size {total})"
)));
}
if o < prev {
return Err(crate::Error::InvalidImage(format!(
"hfs+: record offsets not ascending at index {i} ({o} < {prev})"
)));
}
prev = o;
}
if (offs[n] as usize) > max_data + 2 {
return Err(crate::Error::InvalidImage(format!(
"hfs+: final record offset {} exceeds free-space boundary {}",
offs[n], max_data
)));
}
Ok(offs)
}
pub fn record_bytes<'a>(node: &'a [u8], offs: &[u16], i: usize) -> &'a [u8] {
let start = offs[i] as usize;
let end = offs[i + 1] as usize;
&node[start..end]
}
#[cfg(test)]
mod tests {
use super::*;
fn synth_node(records: &[&[u8]]) -> Vec<u8> {
let node_size = 512usize;
let mut node = vec![0u8; node_size];
node[8] = KIND_LEAF as u8;
node[9] = 1;
node[10..12].copy_from_slice(&(records.len() as u16).to_be_bytes());
let mut cursor = NODE_DESCRIPTOR_SIZE;
let mut offsets = Vec::with_capacity(records.len() + 1);
for r in records {
offsets.push(cursor as u16);
node[cursor..cursor + r.len()].copy_from_slice(r);
cursor += r.len();
}
offsets.push(cursor as u16);
for (i, &o) in offsets.iter().enumerate() {
let pos = node_size - 2 * (i + 1);
node[pos..pos + 2].copy_from_slice(&o.to_be_bytes());
}
node
}
#[test]
fn decode_node_descriptor() {
let mut buf = [0u8; NODE_DESCRIPTOR_SIZE];
buf[0..4].copy_from_slice(&42u32.to_be_bytes());
buf[4..8].copy_from_slice(&0u32.to_be_bytes());
buf[8] = KIND_INDEX as u8;
buf[9] = 2;
buf[10..12].copy_from_slice(&7u16.to_be_bytes());
let d = NodeDescriptor::decode(&buf).unwrap();
assert_eq!(d.f_link, 42);
assert_eq!(d.kind, KIND_INDEX);
assert_eq!(d.height, 2);
assert_eq!(d.num_records, 7);
}
#[test]
fn record_offset_table_round_trip() {
let node = synth_node(&[b"aa", b"bbbb", b"cccccc"]);
let nd = NodeDescriptor::decode(&node).unwrap();
assert_eq!(nd.num_records, 3);
let offs = record_offsets(&node, nd.num_records).unwrap();
assert_eq!(offs.len(), 4);
assert_eq!(offs[0], NODE_DESCRIPTOR_SIZE as u16);
assert_eq!(record_bytes(&node, &offs, 0), b"aa");
assert_eq!(record_bytes(&node, &offs, 1), b"bbbb");
assert_eq!(record_bytes(&node, &offs, 2), b"cccccc");
}
#[test]
fn record_offsets_rejects_descending() {
let node_size = 256usize;
let mut node = vec![0u8; node_size];
node[10..12].copy_from_slice(&2u16.to_be_bytes());
node[node_size - 2..node_size].copy_from_slice(&30u16.to_be_bytes());
node[node_size - 4..node_size - 2].copy_from_slice(&50u16.to_be_bytes());
node[node_size - 6..node_size - 4].copy_from_slice(&20u16.to_be_bytes());
assert!(record_offsets(&node, 2).is_err());
}
#[test]
fn fork_reader_translates_across_extents() {
let fr = ForkReader {
base_offset: 0,
block_size: 100,
extents: vec![
ExtentDescriptor {
start_block: 2,
block_count: 3,
},
ExtentDescriptor {
start_block: 10,
block_count: 2,
},
],
logical_size: 500,
};
let (d, a) = fr.translate(0).unwrap();
assert_eq!(d, 200);
assert_eq!(a, 300);
let (d, a) = fr.translate(250).unwrap();
assert_eq!(d, 450);
assert_eq!(a, 50);
let (d, a) = fr.translate(300).unwrap();
assert_eq!(d, 1000);
assert_eq!(a, 200);
assert!(fr.translate(600).is_none());
}
}