use std::collections::HashMap;
use super::obj::{OBJECT_TYPE_BTREE_NODE, ObjPhys};
pub const BTNODE_ROOT: u16 = 0x0001;
pub const BTNODE_LEAF: u16 = 0x0002;
pub const BTNODE_FIXED_KV_SIZE: u16 = 0x0004;
pub const BTREE_INFO_SIZE: usize = 40;
pub const BTOFF_INVALID: u16 = 0xFFFF;
#[derive(Debug)]
pub struct BTreeNode<'a> {
block: &'a [u8],
pub obj: ObjPhys,
pub flags: u16,
pub level: u16,
pub nkeys: u32,
pub toc_off: u16,
pub toc_len: u16,
pub fixed_kv: bool,
pub is_root: bool,
data_start: usize,
pub keys_start: usize,
pub vals_end: usize,
}
impl<'a> BTreeNode<'a> {
pub fn decode(block: &'a [u8]) -> crate::Result<Self> {
if block.len() < 56 {
return Err(crate::Error::InvalidImage(
"apfs: btree node block too small".into(),
));
}
let obj = ObjPhys::decode(block)?;
if obj.obj_type() != OBJECT_TYPE_BTREE_NODE
&& obj.obj_type() != super::obj::OBJECT_TYPE_BTREE
{
return Err(crate::Error::InvalidImage(format!(
"apfs: o_type {:#x} is not BTREE_NODE / BTREE",
obj.obj_type()
)));
}
let flags = u16::from_le_bytes(block[32..34].try_into().unwrap());
let level = u16::from_le_bytes(block[34..36].try_into().unwrap());
let nkeys = u32::from_le_bytes(block[36..40].try_into().unwrap());
let toc_off = u16::from_le_bytes(block[40..42].try_into().unwrap());
let toc_len = u16::from_le_bytes(block[42..44].try_into().unwrap());
let data_start: usize = 56;
let is_root = flags & BTNODE_ROOT != 0;
let fixed_kv = flags & BTNODE_FIXED_KV_SIZE != 0;
let keys_start = data_start
.checked_add(toc_off as usize)
.and_then(|v| v.checked_add(toc_len as usize))
.ok_or_else(|| crate::Error::InvalidImage("apfs: btree ToC overflow".into()))?;
let vals_end_raw = block.len();
let vals_end = if is_root {
vals_end_raw
.checked_sub(BTREE_INFO_SIZE)
.ok_or_else(|| crate::Error::InvalidImage("apfs: btree root too small".into()))?
} else {
vals_end_raw
};
if keys_start > vals_end {
return Err(crate::Error::InvalidImage(
"apfs: btree node key area collides with value area".into(),
));
}
Ok(Self {
block,
obj,
flags,
level,
nkeys,
toc_off,
toc_len,
fixed_kv,
is_root,
data_start,
keys_start,
vals_end,
})
}
pub fn is_leaf(&self) -> bool {
self.flags & BTNODE_LEAF != 0
}
fn raw_entry(&self, index: u32) -> crate::Result<RawEntry> {
if index >= self.nkeys {
return Err(crate::Error::InvalidImage(format!(
"apfs: btree index {index} >= nkeys {}",
self.nkeys
)));
}
let toc_base = self.data_start + self.toc_off as usize;
if self.fixed_kv {
let off = toc_base + (index as usize) * 4;
if off + 4 > self.block.len() {
return Err(crate::Error::InvalidImage(
"apfs: btree fixed-kv ToC out of bounds".into(),
));
}
let k = u16::from_le_bytes(self.block[off..off + 2].try_into().unwrap());
let v = u16::from_le_bytes(self.block[off + 2..off + 4].try_into().unwrap());
Ok(RawEntry {
key_off: k,
key_len: None,
val_off: v,
val_len: None,
})
} else {
let off = toc_base + (index as usize) * 8;
if off + 8 > self.block.len() {
return Err(crate::Error::InvalidImage(
"apfs: btree var-kv ToC out of bounds".into(),
));
}
let ko = u16::from_le_bytes(self.block[off..off + 2].try_into().unwrap());
let kl = u16::from_le_bytes(self.block[off + 2..off + 4].try_into().unwrap());
let vo = u16::from_le_bytes(self.block[off + 4..off + 6].try_into().unwrap());
let vl = u16::from_le_bytes(self.block[off + 6..off + 8].try_into().unwrap());
Ok(RawEntry {
key_off: ko,
key_len: Some(kl),
val_off: vo,
val_len: Some(vl),
})
}
}
pub fn entries(
&self,
fixed_klen: usize,
fixed_vlen: usize,
) -> impl Iterator<Item = crate::Result<(&'a [u8], &'a [u8])>> + '_ {
(0..self.nkeys).map(move |i| self.entry_at(i, fixed_klen, fixed_vlen))
}
pub fn entry_at(
&self,
index: u32,
fixed_klen: usize,
fixed_vlen: usize,
) -> crate::Result<(&'a [u8], &'a [u8])> {
let e = self.raw_entry(index)?;
let klen = e.key_len.map(usize::from).unwrap_or(fixed_klen);
let kstart = self
.keys_start
.checked_add(e.key_off as usize)
.ok_or_else(|| crate::Error::InvalidImage("apfs: btree key offset overflow".into()))?;
let kend = kstart
.checked_add(klen)
.ok_or_else(|| crate::Error::InvalidImage("apfs: btree key length overflow".into()))?;
if kend > self.vals_end {
return Err(crate::Error::InvalidImage(
"apfs: btree key extends past value area".into(),
));
}
let key = &self.block[kstart..kend];
let val: &[u8] = if e.val_off == BTOFF_INVALID {
&[]
} else {
let vlen = e.val_len.map(usize::from).unwrap_or(fixed_vlen);
let vstart = self
.vals_end
.checked_sub(e.val_off as usize)
.ok_or_else(|| {
crate::Error::InvalidImage("apfs: btree value offset underflow".into())
})?;
let vend = vstart.checked_add(vlen).ok_or_else(|| {
crate::Error::InvalidImage("apfs: btree value length overflow".into())
})?;
if vstart < self.keys_start || vend > self.vals_end {
return Err(crate::Error::InvalidImage(
"apfs: btree value extends outside data area".into(),
));
}
&self.block[vstart..vend]
};
Ok((key, val))
}
pub fn fixed_kv_size(&self) -> Option<(usize, usize)> {
if !self.is_root {
return None;
}
let off = self.block.len().checked_sub(BTREE_INFO_SIZE)?;
if off + 16 > self.block.len() {
return None;
}
let key_size = u32::from_le_bytes(self.block[off + 8..off + 12].try_into().unwrap());
let val_size = u32::from_le_bytes(self.block[off + 12..off + 16].try_into().unwrap());
Some((key_size as usize, val_size as usize))
}
}
#[derive(Debug, Clone, Copy)]
struct RawEntry {
key_off: u16,
key_len: Option<u16>,
val_off: u16,
val_len: Option<u16>,
}
pub const CHILD_PTR_SIZE: usize = 8;
impl<'a> BTreeNode<'a> {
pub fn child_entry_at(&self, index: u32, fixed_klen: usize) -> crate::Result<(&'a [u8], u64)> {
let (kb, vb) = self.entry_at(index, fixed_klen, CHILD_PTR_SIZE)?;
if vb.len() < CHILD_PTR_SIZE {
return Err(crate::Error::InvalidImage(format!(
"apfs: internal node value slot {} too small (got {}, need {})",
index,
vb.len(),
CHILD_PTR_SIZE
)));
}
let oid = u64::from_le_bytes(vb[..8].try_into().unwrap());
Ok((kb, oid))
}
}
#[derive(Debug)]
pub struct NodeCache {
cap: usize,
tick: u64,
map: HashMap<u64, (Vec<u8>, u64)>,
}
impl NodeCache {
pub const DEFAULT_CAP: usize = 64;
pub fn new(cap: usize) -> Self {
Self {
cap,
tick: 0,
map: HashMap::new(),
}
}
pub fn get(&mut self, paddr: u64) -> Option<&[u8]> {
if self.cap == 0 {
return None;
}
self.tick = self.tick.wrapping_add(1);
let tick = self.tick;
let entry = self.map.get_mut(&paddr)?;
entry.1 = tick;
Some(&entry.0)
}
pub fn put(&mut self, paddr: u64, block: Vec<u8>) {
if self.cap == 0 {
return;
}
self.tick = self.tick.wrapping_add(1);
if !self.map.contains_key(&paddr) && self.map.len() >= self.cap {
if let Some((&victim, _)) = self.map.iter().min_by_key(|(_, (_, t))| *t) {
self.map.remove(&victim);
}
}
self.map.insert(paddr, (block, self.tick));
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fs::apfs::obj::OBJECT_TYPE_BTREE_NODE;
#[test]
fn decode_variable_kv_leaf() {
let block_size = 256usize;
let mut block = vec![0u8; block_size];
block[24..28].copy_from_slice(&OBJECT_TYPE_BTREE_NODE.to_le_bytes());
block[32..34].copy_from_slice(&BTNODE_LEAF.to_le_bytes()); block[34..36].copy_from_slice(&0u16.to_le_bytes()); block[36..40].copy_from_slice(&2u32.to_le_bytes());
block[40..42].copy_from_slice(&0u16.to_le_bytes()); block[42..44].copy_from_slice(&16u16.to_le_bytes());
let toc_base = 56;
block[toc_base..toc_base + 2].copy_from_slice(&0u16.to_le_bytes()); block[toc_base + 2..toc_base + 4].copy_from_slice(&2u16.to_le_bytes()); block[toc_base + 4..toc_base + 6].copy_from_slice(&2u16.to_le_bytes()); block[toc_base + 6..toc_base + 8].copy_from_slice(&2u16.to_le_bytes()); block[toc_base + 8..toc_base + 10].copy_from_slice(&2u16.to_le_bytes());
block[toc_base + 10..toc_base + 12].copy_from_slice(&2u16.to_le_bytes());
block[toc_base + 12..toc_base + 14].copy_from_slice(&4u16.to_le_bytes());
block[toc_base + 14..toc_base + 16].copy_from_slice(&2u16.to_le_bytes());
let keys_start = toc_base + 16; block[keys_start..keys_start + 2].copy_from_slice(b"AA");
block[keys_start + 2..keys_start + 4].copy_from_slice(b"BB");
let vend = block_size;
block[vend - 2..vend].copy_from_slice(b"vv");
block[vend - 4..vend - 2].copy_from_slice(b"VV");
let node = BTreeNode::decode(&block).unwrap();
assert!(node.is_leaf());
assert!(!node.fixed_kv);
assert!(!node.is_root);
assert_eq!(node.nkeys, 2);
let (k0, v0) = node.entry_at(0, 0, 0).unwrap();
let (k1, v1) = node.entry_at(1, 0, 0).unwrap();
assert_eq!(k0, b"AA");
assert_eq!(v0, b"vv");
assert_eq!(k1, b"BB");
assert_eq!(v1, b"VV");
}
#[test]
fn node_cache_evicts_lru() {
let mut c = NodeCache::new(2);
c.put(1, vec![1]);
c.put(2, vec![2]);
assert!(c.get(1).is_some());
c.put(3, vec![3]); assert!(c.get(2).is_none());
assert!(c.get(1).is_some());
assert!(c.get(3).is_some());
}
#[test]
fn node_cache_zero_cap_no_op() {
let mut c = NodeCache::new(0);
c.put(1, vec![1, 2, 3]);
assert!(c.get(1).is_none());
}
#[test]
fn child_entry_reads_8byte_oid() {
let block_size = 512usize;
let mut block = vec![0u8; block_size];
block[24..28].copy_from_slice(&OBJECT_TYPE_BTREE_NODE.to_le_bytes());
let flags = BTNODE_ROOT | BTNODE_FIXED_KV_SIZE;
block[32..34].copy_from_slice(&flags.to_le_bytes());
block[34..36].copy_from_slice(&1u16.to_le_bytes()); block[36..40].copy_from_slice(&2u32.to_le_bytes()); block[40..42].copy_from_slice(&0u16.to_le_bytes()); block[42..44].copy_from_slice(&8u16.to_le_bytes());
let toc_base = 56;
block[toc_base..toc_base + 2].copy_from_slice(&0u16.to_le_bytes()); block[toc_base + 2..toc_base + 4].copy_from_slice(&8u16.to_le_bytes()); block[toc_base + 4..toc_base + 6].copy_from_slice(&16u16.to_le_bytes()); block[toc_base + 6..toc_base + 8].copy_from_slice(&16u16.to_le_bytes()); let vals_end = block_size - BTREE_INFO_SIZE;
block[vals_end - 8..vals_end].copy_from_slice(&0x100u64.to_le_bytes());
block[vals_end - 16..vals_end - 8].copy_from_slice(&0x200u64.to_le_bytes());
let info_off = block_size - BTREE_INFO_SIZE;
block[info_off + 8..info_off + 12].copy_from_slice(&16u32.to_le_bytes());
block[info_off + 12..info_off + 16].copy_from_slice(&16u32.to_le_bytes());
let node = BTreeNode::decode(&block).unwrap();
assert!(!node.is_leaf());
let (_k0, c0) = node.child_entry_at(0, 16).unwrap();
let (_k1, c1) = node.child_entry_at(1, 16).unwrap();
assert_eq!(c0, 0x100);
assert_eq!(c1, 0x200);
}
#[test]
fn decode_fixed_kv_root() {
let block_size = 512usize;
let mut block = vec![0u8; block_size];
block[24..28].copy_from_slice(&OBJECT_TYPE_BTREE_NODE.to_le_bytes());
let flags = BTNODE_ROOT | BTNODE_LEAF | BTNODE_FIXED_KV_SIZE;
block[32..34].copy_from_slice(&flags.to_le_bytes());
block[36..40].copy_from_slice(&1u32.to_le_bytes()); block[40..42].copy_from_slice(&0u16.to_le_bytes()); block[42..44].copy_from_slice(&4u16.to_le_bytes());
let toc_base = 56;
block[toc_base..toc_base + 2].copy_from_slice(&0u16.to_le_bytes());
block[toc_base + 2..toc_base + 4].copy_from_slice(&16u16.to_le_bytes());
let keys_start = 60;
for (i, b) in block.iter_mut().enumerate().skip(keys_start).take(16) {
*b = (i as u8).wrapping_add(0x10);
}
let vals_end = block_size - BTREE_INFO_SIZE;
for (i, b) in block[vals_end - 16..vals_end].iter_mut().enumerate() {
*b = (i as u8).wrapping_add(0xa0);
}
let info_off = block_size - BTREE_INFO_SIZE;
block[info_off + 8..info_off + 12].copy_from_slice(&16u32.to_le_bytes());
block[info_off + 12..info_off + 16].copy_from_slice(&16u32.to_le_bytes());
let node = BTreeNode::decode(&block).unwrap();
assert!(node.fixed_kv);
assert!(node.is_root);
let (klen, vlen) = node.fixed_kv_size().unwrap();
assert_eq!((klen, vlen), (16, 16));
let (k, v) = node.entry_at(0, klen, vlen).unwrap();
assert_eq!(k.len(), 16);
assert_eq!(v.len(), 16);
assert_eq!(k[0], 0x10 + keys_start as u8);
assert_eq!(v[0], 0xa0);
}
}