use alloc::string::String;
use alloc::vec::Vec;
use spin::Mutex;
use super::super::{Read, Seek, SeekFrom};
use super::descriptor::DescriptorTag;
use super::descriptor::{
self, AnchorVolumeDescriptorPointer, ExtentDescriptor, FileSetDescriptor,
LogicalVolumeDescriptor, LongAllocationDescriptor, PartitionDescriptor,
PrimaryVolumeDescriptor, TagIdentifier, parse_vrs,
};
use super::dir::{
FileCharacteristics, FileIdentifierDescriptor, UdfDir, UdfDirEntry, decode_filename,
};
use super::file::{AllocationType, ExtendedFileEntry, FileEntry};
use crate::error::{Error, Result};
use crate::{SECTOR_SIZE, UdfRevision};
#[derive(Debug, Clone)]
pub struct UdfVolumeInfo {
pub block_size: u32,
pub partition_start: u32,
pub partition_length: u32,
pub volume_id: String,
pub udf_revision: UdfRevision,
}
pub struct UdfVolume<DATA: Read + Seek> {
data: Mutex<DATA>,
info: UdfVolumeInfo,
root_icb: LongAllocationDescriptor,
}
impl<DATA: Read + Seek> UdfVolume<DATA> {
pub fn into_inner(self) -> DATA {
self.data.into_inner()
}
}
io_transform! {
impl<DATA: Read + Seek> UdfVolume<DATA> {
pub async fn open(mut data: DATA) -> Result<Self> {
let vrs_type = parse_vrs(&mut data).await?;
let avdp = AnchorVolumeDescriptorPointer::find(&mut data, None).await?;
let (pvd, partition, lvd, fsd) = Self::read_vds(&mut data, &avdp.main_vds_extent).await?;
let fallback_revision = match vrs_type {
descriptor::VrsType::Nsr02 => UdfRevision::V1_02,
descriptor::VrsType::Nsr03 => UdfRevision::V2_01,
};
let udf_revision = exact_domain_revision(&lvd, vrs_type).unwrap_or(fallback_revision);
let info = UdfVolumeInfo {
block_size: lvd.logical_block_size,
partition_start: partition.partition_starting_location,
partition_length: partition.partition_length,
volume_id: pvd.volume_id(),
udf_revision,
};
let root_icb = fsd.root_directory_icb;
Ok(Self {
data: Mutex::new(data),
info,
root_icb,
})
}
async fn read_vds(
data: &mut DATA,
extent: &ExtentDescriptor,
) -> Result<(
PrimaryVolumeDescriptor,
PartitionDescriptor,
LogicalVolumeDescriptor,
FileSetDescriptor,
)> {
let start_sector = extent.location as u64;
let num_sectors = (extent.length as u64).div_ceil(SECTOR_SIZE as u64);
let mut pvd: Option<PrimaryVolumeDescriptor> = None;
let mut partition: Option<PartitionDescriptor> = None;
let mut lvd: Option<LogicalVolumeDescriptor> = None;
let mut buffer = [0u8; SECTOR_SIZE];
for i in 0..num_sectors {
let sector = start_sector + i;
data.seek(SeekFrom::Start(sector * SECTOR_SIZE as u64)).await?;
data.read_exact(&mut buffer).await?;
let tag = DescriptorTag::from_disk_bytes(&buffer[..size_of::<DescriptorTag>()])?;
match tag.identifier() {
TagIdentifier::PrimaryVolumeDescriptor => {
DescriptorTag::validate_bytes(
&buffer[..size_of::<PrimaryVolumeDescriptor>()],
TagIdentifier::PrimaryVolumeDescriptor,
sector as u32,
)?;
let desc: PrimaryVolumeDescriptor =
(*bytemuck::try_from_bytes::<PrimaryVolumeDescriptor>(
&buffer[..size_of::<PrimaryVolumeDescriptor>()],
)
.map_err(Error::PodCastError)?)
.into_native();
pvd = Some(desc);
}
TagIdentifier::PartitionDescriptor => {
DescriptorTag::validate_bytes(
&buffer[..size_of::<PartitionDescriptor>()],
TagIdentifier::PartitionDescriptor,
sector as u32,
)?;
let desc: PartitionDescriptor =
(*bytemuck::try_from_bytes::<PartitionDescriptor>(
&buffer[..size_of::<PartitionDescriptor>()],
)
.map_err(Error::PodCastError)?)
.into_native();
partition = Some(desc);
}
TagIdentifier::LogicalVolumeDescriptor => {
DescriptorTag::validate_bytes(
&buffer[..size_of::<LogicalVolumeDescriptor>()],
TagIdentifier::LogicalVolumeDescriptor,
sector as u32,
)?;
let desc: LogicalVolumeDescriptor =
(*bytemuck::try_from_bytes::<LogicalVolumeDescriptor>(
&buffer[..size_of::<LogicalVolumeDescriptor>()],
)
.map_err(Error::PodCastError)?)
.into_native();
lvd = Some(desc);
}
TagIdentifier::TerminatingDescriptor => {
DescriptorTag::validate_bytes(
&buffer,
TagIdentifier::TerminatingDescriptor,
sector as u32,
)?;
break;
}
_ => continue,
}
}
let pvd = pvd.ok_or(Error::InvalidVds("PVD"))?;
let partition = partition.ok_or(Error::InvalidPartition(0))?;
let lvd = lvd.ok_or(Error::InvalidVds("LVD"))?;
let fsd_location = lvd.file_set_location();
let fsd = Self::read_file_set_descriptor(data, &partition, &fsd_location).await?;
Ok((pvd, partition, lvd, fsd))
}
async fn read_file_set_descriptor(
data: &mut DATA,
partition: &PartitionDescriptor,
icb: &LongAllocationDescriptor,
) -> Result<FileSetDescriptor> {
let sector = partition.partition_starting_location as u64 + icb.logical_block_num as u64;
data.seek(SeekFrom::Start(sector * SECTOR_SIZE as u64)).await?;
let mut buffer = [0u8; SECTOR_SIZE];
data.read_exact(&mut buffer).await?;
DescriptorTag::validate_bytes(
&buffer[..size_of::<FileSetDescriptor>()],
TagIdentifier::FileSetDescriptor,
icb.logical_block_num,
)
.map_err(|_| Error::InvalidFsd)?;
let fsd: FileSetDescriptor =
(*bytemuck::try_from_bytes::<FileSetDescriptor>(
&buffer[..size_of::<FileSetDescriptor>()],
)
.map_err(|_| Error::InvalidFsd)?)
.into_native();
Ok(fsd)
}
pub fn info(&self) -> &UdfVolumeInfo {
&self.info
}
pub async fn root_dir(&self) -> Result<UdfDir> {
self.read_directory(&self.root_icb).await
}
pub async fn read_file(&self, entry: &UdfDirEntry) -> Result<Vec<u8>> {
if entry.is_dir() {
return Err(Error::NotAFile);
}
let mut data = self.data.lock();
let meta = self.read_icb(&mut data, &entry.icb).await?;
if meta.is_directory {
return Err(Error::NotAFile);
}
let mut bytes = self
.read_allocation_bytes(
&mut data,
&meta.buffer,
meta.allocation_type,
meta.alloc_offset,
meta.alloc_length,
)
.await?;
let size = usize::try_from(meta.size).map_err(|_| Error::InvalidIcb)?;
if bytes.len() < size {
return Err(Error::InvalidIcb);
}
bytes.truncate(size);
Ok(bytes)
}
pub async fn read_directory(&self, icb: &LongAllocationDescriptor) -> Result<UdfDir> {
let mut data = self.data.lock();
let meta = self.read_icb(&mut data, icb).await?;
if !meta.is_directory {
return Err(Error::NotADirectory);
}
let entries = self
.parse_directory_entries(
&mut data,
&meta.buffer,
meta.allocation_type,
meta.alloc_offset,
meta.alloc_length,
)
.await?;
Ok(UdfDir::new(entries))
}
async fn read_icb(
&self,
data: &mut DATA,
icb: &LongAllocationDescriptor,
) -> Result<IcbMetadata> {
let sector = self.info.partition_start as u64 + icb.logical_block_num as u64;
data.seek(SeekFrom::Start(sector * SECTOR_SIZE as u64))
.await?;
let mut buffer = [0u8; SECTOR_SIZE];
data.read_exact(&mut buffer).await?;
let tag = descriptor::DescriptorTag::from_disk_bytes(&buffer[..16])?;
let (size, is_directory, allocation_type, alloc_offset, alloc_length) =
match tag.identifier() {
TagIdentifier::FileEntry => {
DescriptorTag::validate_bytes(
&buffer,
TagIdentifier::FileEntry,
icb.logical_block_num,
)?;
let fe = (*bytemuck::from_bytes::<FileEntry>(
&buffer[..FileEntry::BASE_SIZE],
))
.into_native();
(
fe.size(),
fe.is_directory(),
fe.allocation_type(),
FileEntry::BASE_SIZE + fe.extended_attributes_length as usize,
fe.allocation_descriptors_length as usize,
)
}
TagIdentifier::ExtendedFileEntry => {
DescriptorTag::validate_bytes(
&buffer,
TagIdentifier::ExtendedFileEntry,
icb.logical_block_num,
)?;
let efe = (*bytemuck::from_bytes::<ExtendedFileEntry>(
&buffer[..ExtendedFileEntry::BASE_SIZE],
))
.into_native();
(
efe.size(),
efe.is_directory(),
efe.allocation_type(),
ExtendedFileEntry::BASE_SIZE + efe.extended_attributes_length as usize,
efe.allocation_descriptors_length as usize,
)
}
_ => return Err(Error::InvalidIcb),
};
Ok(IcbMetadata {
size,
is_directory,
allocation_type,
alloc_offset,
alloc_length,
buffer,
})
}
async fn read_allocation_bytes(
&self,
data: &mut DATA,
buffer: &[u8],
allocation_type: AllocationType,
alloc_offset: usize,
alloc_length: usize,
) -> Result<Vec<u8>> {
let alloc_range = validated_alloc_range(buffer.len(), alloc_offset, alloc_length)?;
let mut out = Vec::new();
match allocation_type {
AllocationType::Embedded => {
out.extend_from_slice(&buffer[alloc_range]);
}
AllocationType::Short => {
let alloc_data = &buffer[alloc_range];
for chunk in alloc_data.chunks(8) {
if chunk.len() < 8 {
break;
}
let sad = (*bytemuck::from_bytes::<
descriptor::ShortAllocationDescriptor,
>(chunk))
.into_native();
if sad.length() == 0 {
break;
}
let sector = self.info.partition_start as u64 + sad.extent_position as u64;
let extent = self
.read_extent(data, sector, sad.length() as usize)
.await?;
out.extend_from_slice(&extent);
}
}
AllocationType::Long => {
let alloc_data = &buffer[alloc_range];
for chunk in alloc_data.chunks(16) {
if chunk.len() < 16 {
break;
}
let lad =
(*bytemuck::from_bytes::<LongAllocationDescriptor>(chunk)).into_native();
if lad.length() == 0 {
break;
}
let sector = self.info.partition_start as u64 + lad.logical_block_num as u64;
let extent = self
.read_extent(data, sector, lad.length() as usize)
.await?;
out.extend_from_slice(&extent);
}
}
AllocationType::Extended => return Err(Error::InvalidIcb),
}
Ok(out)
}
async fn parse_directory_entries(
&self,
data: &mut DATA,
buffer: &[u8],
allocation_type: AllocationType,
alloc_offset: usize,
alloc_length: usize,
) -> Result<Vec<UdfDirEntry>> {
let mut entries = Vec::new();
let dir_bytes = self
.read_allocation_bytes(data, buffer, allocation_type, alloc_offset, alloc_length)
.await?;
self.parse_fids(data, &dir_bytes, &mut entries).await?;
Ok(entries)
}
async fn read_extent(&self, data: &mut DATA, sector: u64, length: usize) -> Result<Vec<u8>> {
let start = sector * SECTOR_SIZE as u64;
let image_len = data.seek(SeekFrom::End(0)).await?;
if start.saturating_add(length as u64) > image_len {
return Err(Error::InvalidIcb);
}
data.seek(SeekFrom::Start(start)).await?;
let mut buffer = alloc::vec![0u8; length];
data.read_exact(&mut buffer).await?;
Ok(buffer)
}
async fn parse_fids(
&self,
data: &mut DATA,
fid_data: &[u8],
entries: &mut Vec<UdfDirEntry>,
) -> Result<()> {
let mut offset = 0;
while offset < fid_data.len() {
if offset + FileIdentifierDescriptor::BASE_SIZE > fid_data.len() {
break;
}
let (fid, variable_data) =
match FileIdentifierDescriptor::from_bytes(&fid_data[offset..]) {
Ok(fid) => fid,
Err(_) => break,
};
let characteristics = FileCharacteristics::from_bits_truncate(fid.file_characteristics);
if characteristics.contains(FileCharacteristics::DELETED) {
offset += fid.total_size();
continue;
}
let impl_use_len = fid.implementation_use_length as usize;
let name_start = impl_use_len;
let name_end = name_start + fid.file_identifier_length as usize;
let name_data = if name_end <= variable_data.len() {
&variable_data[name_start..name_end]
} else {
&[]
};
let name = if characteristics.contains(FileCharacteristics::PARENT) {
String::from("..")
} else if name_data.is_empty() {
String::from("")
} else {
decode_filename(name_data)
};
let is_directory = characteristics.contains(FileCharacteristics::DIRECTORY);
let size = if is_directory || characteristics.contains(FileCharacteristics::PARENT) {
0
} else {
self.read_icb(data, &fid.icb).await?.size
};
entries.push(UdfDirEntry {
name,
is_directory,
size,
icb: fid.icb,
characteristics,
});
offset += fid.total_size();
}
Ok(())
}
}
fn exact_domain_revision(
lvd: &LogicalVolumeDescriptor,
vrs_type: descriptor::VrsType,
) -> Option<UdfRevision> {
if !lvd.is_udf_domain() {
return None;
}
let raw = u16::from_le_bytes([
lvd.domain_identifier.suffix[0],
lvd.domain_identifier.suffix[1],
]);
let revision = UdfRevision::from_raw(raw);
match vrs_type {
descriptor::VrsType::Nsr02 if raw != 0 && revision < UdfRevision::V2_00 => Some(revision),
descriptor::VrsType::Nsr03 if revision >= UdfRevision::V2_00 => Some(revision),
_ => None,
}
}
struct IcbMetadata {
size: u64,
is_directory: bool,
allocation_type: AllocationType,
alloc_offset: usize,
alloc_length: usize,
buffer: [u8; SECTOR_SIZE],
}
}
fn validated_alloc_range(
buffer_len: usize,
offset: usize,
length: usize,
) -> Result<core::ops::Range<usize>> {
let end = offset.checked_add(length).ok_or(Error::InvalidIcb)?;
if end > buffer_len {
return Err(Error::InvalidIcb);
}
Ok(offset..end)
}
sync_only! {
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_udf_info() {
let info = UdfVolumeInfo {
block_size: 2048,
partition_start: 257,
partition_length: 100000,
volume_id: String::from("TEST_VOLUME"),
udf_revision: UdfRevision::V2_01,
};
assert_eq!(info.block_size, 2048);
assert_eq!(info.volume_id, "TEST_VOLUME");
}
#[test]
fn alloc_range_rejects_out_of_bounds_and_overflow() {
assert_eq!(validated_alloc_range(2048, 176, 100).unwrap(), 176..276);
assert_eq!(validated_alloc_range(2048, 0, 2048).unwrap(), 0..2048);
assert!(validated_alloc_range(2048, 5000, 0).is_err());
assert!(validated_alloc_range(2048, 176, 4000).is_err());
assert!(validated_alloc_range(2048, usize::MAX, 10).is_err());
}
fn calculate_tag_checksum(tag: &[u8; 16]) -> u8 {
let mut sum: u8 = 0;
for (i, &byte) in tag.iter().enumerate() {
if i != 4 {
sum = sum.wrapping_add(byte);
}
}
sum
}
fn write_vrs(data: &mut [u8]) {
let offset = 16 * 2048;
data[offset..offset + 7].copy_from_slice(b"\0BEA01\x01");
let offset = 17 * 2048;
data[offset..offset + 7].copy_from_slice(b"\0NSR02\x01");
let offset = 18 * 2048;
data[offset..offset + 7].copy_from_slice(b"\0TEA01\x01");
}
fn write_avdp(data: &mut [u8]) {
let offset = 256 * 2048;
let mut tag = [0u8; 16];
tag[0] = 0x02;
tag[1] = 0x00;
tag[2] = 0x02;
tag[3] = 0x00;
tag[4] = 0;
tag[5] = 0;
tag[6] = 0x01;
tag[7] = 0x00;
tag[8] = 0;
tag[9] = 0;
tag[10] = 0;
tag[11] = 0;
tag[12] = 0x00;
tag[13] = 0x01;
tag[14] = 0x00;
tag[15] = 0x00;
let checksum = calculate_tag_checksum(&tag);
tag[4] = checksum;
data[offset..offset + 16].copy_from_slice(&tag);
data[offset + 16] = 0x00;
data[offset + 17] = 0x20; data[offset + 18] = 0x00;
data[offset + 19] = 0x00;
data[offset + 20] = 0x01;
data[offset + 21] = 0x01; data[offset + 22] = 0x00;
data[offset + 23] = 0x00;
data[offset + 24] = 0x00;
data[offset + 25] = 0x20; data[offset + 26] = 0x00;
data[offset + 27] = 0x00;
data[offset + 28] = 0x13;
data[offset + 29] = 0x01; data[offset + 30] = 0x00;
data[offset + 31] = 0x00;
data[offset + 32..offset + 56].fill(0);
}
fn write_primary_volume_descriptor(data: &mut [u8], sector: u32) {
let offset = (sector as usize) * 2048;
let mut tag = [0u8; 16];
tag[0] = 0x01;
tag[1] = 0x00;
tag[2] = 0x02;
tag[3] = 0x00;
tag[4] = 0;
tag[5] = 0;
tag[6] = 0x01;
tag[7] = 0x00;
tag[8] = 0;
tag[9] = 0;
tag[10] = 0;
tag[11] = 0;
tag[12] = (sector & 0xFF) as u8;
tag[13] = ((sector >> 8) & 0xFF) as u8;
tag[14] = ((sector >> 16) & 0xFF) as u8;
tag[15] = ((sector >> 24) & 0xFF) as u8;
let checksum = calculate_tag_checksum(&tag);
tag[4] = checksum;
data[offset..offset + 16].copy_from_slice(&tag);
let vol_id = b"MOCK_VOLUME ";
data[offset + 24..offset + 24 + vol_id.len()].copy_from_slice(vol_id);
data[offset + 80] = 0x00;
data[offset + 81] = 0x00;
data[offset + 82] = 0x02;
data[offset + 83] = 0x00;
}
fn write_partition_descriptor(data: &mut [u8], sector: u32, partition_start: u32) {
let offset = (sector as usize) * 2048;
let mut tag = [0u8; 16];
tag[0] = 0x05;
tag[1] = 0x00;
tag[2] = 0x02;
tag[3] = 0x00;
tag[4] = 0;
tag[5] = 0;
tag[6] = 0x02;
tag[7] = 0x00;
tag[8] = 0;
tag[9] = 0;
tag[10] = 0;
tag[11] = 0;
tag[12] = (sector & 0xFF) as u8;
tag[13] = ((sector >> 8) & 0xFF) as u8;
tag[14] = ((sector >> 16) & 0xFF) as u8;
tag[15] = ((sector >> 24) & 0xFF) as u8;
let checksum = calculate_tag_checksum(&tag);
tag[4] = checksum;
data[offset..offset + 16].copy_from_slice(&tag);
data[offset + 40] = (partition_start & 0xFF) as u8;
data[offset + 41] = ((partition_start >> 8) & 0xFF) as u8;
data[offset + 42] = ((partition_start >> 16) & 0xFF) as u8;
data[offset + 43] = ((partition_start >> 24) & 0xFF) as u8;
}
fn write_logical_volume_descriptor(data: &mut [u8], sector: u32, fsd_sector: u32) {
let offset = (sector as usize) * 2048;
let mut tag = [0u8; 16];
tag[0] = 0x06;
tag[1] = 0x00;
tag[2] = 0x02;
tag[3] = 0x00;
tag[4] = 0;
tag[5] = 0;
tag[6] = 0x03;
tag[7] = 0x00;
tag[8] = 0;
tag[9] = 0;
tag[10] = 0;
tag[11] = 0;
tag[12] = (sector & 0xFF) as u8;
tag[13] = ((sector >> 8) & 0xFF) as u8;
tag[14] = ((sector >> 16) & 0xFF) as u8;
tag[15] = ((sector >> 24) & 0xFF) as u8;
let checksum = calculate_tag_checksum(&tag);
tag[4] = checksum;
data[offset..offset + 16].copy_from_slice(&tag);
data[offset + 16] = 0x00;
data[offset + 17] = 0x08;
data[offset + 18] = 0x00;
data[offset + 19] = 0x00;
data[offset + 124] = (fsd_sector & 0xFF) as u8;
data[offset + 125] = ((fsd_sector >> 8) & 0xFF) as u8;
data[offset + 126] = ((fsd_sector >> 16) & 0xFF) as u8;
data[offset + 127] = ((fsd_sector >> 24) & 0xFF) as u8;
}
fn write_terminating_descriptor(data: &mut [u8], sector: u32) {
let offset = (sector as usize) * 2048;
let mut tag = [0u8; 16];
tag[0] = 0x08;
tag[1] = 0x00;
tag[2] = 0x02;
tag[3] = 0x00;
tag[4] = 0;
tag[5] = 0;
tag[6] = 0x04;
tag[7] = 0x00;
tag[8] = 0;
tag[9] = 0;
tag[10] = 0;
tag[11] = 0;
tag[12] = (sector & 0xFF) as u8;
tag[13] = ((sector >> 8) & 0xFF) as u8;
tag[14] = ((sector >> 16) & 0xFF) as u8;
tag[15] = ((sector >> 24) & 0xFF) as u8;
let checksum = calculate_tag_checksum(&tag);
tag[4] = checksum;
data[offset..offset + 16].copy_from_slice(&tag);
}
fn write_file_set_descriptor(data: &mut [u8], sector: u32, root_icb_sector: u32) {
let offset = (sector as usize) * 2048;
let mut tag = [0u8; 16];
tag[0] = 0x00;
tag[1] = 0x01; tag[2] = 0x02;
tag[3] = 0x00;
tag[4] = 0;
tag[5] = 0;
tag[6] = 0x05;
tag[7] = 0x00;
tag[8] = 0;
tag[9] = 0;
tag[10] = 0;
tag[11] = 0;
tag[12] = (sector & 0xFF) as u8;
tag[13] = ((sector >> 8) & 0xFF) as u8;
tag[14] = ((sector >> 16) & 0xFF) as u8;
tag[15] = ((sector >> 24) & 0xFF) as u8;
let checksum = calculate_tag_checksum(&tag);
tag[4] = checksum;
data[offset..offset + 16].copy_from_slice(&tag);
data[offset + 160] = (root_icb_sector & 0xFF) as u8;
data[offset + 161] = ((root_icb_sector >> 8) & 0xFF) as u8;
data[offset + 162] = ((root_icb_sector >> 16) & 0xFF) as u8;
data[offset + 163] = ((root_icb_sector >> 24) & 0xFF) as u8;
}
fn create_mock_udf_data() -> std::boxed::Box<[u8]> {
let mut data = std::vec![0u8; 2048 * 300].into_boxed_slice();
write_vrs(&mut data);
write_avdp(&mut data);
write_primary_volume_descriptor(&mut data, 257);
write_partition_descriptor(&mut data, 258, 260);
write_logical_volume_descriptor(&mut data, 259, 261);
write_terminating_descriptor(&mut data, 260);
write_file_set_descriptor(&mut data, 261, 262);
data
}
#[test]
fn open_rejects_structurally_invalid_mock_descriptors() {
let data = create_mock_udf_data();
let cursor = std::io::Cursor::new(data);
let result = UdfVolume::open(cursor);
assert!(result.is_err())
}
}
}