use super::btree::{BTreeNode, NodeCache};
use super::obj::{OBJECT_TYPE_OMAP, ObjPhys};
#[derive(Debug, Clone)]
pub struct OmapPhys {
pub obj: ObjPhys,
pub flags: u32,
pub snap_count: u32,
pub tree_type: u32,
pub tree_oid: u64,
}
impl OmapPhys {
pub const MIN_SIZE: usize = 88;
pub fn decode(buf: &[u8]) -> crate::Result<Self> {
if buf.len() < Self::MIN_SIZE {
return Err(crate::Error::InvalidImage(
"apfs: omap_phys buffer too short".into(),
));
}
let obj = ObjPhys::decode(buf)?;
if obj.obj_type() != OBJECT_TYPE_OMAP {
return Err(crate::Error::InvalidImage(format!(
"apfs: o_type {:#x} is not OMAP",
obj.obj_type()
)));
}
let flags = u32::from_le_bytes(buf[32..36].try_into().unwrap());
let snap_count = u32::from_le_bytes(buf[36..40].try_into().unwrap());
let tree_type = u32::from_le_bytes(buf[40..44].try_into().unwrap());
let tree_oid = u64::from_le_bytes(buf[48..56].try_into().unwrap());
Ok(Self {
obj,
flags,
snap_count,
tree_type,
tree_oid,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OmapKey {
pub oid: u64,
pub xid: u64,
}
impl OmapKey {
pub const SIZE: usize = 16;
pub fn decode(buf: &[u8]) -> crate::Result<Self> {
if buf.len() < Self::SIZE {
return Err(crate::Error::InvalidImage(
"apfs: omap_key too short".into(),
));
}
Ok(Self {
oid: u64::from_le_bytes(buf[0..8].try_into().unwrap()),
xid: u64::from_le_bytes(buf[8..16].try_into().unwrap()),
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct OmapVal {
pub flags: u32,
pub size: u32,
pub paddr: u64,
}
impl OmapVal {
pub const SIZE: usize = 16;
pub fn decode(buf: &[u8]) -> crate::Result<Self> {
if buf.len() < Self::SIZE {
return Err(crate::Error::InvalidImage(
"apfs: omap_val too short".into(),
));
}
Ok(Self {
flags: u32::from_le_bytes(buf[0..4].try_into().unwrap()),
size: u32::from_le_bytes(buf[4..8].try_into().unwrap()),
paddr: u64::from_le_bytes(buf[8..16].try_into().unwrap()),
})
}
}
fn cmp_omap_key(a: &OmapKey, b: &OmapKey) -> std::cmp::Ordering {
a.oid.cmp(&b.oid).then(a.xid.cmp(&b.xid))
}
pub fn lookup<F>(
root_block: &[u8],
target_oid: u64,
target_xid: u64,
read_block: &mut F,
) -> crate::Result<Option<OmapVal>>
where
F: FnMut(u64, &mut [u8]) -> crate::Result<()>,
{
let mut cache = NodeCache::new(NodeCache::DEFAULT_CAP);
lookup_with_cache(root_block, target_oid, target_xid, read_block, &mut cache)
}
pub fn lookup_with_cache<F>(
root_block: &[u8],
target_oid: u64,
target_xid: u64,
read_block: &mut F,
cache: &mut NodeCache,
) -> crate::Result<Option<OmapVal>>
where
F: FnMut(u64, &mut [u8]) -> crate::Result<()>,
{
let target = OmapKey {
oid: target_oid,
xid: target_xid,
};
let block_size = root_block.len();
let root = BTreeNode::decode(root_block)?;
let (klen, vlen) = root
.fixed_kv_size()
.ok_or_else(|| crate::Error::InvalidImage("apfs: omap root missing btree_info".into()))?;
if klen != OmapKey::SIZE || vlen != OmapVal::SIZE {
return Err(crate::Error::InvalidImage(format!(
"apfs: omap fixed kv size ({klen}, {vlen}) != (16, 16)"
)));
}
if root.is_leaf() {
return scan_leaf_for_best(&root, klen, target_oid, target_xid);
}
let child_idx = find_child_for_key(&root, klen, &target)?;
let (_, mut child_paddr) = root.child_entry_at(child_idx, klen)?;
let mut cur = fetch_block(child_paddr, block_size, read_block, cache)?;
loop {
let node = BTreeNode::decode(&cur)?;
if node.is_leaf() {
return scan_leaf_for_best(&node, klen, target_oid, target_xid);
}
let idx = find_child_for_key(&node, klen, &target)?;
let (_, paddr) = node.child_entry_at(idx, klen)?;
child_paddr = paddr;
cur = fetch_block(child_paddr, block_size, read_block, cache)?;
}
}
fn find_child_for_key(node: &BTreeNode<'_>, klen: usize, target: &OmapKey) -> crate::Result<u32> {
if node.nkeys == 0 {
return Err(crate::Error::InvalidImage(
"apfs: empty omap internal node".into(),
));
}
let mut lo: i64 = 0;
let mut hi: i64 = node.nkeys as i64 - 1;
let mut best: i64 = 0; while lo <= hi {
let mid = (lo + hi) / 2;
let (kb, _) = node.child_entry_at(mid as u32, klen)?;
let k = OmapKey::decode(kb)?;
match cmp_omap_key(&k, target) {
std::cmp::Ordering::Less | std::cmp::Ordering::Equal => {
best = mid;
lo = mid + 1;
}
std::cmp::Ordering::Greater => {
hi = mid - 1;
}
}
}
Ok(best as u32)
}
fn scan_leaf_for_best(
node: &BTreeNode<'_>,
klen: usize,
target_oid: u64,
target_xid: u64,
) -> crate::Result<Option<OmapVal>> {
let vlen = OmapVal::SIZE;
let mut best: Option<OmapVal> = None;
let mut best_xid: u64 = 0;
for i in 0..node.nkeys {
let (kb, vb) = node.entry_at(i, klen, vlen)?;
let k = OmapKey::decode(kb)?;
if k.oid != target_oid {
continue;
}
if k.xid > target_xid {
continue;
}
if best.is_none() || k.xid > best_xid {
best_xid = k.xid;
best = Some(OmapVal::decode(vb)?);
}
}
Ok(best)
}
fn fetch_block<F>(
paddr: u64,
block_size: usize,
read_block: &mut F,
cache: &mut NodeCache,
) -> crate::Result<Vec<u8>>
where
F: FnMut(u64, &mut [u8]) -> crate::Result<()>,
{
if let Some(b) = cache.get(paddr) {
return Ok(b.to_vec());
}
let mut buf = vec![0u8; block_size];
read_block(paddr, &mut buf)?;
cache.put(paddr, buf.clone());
Ok(buf)
}
#[cfg(test)]
mod tests {
use super::super::btree::{BTNODE_FIXED_KV_SIZE, BTNODE_LEAF, BTNODE_ROOT, BTREE_INFO_SIZE};
use super::super::obj::OBJECT_TYPE_BTREE_NODE;
use super::*;
fn build_leaf_omap_node(
block_size: usize,
entries: &[(u64, u64, u64)],
is_root: bool,
) -> Vec<u8> {
let mut block = vec![0u8; block_size];
block[24..28].copy_from_slice(&OBJECT_TYPE_BTREE_NODE.to_le_bytes());
let mut flags = BTNODE_LEAF | BTNODE_FIXED_KV_SIZE;
if is_root {
flags |= BTNODE_ROOT;
}
block[32..34].copy_from_slice(&flags.to_le_bytes());
block[34..36].copy_from_slice(&0u16.to_le_bytes()); block[36..40].copy_from_slice(&(entries.len() as u32).to_le_bytes());
let toc_len = entries.len() * 4;
block[40..42].copy_from_slice(&0u16.to_le_bytes()); block[42..44].copy_from_slice(&(toc_len as u16).to_le_bytes());
let toc_base = 56;
let keys_start = toc_base + toc_len;
let vals_end = if is_root {
block_size - BTREE_INFO_SIZE
} else {
block_size
};
for (i, &(oid, xid, paddr)) in entries.iter().enumerate() {
let k_off = (i * 16) as u16;
let v_off = ((i + 1) * 16) as u16;
block[toc_base + i * 4..toc_base + i * 4 + 2].copy_from_slice(&k_off.to_le_bytes());
block[toc_base + i * 4 + 2..toc_base + i * 4 + 4].copy_from_slice(&v_off.to_le_bytes());
let ks = keys_start + k_off as usize;
block[ks..ks + 8].copy_from_slice(&oid.to_le_bytes());
block[ks + 8..ks + 16].copy_from_slice(&xid.to_le_bytes());
let vs = vals_end - v_off as usize;
block[vs..vs + 4].copy_from_slice(&0u32.to_le_bytes());
block[vs + 4..vs + 8].copy_from_slice(&0u32.to_le_bytes());
block[vs + 8..vs + 16].copy_from_slice(&paddr.to_le_bytes());
}
if is_root {
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());
}
block
}
fn build_internal_omap_node(
block_size: usize,
entries: &[(u64, u64, u64)],
is_root: bool,
) -> Vec<u8> {
let mut block = vec![0u8; block_size];
block[24..28].copy_from_slice(&OBJECT_TYPE_BTREE_NODE.to_le_bytes());
let mut flags = BTNODE_FIXED_KV_SIZE;
if is_root {
flags |= BTNODE_ROOT;
}
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(&(entries.len() as u32).to_le_bytes());
let toc_len = entries.len() * 4;
block[40..42].copy_from_slice(&0u16.to_le_bytes());
block[42..44].copy_from_slice(&(toc_len as u16).to_le_bytes());
let toc_base = 56;
let keys_start = toc_base + toc_len;
let vals_end = if is_root {
block_size - BTREE_INFO_SIZE
} else {
block_size
};
for (i, &(oid, xid, child_paddr)) in entries.iter().enumerate() {
let k_off = (i * 16) as u16;
let v_off = ((i + 1) * 8) as u16;
block[toc_base + i * 4..toc_base + i * 4 + 2].copy_from_slice(&k_off.to_le_bytes());
block[toc_base + i * 4 + 2..toc_base + i * 4 + 4].copy_from_slice(&v_off.to_le_bytes());
let ks = keys_start + k_off as usize;
block[ks..ks + 8].copy_from_slice(&oid.to_le_bytes());
block[ks + 8..ks + 16].copy_from_slice(&xid.to_le_bytes());
let vs = vals_end - v_off as usize;
block[vs..vs + 8].copy_from_slice(&child_paddr.to_le_bytes());
}
if is_root {
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());
}
block
}
#[test]
fn lookup_single_leaf_picks_latest_xid_below_target() {
let block_size = 512usize;
let root = build_leaf_omap_node(
block_size,
&[(5, 1, 0x100), (5, 3, 0x200), (5, 7, 0x300)],
true,
);
let mut no_read =
|_p: u64, _b: &mut [u8]| panic!("single-leaf lookup must not read from device");
let mut cache = NodeCache::new(NodeCache::DEFAULT_CAP);
let v = lookup_with_cache(&root, 5, 5, &mut no_read, &mut cache).unwrap();
assert_eq!(v.unwrap().paddr, 0x200);
let v = lookup_with_cache(&root, 5, 10, &mut no_read, &mut cache).unwrap();
assert_eq!(v.unwrap().paddr, 0x300);
let v = lookup_with_cache(&root, 5, 0, &mut no_read, &mut cache).unwrap();
assert!(v.is_none());
let v = lookup_with_cache(&root, 99, 5, &mut no_read, &mut cache).unwrap();
assert!(v.is_none());
}
#[test]
fn lookup_two_level_descends_to_correct_leaf() {
let block_size = 512usize;
let leaf_a = build_leaf_omap_node(block_size, &[(5, 1, 0x100), (5, 3, 0x200)], false);
let leaf_b = build_leaf_omap_node(block_size, &[(9, 2, 0x900)], false);
let root = build_internal_omap_node(block_size, &[(5, 0, 10), (9, 0, 11)], true);
let mut device: std::collections::HashMap<u64, Vec<u8>> = Default::default();
device.insert(10, leaf_a);
device.insert(11, leaf_b);
let mut read = |paddr: u64, buf: &mut [u8]| -> crate::Result<()> {
let b = device
.get(&paddr)
.ok_or_else(|| crate::Error::InvalidImage(format!("no block at {paddr}")))?;
buf.copy_from_slice(b);
Ok(())
};
let mut cache = NodeCache::new(NodeCache::DEFAULT_CAP);
let v = lookup_with_cache(&root, 5, 3, &mut read, &mut cache).unwrap();
assert_eq!(v.unwrap().paddr, 0x200);
let v = lookup_with_cache(&root, 9, 10, &mut read, &mut cache).unwrap();
assert_eq!(v.unwrap().paddr, 0x900);
let v = lookup_with_cache(&root, 7, 10, &mut read, &mut cache).unwrap();
assert!(v.is_none());
}
#[test]
fn decode_omap_phys() {
let mut buf = vec![0u8; 4096];
buf[24..28].copy_from_slice(&OBJECT_TYPE_OMAP.to_le_bytes());
buf[32..36].copy_from_slice(&1u32.to_le_bytes()); buf[40..44].copy_from_slice(&0xeu32.to_le_bytes()); buf[48..56].copy_from_slice(&0x1234u64.to_le_bytes()); let o = OmapPhys::decode(&buf).unwrap();
assert_eq!(o.tree_oid, 0x1234);
assert_eq!(o.flags, 1);
}
#[test]
fn omap_key_val_decode() {
let mut kb = [0u8; 16];
kb[0..8].copy_from_slice(&7u64.to_le_bytes());
kb[8..16].copy_from_slice(&9u64.to_le_bytes());
let k = OmapKey::decode(&kb).unwrap();
assert_eq!(k.oid, 7);
assert_eq!(k.xid, 9);
let mut vb = [0u8; 16];
vb[0..4].copy_from_slice(&2u32.to_le_bytes());
vb[4..8].copy_from_slice(&1u32.to_le_bytes());
vb[8..16].copy_from_slice(&0x4000u64.to_le_bytes());
let v = OmapVal::decode(&vb).unwrap();
assert_eq!(v.flags, 2);
assert_eq!(v.size, 1);
assert_eq!(v.paddr, 0x4000);
}
}