#![allow(clippy::unwrap_used, clippy::expect_used)]
#![allow(clippy::unusual_byte_groupings, clippy::identity_op)]
use apfs_core::fsrecord::{decode_jkey, parse_xfields, RecordType};
fn jkey(ty: u64, oid: u64) -> u64 {
(ty << 60) | oid
}
#[test]
fn decode_jkey_splits_type_and_oid() {
let raw = jkey(3, 20);
let (oid, ty) = decode_jkey(raw);
assert_eq!(oid, 20);
assert_eq!(ty, Some(RecordType::Inode));
let raw = jkey(9, 2);
let (oid, ty) = decode_jkey(raw);
assert_eq!(oid, 2);
assert_eq!(ty, Some(RecordType::DirRec));
}
#[test]
fn decode_jkey_masks_full_60_bit_oid() {
let oid_max = 0x0fff_ffff_ffff_ffff;
let raw = jkey(8, oid_max);
let (oid, ty) = decode_jkey(raw);
assert_eq!(oid, oid_max);
assert_eq!(ty, Some(RecordType::FileExtent));
}
#[test]
fn decode_jkey_unknown_type_is_none() {
let raw = jkey(0, 7);
let (oid, ty) = decode_jkey(raw);
assert_eq!(oid, 7);
assert_eq!(ty, None);
let raw = jkey(15, 7);
let (oid, ty) = decode_jkey(raw);
assert_eq!(oid, 7);
assert_eq!(ty, None);
}
#[test]
fn decode_jkey_all_defined_types() {
let cases = [
(1u64, RecordType::SnapMetadata),
(2, RecordType::Extent),
(3, RecordType::Inode),
(4, RecordType::Xattr),
(5, RecordType::SiblingLink),
(6, RecordType::DstreamId),
(7, RecordType::CryptoState),
(8, RecordType::FileExtent),
(9, RecordType::DirRec),
(10, RecordType::DirStats),
(11, RecordType::SnapName),
(12, RecordType::SiblingMap),
(13, RecordType::FileInfo),
];
for (n, expect) in cases {
let (oid, ty) = decode_jkey(jkey(n, 0x42));
assert_eq!(oid, 0x42);
assert_eq!(ty, Some(expect));
}
}
#[test]
fn parse_xfields_decodes_name_tlv() {
let mut blob = Vec::new();
blob.extend_from_slice(&1u16.to_le_bytes()); blob.extend_from_slice(&3u16.to_le_bytes()); blob.push(4); blob.push(0); blob.extend_from_slice(&3u16.to_le_bytes()); blob.extend_from_slice(b"Hi\0"); blob.extend_from_slice(&[0u8; 5]);
let fields = parse_xfields(&blob);
assert_eq!(fields.len(), 1);
assert_eq!(fields[0].0, 4);
assert_eq!(fields[0].1, b"Hi\0");
}
#[test]
fn parse_xfields_two_fields_aligned() {
let mut blob = Vec::new();
blob.extend_from_slice(&2u16.to_le_bytes()); blob.extend_from_slice(&0u16.to_le_bytes());
blob.push(4);
blob.push(0);
blob.extend_from_slice(&2u16.to_le_bytes()); blob.push(8);
blob.push(0);
blob.extend_from_slice(&40u16.to_le_bytes()); blob.extend_from_slice(b"A\0");
blob.extend_from_slice(&[0u8; 6]); blob.extend_from_slice(&[0xCDu8; 40]);
let fields = parse_xfields(&blob);
assert_eq!(fields.len(), 2);
assert_eq!(fields[0], (4, &b"A\0"[..]));
assert_eq!(fields[1].0, 8);
assert_eq!(fields[1].1.len(), 40);
}
#[test]
fn parse_xfields_rejects_out_of_bounds() {
let mut blob = Vec::new();
blob.extend_from_slice(&u16::MAX.to_le_bytes()); blob.extend_from_slice(&0u16.to_le_bytes());
let fields = parse_xfields(&blob);
assert!(fields.is_empty());
}
#[test]
fn parse_xfields_empty_blob() {
assert!(parse_xfields(&[]).is_empty());
assert!(parse_xfields(&[0, 0, 0, 0]).is_empty());
}
#[test]
fn parse_xfields_value_running_past_blob_is_dropped() {
let mut blob = Vec::new();
blob.extend_from_slice(&1u16.to_le_bytes()); blob.extend_from_slice(&0u16.to_le_bytes());
blob.push(4); blob.push(0); blob.extend_from_slice(&64u16.to_le_bytes()); let fields = parse_xfields(&blob);
assert!(fields.is_empty());
}