#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::io::Cursor;
use apfs_core::checkpoint::resolve_live_checkpoint;
use apfs_core::container::NxSuperblock;
use apfs_core::ApfsError;
const HEAD: &[u8] = include_bytes!("../../tests/data/apfs_nxsb_head.bin");
const BLOCK_SIZE: usize = 4096;
fn block0() -> &'static [u8] {
&HEAD[0..BLOCK_SIZE]
}
#[test]
fn resolve_picks_highest_valid_xid_superblock() {
let bootstrap = NxSuperblock::parse(block0()).expect("bootstrap NXSB");
let mut reader = Cursor::new(HEAD);
let live = resolve_live_checkpoint(&mut reader, &bootstrap).expect("resolve checkpoint");
assert_eq!(live.xid, 2);
assert_eq!(live.superblock_paddr, 4);
}
#[test]
fn resolved_superblock_reparses_consistently() {
let bootstrap = NxSuperblock::parse(block0()).expect("bootstrap NXSB");
let mut reader = Cursor::new(HEAD);
let live = resolve_live_checkpoint(&mut reader, &bootstrap).expect("resolve");
let off = live.superblock_paddr as usize * BLOCK_SIZE;
let chosen = NxSuperblock::parse(&HEAD[off..off + BLOCK_SIZE]).expect("reparse chosen");
assert_eq!(chosen.xid, live.xid);
assert_eq!(chosen.omap_oid, 343);
assert_eq!(chosen.fs_oids, vec![1026]);
assert_eq!(chosen.block_size, 4096);
}
#[test]
fn empty_descriptor_ring_fails_loud_not_empty_ok() {
let mut blk = block0().to_vec();
blk[104..108].copy_from_slice(&1u32.to_le_bytes());
blk[112..120].copy_from_slice(&15u64.to_le_bytes());
let cks = apfs_core::object::fletcher64_checksum(&blk);
blk[0..8].copy_from_slice(&cks.to_le_bytes());
let bootstrap = NxSuperblock::parse(&blk).expect("mutated bootstrap parses");
let mut reader = Cursor::new(HEAD);
match resolve_live_checkpoint(&mut reader, &bootstrap) {
Err(ApfsError::NoValidSuperblock { checked, .. }) => assert!(checked >= 1),
other => panic!("expected NoValidSuperblock, got {other:?}"),
}
}
#[test]
fn tree_backed_descriptor_area_is_rejected_loudly() {
let mut blk = block0().to_vec();
let raw = u32::from_le_bytes(blk[104..108].try_into().unwrap()) | 0x8000_0000;
blk[104..108].copy_from_slice(&raw.to_le_bytes());
let cks = apfs_core::object::fletcher64_checksum(&blk);
blk[0..8].copy_from_slice(&cks.to_le_bytes());
let bootstrap = NxSuperblock::parse(&blk).expect("tree-flagged bootstrap parses");
assert!(bootstrap.xp_desc_is_tree());
let mut reader = Cursor::new(HEAD);
assert!(resolve_live_checkpoint(&mut reader, &bootstrap).is_err());
}
#[test]
fn descriptor_blocks_past_the_image_end_are_skipped_not_fatal() {
let mut blk = block0().to_vec();
blk[104..108].copy_from_slice(&2u32.to_le_bytes());
blk[112..120].copy_from_slice(&10_000u64.to_le_bytes());
let cks = apfs_core::object::fletcher64_checksum(&blk);
blk[0..8].copy_from_slice(&cks.to_le_bytes());
let bootstrap = NxSuperblock::parse(&blk).expect("mutated bootstrap parses");
let mut reader = Cursor::new(HEAD);
assert!(matches!(
resolve_live_checkpoint(&mut reader, &bootstrap),
Err(ApfsError::NoValidSuperblock { .. })
));
}
#[test]
fn out_of_range_block_size_is_rejected_loudly() {
let mut blk = block0().to_vec();
blk[36..40].copy_from_slice(&131_072u32.to_le_bytes()); let cks = apfs_core::object::fletcher64_checksum(&blk);
blk[0..8].copy_from_slice(&cks.to_le_bytes());
let bootstrap = NxSuperblock::parse(&blk).expect("mutated bootstrap parses");
assert_eq!(bootstrap.block_size, 131_072);
let mut reader = Cursor::new(HEAD);
match resolve_live_checkpoint(&mut reader, &bootstrap) {
Err(ApfsError::FieldOutOfRange { field, value, .. }) => {
assert_eq!(field, "nx_block_size");
assert_eq!(value, 131_072);
}
other => panic!("expected FieldOutOfRange(nx_block_size), got {other:?}"),
}
}