use byteorder::{LittleEndian, ReadBytesExt};
use std::io::{Cursor, Read, Seek};
use crate::btree;
use crate::error::{ApfsError, Result};
use crate::object;
const OMAP_KEY_SIZE: u32 = 16;
const OMAP_VAL_SIZE: u32 = 16;
pub fn read_omap_tree_root<R: Read + Seek>(
reader: &mut R,
omap_block: u64,
block_size: u32,
) -> Result<u64> {
let block_data = object::read_block(reader, omap_block, block_size)?;
let mut cursor = Cursor::new(&block_data[object::ObjectHeader::SIZE..]);
let _om_flags = cursor.read_u32::<LittleEndian>()?;
let _om_snap_count = cursor.read_u32::<LittleEndian>()?;
let _om_tree_type = cursor.read_u32::<LittleEndian>()?;
let _om_snap_tree_type = cursor.read_u32::<LittleEndian>()?;
let om_tree_oid = cursor.read_u64::<LittleEndian>()?;
Ok(om_tree_oid)
}
pub fn omap_lookup<R: Read + Seek>(
reader: &mut R,
omap_tree_root: u64,
block_size: u32,
target_oid: u64,
) -> Result<u64> {
let compare_fn = |key: &[u8]| -> std::cmp::Ordering {
if key.len() < 16 {
return std::cmp::Ordering::Less;
}
let key_oid = u64::from_le_bytes([
key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7],
]);
key_oid.cmp(&target_oid)
};
let result = btree::btree_lookup(
reader,
omap_tree_root,
block_size,
OMAP_KEY_SIZE,
OMAP_VAL_SIZE,
&compare_fn,
None,
)?;
if let Some(val) = result {
return parse_omap_val(&val);
}
let range_fn = |key: &[u8]| -> Option<bool> {
if key.len() < 16 {
return Some(false);
}
let key_oid = u64::from_le_bytes([
key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7],
]);
if key_oid < target_oid {
Some(false) } else if key_oid == target_oid {
Some(true) } else {
None }
};
let entries = btree::btree_scan(
reader,
omap_tree_root,
block_size,
OMAP_KEY_SIZE,
OMAP_VAL_SIZE,
&range_fn,
None,
)?;
if entries.is_empty() {
return Err(ApfsError::CorruptedData(format!(
"OMAP lookup failed: OID {} not found",
target_oid
)));
}
let mut best_xid: u64 = 0;
let mut best_paddr: u64 = 0;
for (key, val) in &entries {
if key.len() >= 16 {
let xid = u64::from_le_bytes([
key[8], key[9], key[10], key[11], key[12], key[13], key[14], key[15],
]);
if xid >= best_xid {
best_xid = xid;
best_paddr = parse_omap_val(val)?;
}
}
}
if best_paddr == 0 {
return Err(ApfsError::CorruptedData(format!(
"OMAP lookup: OID {} resolved to paddr 0",
target_oid
)));
}
Ok(best_paddr)
}
fn parse_omap_val(val: &[u8]) -> Result<u64> {
if val.len() < 16 {
return Err(ApfsError::InvalidBTree("omap value too short".into()));
}
let paddr = u64::from_le_bytes([
val[8], val[9], val[10], val[11], val[12], val[13], val[14], val[15],
]);
Ok(paddr)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::superblock;
use std::io::BufReader;
#[test]
#[ignore]
fn test_omap_lookup() {
let file = std::fs::File::open("../tests/appfs.raw").unwrap();
let mut reader = BufReader::new(file);
let nxsb = superblock::read_nxsb(&mut reader).unwrap();
let latest = superblock::find_latest_nxsb(&mut reader, &nxsb).unwrap();
let omap_root =
read_omap_tree_root(&mut reader, latest.omap_oid, latest.block_size).unwrap();
let vol_oid = latest.fs_oids.iter().find(|&&o| o != 0).copied().unwrap();
let vol_block = omap_lookup(&mut reader, omap_root, latest.block_size, vol_oid).unwrap();
assert!(
vol_block > 0 && vol_block < latest.block_count,
"Physical block {} should be within container",
vol_block
);
let vol_data = object::read_block(&mut reader, vol_block, latest.block_size).unwrap();
let vol_sb = superblock::ApfsSuperblock::parse(&vol_data).unwrap();
assert_eq!(vol_sb.magic, superblock::APSB_MAGIC);
}
}