use libfreemkv::error::Result;
use libfreemkv::{read_filesystem, SectorReader};
use std::collections::HashMap;
const SECTOR_SIZE: usize = 2048;
struct MockSectorReader {
sectors: HashMap<u32, Vec<u8>>,
}
impl MockSectorReader {
fn new() -> Self {
Self {
sectors: HashMap::new(),
}
}
fn set_sector(&mut self, lba: u32, data: Vec<u8>) {
assert_eq!(
data.len(),
SECTOR_SIZE,
"sector data must be exactly 2048 bytes"
);
self.sectors.insert(lba, data);
}
fn set_sector_partial(&mut self, lba: u32, data: &[u8]) {
let mut sector = vec![0u8; SECTOR_SIZE];
let len = data.len().min(SECTOR_SIZE);
sector[..len].copy_from_slice(&data[..len]);
self.sectors.insert(lba, sector);
}
}
impl SectorReader for MockSectorReader {
fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize> {
let total = count as usize * SECTOR_SIZE;
assert!(buf.len() >= total, "buffer too small");
for i in 0..count as u32 {
let offset = i as usize * SECTOR_SIZE;
if let Some(data) = self.sectors.get(&(lba + i)) {
buf[offset..offset + SECTOR_SIZE].copy_from_slice(data);
} else {
buf[offset..offset + SECTOR_SIZE].fill(0);
}
}
Ok(total)
}
}
fn make_avdp_sector(vds_lba: u32) -> Vec<u8> {
let mut s = vec![0u8; SECTOR_SIZE];
s[0..2].copy_from_slice(&2u16.to_le_bytes());
s[16..20].copy_from_slice(&vds_lba.to_le_bytes());
s[20..24].copy_from_slice(&(6u32 * SECTOR_SIZE as u32).to_le_bytes());
s
}
fn make_pvd_sector(volume_id: &str) -> Vec<u8> {
let mut s = vec![0u8; SECTOR_SIZE];
s[0..2].copy_from_slice(&1u16.to_le_bytes());
if !volume_id.is_empty() {
let id_bytes = volume_id.as_bytes();
s[24] = 8; let copy_len = id_bytes.len().min(30);
s[25..25 + copy_len].copy_from_slice(&id_bytes[..copy_len]);
s[55] = (1 + copy_len) as u8; }
s
}
fn make_partition_desc(partition_start: u32) -> Vec<u8> {
let mut s = vec![0u8; SECTOR_SIZE];
s[0..2].copy_from_slice(&5u16.to_le_bytes());
s[188..192].copy_from_slice(&partition_start.to_le_bytes());
s
}
fn make_lvd_sector_simple() -> Vec<u8> {
let mut s = vec![0u8; SECTOR_SIZE];
s[0..2].copy_from_slice(&6u16.to_le_bytes());
s[268..272].copy_from_slice(&1u32.to_le_bytes());
s
}
fn make_terminator() -> Vec<u8> {
let mut s = vec![0u8; SECTOR_SIZE];
s[0..2].copy_from_slice(&8u16.to_le_bytes());
s
}
fn make_fsd_sector(root_meta_lba: u32) -> Vec<u8> {
let mut s = vec![0u8; SECTOR_SIZE];
s[0..2].copy_from_slice(&256u16.to_le_bytes());
s[400..404].copy_from_slice(&(SECTOR_SIZE as u32).to_le_bytes());
s[404..408].copy_from_slice(&root_meta_lba.to_le_bytes());
s
}
fn make_dir_icb(data_meta_lba: u32, data_len: u32) -> Vec<u8> {
let mut s = vec![0u8; SECTOR_SIZE];
s[0..2].copy_from_slice(&266u16.to_le_bytes());
s[56..64].copy_from_slice(&(data_len as u64).to_le_bytes());
s[208..212].copy_from_slice(&0u32.to_le_bytes());
s[212..216].copy_from_slice(&8u32.to_le_bytes());
s[216..220].copy_from_slice(&data_len.to_le_bytes());
s[220..224].copy_from_slice(&data_meta_lba.to_le_bytes());
s
}
fn make_fid(name: &str, icb_meta_lba: u32, is_dir: bool) -> Vec<u8> {
let mut name_bytes = vec![8u8]; name_bytes.extend_from_slice(name.as_bytes());
let l_fi = name_bytes.len() as u8;
let file_chars: u8 = if is_dir { 0x02 } else { 0x00 };
let fid_len = (38 + l_fi as usize + 3) & !3; let mut fid = vec![0u8; fid_len];
fid[0..2].copy_from_slice(&257u16.to_le_bytes());
fid[18] = file_chars;
fid[19] = l_fi;
fid[20..24].copy_from_slice(&(SECTOR_SIZE as u32).to_le_bytes());
fid[24..28].copy_from_slice(&icb_meta_lba.to_le_bytes());
fid[36..38].copy_from_slice(&0u16.to_le_bytes());
fid[38..38 + name_bytes.len()].copy_from_slice(&name_bytes);
fid
}
fn make_parent_fid() -> Vec<u8> {
let fid_len = (38 + 3) & !3;
let mut fid = vec![0u8; fid_len];
fid[0..2].copy_from_slice(&257u16.to_le_bytes());
fid[18] = 0x08; fid[19] = 0; fid
}
fn make_file_icb(data_lba: u32, data_len: u32, file_size: u64) -> Vec<u8> {
let mut s = vec![0u8; SECTOR_SIZE];
s[0..2].copy_from_slice(&261u16.to_le_bytes());
s[56..64].copy_from_slice(&file_size.to_le_bytes());
s[168..172].copy_from_slice(&0u32.to_le_bytes());
s[172..176].copy_from_slice(&8u32.to_le_bytes());
s[176..180].copy_from_slice(&data_len.to_le_bytes());
s[180..184].copy_from_slice(&data_lba.to_le_bytes());
s
}
#[test]
fn mock_sector_reader_roundtrip() {
let mut reader = MockSectorReader::new();
let mut data = vec![0u8; SECTOR_SIZE];
data[0] = 0xAB;
data[1] = 0xCD;
data[2047] = 0xFF;
reader.set_sector(100, data.clone());
let mut buf = vec![0u8; SECTOR_SIZE];
let n = reader.read_sectors(100, 1, &mut buf).unwrap();
assert_eq!(n, SECTOR_SIZE);
assert_eq!(buf[0], 0xAB);
assert_eq!(buf[1], 0xCD);
assert_eq!(buf[2047], 0xFF);
let mut buf2 = vec![0xFFu8; SECTOR_SIZE];
let n2 = reader.read_sectors(999, 1, &mut buf2).unwrap();
assert_eq!(n2, SECTOR_SIZE);
assert_eq!(buf2[0], 0);
assert_eq!(buf2[2047], 0);
}
#[test]
fn mock_sector_reader_multi_sector() {
let mut reader = MockSectorReader::new();
let mut s10 = vec![0u8; SECTOR_SIZE];
s10[0] = 10;
reader.set_sector(10, s10);
let mut s11 = vec![0u8; SECTOR_SIZE];
s11[0] = 11;
reader.set_sector(11, s11);
let mut buf = vec![0u8; SECTOR_SIZE * 2];
let n = reader.read_sectors(10, 2, &mut buf).unwrap();
assert_eq!(n, SECTOR_SIZE * 2);
assert_eq!(buf[0], 10);
assert_eq!(buf[SECTOR_SIZE], 11);
}
#[test]
fn read_filesystem_no_avdp() {
let mut reader = MockSectorReader::new();
let result = read_filesystem(&mut reader);
assert!(result.is_err(), "should fail when no AVDP at sector 256");
}
#[test]
fn read_filesystem_bad_avdp_tag() {
let mut reader = MockSectorReader::new();
let mut bad = vec![0u8; SECTOR_SIZE];
bad[0..2].copy_from_slice(&99u16.to_le_bytes()); reader.set_sector(256, bad);
let result = read_filesystem(&mut reader);
assert!(result.is_err(), "should fail when AVDP tag_id is not 2");
}
#[test]
fn read_filesystem_no_partition_descriptor() {
let mut reader = MockSectorReader::new();
reader.set_sector(256, make_avdp_sector(32));
reader.set_sector(32, make_terminator());
let result = read_filesystem(&mut reader);
assert!(
result.is_err(),
"should fail when no partition descriptor in VDS"
);
}
#[test]
fn read_filesystem_bad_fsd_tag() {
let mut reader = MockSectorReader::new();
let partition_start = 512;
reader.set_sector(256, make_avdp_sector(32));
reader.set_sector(32, make_pvd_sector("TEST_DISC"));
reader.set_sector(33, make_partition_desc(partition_start));
reader.set_sector(34, make_lvd_sector_simple());
reader.set_sector(35, make_terminator());
let result = read_filesystem(&mut reader);
assert!(result.is_err(), "should fail when FSD tag_id is not 256");
}
#[test]
fn read_filesystem_minimal_valid() {
let mut reader = MockSectorReader::new();
let partition_start: u32 = 512;
let root_icb_meta_lba: u32 = 1; let root_data_meta_lba: u32 = 2;
reader.set_sector(256, make_avdp_sector(32));
reader.set_sector(32, make_pvd_sector("MY_DISC"));
reader.set_sector(33, make_partition_desc(partition_start));
reader.set_sector(34, make_lvd_sector_simple());
reader.set_sector(35, make_terminator());
reader.set_sector(partition_start, make_fsd_sector(root_icb_meta_lba));
let parent_fid = make_parent_fid();
let dir_data_len = parent_fid.len() as u32;
reader.set_sector(
partition_start + root_icb_meta_lba,
make_dir_icb(root_data_meta_lba, dir_data_len),
);
reader.set_sector_partial(partition_start + root_data_meta_lba, &parent_fid);
let fs = read_filesystem(&mut reader).expect("should parse minimal UDF");
assert_eq!(fs.volume_id, "MY_DISC");
assert!(fs.root.is_dir);
assert!(fs.root.entries.is_empty(), "root should have no children");
}
#[test]
fn read_filesystem_with_subdirectory() {
let mut reader = MockSectorReader::new();
let partition_start: u32 = 512;
reader.set_sector(256, make_avdp_sector(32));
reader.set_sector(32, make_pvd_sector("DISC_WITH_BDMV"));
reader.set_sector(33, make_partition_desc(partition_start));
reader.set_sector(34, make_lvd_sector_simple());
reader.set_sector(35, make_terminator());
reader.set_sector(partition_start, make_fsd_sector(1));
let parent_fid = make_parent_fid();
let bdmv_fid = make_fid("BDMV", 3, true);
let mut root_data = Vec::new();
root_data.extend_from_slice(&parent_fid);
root_data.extend_from_slice(&bdmv_fid);
let root_data_len = root_data.len() as u32;
reader.set_sector(partition_start + 1, make_dir_icb(2, root_data_len));
reader.set_sector_partial(partition_start + 2, &root_data);
let bdmv_parent = make_parent_fid();
let mpls_fid = make_fid("test.mpls", 5, false);
let mut bdmv_data = Vec::new();
bdmv_data.extend_from_slice(&bdmv_parent);
bdmv_data.extend_from_slice(&mpls_fid);
let bdmv_data_len = bdmv_data.len() as u32;
reader.set_sector(partition_start + 3, make_dir_icb(4, bdmv_data_len));
reader.set_sector_partial(partition_start + 4, &bdmv_data);
reader.set_sector(partition_start + 5, make_file_icb(10, 1024, 1024));
let fs = read_filesystem(&mut reader).expect("should parse UDF with subdir");
assert_eq!(fs.volume_id, "DISC_WITH_BDMV");
assert_eq!(fs.root.entries.len(), 1);
let bdmv = &fs.root.entries[0];
assert_eq!(bdmv.name, "BDMV");
assert!(bdmv.is_dir);
assert_eq!(bdmv.entries.len(), 1);
let mpls = &bdmv.entries[0];
assert_eq!(mpls.name, "test.mpls");
assert!(!mpls.is_dir);
assert_eq!(mpls.size, 1024);
}
#[test]
fn find_dir_case_insensitive() {
let mut reader = MockSectorReader::new();
let partition_start: u32 = 512;
reader.set_sector(256, make_avdp_sector(32));
reader.set_sector(32, make_pvd_sector("CASE_TEST"));
reader.set_sector(33, make_partition_desc(partition_start));
reader.set_sector(34, make_lvd_sector_simple());
reader.set_sector(35, make_terminator());
reader.set_sector(partition_start, make_fsd_sector(1));
let root_data = [make_parent_fid(), make_fid("BDMV", 3, true)].concat();
reader.set_sector(partition_start + 1, make_dir_icb(2, root_data.len() as u32));
reader.set_sector_partial(partition_start + 2, &root_data);
let bdmv_data = [make_parent_fid(), make_fid("PLAYLIST", 5, true)].concat();
reader.set_sector(partition_start + 3, make_dir_icb(4, bdmv_data.len() as u32));
reader.set_sector_partial(partition_start + 4, &bdmv_data);
let playlist_data = make_parent_fid();
reader.set_sector(
partition_start + 5,
make_dir_icb(6, playlist_data.len() as u32),
);
reader.set_sector_partial(partition_start + 6, &playlist_data);
let fs = read_filesystem(&mut reader).expect("should parse");
assert!(fs.find_dir("BDMV/PLAYLIST").is_some());
assert!(fs.find_dir("bdmv/playlist").is_some());
assert!(fs.find_dir("Bdmv/Playlist").is_some());
assert!(fs.find_dir("BdMv/PlayList").is_some());
assert!(fs.find_dir("/BDMV/PLAYLIST/").is_some());
assert!(fs.find_dir("BDMV/STREAM").is_none());
assert!(fs.find_dir("NONEXISTENT").is_none());
}
#[test]
fn sector_reader_is_object_safe() {
let mut reader = MockSectorReader::new();
reader.set_sector(0, vec![42u8; SECTOR_SIZE]);
let dyn_reader: &mut dyn SectorReader = &mut reader;
let mut buf = vec![0u8; SECTOR_SIZE];
let n = dyn_reader.read_sectors(0, 1, &mut buf).unwrap();
assert_eq!(n, SECTOR_SIZE);
assert_eq!(buf[0], 42);
}