use crate::tree::TreeNode;
use crate::Result;
use std::io::{Read, Seek, SeekFrom};
const SECTOR_SIZE: u64 = 2048;
#[derive(Debug, Clone, Copy)]
struct ExtentAd {
length: u32,
location: u32,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
struct LongAd {
length: u32,
location: u32,
partition: u16,
}
#[derive(Debug, Clone)]
struct PartitionInfo {
number: u16,
start_sector: u64,
}
#[derive(Debug, Clone)]
struct MetadataPartitionInfo {
file_location: u32,
partition_ref: u16,
}
#[derive(Debug, Clone)]
struct FileAllocation {
extents: Vec<ExtentAd>,
total_length: u64,
inline_data: Option<Vec<u8>>,
}
fn read_extent_ad(buffer: &[u8]) -> ExtentAd {
ExtentAd {
length: u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]),
location: u32::from_le_bytes([buffer[4], buffer[5], buffer[6], buffer[7]]),
}
}
fn read_long_ad(buffer: &[u8]) -> LongAd {
LongAd {
length: u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]),
location: u32::from_le_bytes([buffer[4], buffer[5], buffer[6], buffer[7]]),
partition: u16::from_le_bytes([buffer[8], buffer[9]]),
}
}
pub fn parse_udf<R: Read + Seek>(file: &mut R) -> Result<TreeNode> {
parse_udf_verbose(file, false)
}
pub fn parse_udf_verbose<R: Read + Seek>(file: &mut R, verbose: bool) -> Result<TreeNode> {
let mut found_udf_marker = false;
if verbose {
eprintln!("Scanning sectors 16-31 for UDF Volume Recognition Sequence...");
}
for sector in 16..32 {
if file.seek(SeekFrom::Start(sector * SECTOR_SIZE)).is_err() {
continue;
}
let mut buffer = [0u8; 16];
if file.read_exact(&mut buffer).is_err() {
continue;
}
let id = &buffer[1..6];
if id == b"NSR02" || id == b"NSR03" || id == b"BEA01" || id == b"TEA01" {
if verbose {
eprintln!(
" Found UDF marker '{:?}' at sector {}",
String::from_utf8_lossy(id),
sector
);
}
found_udf_marker = true;
break;
}
}
if !found_udf_marker {
return Err("Not a valid UDF filesystem (no VRS markers found)".into());
}
if verbose {
eprintln!("Looking for Anchor Volume Descriptor Pointer...");
}
let image_size = file.seek(SeekFrom::End(0)).unwrap_or(0);
let last_sector = image_size / SECTOR_SIZE;
let mut candidates: Vec<u64> = vec![256];
if last_sector > 0 && last_sector != 256 {
candidates.push(last_sector);
}
if last_sector > 256 {
candidates.push(last_sector - 256);
}
for s in 32..256.min(last_sector) {
if !candidates.contains(&s) {
candidates.push(s);
}
}
let mut avdp_buffer = [0u8; 512];
let mut found_avdp = false;
for candidate in &candidates {
if file.seek(SeekFrom::Start(candidate * SECTOR_SIZE)).is_err() {
continue;
}
if file.read_exact(&mut avdp_buffer).is_err() {
continue;
}
let tag_id = u16::from_le_bytes([avdp_buffer[0], avdp_buffer[1]]);
if tag_id == 2 {
if verbose {
eprintln!(" Found AVDP at sector {}", candidate);
}
found_avdp = true;
break;
}
}
if !found_avdp {
if verbose {
eprintln!(" AVDP not found in any candidate sector");
}
return Err("UDF detected but no Anchor Volume Descriptor Pointer found.".into());
}
let main_vds_extent = read_extent_ad(&avdp_buffer[16..24]);
if verbose {
eprintln!(
" Found AVDP. Main VDS at sector {}, length {}",
main_vds_extent.location, main_vds_extent.length
);
}
let mut partitions: Vec<PartitionInfo> = Vec::new();
let mut root_fsd_long_ad = None;
let mut metadata_partition: Option<MetadataPartitionInfo> = None;
let mut sector = main_vds_extent.location as u64;
let end_sector = sector + (main_vds_extent.length as u64).div_ceil(SECTOR_SIZE);
if verbose {
eprintln!(
"Parsing Main Volume Descriptor Sequence (sectors {} to {})...",
sector, end_sector
);
}
while sector < end_sector {
file.seek(SeekFrom::Start(sector * SECTOR_SIZE))?;
let mut vds_buffer = vec![0u8; SECTOR_SIZE as usize];
file.read_exact(&mut vds_buffer)?;
let vds_tag_id = u16::from_le_bytes([vds_buffer[0], vds_buffer[1]]);
match vds_tag_id {
5 => {
let part_num = u16::from_le_bytes([vds_buffer[22], vds_buffer[23]]);
let part_start = u32::from_le_bytes([
vds_buffer[188],
vds_buffer[189],
vds_buffer[190],
vds_buffer[191],
]) as u64;
if verbose {
eprintln!(
" Found Partition Descriptor #{}: starts at sector {}",
part_num, part_start
);
}
partitions.push(PartitionInfo {
number: part_num,
start_sector: part_start,
});
}
6 => {
root_fsd_long_ad = Some(read_long_ad(&vds_buffer[248..264]));
if verbose {
let ad = root_fsd_long_ad.unwrap();
eprintln!(
" Found Logical Volume Descriptor. FSD at location {} in partition {}",
ad.location, ad.partition
);
}
let map_table_length = u32::from_le_bytes([
vds_buffer[264],
vds_buffer[265],
vds_buffer[266],
vds_buffer[267],
]) as usize;
let num_partition_maps = u32::from_le_bytes([
vds_buffer[268],
vds_buffer[269],
vds_buffer[270],
vds_buffer[271],
]);
if verbose {
eprintln!(
" {} partition maps, table length {} bytes",
num_partition_maps, map_table_length
);
}
let mut map_offset = 440usize;
for map_idx in 0..num_partition_maps {
if map_offset + 2 > vds_buffer.len() {
break;
}
let map_type = vds_buffer[map_offset];
let map_length = vds_buffer[map_offset + 1] as usize;
if map_length == 0 {
break;
}
if verbose {
eprintln!(
" Partition map {}: type {}, length {}",
map_idx, map_type, map_length
);
}
if map_type == 2 && map_length >= 64 {
let id_string = &vds_buffer[map_offset + 5..map_offset + 28];
if verbose {
let id_printable: String = id_string
.iter()
.take_while(|&&b| b != 0)
.map(|&b| {
if (0x20..0x7f).contains(&b) {
b as char
} else {
'.'
}
})
.collect();
eprintln!(" Type 2 identifier: '{}'", id_printable);
}
if id_string.starts_with(b"*UDF Metadata Partition") {
let meta_part_ref = u16::from_le_bytes([
vds_buffer[map_offset + 38],
vds_buffer[map_offset + 39],
]);
let meta_file_loc = u32::from_le_bytes([
vds_buffer[map_offset + 40],
vds_buffer[map_offset + 41],
vds_buffer[map_offset + 42],
vds_buffer[map_offset + 43],
]);
if verbose {
eprintln!(
" Metadata Partition: file at location {} in partition {}",
meta_file_loc, meta_part_ref
);
}
metadata_partition = Some(MetadataPartitionInfo {
file_location: meta_file_loc,
partition_ref: meta_part_ref,
});
}
}
map_offset += map_length;
}
}
8 => {
if verbose {
eprintln!(" Found Terminating Descriptor at sector {}", sector);
}
break;
}
_ => {}
}
sector += 1;
}
let fsd_long_ad =
root_fsd_long_ad.ok_or("Failed to find File Set Descriptor location in LVD")?;
let fsd_partition_ref = fsd_long_ad.partition;
let (fsd_sector, partition_start) = if let Some(ref meta_info) = metadata_partition {
if verbose {
eprintln!("FSD is in metadata partition, reading via metadata file...");
}
let meta_phys_partition = partitions
.iter()
.find(|p| p.number == meta_info.partition_ref)
.ok_or("Cannot find physical partition for metadata file")?;
let meta_fe_sector = meta_phys_partition.start_sector + meta_info.file_location as u64;
if verbose {
eprintln!(" Metadata File Entry at sector {}", meta_fe_sector);
}
file.seek(SeekFrom::Start(meta_fe_sector * SECTOR_SIZE))?;
let mut meta_fe_buffer = vec![0u8; SECTOR_SIZE as usize];
file.read_exact(&mut meta_fe_buffer)?;
let meta_tag_id = u16::from_le_bytes([meta_fe_buffer[0], meta_fe_buffer[1]]);
if verbose {
eprintln!(" Metadata FE tag: {}", meta_tag_id);
}
let meta_alloc = get_file_allocation(&meta_fe_buffer)?;
let first_extent = meta_alloc
.extents
.first()
.ok_or("Metadata file has no allocation extents")?;
if verbose {
eprintln!(
" Metadata file extent: location {}, length {}",
first_extent.location, first_extent.length
);
}
let metadata_data_sector = meta_phys_partition.start_sector + first_extent.location as u64;
let fsd_offset_in_metadata = fsd_long_ad.location as u64;
(
metadata_data_sector + fsd_offset_in_metadata,
metadata_data_sector,
)
} else {
let partition = partitions
.iter()
.find(|p| p.number == fsd_partition_ref)
.or_else(|| partitions.first())
.ok_or("No partition found")?;
(
partition.start_sector + fsd_long_ad.location as u64,
partition.start_sector,
)
};
if verbose {
eprintln!("Reading File Set Descriptor at sector {}...", fsd_sector);
}
file.seek(SeekFrom::Start(fsd_sector * SECTOR_SIZE))?;
let mut fsd_buffer = [0u8; 512];
file.read_exact(&mut fsd_buffer)?;
let fsd_tag_id = u16::from_le_bytes([fsd_buffer[0], fsd_buffer[1]]);
if fsd_tag_id != 256 {
if verbose {
eprintln!(
" Tag {} at expected FSD location, scanning nearby...",
fsd_tag_id
);
}
let mut found_fsd = false;
for offset in 1..32 {
file.seek(SeekFrom::Start((fsd_sector + offset) * SECTOR_SIZE))?;
file.read_exact(&mut fsd_buffer)?;
let tag = u16::from_le_bytes([fsd_buffer[0], fsd_buffer[1]]);
if tag == 256 {
if verbose {
eprintln!(
" Found FSD at sector {} (offset +{})",
fsd_sector + offset,
offset
);
}
found_fsd = true;
break;
}
}
if !found_fsd {
return Err(format!(
"Invalid File Set Descriptor tag: expected 256, found {}",
fsd_tag_id
)
.into());
}
}
let root_icb_long_ad = read_long_ad(&fsd_buffer[400..416]);
if verbose {
eprintln!(
" Found FSD. Root ICB at location {} in partition {}",
root_icb_long_ad.location, root_icb_long_ad.partition
);
}
let mut root_node = TreeNode::new_directory("/".to_string());
if verbose {
eprintln!("Parsing root directory...");
}
parse_directory(
file,
partition_start,
&root_icb_long_ad,
&mut root_node,
verbose,
)?;
root_node.calculate_directory_size();
Ok(root_node)
}
fn get_file_allocation(fe_buffer: &[u8]) -> Result<FileAllocation> {
let tag_id = u16::from_le_bytes([fe_buffer[0], fe_buffer[1]]);
let (ad_length_offset, ea_length_offset, ad_data_offset_base) = match tag_id {
261 => (172, 168, 176usize), 266 => (212, 208, 216usize), _ => return Err(format!("Unsupported ICB tag: {}", tag_id).into()),
};
let icb_flags = u16::from_le_bytes([fe_buffer[18], fe_buffer[19]]);
let ad_type = icb_flags & 0x07;
let ea_length = u32::from_le_bytes([
fe_buffer[ea_length_offset],
fe_buffer[ea_length_offset + 1],
fe_buffer[ea_length_offset + 2],
fe_buffer[ea_length_offset + 3],
]) as usize;
let ad_length = u32::from_le_bytes([
fe_buffer[ad_length_offset],
fe_buffer[ad_length_offset + 1],
fe_buffer[ad_length_offset + 2],
fe_buffer[ad_length_offset + 3],
]) as usize;
let ad_offset = ad_data_offset_base + ea_length;
let mut extents = Vec::new();
let mut total_length: u64 = 0;
let mut inline_data = None;
match ad_type {
0 => {
let mut pos = ad_offset;
while pos + 8 <= fe_buffer.len() && pos < ad_offset + ad_length {
let raw_length = u32::from_le_bytes([
fe_buffer[pos],
fe_buffer[pos + 1],
fe_buffer[pos + 2],
fe_buffer[pos + 3],
]);
let extent_type = raw_length >> 30;
let length = raw_length & 0x3FFFFFFF;
let location = u32::from_le_bytes([
fe_buffer[pos + 4],
fe_buffer[pos + 5],
fe_buffer[pos + 6],
fe_buffer[pos + 7],
]);
if length == 0 {
break;
}
if extent_type == 3 {
break;
}
if extent_type == 0 {
extents.push(ExtentAd { length, location });
}
total_length += length as u64;
pos += 8;
}
}
1 => {
let mut pos = ad_offset;
while pos + 16 <= fe_buffer.len() && pos < ad_offset + ad_length {
let raw_length = u32::from_le_bytes([
fe_buffer[pos],
fe_buffer[pos + 1],
fe_buffer[pos + 2],
fe_buffer[pos + 3],
]);
let extent_type = raw_length >> 30;
let length = raw_length & 0x3FFFFFFF;
let location = u32::from_le_bytes([
fe_buffer[pos + 4],
fe_buffer[pos + 5],
fe_buffer[pos + 6],
fe_buffer[pos + 7],
]);
if length == 0 {
break;
}
if extent_type == 3 {
break;
}
if extent_type == 0 {
extents.push(ExtentAd { length, location });
}
total_length += length as u64;
pos += 16;
}
}
3 => {
let end = (ad_offset + ad_length).min(fe_buffer.len());
if ad_offset < end {
inline_data = Some(fe_buffer[ad_offset..end].to_vec());
total_length = (end - ad_offset) as u64;
}
}
_ => {
if ad_offset + 8 <= fe_buffer.len() {
let ext = read_extent_ad(&fe_buffer[ad_offset..ad_offset + 8]);
if ext.length > 0 {
total_length = ext.length as u64;
extents.push(ext);
}
}
}
}
if extents.is_empty() && inline_data.is_none() {
return Err("No allocation extents found in file entry".into());
}
Ok(FileAllocation {
extents,
total_length,
inline_data,
})
}
fn parse_directory<R: Read + Seek>(
file: &mut R,
partition_start: u64,
icb_long_ad: &LongAd,
parent_node: &mut TreeNode,
verbose: bool,
) -> Result<()> {
file.seek(SeekFrom::Start(
(partition_start + icb_long_ad.location as u64) * SECTOR_SIZE,
))?;
let mut fe_buffer = vec![0u8; SECTOR_SIZE as usize];
file.read_exact(&mut fe_buffer)?;
let alloc = get_file_allocation(&fe_buffer)?;
if verbose {
if alloc.inline_data.is_some() {
eprintln!(" Directory has inline data, {} bytes", alloc.total_length);
} else {
eprintln!(
" Directory has {} extent(s), total {} bytes",
alloc.extents.len(),
alloc.total_length
);
}
}
let buffer = if let Some(data) = alloc.inline_data {
data
} else {
let cap = usize::try_from(alloc.total_length)
.map_err(|_| format!("directory too large: {} bytes", alloc.total_length))?;
let mut buf = Vec::with_capacity(cap);
for extent in &alloc.extents {
file.seek(SeekFrom::Start(
(partition_start + extent.location as u64) * SECTOR_SIZE,
))?;
let mut chunk = vec![0u8; extent.length as usize];
file.read_exact(&mut chunk)?;
buf.extend_from_slice(&chunk);
}
buf
};
let mut offset = 0;
while offset < buffer.len() {
if offset + 40 > buffer.len() {
break;
}
let tag_id = u16::from_le_bytes([buffer[offset], buffer[offset + 1]]);
if tag_id == 0 {
offset += 4;
continue;
}
if tag_id != 257 {
if verbose {
eprintln!(
" Warning: Expected FID (257) at offset {}, found {}",
offset, tag_id
);
}
break;
}
let file_characteristics = buffer[offset + 18];
let length_of_fi = buffer[offset + 19] as usize;
let icb = read_long_ad(&buffer[offset + 20..offset + 36]);
let length_of_iu = u16::from_le_bytes([buffer[offset + 36], buffer[offset + 37]]) as usize;
let name_offset = offset + 38 + length_of_iu;
if name_offset + length_of_fi > buffer.len() {
if verbose {
eprintln!(
" Warning: FID name offset out of bounds at offset {}",
offset
);
}
break;
}
let name = if length_of_fi == 0 {
String::new()
} else {
parse_udf_name(&buffer[name_offset..name_offset + length_of_fi])
};
let is_directory = (file_characteristics & 0x02) != 0;
let is_deleted = (file_characteristics & 0x04) != 0;
let is_parent = (file_characteristics & 0x08) != 0;
if !is_deleted && !is_parent && !name.is_empty() {
if verbose {
eprintln!(
" Found {}: {}",
if is_directory { "dir" } else { "file" },
name
);
}
if is_directory {
let mut dir_node = TreeNode::new_directory(name);
if let Err(e) = parse_directory(file, partition_start, &icb, &mut dir_node, verbose)
{
if verbose {
eprintln!(" Warning: Failed to parse subdirectory: {}", e);
}
}
parent_node.add_child(dir_node);
} else {
match get_file_info(file, partition_start, &icb) {
Ok(alloc) => {
let file_node = if let Some(first) = alloc.extents.first() {
TreeNode::new_file_with_location(
name,
alloc.total_length,
(partition_start + first.location as u64) * SECTOR_SIZE,
alloc.total_length,
)
} else {
TreeNode::new_file(name, alloc.total_length)
};
parent_node.add_child(file_node);
}
Err(e) => {
if verbose {
eprintln!(" Warning: Failed to get file extent: {}", e);
}
let file_node = TreeNode::new_file(name, 0);
parent_node.add_child(file_node);
}
}
}
}
let fid_length = 38 + length_of_iu + length_of_fi;
offset += (fid_length + 3) & !3;
}
Ok(())
}
fn get_file_info<R: Read + Seek>(
file: &mut R,
partition_start: u64,
icb_long_ad: &LongAd,
) -> Result<FileAllocation> {
file.seek(SeekFrom::Start(
(partition_start + icb_long_ad.location as u64) * SECTOR_SIZE,
))?;
let mut fe_buffer = vec![0u8; SECTOR_SIZE as usize];
file.read_exact(&mut fe_buffer)?;
get_file_allocation(&fe_buffer)
}
fn parse_udf_name(data: &[u8]) -> String {
if data.is_empty() {
return String::new();
}
let compression_id = data[0];
if compression_id == 8 {
String::from_utf8_lossy(&data[1..]).to_string()
} else if compression_id == 16 {
let utf16_data: Vec<u16> = data[1..]
.chunks_exact(2)
.map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]]))
.collect();
String::from_utf16_lossy(&utf16_data)
} else {
String::from_utf8_lossy(data).to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
const S: usize = 2048;
fn w16(buf: &mut [u8], offset: usize, val: u16) {
buf[offset..offset + 2].copy_from_slice(&val.to_le_bytes());
}
fn w32(buf: &mut [u8], offset: usize, val: u32) {
buf[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
}
fn make_udf_image() -> Vec<u8> {
let mut img = vec![0u8; S * 270];
img[16 * S + 1..16 * S + 6].copy_from_slice(b"BEA01");
img[17 * S + 1..17 * S + 6].copy_from_slice(b"NSR02");
img[18 * S + 1..18 * S + 6].copy_from_slice(b"TEA01");
let avdp = 256 * S;
w16(&mut img, avdp, 2);
w32(&mut img, avdp + 16, (3 * S) as u32); w32(&mut img, avdp + 20, 257);
let pd = 257 * S;
w16(&mut img, pd, 5);
w16(&mut img, pd + 22, 0); w32(&mut img, pd + 188, 260);
let lvd = 258 * S;
w16(&mut img, lvd, 6);
w32(&mut img, lvd + 248, S as u32); w32(&mut img, lvd + 252, 0); w16(&mut img, lvd + 256, 0);
w16(&mut img, 259 * S, 8);
let fsd = 260 * S;
w16(&mut img, fsd, 256); w32(&mut img, fsd + 400, S as u32); w32(&mut img, fsd + 404, 1); w16(&mut img, fsd + 408, 0);
let rfe = 261 * S;
w16(&mut img, rfe, 261); w16(&mut img, rfe + 18, 3); let fid_data = make_fid_data();
w32(&mut img, rfe + 172, fid_data.len() as u32); img[rfe + 176..rfe + 176 + fid_data.len()].copy_from_slice(&fid_data);
let content = b"Hello UDF!";
let hfe = 262 * S;
w16(&mut img, hfe, 261); w16(&mut img, hfe + 18, 3); w32(&mut img, hfe + 172, content.len() as u32); img[hfe + 176..hfe + 176 + content.len()].copy_from_slice(content);
img
}
fn make_fid_data() -> Vec<u8> {
let mut data = Vec::new();
let mut parent = vec![0u8; 40];
w16(&mut parent, 0, 257); parent[18] = 0x08; parent[19] = 0; data.extend_from_slice(&parent);
let name_raw: Vec<u8> = {
let mut n = vec![8u8]; n.extend_from_slice(b"hello.txt");
n
};
let len_fi = name_raw.len() as u8;
let total_unpadded = 38 + len_fi as usize;
let padded = (total_unpadded + 3) & !3;
let mut file_fid = vec![0u8; padded];
w16(&mut file_fid, 0, 257); file_fid[18] = 0x00; file_fid[19] = len_fi; w32(&mut file_fid, 20, 512); w32(&mut file_fid, 24, 2); w16(&mut file_fid, 28, 0); file_fid[38..38 + name_raw.len()].copy_from_slice(&name_raw);
data.extend_from_slice(&file_fid);
data
}
#[test]
fn read_extent_ad_little_endian() {
let buf = [0x00, 0x10, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00];
let ext = read_extent_ad(&buf);
assert_eq!(ext.length, 0x1000);
assert_eq!(ext.location, 5);
}
#[test]
fn read_long_ad_parses_partition() {
let mut buf = [0u8; 16];
buf[0..4].copy_from_slice(&512u32.to_le_bytes()); buf[4..8].copy_from_slice(&42u32.to_le_bytes()); buf[8..10].copy_from_slice(&7u16.to_le_bytes()); let ad = read_long_ad(&buf);
assert_eq!(ad.length, 512);
assert_eq!(ad.location, 42);
assert_eq!(ad.partition, 7);
}
fn make_fe_buf(tag_id: u16, ad_type: u16, ad_bytes: &[u8]) -> Vec<u8> {
let mut buf = vec![0u8; 2048];
w16(&mut buf, 0, tag_id);
w16(&mut buf, 18, ad_type); w32(&mut buf, 168, 0); w32(&mut buf, 172, ad_bytes.len() as u32); buf[176..176 + ad_bytes.len()].copy_from_slice(ad_bytes);
buf
}
#[test]
fn file_alloc_short_ad() {
let mut ad = vec![0u8; 8];
w32(&mut ad, 0, 1024); w32(&mut ad, 4, 5); let buf = make_fe_buf(261, 0, &ad);
let alloc = get_file_allocation(&buf).unwrap();
assert_eq!(alloc.total_length, 1024);
assert_eq!(alloc.extents.len(), 1);
assert_eq!(alloc.extents[0].location, 5);
}
#[test]
fn file_alloc_short_ad_sparse_skipped() {
let mut ad = vec![0u8; 8];
let raw = (1u32 << 30) | 512; w32(&mut ad, 0, raw);
w32(&mut ad, 4, 99);
let buf = make_fe_buf(261, 0, &ad);
assert!(get_file_allocation(&buf).is_err());
}
#[test]
fn file_alloc_long_ad() {
let mut ad = vec![0u8; 16];
w32(&mut ad, 0, 4096); w32(&mut ad, 4, 10); w16(&mut ad, 8, 0); let buf = make_fe_buf(261, 1, &ad);
let alloc = get_file_allocation(&buf).unwrap();
assert_eq!(alloc.total_length, 4096);
assert_eq!(alloc.extents.len(), 1);
}
#[test]
fn file_alloc_inline_data() {
let content = b"hello inline";
let buf = make_fe_buf(261, 3, content);
let alloc = get_file_allocation(&buf).unwrap();
assert_eq!(alloc.total_length, content.len() as u64);
assert!(alloc.inline_data.is_some());
assert_eq!(alloc.inline_data.unwrap(), content);
}
#[test]
fn file_alloc_extended_file_entry() {
let content = b"efe data";
let mut buf = vec![0u8; 2048];
w16(&mut buf, 0, 266); w16(&mut buf, 18, 3); w32(&mut buf, 208, 0); w32(&mut buf, 212, content.len() as u32); buf[216..216 + content.len()].copy_from_slice(content);
let alloc = get_file_allocation(&buf).unwrap();
assert_eq!(alloc.total_length, content.len() as u64);
}
#[test]
fn file_alloc_rejects_unknown_tag() {
let buf = make_fe_buf(999, 0, &[]);
assert!(get_file_allocation(&buf).is_err());
}
#[test]
fn file_alloc_fallback_ad_type() {
let mut ad = [0u8; 8];
w32(&mut ad, 0, 512); w32(&mut ad, 4, 7); let buf = make_fe_buf(261, 2, &ad);
let alloc = get_file_allocation(&buf).unwrap();
assert_eq!(alloc.total_length, 512);
}
#[test]
fn udf_name_cs0_8bit() {
let data = [8u8, b'h', b'i'];
assert_eq!(parse_udf_name(&data), "hi");
}
#[test]
fn udf_name_cs0_16bit() {
let data = [16u8, 0x00, 0x41];
assert_eq!(parse_udf_name(&data), "A");
}
#[test]
fn udf_name_fallback_raw() {
let data = [42u8, b'x', b'y'];
let name = parse_udf_name(&data);
assert!(!name.is_empty());
}
#[test]
fn udf_name_empty() {
assert_eq!(parse_udf_name(&[]), "");
}
#[test]
fn parse_udf_rejects_non_udf() {
let mut c = Cursor::new(vec![0u8; 4096]);
assert!(parse_udf(&mut c).is_err());
}
#[test]
fn parse_udf_no_avdp_after_vrs() {
let mut img = vec![0u8; S * 270];
img[16 * S + 1..16 * S + 6].copy_from_slice(b"BEA01");
img[17 * S + 1..17 * S + 6].copy_from_slice(b"NSR03");
img[18 * S + 1..18 * S + 6].copy_from_slice(b"TEA01");
let mut c = Cursor::new(img);
let err = parse_udf(&mut c).unwrap_err();
assert!(err.to_string().contains("Anchor") || err.to_string().contains("AVDP"));
}
#[test]
fn parse_udf_no_lvd_returns_err() {
let mut img = vec![0u8; S * 270];
img[16 * S + 1..16 * S + 6].copy_from_slice(b"BEA01");
img[17 * S + 1..17 * S + 6].copy_from_slice(b"NSR02");
img[18 * S + 1..18 * S + 6].copy_from_slice(b"TEA01");
let avdp = 256 * S;
w16(&mut img, avdp, 2);
w32(&mut img, avdp + 16, (2 * S) as u32);
w32(&mut img, avdp + 20, 257);
w16(&mut img, 257 * S, 5);
w16(&mut img, 257 * S + 22, 0);
w32(&mut img, 257 * S + 188, 260);
w16(&mut img, 258 * S, 8);
let mut c = Cursor::new(img);
assert!(parse_udf(&mut c).is_err());
}
#[test]
fn parse_udf_verbose_non_udf() {
let mut c = Cursor::new(vec![0u8; 4096]);
assert!(parse_udf_verbose(&mut c, true).is_err());
}
#[test]
fn parse_udf_synthetic_image_root_found() {
let img = make_udf_image();
let mut c = Cursor::new(img);
let root = parse_udf(&mut c).expect("parse should succeed");
assert_eq!(root.name, "/");
assert!(root.is_directory);
}
#[test]
fn parse_udf_synthetic_finds_hello_txt() {
let img = make_udf_image();
let mut c = Cursor::new(img);
let root = parse_udf(&mut c).expect("parse should succeed");
let node = root.find_node("/hello.txt");
assert!(node.is_some(), "hello.txt should be in root");
}
#[test]
fn parse_udf_verbose_synthetic_image() {
let img = make_udf_image();
let mut c = Cursor::new(img);
let root = parse_udf_verbose(&mut c, true).expect("parse should succeed");
assert_eq!(root.name, "/");
}
fn make_udf_image_edge_fids() -> Vec<u8> {
let mut img = make_udf_image();
let mut fids: Vec<u8> = Vec::new();
fids.extend_from_slice(&[0u8; 4]);
let mut parent = vec![0u8; 40];
let mut tmp = parent.clone();
w16(&mut tmp, 0, 257);
tmp[18] = 0x08;
parent.copy_from_slice(&tmp);
fids.extend_from_slice(&parent);
let del_name: Vec<u8> = {
let mut n = vec![8u8];
n.extend_from_slice(b"deleted.txt");
n
};
let del_len = del_name.len() as u8;
let del_total = 38 + del_len as usize;
let del_padded = (del_total + 3) & !3;
let mut del_fid = vec![0u8; del_padded];
w16(&mut del_fid, 0, 257);
del_fid[18] = 0x04; del_fid[19] = del_len;
del_fid[38..38 + del_name.len()].copy_from_slice(&del_name);
fids.extend_from_slice(&del_fid);
let name_raw: Vec<u8> = {
let mut n = vec![8u8];
n.extend_from_slice(b"hello.txt");
n
};
let len_fi = name_raw.len() as u8;
let total_unpadded = 38 + len_fi as usize;
let padded = (total_unpadded + 3) & !3;
let mut file_fid = vec![0u8; padded];
w16(&mut file_fid, 0, 257);
file_fid[18] = 0x00;
file_fid[19] = len_fi;
w32(&mut file_fid, 20, 512);
w32(&mut file_fid, 24, 2); w16(&mut file_fid, 28, 0);
file_fid[38..38 + name_raw.len()].copy_from_slice(&name_raw);
fids.extend_from_slice(&file_fid);
let rfe = 261 * S;
w16(&mut img, rfe, 261);
w16(&mut img, rfe + 18, 3); w32(&mut img, rfe + 172, fids.len() as u32);
img[rfe + 176..rfe + 176 + fids.len()].copy_from_slice(&fids);
img
}
#[test]
fn parse_udf_deleted_fid_skipped() {
let img = make_udf_image_edge_fids();
let mut c = Cursor::new(img);
let root = parse_udf(&mut c).expect("parse should succeed");
assert!(root.find_node("/deleted.txt").is_none());
assert!(root.find_node("/hello.txt").is_some());
}
#[test]
fn parse_udf_deleted_fid_verbose() {
let img = make_udf_image_edge_fids();
let mut c = Cursor::new(img);
let root = parse_udf_verbose(&mut c, true).expect("parse should succeed");
assert_eq!(root.name, "/");
}
fn make_udf_image_bad_file_entry() -> Vec<u8> {
let mut img = make_udf_image();
let mut fids: Vec<u8> = Vec::new();
let mut parent = vec![0u8; 40];
w16(&mut parent, 0, 257);
parent[18] = 0x08;
fids.extend_from_slice(&parent);
let name_raw: Vec<u8> = {
let mut n = vec![8u8];
n.extend_from_slice(b"badfile.txt");
n
};
let len_fi = name_raw.len() as u8;
let total_unpadded = 38 + len_fi as usize;
let padded = (total_unpadded + 3) & !3;
let mut file_fid = vec![0u8; padded];
w16(&mut file_fid, 0, 257);
file_fid[18] = 0x00;
file_fid[19] = len_fi;
w32(&mut file_fid, 20, 512);
w32(&mut file_fid, 24, 5); w16(&mut file_fid, 28, 0);
file_fid[38..38 + name_raw.len()].copy_from_slice(&name_raw);
fids.extend_from_slice(&file_fid);
let rfe = 261 * S;
w16(&mut img, rfe, 261);
w16(&mut img, rfe + 18, 3);
w32(&mut img, rfe + 172, fids.len() as u32);
img[rfe + 176..rfe + 176 + fids.len()].copy_from_slice(&fids);
img
}
#[test]
fn parse_udf_bad_file_entry_emits_zero_size_node() {
let img = make_udf_image_bad_file_entry();
let mut c = Cursor::new(img);
let root = parse_udf(&mut c).expect("parse should succeed even with bad file entry");
let node = root.find_node("/badfile.txt");
assert!(node.is_some(), "bad file entry should still emit a node");
assert_eq!(node.unwrap().size, 0);
}
#[test]
fn parse_udf_bad_file_entry_verbose() {
let img = make_udf_image_bad_file_entry();
let mut c = Cursor::new(img);
let root = parse_udf_verbose(&mut c, true).expect("parse should succeed");
assert_eq!(root.name, "/");
}
fn make_udf_image_fsd_at_offset() -> Vec<u8> {
let mut img = vec![0u8; S * 280];
img[16 * S + 1..16 * S + 6].copy_from_slice(b"BEA01");
img[17 * S + 1..17 * S + 6].copy_from_slice(b"NSR02");
img[18 * S + 1..18 * S + 6].copy_from_slice(b"TEA01");
let avdp = 256 * S;
w16(&mut img, avdp, 2);
w32(&mut img, avdp + 16, (3 * S) as u32);
w32(&mut img, avdp + 20, 257);
w16(&mut img, 257 * S, 5);
w16(&mut img, 257 * S + 22, 0);
w32(&mut img, 257 * S + 188, 260);
w16(&mut img, 258 * S, 6);
w32(&mut img, 258 * S + 248, S as u32);
w32(&mut img, 258 * S + 252, 0); w16(&mut img, 258 * S + 256, 0);
w16(&mut img, 259 * S, 8);
let fsd = 261 * S;
w16(&mut img, fsd, 256);
w32(&mut img, fsd + 400, S as u32);
w32(&mut img, fsd + 404, 2); w16(&mut img, fsd + 408, 0);
let rfe = 262 * S;
w16(&mut img, rfe, 261);
w16(&mut img, rfe + 18, 3); let mut parent = vec![0u8; 40];
w16(&mut parent, 0, 257);
parent[18] = 0x08;
w32(&mut img, rfe + 172, parent.len() as u32);
img[rfe + 176..rfe + 176 + parent.len()].copy_from_slice(&parent);
img
}
#[test]
fn parse_udf_fsd_found_via_nearby_scan() {
let img = make_udf_image_fsd_at_offset();
let mut c = Cursor::new(img);
let root = parse_udf(&mut c).expect("FSD nearby scan should succeed");
assert_eq!(root.name, "/");
}
#[test]
fn parse_udf_fsd_nearby_scan_verbose() {
let img = make_udf_image_fsd_at_offset();
let mut c = Cursor::new(img);
let root = parse_udf_verbose(&mut c, true).expect("FSD nearby scan should succeed");
assert_eq!(root.name, "/");
}
fn make_udf_image_no_fsd() -> Vec<u8> {
let mut img = vec![0u8; S * 280];
img[16 * S + 1..16 * S + 6].copy_from_slice(b"BEA01");
img[17 * S + 1..17 * S + 6].copy_from_slice(b"NSR02");
img[18 * S + 1..18 * S + 6].copy_from_slice(b"TEA01");
let avdp = 256 * S;
w16(&mut img, avdp, 2);
w32(&mut img, avdp + 16, (3 * S) as u32);
w32(&mut img, avdp + 20, 257);
w16(&mut img, 257 * S, 5);
w16(&mut img, 257 * S + 22, 0);
w32(&mut img, 257 * S + 188, 260);
w16(&mut img, 258 * S, 6);
w32(&mut img, 258 * S + 248, S as u32);
w32(&mut img, 258 * S + 252, 0); w16(&mut img, 258 * S + 256, 0);
w16(&mut img, 259 * S, 8);
img
}
#[test]
fn parse_udf_no_fsd_returns_err() {
let img = make_udf_image_no_fsd();
let mut c = Cursor::new(img);
assert!(parse_udf(&mut c).is_err());
}
fn make_udf_image_extent_file() -> Vec<u8> {
let mut img = vec![0u8; S * 280];
img[16 * S + 1..16 * S + 6].copy_from_slice(b"BEA01");
img[17 * S + 1..17 * S + 6].copy_from_slice(b"NSR02");
img[18 * S + 1..18 * S + 6].copy_from_slice(b"TEA01");
let avdp = 256 * S;
w16(&mut img, avdp, 2);
w32(&mut img, avdp + 16, (3 * S) as u32);
w32(&mut img, avdp + 20, 257);
w16(&mut img, 257 * S, 5);
w16(&mut img, 257 * S + 22, 0);
w32(&mut img, 257 * S + 188, 260);
w16(&mut img, 258 * S, 6);
w32(&mut img, 258 * S + 248, S as u32);
w32(&mut img, 258 * S + 252, 0);
w16(&mut img, 258 * S + 256, 0);
w16(&mut img, 259 * S, 8);
let fsd = 260 * S;
w16(&mut img, fsd, 256);
w32(&mut img, fsd + 400, S as u32);
w32(&mut img, fsd + 404, 1);
w16(&mut img, fsd + 408, 0);
let rfe = 261 * S;
w16(&mut img, rfe, 261);
w16(&mut img, rfe + 18, 3);
let mut fids: Vec<u8> = Vec::new();
let mut parent = vec![0u8; 40];
w16(&mut parent, 0, 257);
parent[18] = 0x08;
fids.extend_from_slice(&parent);
let name_raw: Vec<u8> = {
let mut n = vec![8u8];
n.extend_from_slice(b"data.bin");
n
};
let len_fi = name_raw.len() as u8;
let total_unpadded = 38 + len_fi as usize;
let padded = (total_unpadded + 3) & !3;
let mut file_fid = vec![0u8; padded];
w16(&mut file_fid, 0, 257);
file_fid[18] = 0x00;
file_fid[19] = len_fi;
w32(&mut file_fid, 20, S as u32);
w32(&mut file_fid, 24, 3); w16(&mut file_fid, 28, 0);
file_fid[38..38 + name_raw.len()].copy_from_slice(&name_raw);
fids.extend_from_slice(&file_fid);
w32(&mut img, rfe + 172, fids.len() as u32);
img[rfe + 176..rfe + 176 + fids.len()].copy_from_slice(&fids);
let fe = 263 * S;
w16(&mut img, fe, 261); w16(&mut img, fe + 18, 0); let file_len: u32 = 512;
w32(&mut img, fe + 172, 8u32); w32(&mut img, fe + 176, file_len); w32(&mut img, fe + 180, 4);
let data_sector = 264 * S;
img[data_sector..data_sector + 512].fill(0xAB);
img
}
#[test]
fn parse_udf_extent_based_file_has_location() {
let img = make_udf_image_extent_file();
let mut c = Cursor::new(img);
let root = parse_udf(&mut c).expect("parse should succeed");
let node = root.find_node("/data.bin");
assert!(node.is_some(), "data.bin should exist");
let node = node.unwrap();
assert_eq!(node.size, 512);
assert!(
node.file_location.is_some(),
"extent-based file should have file_location"
);
assert_eq!(node.file_location.unwrap(), 264 * 2048);
}
#[test]
fn file_alloc_empty_inline_data_returns_err() {
let buf = make_fe_buf(261, 3, &[]);
assert!(
get_file_allocation(&buf).is_err(),
"empty inline data should error (no extents, no inline_data)"
);
}
#[test]
fn file_alloc_short_ad_next_extent_type_breaks() {
let mut ad = vec![0u8; 8];
let raw = (3u32 << 30) | 512; w32(&mut ad, 0, raw);
w32(&mut ad, 4, 7);
let buf = make_fe_buf(261, 0, &ad);
assert!(get_file_allocation(&buf).is_err());
}
#[test]
fn file_alloc_long_ad_next_extent_type_breaks() {
let mut ad = vec![0u8; 16];
let raw = (3u32 << 30) | 512;
w32(&mut ad, 0, raw);
w32(&mut ad, 4, 7);
let buf = make_fe_buf(261, 1, &ad);
assert!(get_file_allocation(&buf).is_err());
}
#[test]
fn file_alloc_long_ad_sparse_skipped() {
let mut ad = vec![0u8; 16];
let raw = (1u32 << 30) | 512;
w32(&mut ad, 0, raw);
w32(&mut ad, 4, 7);
let buf = make_fe_buf(261, 1, &ad);
assert!(get_file_allocation(&buf).is_err());
}
#[test]
fn file_alloc_short_ad_zero_length_breaks() {
let ad = vec![0u8; 8];
let buf = make_fe_buf(261, 0, &ad);
assert!(get_file_allocation(&buf).is_err());
}
#[test]
fn file_alloc_long_ad_zero_length_breaks() {
let ad = vec![0u8; 16];
let buf = make_fe_buf(261, 1, &ad);
assert!(get_file_allocation(&buf).is_err());
}
#[test]
fn file_alloc_fallback_unknown_ad_type() {
let mut ad = vec![0u8; 8];
w32(&mut ad, 0, 512u32); w32(&mut ad, 4, 5u32); let buf = make_fe_buf(261, 2, &ad);
let alloc = get_file_allocation(&buf).expect("fallback should succeed");
assert_eq!(alloc.extents.len(), 1);
assert_eq!(alloc.extents[0].length, 512);
}
fn make_udf_image_extent_dir() -> Vec<u8> {
let mut img = vec![0u8; S * 270];
img[16 * S + 1..16 * S + 6].copy_from_slice(b"BEA01");
img[17 * S + 1..17 * S + 6].copy_from_slice(b"NSR02");
w16(&mut img, 256 * S, 2);
w32(&mut img, 256 * S + 16, (3 * S) as u32);
w32(&mut img, 256 * S + 20, 257);
w16(&mut img, 257 * S, 5);
w16(&mut img, 257 * S + 22, 0);
w32(&mut img, 257 * S + 188, 260);
w16(&mut img, 258 * S, 6);
w32(&mut img, 258 * S + 248, S as u32);
w32(&mut img, 258 * S + 252, 0);
w16(&mut img, 258 * S + 256, 0);
w16(&mut img, 259 * S, 8);
w16(&mut img, 260 * S, 256);
w32(&mut img, 260 * S + 400, S as u32);
w32(&mut img, 260 * S + 404, 1);
w16(&mut img, 260 * S + 408, 0);
let fid_data = make_fid_data();
let rfe = 261 * S;
w16(&mut img, rfe, 261);
w16(&mut img, rfe + 18, 0); w32(&mut img, rfe + 168, 0); w32(&mut img, rfe + 172, 8); w32(&mut img, rfe + 176, fid_data.len() as u32); w32(&mut img, rfe + 180, 3);
let fid_sec = 263 * S;
img[fid_sec..fid_sec + fid_data.len()].copy_from_slice(&fid_data);
let hfe = 262 * S;
let content = b"Hi!";
w16(&mut img, hfe, 261);
w16(&mut img, hfe + 18, 3);
w32(&mut img, hfe + 172, content.len() as u32);
img[hfe + 176..hfe + 176 + content.len()].copy_from_slice(content);
img
}
#[test]
fn parse_udf_extent_based_root_dir_verbose() {
let img = make_udf_image_extent_dir();
let mut c = Cursor::new(img);
let root = parse_udf_verbose(&mut c, true).expect("extent-based dir should succeed");
assert!(root.find_node("/hello.txt").is_some());
}
fn make_udf_image_bad_tag_in_fid() -> Vec<u8> {
let mut img = make_udf_image();
let mut fids = Vec::new();
let mut parent = vec![0u8; 40];
w16(&mut parent, 0, 257);
parent[18] = 0x08;
fids.extend_from_slice(&parent);
let mut bad = vec![0u8; 40];
w16(&mut bad, 0, 99);
fids.extend_from_slice(&bad);
let rfe = 261 * S;
w16(&mut img, rfe, 261);
w16(&mut img, rfe + 18, 3);
w32(&mut img, rfe + 172, fids.len() as u32);
img[rfe + 176..rfe + 176 + fids.len()].copy_from_slice(&fids);
img
}
#[test]
fn parse_udf_bad_tag_in_fid_verbose() {
let img = make_udf_image_bad_tag_in_fid();
let mut c = Cursor::new(img);
let root = parse_udf_verbose(&mut c, true).expect("parse should succeed despite bad tag");
assert_eq!(root.name, "/");
}
fn make_udf_image_fid_name_oob() -> Vec<u8> {
let mut img = make_udf_image();
let mut fids = Vec::new();
let mut parent = vec![0u8; 40];
w16(&mut parent, 0, 257);
parent[18] = 0x08;
fids.extend_from_slice(&parent);
let mut bad_fid = vec![0u8; 40];
w16(&mut bad_fid, 0, 257);
bad_fid[18] = 0x00; bad_fid[19] = 200; fids.extend_from_slice(&bad_fid);
let rfe = 261 * S;
w16(&mut img, rfe, 261);
w16(&mut img, rfe + 18, 3);
w32(&mut img, rfe + 172, fids.len() as u32);
img[rfe + 176..rfe + 176 + fids.len()].copy_from_slice(&fids);
img
}
#[test]
fn parse_udf_fid_name_oob_verbose() {
let img = make_udf_image_fid_name_oob();
let mut c = Cursor::new(img);
let root = parse_udf_verbose(&mut c, true).expect("parse should succeed");
assert_eq!(root.name, "/");
}
fn make_udf_image_subdir_bad_fe() -> Vec<u8> {
let mut img = make_udf_image();
let mut fids = Vec::new();
let mut parent = vec![0u8; 40];
w16(&mut parent, 0, 257);
parent[18] = 0x08;
fids.extend_from_slice(&parent);
let name_raw = [8u8, b's', b'u', b'b']; let len_fi = name_raw.len() as u8;
let total = 38 + name_raw.len();
let padded = (total + 3) & !3;
let mut dir_fid = vec![0u8; padded];
w16(&mut dir_fid, 0, 257);
dir_fid[18] = 0x02; dir_fid[19] = len_fi;
w32(&mut dir_fid, 20, S as u32);
w32(&mut dir_fid, 24, 3); w16(&mut dir_fid, 28, 0);
dir_fid[38..38 + name_raw.len()].copy_from_slice(&name_raw);
fids.extend_from_slice(&dir_fid);
let rfe = 261 * S;
w16(&mut img, rfe, 261);
w16(&mut img, rfe + 18, 3);
w32(&mut img, rfe + 172, fids.len() as u32);
img[rfe + 176..rfe + 176 + fids.len()].copy_from_slice(&fids);
img
}
#[test]
fn parse_udf_subdir_bad_fe_verbose() {
let img = make_udf_image_subdir_bad_fe();
let mut c = Cursor::new(img);
let root = parse_udf_verbose(&mut c, true).expect("parse should succeed");
assert!(root
.children
.iter()
.any(|c| c.name == "sub" && c.is_directory));
}
fn make_udf_image_with_type2_map() -> Vec<u8> {
let mut img = vec![0u8; S * 270];
img[16 * S + 1..16 * S + 6].copy_from_slice(b"BEA01");
img[17 * S + 1..17 * S + 6].copy_from_slice(b"NSR02");
w16(&mut img, 256 * S, 2);
w32(&mut img, 256 * S + 16, (3 * S) as u32);
w32(&mut img, 256 * S + 20, 257);
w16(&mut img, 257 * S, 5);
w16(&mut img, 257 * S + 22, 0);
w32(&mut img, 257 * S + 188, 260);
let lvd = 258 * S;
w16(&mut img, lvd, 6);
w32(&mut img, lvd + 248, S as u32);
w32(&mut img, lvd + 252, 0); w16(&mut img, lvd + 256, 0);
w32(&mut img, lvd + 264, 64); w32(&mut img, lvd + 268, 1); img[lvd + 440] = 2; img[lvd + 441] = 64; img[lvd + 445..lvd + 458].copy_from_slice(b"*Custom Map ");
w16(&mut img, 259 * S, 8);
w16(&mut img, 260 * S, 256);
w32(&mut img, 260 * S + 400, S as u32);
w32(&mut img, 260 * S + 404, 1);
w16(&mut img, 260 * S + 408, 0);
let rfe = 261 * S;
w16(&mut img, rfe, 261);
w16(&mut img, rfe + 18, 3);
let mut parent = vec![0u8; 40];
w16(&mut parent, 0, 257);
parent[18] = 0x08;
w32(&mut img, rfe + 172, parent.len() as u32);
img[rfe + 176..rfe + 216].copy_from_slice(&parent);
img
}
#[test]
fn parse_udf_lvd_type2_map_verbose() {
let img = make_udf_image_with_type2_map();
let mut c = Cursor::new(img);
let root = parse_udf_verbose(&mut c, true).expect("type-2 map should not error");
assert_eq!(root.name, "/");
}
#[test]
fn parse_udf_lvd_type2_map_non_verbose() {
let img = make_udf_image_with_type2_map();
let mut c = Cursor::new(img);
let root = parse_udf(&mut c).expect("type-2 map should not error");
assert_eq!(root.name, "/");
}
#[test]
fn file_alloc_fallback_zero_length_extent() {
let ad = vec![0u8; 8];
let buf = make_fe_buf(261, 2, &ad);
assert!(get_file_allocation(&buf).is_err());
}
#[test]
fn file_alloc_fallback_too_short_for_extent() {
let mut buf = vec![0u8; 180];
w16(&mut buf, 0, 261); w16(&mut buf, 18, 2); assert!(get_file_allocation(&buf).is_err());
}
fn make_udf_image_short_fid_tail() -> Vec<u8> {
let mut img = make_udf_image();
let mut fids = vec![0u8; 50];
w16(&mut fids, 0, 257); fids[18] = 0x08;
let rfe = 261 * S;
w16(&mut img, rfe, 261);
w16(&mut img, rfe + 18, 3);
w32(&mut img, rfe + 172, fids.len() as u32);
img[rfe + 176..rfe + 176 + fids.len()].copy_from_slice(&fids);
img
}
#[test]
fn parse_udf_short_fid_tail_breaks_early() {
let img = make_udf_image_short_fid_tail();
let mut c = Cursor::new(img);
let root = parse_udf(&mut c).expect("short tail should succeed");
assert!(root.children.is_empty());
}
fn make_udf_image_with_subdir() -> Vec<u8> {
let mut img = make_udf_image();
let mut fids = Vec::new();
let mut parent = vec![0u8; 40];
w16(&mut parent, 0, 257);
parent[18] = 0x08;
fids.extend_from_slice(&parent);
let name_raw = [8u8, b'm', b'y', b'd', b'i', b'r'];
let len_fi = name_raw.len() as u8;
let total = 38 + name_raw.len();
let padded = (total + 3) & !3;
let mut dir_fid = vec![0u8; padded];
w16(&mut dir_fid, 0, 257);
dir_fid[18] = 0x02; dir_fid[19] = len_fi;
w32(&mut dir_fid, 20, S as u32);
w32(&mut dir_fid, 24, 3); w16(&mut dir_fid, 28, 0);
dir_fid[38..38 + name_raw.len()].copy_from_slice(&name_raw);
fids.extend_from_slice(&dir_fid);
let rfe = 261 * S;
w16(&mut img, rfe, 261);
w16(&mut img, rfe + 18, 3);
w32(&mut img, rfe + 172, fids.len() as u32);
img[rfe + 176..rfe + 176 + fids.len()].copy_from_slice(&fids);
let dir_fe = 263 * S;
w16(&mut img, dir_fe, 261);
w16(&mut img, dir_fe + 18, 3); let mut sub_parent = vec![0u8; 40];
w16(&mut sub_parent, 0, 257);
sub_parent[18] = 0x08;
w32(&mut img, dir_fe + 172, sub_parent.len() as u32);
img[dir_fe + 176..dir_fe + 216].copy_from_slice(&sub_parent);
img
}
#[test]
fn parse_udf_valid_subdir_success_path() {
let img = make_udf_image_with_subdir();
let mut c = Cursor::new(img);
let root = parse_udf(&mut c).expect("valid subdir should parse");
assert!(root
.children
.iter()
.any(|c| c.name == "mydir" && c.is_directory));
}
fn make_udf_image_zero_map_length() -> Vec<u8> {
let mut img = vec![0u8; S * 270];
img[16 * S + 1..16 * S + 6].copy_from_slice(b"BEA01");
img[17 * S + 1..17 * S + 6].copy_from_slice(b"NSR02");
w16(&mut img, 256 * S, 2);
w32(&mut img, 256 * S + 16, (3 * S) as u32);
w32(&mut img, 256 * S + 20, 257);
w16(&mut img, 257 * S, 5);
w16(&mut img, 257 * S + 22, 0);
w32(&mut img, 257 * S + 188, 260);
let lvd = 258 * S;
w16(&mut img, lvd, 6);
w32(&mut img, lvd + 248, S as u32);
w32(&mut img, lvd + 252, 0);
w16(&mut img, lvd + 256, 0);
w32(&mut img, lvd + 264, 8); w32(&mut img, lvd + 268, 1); img[lvd + 440] = 2; img[lvd + 441] = 0;
w16(&mut img, 259 * S, 8);
w16(&mut img, 260 * S, 256);
w32(&mut img, 260 * S + 400, S as u32);
w32(&mut img, 260 * S + 404, 1);
w16(&mut img, 260 * S + 408, 0);
let rfe = 261 * S;
w16(&mut img, rfe, 261);
w16(&mut img, rfe + 18, 3);
let mut parent = vec![0u8; 40];
w16(&mut parent, 0, 257);
parent[18] = 0x08;
w32(&mut img, rfe + 172, parent.len() as u32);
img[rfe + 176..rfe + 216].copy_from_slice(&parent);
img
}
#[test]
fn parse_udf_lvd_zero_map_length_breaks() {
let img = make_udf_image_zero_map_length();
let mut c = Cursor::new(img);
let root = parse_udf(&mut c).expect("zero map_length should not error");
assert_eq!(root.name, "/");
}
fn make_udf_image_unknown_vds_tag() -> Vec<u8> {
let mut img = vec![0u8; S * 270];
img[16 * S + 1..16 * S + 6].copy_from_slice(b"BEA01");
img[17 * S + 1..17 * S + 6].copy_from_slice(b"NSR02");
w16(&mut img, 256 * S, 2);
w32(&mut img, 256 * S + 16, (4 * S) as u32); w32(&mut img, 256 * S + 20, 257);
w16(&mut img, 257 * S, 5);
w16(&mut img, 257 * S + 22, 0);
w32(&mut img, 257 * S + 188, 260);
w16(&mut img, 258 * S, 7);
let lvd = 259 * S;
w16(&mut img, lvd, 6);
w32(&mut img, lvd + 248, S as u32);
w32(&mut img, lvd + 252, 1); w16(&mut img, lvd + 256, 0);
w16(&mut img, 260 * S, 8);
w16(&mut img, 261 * S, 256);
w32(&mut img, 261 * S + 400, S as u32);
w32(&mut img, 261 * S + 404, 2); w16(&mut img, 261 * S + 408, 0);
let rfe = 262 * S;
w16(&mut img, rfe, 261);
w16(&mut img, rfe + 18, 3);
let mut parent = vec![0u8; 40];
w16(&mut parent, 0, 257);
parent[18] = 0x08;
w32(&mut img, rfe + 172, parent.len() as u32);
img[rfe + 176..rfe + 216].copy_from_slice(&parent);
img
}
#[test]
fn parse_udf_unknown_vds_tag_passes_through() {
let img = make_udf_image_unknown_vds_tag();
let mut c = Cursor::new(img);
let root = parse_udf(&mut c).expect("unknown VDS tag should be ignored");
assert_eq!(root.name, "/");
}
fn make_udf_image_type2_map_nonprintable() -> Vec<u8> {
let mut img = make_udf_image_with_type2_map();
let lvd = 258 * S;
img[lvd + 445..lvd + 458].copy_from_slice(b"*Cust\x01m Map ");
img
}
#[test]
fn parse_udf_type2_map_nonprintable_verbose() {
let img = make_udf_image_type2_map_nonprintable();
let mut c = Cursor::new(img);
let root = parse_udf_verbose(&mut c, true).expect("non-printable id should succeed");
assert_eq!(root.name, "/");
}
#[test]
fn parse_udf_verbose_no_avdp() {
let mut img = make_udf_image();
img[256 * S] = 0;
img[256 * S + 1] = 0;
let mut c = Cursor::new(img);
let result = parse_udf_verbose(&mut c, true);
assert!(result.is_err());
}
fn make_udf_metadata_partition_image() -> Vec<u8> {
let mut img = vec![0u8; 270 * S];
img[16 * S + 1..16 * S + 6].copy_from_slice(b"BEA01");
img[17 * S + 1..17 * S + 6].copy_from_slice(b"NSR02");
img[18 * S + 1..18 * S + 6].copy_from_slice(b"TEA01");
w16(&mut img, 256 * S, 2);
w32(&mut img, 256 * S + 16, (5 * S) as u32);
w32(&mut img, 256 * S + 20, 257);
w16(&mut img, 257 * S, 5);
w16(&mut img, 257 * S + 22, 0);
w32(&mut img, 257 * S + 188, 263);
w16(&mut img, 258 * S, 5);
w16(&mut img, 258 * S + 22, 1);
w32(&mut img, 258 * S + 188, 267);
w16(&mut img, 259 * S, 6);
w32(&mut img, 259 * S + 248, S as u32); w32(&mut img, 259 * S + 252, 0); w16(&mut img, 259 * S + 256, 0); w32(&mut img, 259 * S + 264, 64); w32(&mut img, 259 * S + 268, 1); img[259 * S + 440] = 2; img[259 * S + 441] = 64; img[259 * S + 445..259 * S + 468].copy_from_slice(b"*UDF Metadata Partition");
w16(&mut img, 259 * S + 478, 1); w32(&mut img, 259 * S + 480, 0);
w16(&mut img, 260 * S, 8);
w16(&mut img, 267 * S, 261);
w16(&mut img, 267 * S + 18, 0); w32(&mut img, 267 * S + 172, 8); w32(&mut img, 267 * S + 176, S as u32); w32(&mut img, 267 * S + 180, 1);
w16(&mut img, 268 * S, 256);
w32(&mut img, 268 * S + 400, S as u32);
w32(&mut img, 268 * S + 404, 1); w16(&mut img, 268 * S + 408, 0);
w16(&mut img, 269 * S, 261);
w16(&mut img, 269 * S + 18, 3); w32(&mut img, 269 * S + 172, 40); w16(&mut img, 269 * S + 176, 257); img[269 * S + 176 + 18] = 0x08;
img
}
#[test]
fn parse_udf_metadata_partition_path() {
let img = make_udf_metadata_partition_image();
let mut c = Cursor::new(img);
let root =
parse_udf(&mut c).expect("parse_udf failed on metadata-partition synthetic image");
assert_eq!(root.name, "/");
assert!(root.is_directory);
assert!(root.children.is_empty());
}
#[test]
fn parse_udf_metadata_partition_verbose() {
let img = make_udf_metadata_partition_image();
let mut c = Cursor::new(img);
let root = parse_udf_verbose(&mut c, true)
.expect("verbose parse failed on metadata-partition image");
assert_eq!(root.name, "/");
assert!(root.is_directory);
}
}