use std::cmp::Ordering;
use super::btree::{BTreeNode, NodeCache};
use super::jrec::{
APFS_TYPE_DIR_REC, APFS_TYPE_FILE_EXTENT, APFS_TYPE_XATTR, J_DREC_LEN_MASK, split_obj_id,
};
use super::omap::lookup_with_cache as omap_lookup;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DrecKeyLayout {
Hashed,
Plain,
}
#[derive(Debug, Clone)]
pub struct FsKey<'a> {
pub oid: u64,
pub kind: u8,
pub raw: &'a [u8],
}
impl<'a> FsKey<'a> {
pub fn decode(raw: &'a [u8]) -> crate::Result<Self> {
if raw.len() < 8 {
return Err(crate::Error::InvalidImage(
"apfs: fs-tree key shorter than j_key_t".into(),
));
}
let hdr = u64::from_le_bytes(raw[0..8].try_into().unwrap());
let (kind, oid) = split_obj_id(hdr);
Ok(Self { oid, kind, raw })
}
}
#[derive(Debug, Clone, Copy)]
pub struct FsKeyTarget<'a> {
pub oid: u64,
pub kind: u8,
pub tail: &'a [u8],
pub drec_layout: DrecKeyLayout,
}
pub fn cmp_fs_key(key: &FsKey<'_>, target: &FsKeyTarget<'_>) -> Ordering {
match key.oid.cmp(&target.oid) {
Ordering::Equal => {}
other => return other,
}
match key.kind.cmp(&target.kind) {
Ordering::Equal => {}
other => return other,
}
cmp_tail(key, target)
}
fn cmp_tail(key: &FsKey<'_>, target: &FsKeyTarget<'_>) -> Ordering {
let kt = if key.raw.len() > 8 {
&key.raw[8..]
} else {
&[][..]
};
match key.kind {
APFS_TYPE_DIR_REC => match target.drec_layout {
DrecKeyLayout::Hashed => cmp_drec_hashed_tail(kt, target.tail),
DrecKeyLayout::Plain => cmp_drec_plain_tail(kt, target.tail),
},
APFS_TYPE_FILE_EXTENT => cmp_u64_le_tail(kt, target.tail),
APFS_TYPE_XATTR => cmp_xattr_tail(kt, target.tail),
_ => kt.cmp(target.tail),
}
}
fn cmp_drec_hashed_tail(stored: &[u8], target: &[u8]) -> Ordering {
if target.is_empty() {
return if stored.is_empty() {
Ordering::Equal
} else {
Ordering::Greater
};
}
if stored.len() < 4 || target.len() < 4 {
return stored.cmp(target);
}
let s_nh = u32::from_le_bytes(stored[0..4].try_into().unwrap());
let t_nh = u32::from_le_bytes(target[0..4].try_into().unwrap());
let s_hash = s_nh & !J_DREC_LEN_MASK;
let t_hash = t_nh & !J_DREC_LEN_MASK;
match s_hash.cmp(&t_hash) {
Ordering::Equal => {}
o => return o,
}
let s_len = (s_nh & J_DREC_LEN_MASK) as usize;
let t_len = (t_nh & J_DREC_LEN_MASK) as usize;
let s_name = &stored[4..4 + s_len.min(stored.len().saturating_sub(4))];
let t_name = &target[4..4 + t_len.min(target.len().saturating_sub(4))];
s_name.cmp(t_name)
}
fn cmp_drec_plain_tail(stored: &[u8], target: &[u8]) -> Ordering {
if target.is_empty() {
return if stored.is_empty() {
Ordering::Equal
} else {
Ordering::Greater
};
}
if stored.len() < 2 || target.len() < 2 {
return stored.cmp(target);
}
let s_len = u16::from_le_bytes(stored[0..2].try_into().unwrap()) as usize;
let t_len = u16::from_le_bytes(target[0..2].try_into().unwrap()) as usize;
let s_name = &stored[2..2 + s_len.min(stored.len().saturating_sub(2))];
let t_name = &target[2..2 + t_len.min(target.len().saturating_sub(2))];
s_name.cmp(t_name)
}
fn cmp_u64_le_tail(stored: &[u8], target: &[u8]) -> Ordering {
if target.is_empty() {
return if stored.is_empty() {
Ordering::Equal
} else {
Ordering::Greater
};
}
if stored.len() < 8 || target.len() < 8 {
return stored.cmp(target);
}
let s = u64::from_le_bytes(stored[0..8].try_into().unwrap());
let t = u64::from_le_bytes(target[0..8].try_into().unwrap());
s.cmp(&t)
}
fn cmp_xattr_tail(stored: &[u8], target: &[u8]) -> Ordering {
if target.is_empty() {
return if stored.is_empty() {
Ordering::Equal
} else {
Ordering::Greater
};
}
if stored.len() < 2 || target.len() < 2 {
return stored.cmp(target);
}
let s_len = u16::from_le_bytes(stored[0..2].try_into().unwrap()) as usize;
let t_len = u16::from_le_bytes(target[0..2].try_into().unwrap()) as usize;
let s_name = &stored[2..2 + s_len.min(stored.len().saturating_sub(2))];
let t_name = &target[2..2 + t_len.min(target.len().saturating_sub(2))];
s_name.cmp(t_name)
}
pub struct FsTreeCtx {
pub omap_root: Vec<u8>,
pub target_xid: u64,
pub block_size: usize,
pub omap_cache: NodeCache,
pub fs_cache: NodeCache,
}
impl FsTreeCtx {
pub fn new(omap_root: Vec<u8>, target_xid: u64, block_size: usize) -> Self {
Self {
omap_root,
target_xid,
block_size,
omap_cache: NodeCache::new(NodeCache::DEFAULT_CAP),
fs_cache: NodeCache::new(NodeCache::DEFAULT_CAP),
}
}
fn resolve_vid<F>(&mut self, vid: u64, read_block: &mut F) -> crate::Result<u64>
where
F: FnMut(u64, &mut [u8]) -> crate::Result<()>,
{
let val = omap_lookup(
&self.omap_root,
vid,
self.target_xid,
read_block,
&mut self.omap_cache,
)?;
let v = v_or_invalid(val, vid)?;
Ok(v.paddr)
}
fn fetch_fs_block<F>(&mut self, paddr: u64, read_block: &mut F) -> crate::Result<Vec<u8>>
where
F: FnMut(u64, &mut [u8]) -> crate::Result<()>,
{
if let Some(b) = self.fs_cache.get(paddr) {
return Ok(b.to_vec());
}
let mut buf = vec![0u8; self.block_size];
read_block(paddr, &mut buf)?;
self.fs_cache.put(paddr, buf.clone());
Ok(buf)
}
}
fn v_or_invalid(
val: Option<super::omap::OmapVal>,
vid: u64,
) -> crate::Result<super::omap::OmapVal> {
val.ok_or_else(|| {
crate::Error::InvalidImage(format!("apfs: volume omap has no entry for vid {vid:#x}"))
})
}
pub fn lookup<F>(
fsroot_block: &[u8],
target: &FsKeyTarget<'_>,
ctx: &mut FsTreeCtx,
read_block: &mut F,
) -> crate::Result<Option<Vec<u8>>>
where
F: FnMut(u64, &mut [u8]) -> crate::Result<()>,
{
let (fixed_klen, _fixed_kv) = read_root_kv_sizes(fsroot_block);
let mut cur: Vec<u8> = fsroot_block.to_vec();
loop {
let node = BTreeNode::decode(&cur)?;
if node.is_leaf() {
return scan_leaf_for_exact(&node, fixed_klen, target);
}
let idx = find_child_idx(&node, fixed_klen, target)?;
let (_, child_vid) = node.child_entry_at(idx, fixed_klen)?;
let paddr = ctx.resolve_vid(child_vid, read_block)?;
cur = ctx.fetch_fs_block(paddr, read_block)?;
}
}
pub struct RangeScan {
stack: Vec<(Vec<u8>, u32)>,
fixed_klen: usize,
stop_oid: u64,
stop_kind: u8,
}
impl RangeScan {
pub fn start<F>(
fsroot_block: &[u8],
start: &FsKeyTarget<'_>,
ctx: &mut FsTreeCtx,
read_block: &mut F,
) -> crate::Result<Self>
where
F: FnMut(u64, &mut [u8]) -> crate::Result<()>,
{
let (fixed_klen, _) = read_root_kv_sizes(fsroot_block);
let mut stack: Vec<(Vec<u8>, u32)> = Vec::new();
let mut cur = fsroot_block.to_vec();
loop {
let node = BTreeNode::decode(&cur)?;
if node.is_leaf() {
let mut first = node.nkeys;
for i in 0..node.nkeys {
let (kb, _) = node.entry_at(i, fixed_klen, 0)?;
let key = FsKey::decode(kb)?;
if cmp_fs_key(&key, start) != Ordering::Less {
first = i;
break;
}
}
stack.push((cur, first));
break;
}
let idx = find_child_idx(&node, fixed_klen, start)?;
let (_, child_vid) = node.child_entry_at(idx, fixed_klen)?;
stack.push((cur, idx));
let paddr = ctx.resolve_vid(child_vid, read_block)?;
cur = ctx.fetch_fs_block(paddr, read_block)?;
}
Ok(Self {
stack,
fixed_klen,
stop_oid: start.oid,
stop_kind: start.kind,
})
}
pub fn next<F>(
&mut self,
ctx: &mut FsTreeCtx,
read_block: &mut F,
) -> crate::Result<Option<(Vec<u8>, Vec<u8>)>>
where
F: FnMut(u64, &mut [u8]) -> crate::Result<()>,
{
loop {
if self.stack.is_empty() {
return Ok(None);
}
let leaf_idx = self.stack.len() - 1;
let (leaf_bytes, cursor) = {
let (lb, c) = &self.stack[leaf_idx];
(lb.clone(), *c)
};
let node = BTreeNode::decode(&leaf_bytes)?;
if !node.is_leaf() {
return Err(crate::Error::InvalidImage(
"apfs: range scan: top of stack is not a leaf".into(),
));
}
if cursor < node.nkeys {
let (kb, vb) = node.entry_at(cursor, self.fixed_klen, 0)?;
let key = FsKey::decode(kb)?;
if key.oid != self.stop_oid || key.kind != self.stop_kind {
return Ok(None);
}
let kb_owned = kb.to_vec();
let vb_owned = vb.to_vec();
self.stack[leaf_idx].1 += 1;
return Ok(Some((kb_owned, vb_owned)));
}
self.stack.pop();
if self.stack.is_empty() {
return Ok(None);
}
self.descend_next(ctx, read_block)?;
if self.stack.is_empty() {
return Ok(None);
}
}
}
fn descend_next<F>(&mut self, ctx: &mut FsTreeCtx, read_block: &mut F) -> crate::Result<()>
where
F: FnMut(u64, &mut [u8]) -> crate::Result<()>,
{
loop {
let parent_idx = self.stack.len() - 1;
let (parent_bytes, cursor) = {
let (pb, c) = &self.stack[parent_idx];
(pb.clone(), *c)
};
let parent = BTreeNode::decode(&parent_bytes)?;
let next_idx = cursor + 1;
if next_idx >= parent.nkeys {
self.stack.pop();
if self.stack.is_empty() {
return Ok(());
}
continue;
}
self.stack[parent_idx].1 = next_idx;
let (_, child_vid) = parent.child_entry_at(next_idx, self.fixed_klen)?;
let paddr = ctx.resolve_vid(child_vid, read_block)?;
let mut child = ctx.fetch_fs_block(paddr, read_block)?;
loop {
let cn = BTreeNode::decode(&child)?;
if cn.is_leaf() {
self.stack.push((child, 0));
return Ok(());
}
let (_, vid) = cn.child_entry_at(0, self.fixed_klen)?;
self.stack.push((child, 0));
let p = ctx.resolve_vid(vid, read_block)?;
child = ctx.fetch_fs_block(p, read_block)?;
}
}
}
}
fn read_root_kv_sizes(root_block: &[u8]) -> (usize, bool) {
if let Ok(root) = BTreeNode::decode(root_block) {
let fixed_kv = root.fixed_kv;
let klen = root.fixed_kv_size().map(|(k, _)| k).unwrap_or(0);
(klen, fixed_kv)
} else {
(0, false)
}
}
fn find_child_idx(
node: &BTreeNode<'_>,
fixed_klen: usize,
target: &FsKeyTarget<'_>,
) -> crate::Result<u32> {
if node.nkeys == 0 {
return Err(crate::Error::InvalidImage(
"apfs: empty fs-tree 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, fixed_klen)?;
let key = FsKey::decode(kb)?;
match cmp_fs_key(&key, target) {
Ordering::Less | Ordering::Equal => {
best = mid;
lo = mid + 1;
}
Ordering::Greater => {
hi = mid - 1;
}
}
}
Ok(best as u32)
}
fn scan_leaf_for_exact(
node: &BTreeNode<'_>,
fixed_klen: usize,
target: &FsKeyTarget<'_>,
) -> crate::Result<Option<Vec<u8>>> {
for i in 0..node.nkeys {
let (kb, vb) = node.entry_at(i, fixed_klen, 0)?;
let key = FsKey::decode(kb)?;
if cmp_fs_key(&key, target) == Ordering::Equal {
return Ok(Some(vb.to_vec()));
}
}
Ok(None)
}
pub fn make_drec_target_tail(name: &str, layout: DrecKeyLayout) -> Vec<u8> {
match layout {
DrecKeyLayout::Plain => {
let bytes = name.as_bytes();
let mut out = Vec::with_capacity(2 + bytes.len() + 1);
let nlen = (bytes.len() + 1) as u16;
out.extend_from_slice(&nlen.to_le_bytes());
out.extend_from_slice(bytes);
out.push(0);
out
}
DrecKeyLayout::Hashed => {
let bytes = name.as_bytes();
let nlen = ((bytes.len() + 1) as u32) & J_DREC_LEN_MASK;
let mut out = Vec::with_capacity(4 + bytes.len() + 1);
out.extend_from_slice(&nlen.to_le_bytes());
out.extend_from_slice(bytes);
out.push(0);
out
}
}
}
pub fn make_file_extent_tail(logical_addr: u64) -> [u8; 8] {
logical_addr.to_le_bytes()
}
#[cfg(test)]
mod tests {
use super::super::jrec::{OBJ_ID_MASK, OBJ_TYPE_SHIFT};
use super::*;
#[test]
fn cmp_fs_key_orders_by_oid_first() {
let mut raw1 = [0u8; 8];
let mut raw2 = [0u8; 8];
let h1 = ((APFS_TYPE_DIR_REC as u64) << OBJ_TYPE_SHIFT) | (5 & OBJ_ID_MASK);
let h2 = ((APFS_TYPE_DIR_REC as u64) << OBJ_TYPE_SHIFT) | (6 & OBJ_ID_MASK);
raw1.copy_from_slice(&h1.to_le_bytes());
raw2.copy_from_slice(&h2.to_le_bytes());
let k1 = FsKey::decode(&raw1).unwrap();
let k2 = FsKey::decode(&raw2).unwrap();
let t = FsKeyTarget {
oid: 6,
kind: APFS_TYPE_DIR_REC,
tail: &[],
drec_layout: DrecKeyLayout::Hashed,
};
assert!(cmp_fs_key(&k1, &t) == Ordering::Less);
assert_eq!(cmp_fs_key(&k2, &t), Ordering::Equal);
}
#[test]
fn cmp_fs_key_kind_breaks_oid_tie() {
let mut raw1 = [0u8; 8];
let mut raw2 = [0u8; 8];
let h1 = ((APFS_TYPE_DIR_REC as u64) << OBJ_TYPE_SHIFT) | 5;
let h2 = ((APFS_TYPE_FILE_EXTENT as u64) << OBJ_TYPE_SHIFT) | 5;
raw1.copy_from_slice(&h1.to_le_bytes());
raw2.copy_from_slice(&h2.to_le_bytes());
let k1 = FsKey::decode(&raw1).unwrap();
let k2 = FsKey::decode(&raw2).unwrap();
let t = FsKeyTarget {
oid: 5,
kind: APFS_TYPE_DIR_REC,
tail: &[],
drec_layout: DrecKeyLayout::Hashed,
};
assert_eq!(cmp_fs_key(&k1, &t), Ordering::Equal);
assert_eq!(cmp_fs_key(&k2, &t), Ordering::Less);
}
#[test]
fn drec_hashed_target_tail_layout() {
let t = make_drec_target_tail("foo", DrecKeyLayout::Hashed);
assert_eq!(t.len(), 8);
let nh = u32::from_le_bytes(t[0..4].try_into().unwrap());
assert_eq!(nh & J_DREC_LEN_MASK, 4); }
#[test]
fn file_extent_tail_is_le_u64() {
let t = make_file_extent_tail(0x1234_5678_9abc_def0);
assert_eq!(u64::from_le_bytes(t), 0x1234_5678_9abc_def0);
}
use super::super::btree::{BTNODE_FIXED_KV_SIZE, BTNODE_LEAF, BTNODE_ROOT, BTREE_INFO_SIZE};
use super::super::jrec::{APFS_TYPE_DIR_REC, APFS_TYPE_INODE};
use super::super::obj::OBJECT_TYPE_BTREE_NODE;
fn build_var_leaf(block_size: usize, entries: &[(Vec<u8>, Vec<u8>)], 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;
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() * 8;
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
};
let mut k_cursor = 0usize;
let mut v_cursor_back = 0usize;
for (i, (kb, vb)) in entries.iter().enumerate() {
let k_off = k_cursor as u16;
let k_len = kb.len() as u16;
v_cursor_back += vb.len();
let v_off = v_cursor_back as u16;
let v_len = vb.len() as u16;
block[toc_base + i * 8..toc_base + i * 8 + 2].copy_from_slice(&k_off.to_le_bytes());
block[toc_base + i * 8 + 2..toc_base + i * 8 + 4].copy_from_slice(&k_len.to_le_bytes());
block[toc_base + i * 8 + 4..toc_base + i * 8 + 6].copy_from_slice(&v_off.to_le_bytes());
block[toc_base + i * 8 + 6..toc_base + i * 8 + 8].copy_from_slice(&v_len.to_le_bytes());
let ks = keys_start + k_off as usize;
block[ks..ks + kb.len()].copy_from_slice(kb);
let vs = vals_end - v_off as usize;
block[vs..vs + vb.len()].copy_from_slice(vb);
k_cursor += kb.len();
}
block
}
fn drec_hashed_key(parent_oid: u64, name: &str, fake_hash: u32) -> Vec<u8> {
let mut out = Vec::with_capacity(12 + name.len() + 1);
let hdr = ((APFS_TYPE_DIR_REC as u64) << OBJ_TYPE_SHIFT) | (parent_oid & OBJ_ID_MASK);
out.extend_from_slice(&hdr.to_le_bytes());
let nh = (fake_hash << 10) | ((name.len() as u32 + 1) & J_DREC_LEN_MASK);
out.extend_from_slice(&nh.to_le_bytes());
out.extend_from_slice(name.as_bytes());
out.push(0);
out
}
fn drec_val(file_id: u64, dtype: u16) -> Vec<u8> {
let mut out = vec![0u8; 18];
out[0..8].copy_from_slice(&file_id.to_le_bytes());
out[16..18].copy_from_slice(&dtype.to_le_bytes());
out
}
#[test]
fn range_scan_single_leaf_yields_all_drecs_under_parent() {
let block_size = 1024usize;
let entries = vec![
(
drec_hashed_key(2, "bar", 0x100),
drec_val(0x11, super::super::jrec::DT_REG),
),
(
drec_hashed_key(2, "foo", 0x200),
drec_val(0x12, super::super::jrec::DT_DIR),
),
(
drec_hashed_key(3, "qux", 0x100),
drec_val(0x13, super::super::jrec::DT_REG),
),
];
let root = build_var_leaf(block_size, &entries, true);
let omap_root = build_empty_omap_root(block_size);
let mut ctx = FsTreeCtx::new(omap_root, 0, block_size);
let target = FsKeyTarget {
oid: 2,
kind: APFS_TYPE_DIR_REC,
tail: &[],
drec_layout: DrecKeyLayout::Hashed,
};
let mut never = |_p: u64, _b: &mut [u8]| -> crate::Result<()> {
panic!("single-leaf walk must not hit the device")
};
let mut scan = RangeScan::start(&root, &target, &mut ctx, &mut never).unwrap();
let a = scan.next(&mut ctx, &mut never).unwrap().unwrap();
let b = scan.next(&mut ctx, &mut never).unwrap().unwrap();
let c = scan.next(&mut ctx, &mut never).unwrap();
assert!(c.is_none(), "third call should stop at prefix boundary");
let k_a = super::super::jrec::DrecKey::decode_hashed(&a.0).unwrap();
let k_b = super::super::jrec::DrecKey::decode_hashed(&b.0).unwrap();
assert_eq!(k_a.name, "bar");
assert_eq!(k_b.name, "foo");
}
fn build_empty_omap_root(block_size: usize) -> Vec<u8> {
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(&0u32.to_le_bytes()); block[40..42].copy_from_slice(&0u16.to_le_bytes());
block[42..44].copy_from_slice(&0u16.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());
block
}
#[test]
fn range_scan_two_level_descends_via_omap() {
let block_size = 1024usize;
let inode_key = {
let mut k = vec![0u8; 8];
let hdr = ((APFS_TYPE_INODE as u64) << OBJ_TYPE_SHIFT) | (5 & OBJ_ID_MASK);
k.copy_from_slice(&hdr.to_le_bytes());
k
};
let inode_val = vec![0u8; super::super::jrec::J_INODE_VAL_FIXED_SIZE];
let inode_leaf = build_var_leaf(block_size, &[(inode_key, inode_val)], false);
let drec_entries = vec![
(
drec_hashed_key(5, "a", 0x100),
drec_val(0x21, super::super::jrec::DT_REG),
),
(
drec_hashed_key(5, "b", 0x200),
drec_val(0x22, super::super::jrec::DT_REG),
),
];
let drec_leaf = build_var_leaf(block_size, &drec_entries, false);
let omap_root = build_omap_leaf_root(block_size, &[(100, 1, 0x10), (101, 1, 0x11)]);
let fs_root = build_var_internal_fsroot(
block_size,
&[
((APFS_TYPE_INODE, 5), 100u64),
((APFS_TYPE_DIR_REC, 5), 101u64),
],
);
let mut device: std::collections::HashMap<u64, Vec<u8>> = Default::default();
device.insert(0x10, inode_leaf);
device.insert(0x11, drec_leaf);
let mut ctx = FsTreeCtx::new(omap_root, 5, block_size);
let target = FsKeyTarget {
oid: 5,
kind: APFS_TYPE_DIR_REC,
tail: &[],
drec_layout: DrecKeyLayout::Hashed,
};
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 {paddr}")))?;
buf.copy_from_slice(b);
Ok(())
};
let mut scan = RangeScan::start(&fs_root, &target, &mut ctx, &mut read).unwrap();
let a = scan.next(&mut ctx, &mut read).unwrap().unwrap();
let b = scan.next(&mut ctx, &mut read).unwrap().unwrap();
let c = scan.next(&mut ctx, &mut read).unwrap();
assert!(c.is_none());
let k_a = super::super::jrec::DrecKey::decode_hashed(&a.0).unwrap();
let k_b = super::super::jrec::DrecKey::decode_hashed(&b.0).unwrap();
assert_eq!((k_a.name.as_str(), k_b.name.as_str()), ("a", "b"));
}
fn build_omap_leaf_root(block_size: usize, entries: &[(u64, u64, u64)]) -> Vec<u8> {
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(&(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 = block_size - BTREE_INFO_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 + 8..vs + 16].copy_from_slice(&paddr.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());
block
}
fn build_var_internal_fsroot(block_size: usize, entries: &[((u8, u64), u64)]) -> Vec<u8> {
let mapped: Vec<(Vec<u8>, Vec<u8>)> = entries
.iter()
.map(|((kind, oid), vid)| {
let hdr = ((*kind as u64) << OBJ_TYPE_SHIFT) | (oid & OBJ_ID_MASK);
(hdr.to_le_bytes().to_vec(), vid.to_le_bytes().to_vec())
})
.collect();
let mut block = vec![0u8; block_size];
block[24..28].copy_from_slice(&OBJECT_TYPE_BTREE_NODE.to_le_bytes());
let 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(&(mapped.len() as u32).to_le_bytes());
let toc_len = mapped.len() * 8;
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 = block_size - BTREE_INFO_SIZE;
let mut k_cursor = 0usize;
let mut v_cursor_back = 0usize;
for (i, (kb, vb)) in mapped.iter().enumerate() {
let k_off = k_cursor as u16;
let k_len = kb.len() as u16;
v_cursor_back += vb.len();
let v_off = v_cursor_back as u16;
let v_len = vb.len() as u16;
block[toc_base + i * 8..toc_base + i * 8 + 2].copy_from_slice(&k_off.to_le_bytes());
block[toc_base + i * 8 + 2..toc_base + i * 8 + 4].copy_from_slice(&k_len.to_le_bytes());
block[toc_base + i * 8 + 4..toc_base + i * 8 + 6].copy_from_slice(&v_off.to_le_bytes());
block[toc_base + i * 8 + 6..toc_base + i * 8 + 8].copy_from_slice(&v_len.to_le_bytes());
let ks = keys_start + k_off as usize;
block[ks..ks + kb.len()].copy_from_slice(kb);
let vs = vals_end - v_off as usize;
block[vs..vs + vb.len()].copy_from_slice(vb);
k_cursor += kb.len();
}
block
}
}