use super::{
ExtentAd, LongAd, TAG_ANCHOR_VOLUME_POINTER, TAG_FILE_SET, TAG_LOGICAL_VOLUME, TAG_PARTITION,
TAG_PRIMARY_VOLUME, TAG_VOLUME_DESCRIPTOR_POINTER, Tag, as_offset, cs0, u8_at, u16_le, u32_le,
};
const METADATA_PARTITION_ID: &[u8; 23] = b"*UDF Metadata Partition";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Avdp {
pub main_vds: ExtentAd,
pub reserve_vds: ExtentAd,
}
impl Avdp {
#[must_use]
pub fn parse(buf: &[u8]) -> Option<Self> {
let tag = Tag::parse(buf, 0)?;
if tag.identifier != TAG_ANCHOR_VOLUME_POINTER {
return None;
}
let main_vds = ExtentAd::parse(buf, 16)?;
let reserve_vds = ExtentAd::parse(buf, 24)?;
Some(Self { main_vds, reserve_vds })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Pvd {
pub volume_identifier: String,
}
impl Pvd {
#[must_use]
pub fn parse(buf: &[u8]) -> Option<Self> {
let tag = Tag::parse(buf, 0)?;
if tag.identifier != TAG_PRIMARY_VOLUME {
return None;
}
let volume_identifier = cs0::decode_dstring(buf.get(24..56)?);
Some(Self { volume_identifier })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Vdp {
pub next: ExtentAd,
}
impl Vdp {
#[must_use]
pub fn parse(buf: &[u8]) -> Option<Self> {
let tag = Tag::parse(buf, 0)?;
if tag.identifier != TAG_VOLUME_DESCRIPTOR_POINTER {
return None;
}
let next = ExtentAd::parse(buf, 20)?;
Some(Self { next })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PartitionMap {
Physical {
partition_number: u16,
},
Metadata(MetadataPartitionMap),
Other {
map_type: u8,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MetadataPartitionMap {
pub physical_partition: u16,
pub metadata_file_location: u32,
pub metadata_mirror_file_location: u32,
}
impl MetadataPartitionMap {
#[must_use]
pub fn parse(map: &[u8]) -> Option<Self> {
let physical_partition = u16_le(map, 38)?;
let metadata_file_location = u32_le(map, 40)?;
let metadata_mirror_file_location = u32_le(map, 44)?;
Some(Self { physical_partition, metadata_file_location, metadata_mirror_file_location })
}
}
fn parse_partition_map(map_type: u8, slice: &[u8]) -> PartitionMap {
match map_type {
1 => u16_le(slice, 4).map_or(PartitionMap::Other { map_type }, |partition_number| {
PartitionMap::Physical { partition_number }
}),
2 if slice.get(5..28) == Some(METADATA_PARTITION_ID.as_slice()) => {
MetadataPartitionMap::parse(slice)
.map_or(PartitionMap::Other { map_type }, PartitionMap::Metadata)
}
_ => PartitionMap::Other { map_type },
}
}
fn parse_partition_maps(bytes: &[u8], count: u32) -> Vec<PartitionMap> {
let mut maps = Vec::new();
let mut off: usize = 0;
for _ in 0..count {
let Some(map_type) = u8_at(bytes, off) else { break };
let Some(map_len) = u8_at(bytes, off.saturating_add(1)) else { break };
let map_len = usize::from(map_len);
if map_len == 0 {
break;
}
let end = off.saturating_add(map_len);
let Some(slice) = bytes.get(off..end) else { break };
maps.push(parse_partition_map(map_type, slice));
off = end;
}
maps
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Lvd {
pub logical_volume_identifier: String,
pub logical_block_size: u32,
pub file_set_descriptor: LongAd,
pub partition_maps: Vec<PartitionMap>,
}
impl Lvd {
#[must_use]
pub fn parse(buf: &[u8]) -> Option<Self> {
let tag = Tag::parse(buf, 0)?;
if tag.identifier != TAG_LOGICAL_VOLUME {
return None;
}
let logical_volume_identifier = cs0::decode_dstring(buf.get(84..212)?);
let logical_block_size = u32_le(buf, 212)?;
let file_set_descriptor = LongAd::parse(buf, 248)?;
let map_table_length = u32_le(buf, 264)?;
let num_partition_maps = u32_le(buf, 268)?;
let maps_all = buf.get(440..)?;
let limit = as_offset(map_table_length).min(maps_all.len());
let maps_bytes = maps_all.get(..limit).unwrap_or(maps_all);
let partition_maps = parse_partition_maps(maps_bytes, num_partition_maps);
Some(Self {
logical_volume_identifier,
logical_block_size,
file_set_descriptor,
partition_maps,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PartitionDescriptor {
pub partition_number: u16,
pub starting_location: u32,
pub length: u32,
}
impl PartitionDescriptor {
#[must_use]
pub fn parse(buf: &[u8]) -> Option<Self> {
let tag = Tag::parse(buf, 0)?;
if tag.identifier != TAG_PARTITION {
return None;
}
let partition_number = u16_le(buf, 22)?;
let starting_location = u32_le(buf, 188)?;
let length = u32_le(buf, 192)?;
Some(Self { partition_number, starting_location, length })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Fsd {
pub root_directory_icb: LongAd,
}
impl Fsd {
#[must_use]
pub fn parse(buf: &[u8]) -> Option<Self> {
let tag = Tag::parse(buf, 0)?;
if tag.identifier != TAG_FILE_SET {
return None;
}
let root_directory_icb = LongAd::parse(buf, 400)?;
Some(Self { root_directory_icb })
}
}
#[must_use]
pub fn physical_partition_start(
maps: &[PartitionMap],
descriptors: &[PartitionDescriptor],
partition_ref: u16,
) -> Option<u32> {
let partition_number = match maps.get(usize::from(partition_ref))? {
PartitionMap::Physical { partition_number } => *partition_number,
PartitionMap::Metadata(meta) => meta.physical_partition,
PartitionMap::Other { .. } => return None,
};
descriptors.iter().find(|d| d.partition_number == partition_number).map(|d| d.starting_location)
}
#[cfg(test)]
mod tests {
use super::{
Avdp, Fsd, Lvd, METADATA_PARTITION_ID, MetadataPartitionMap, PartitionDescriptor,
PartitionMap, Pvd, Vdp, parse_partition_maps, physical_partition_start,
};
use crate::vfs::udf::{ExtentAd, LbAddr, LongAd};
fn put(buf: &mut [u8], off: usize, bytes: &[u8]) {
for (dst, &src) in buf.iter_mut().skip(off).zip(bytes) {
*dst = src;
}
}
fn descriptor(size: usize, identifier: u16) -> Vec<u8> {
let mut buf = vec![0_u8; size];
put(&mut buf, 0, &identifier.to_le_bytes());
put(&mut buf, 2, &3_u16.to_le_bytes());
let sum = buf
.iter()
.take(16)
.enumerate()
.filter(|&(i, _)| i != 4)
.fold(0_u8, |acc, (_, &b)| acc.wrapping_add(b));
put(&mut buf, 4, &[sum]);
buf
}
#[test]
fn metadata_partition_id_is_23_bytes() {
assert_eq!(METADATA_PARTITION_ID.len(), 23);
}
#[test]
fn avdp_parses_volume_sequence_extents() {
let mut buf = descriptor(512, super::TAG_ANCHOR_VOLUME_POINTER);
put(&mut buf, 16, &32_u32.to_le_bytes()); put(&mut buf, 20, &0x20_u32.to_le_bytes()); put(&mut buf, 24, &16_u32.to_le_bytes());
put(&mut buf, 28, &0x40_u32.to_le_bytes());
let avdp = Avdp::parse(&buf).expect("avdp");
assert_eq!(avdp.main_vds, ExtentAd { length: 32, location: 0x20 });
assert_eq!(avdp.reserve_vds, ExtentAd { length: 16, location: 0x40 });
}
#[test]
fn avdp_rejects_wrong_tag_and_truncations() {
assert_eq!(Avdp::parse(&descriptor(512, super::TAG_LOGICAL_VOLUME)), None);
let full = descriptor(32, super::TAG_ANCHOR_VOLUME_POINTER);
assert!(Avdp::parse(&full).is_some());
for len in 0..32 {
assert_eq!(Avdp::parse(full.get(..len).unwrap_or_default()), None, "avdp {len}");
}
}
#[test]
fn pvd_parses_the_volume_identifier() {
let mut buf = descriptor(512, super::TAG_PRIMARY_VOLUME);
put(&mut buf, 24, &[8, b'D', b'I', b'S', b'C']);
put(&mut buf, 55, &[5]); let pvd = Pvd::parse(&buf).expect("pvd");
assert_eq!(pvd.volume_identifier, "DISC");
}
#[test]
fn pvd_rejects_wrong_tag_and_truncations() {
assert_eq!(Pvd::parse(&descriptor(512, super::TAG_LOGICAL_VOLUME)), None);
let full = descriptor(56, super::TAG_PRIMARY_VOLUME);
assert!(Pvd::parse(&full).is_some());
for len in 0..56 {
assert_eq!(Pvd::parse(full.get(..len).unwrap_or_default()), None, "pvd {len}");
}
}
#[test]
fn vdp_parses_the_continuation_extent() {
let mut buf = descriptor(512, super::TAG_VOLUME_DESCRIPTOR_POINTER);
put(&mut buf, 20, &(2048_u32).to_le_bytes()); put(&mut buf, 24, &0x90_u32.to_le_bytes()); let vdp = Vdp::parse(&buf).expect("vdp");
assert_eq!(vdp.next, ExtentAd { length: 2048, location: 0x90 });
}
#[test]
fn vdp_rejects_wrong_tag_and_truncations() {
assert_eq!(Vdp::parse(&descriptor(512, super::TAG_PARTITION)), None);
let full = descriptor(28, super::TAG_VOLUME_DESCRIPTOR_POINTER);
assert!(Vdp::parse(&full).is_some());
for len in 0..28 {
assert_eq!(Vdp::parse(full.get(..len).unwrap_or_default()), None, "vdp {len}");
}
}
#[test]
fn lvd_parses_block_size_fsd_and_physical_map() {
let mut buf = descriptor(2048, super::TAG_LOGICAL_VOLUME);
put(&mut buf, 84, &[8, b'M', b'y', b'D', b'i', b's', b'c']);
put(&mut buf, 211, &[7]); put(&mut buf, 212, &2048_u32.to_le_bytes()); put(&mut buf, 248, &0x800_u32.to_le_bytes()); put(&mut buf, 252, &0x100_u32.to_le_bytes()); put(&mut buf, 256, &0_u16.to_le_bytes()); put(&mut buf, 264, &6_u32.to_le_bytes()); put(&mut buf, 268, &1_u32.to_le_bytes()); put(&mut buf, 440, &[1, 6, 0, 0, 0, 0]);
let lvd = Lvd::parse(&buf).expect("lvd");
assert_eq!(lvd.logical_volume_identifier, "MyDisc");
assert_eq!(lvd.logical_block_size, 2048);
assert_eq!(lvd.file_set_descriptor.location, LbAddr { block: 0x100, partition: 0 });
assert_eq!(lvd.file_set_descriptor.length_bytes(), 0x800);
assert_eq!(lvd.partition_maps, vec![PartitionMap::Physical { partition_number: 0 }]);
}
#[test]
fn lvd_parses_metadata_partition_map() {
let mut buf = descriptor(2048, super::TAG_LOGICAL_VOLUME);
put(&mut buf, 212, &2048_u32.to_le_bytes());
put(&mut buf, 248, &0x800_u32.to_le_bytes());
put(&mut buf, 264, &70_u32.to_le_bytes()); put(&mut buf, 268, &2_u32.to_le_bytes());
put(&mut buf, 440, &[1, 6, 0, 0, 0, 0]);
let meta_off = 446;
put(&mut buf, meta_off, &[2, 64]);
put(&mut buf, meta_off + 5, METADATA_PARTITION_ID.as_slice());
put(&mut buf, meta_off + 38, &0_u16.to_le_bytes()); put(&mut buf, meta_off + 40, &0x50_u32.to_le_bytes()); put(&mut buf, meta_off + 44, &0x51_u32.to_le_bytes());
let lvd = Lvd::parse(&buf).expect("lvd");
assert_eq!(lvd.partition_maps.len(), 2);
assert_eq!(
lvd.partition_maps.first(),
Some(&PartitionMap::Physical { partition_number: 0 })
);
assert_eq!(
lvd.partition_maps.get(1),
Some(&PartitionMap::Metadata(MetadataPartitionMap {
physical_partition: 0,
metadata_file_location: 0x50,
metadata_mirror_file_location: 0x51,
}))
);
}
#[test]
fn lvd_rejects_wrong_tag_and_truncations() {
assert_eq!(Lvd::parse(&descriptor(2048, super::TAG_PARTITION)), None);
let full = descriptor(2048, super::TAG_LOGICAL_VOLUME);
assert!(Lvd::parse(full.get(..440).unwrap_or_default()).is_some());
for len in 0..440 {
assert_eq!(Lvd::parse(full.get(..len).unwrap_or_default()), None, "lvd {len}");
}
}
#[test]
fn partition_map_loop_handles_type2_other_unknown_and_truncation() {
let mut bytes = vec![0_u8; 64 + 6 + 1];
put(&mut bytes, 0, &[2, 64]); put(&mut bytes, 64, &[9, 6]); put(&mut bytes, 70, &[1]);
let maps = parse_partition_maps(&bytes, 3);
assert_eq!(
maps,
vec![PartitionMap::Other { map_type: 2 }, PartitionMap::Other { map_type: 9 }]
);
}
#[test]
fn partition_map_loop_stops_on_zero_length() {
let bytes = [1_u8, 0, 0, 0];
let maps = parse_partition_maps(&bytes, 5);
assert!(maps.is_empty());
}
#[test]
fn partition_map_loop_stops_when_empty() {
assert!(parse_partition_maps(&[], 4).is_empty());
}
#[test]
fn partition_map_loop_stops_when_map_runs_past_buffer() {
let maps = parse_partition_maps(&[1, 6, 0, 0], 1);
assert!(maps.is_empty());
}
#[test]
fn metadata_partition_map_parses_and_rejects_truncations() {
let mut map = vec![0_u8; 64];
put(&mut map, 38, &0_u16.to_le_bytes());
put(&mut map, 40, &0x50_u32.to_le_bytes());
put(&mut map, 44, &0x51_u32.to_le_bytes());
assert_eq!(
MetadataPartitionMap::parse(&map),
Some(MetadataPartitionMap {
physical_partition: 0,
metadata_file_location: 0x50,
metadata_mirror_file_location: 0x51,
})
);
assert_eq!(MetadataPartitionMap::parse(map.get(..39).unwrap_or_default()), None);
assert_eq!(MetadataPartitionMap::parse(map.get(..42).unwrap_or_default()), None);
assert_eq!(MetadataPartitionMap::parse(map.get(..46).unwrap_or_default()), None);
}
#[test]
fn metadata_map_short_slice_is_other() {
let mut bytes = vec![0_u8; 40];
put(&mut bytes, 0, &[2, 40]);
put(&mut bytes, 5, METADATA_PARTITION_ID.as_slice());
let maps = parse_partition_maps(&bytes, 1);
assert_eq!(maps, vec![PartitionMap::Other { map_type: 2 }]);
}
#[test]
fn physical_map_short_slice_is_other() {
let bytes = [1_u8, 3, 0]; let maps = parse_partition_maps(&bytes, 1);
assert_eq!(maps, vec![PartitionMap::Other { map_type: 1 }]);
}
#[test]
fn partition_descriptor_parses_number_start_length() {
let mut buf = descriptor(512, super::TAG_PARTITION);
put(&mut buf, 22, &0_u16.to_le_bytes());
put(&mut buf, 188, &0x120_u32.to_le_bytes());
put(&mut buf, 192, &0x4000_u32.to_le_bytes());
let pd = PartitionDescriptor::parse(&buf).expect("pd");
assert_eq!(
pd,
PartitionDescriptor { partition_number: 0, starting_location: 0x120, length: 0x4000 }
);
}
#[test]
fn partition_descriptor_rejects_wrong_tag_and_truncations() {
assert_eq!(PartitionDescriptor::parse(&descriptor(512, super::TAG_FILE_SET)), None);
let full = descriptor(196, super::TAG_PARTITION);
assert!(PartitionDescriptor::parse(&full).is_some());
for len in 0..196 {
assert_eq!(
PartitionDescriptor::parse(full.get(..len).unwrap_or_default()),
None,
"pd {len}"
);
}
}
#[test]
fn fsd_parses_root_directory_icb() {
let mut buf = descriptor(512, super::TAG_FILE_SET);
put(&mut buf, 400, &0x800_u32.to_le_bytes()); put(&mut buf, 404, &0x10_u32.to_le_bytes()); put(&mut buf, 408, &1_u16.to_le_bytes()); let fsd = Fsd::parse(&buf).expect("fsd");
assert_eq!(fsd.root_directory_icb.location, LbAddr { block: 0x10, partition: 1 });
assert_eq!(fsd.root_directory_icb.length_bytes(), 0x800);
}
#[test]
fn fsd_rejects_wrong_tag_and_truncations() {
assert_eq!(Fsd::parse(&descriptor(512, super::TAG_LOGICAL_VOLUME)), None);
let full = descriptor(416, super::TAG_FILE_SET);
assert!(Fsd::parse(full.get(..410).unwrap_or_default()).is_some());
for len in 0..410 {
assert_eq!(Fsd::parse(full.get(..len).unwrap_or_default()), None, "fsd {len}");
}
}
#[test]
fn physical_partition_start_resolves_physical_and_metadata_refs() {
let maps = [
PartitionMap::Physical { partition_number: 0 },
PartitionMap::Metadata(MetadataPartitionMap {
physical_partition: 0,
metadata_file_location: 0x50,
metadata_mirror_file_location: 0x51,
}),
PartitionMap::Other { map_type: 2 },
];
let descriptors =
[PartitionDescriptor { partition_number: 0, starting_location: 0x120, length: 0x4000 }];
assert_eq!(physical_partition_start(&maps, &descriptors, 0), Some(0x120));
assert_eq!(physical_partition_start(&maps, &descriptors, 1), Some(0x120));
assert_eq!(physical_partition_start(&maps, &descriptors, 2), None);
assert_eq!(physical_partition_start(&maps, &descriptors, 9), None);
}
#[test]
fn physical_partition_start_none_without_matching_descriptor() {
let maps = [PartitionMap::Physical { partition_number: 7 }];
let descriptors =
[PartitionDescriptor { partition_number: 0, starting_location: 0x120, length: 1 }];
assert_eq!(physical_partition_start(&maps, &descriptors, 0), None);
}
#[test]
fn long_ad_helpers_used_by_fsd_round_trip() {
let buf = [0x00_u8, 0x08, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00];
let ad = LongAd::parse(&buf, 0).expect("long_ad");
assert_eq!(ad.length_bytes(), 0x800);
}
}