use alloc::string::String;
use alloc::vec::Vec;
use crate::descriptor::{DescriptorTag, LongAllocationDescriptor, TagIdentifier};
use crate::error::{UdfError, UdfResult};
#[derive(Debug, Clone)]
pub struct UdfDirEntry {
pub name: String,
pub is_directory: bool,
pub size: u64,
pub icb: LongAllocationDescriptor,
pub characteristics: FileCharacteristics,
}
impl UdfDirEntry {
pub fn name(&self) -> &str {
&self.name
}
pub fn is_dir(&self) -> bool {
self.is_directory
}
pub fn is_file(&self) -> bool {
!self.is_directory
}
pub fn is_hidden(&self) -> bool {
self.characteristics.contains(FileCharacteristics::HIDDEN)
}
pub fn is_parent(&self) -> bool {
self.characteristics.contains(FileCharacteristics::PARENT)
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct FileIdentifierDescriptor {
pub tag: DescriptorTag,
pub file_version_number: u16,
pub file_characteristics: u8,
pub file_identifier_length: u8,
pub icb: LongAllocationDescriptor,
pub implementation_use_length: u16,
}
unsafe impl bytemuck::Zeroable for FileIdentifierDescriptor {}
unsafe impl bytemuck::Pod for FileIdentifierDescriptor {}
impl FileIdentifierDescriptor {
pub const BASE_SIZE: usize = 38;
pub fn total_size(&self) -> usize {
let base = Self::BASE_SIZE;
let variable =
self.implementation_use_length as usize + self.file_identifier_length as usize;
(base + variable + 3) & !3
}
pub fn from_bytes(data: &[u8]) -> UdfResult<(Self, &[u8])> {
if data.len() < Self::BASE_SIZE {
return Err(UdfError::Io(hadris_io::Error::new(
hadris_io::ErrorKind::UnexpectedEof,
"buffer too small for FID",
)));
}
let tag: DescriptorTag = *bytemuck::from_bytes(&data[0..16]);
let file_version_number = u16::from_le_bytes([data[16], data[17]]);
let file_characteristics = data[18];
let file_identifier_length = data[19];
let icb: LongAllocationDescriptor = *bytemuck::from_bytes(&data[20..36]);
let implementation_use_length = u16::from_le_bytes([data[36], data[37]]);
let fid = Self {
tag,
file_version_number,
file_characteristics,
file_identifier_length,
icb,
implementation_use_length,
};
if fid.tag.identifier() != TagIdentifier::FileIdentifierDescriptor {
return Err(UdfError::InvalidTag {
expected: TagIdentifier::FileIdentifierDescriptor.to_u16(),
found: fid.tag.tag_identifier,
});
}
let total_size = fid.total_size();
if data.len() < total_size {
return Err(UdfError::Io(hadris_io::Error::new(
hadris_io::ErrorKind::UnexpectedEof,
"buffer too small for FID data",
)));
}
Ok((fid, &data[Self::BASE_SIZE..total_size]))
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FileCharacteristics: u8 {
const EXISTENCE = 0x01;
const DIRECTORY = 0x02;
const DELETED = 0x04;
const PARENT = 0x08;
const METADATA = 0x10;
const HIDDEN = 0x20;
}
}
pub struct UdfDir {
entries: Vec<UdfDirEntry>,
}
impl UdfDir {
pub(crate) fn new(entries: Vec<UdfDirEntry>) -> Self {
Self { entries }
}
pub fn entries(&self) -> impl Iterator<Item = &UdfDirEntry> {
self.entries.iter().filter(|e| !e.is_parent())
}
pub fn all_entries(&self) -> impl Iterator<Item = &UdfDirEntry> {
self.entries.iter()
}
pub fn find(&self, name: &str) -> Option<&UdfDirEntry> {
self.entries.iter().find(|e| e.name == name)
}
pub fn len(&self) -> usize {
self.entries.iter().filter(|e| !e.is_parent()).count()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
pub fn decode_filename(data: &[u8]) -> String {
if data.is_empty() {
return String::new();
}
let compression_id = data[0];
let content = &data[1..];
match compression_id {
8 => {
String::from_utf8_lossy(content).into_owned()
}
16 => {
let mut result = String::new();
for chunk in content.chunks(2) {
if chunk.len() == 2 {
let code_unit = u16::from_be_bytes([chunk[0], chunk[1]]);
if let Some(c) = char::from_u32(code_unit as u32) {
result.push(c);
}
}
}
result
}
_ => String::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
static_assertions::const_assert_eq!(size_of::<FileIdentifierDescriptor>(), 40);
#[test]
fn test_file_characteristics() {
let chars = FileCharacteristics::DIRECTORY | FileCharacteristics::EXISTENCE;
assert!(chars.contains(FileCharacteristics::DIRECTORY));
assert!(!chars.contains(FileCharacteristics::HIDDEN));
}
#[test]
fn test_decode_filename_8bit() {
let data = [8, b'h', b'e', b'l', b'l', b'o'];
assert_eq!(decode_filename(&data), "hello");
}
#[test]
fn test_decode_filename_16bit() {
let data = [16, 0x00, b'h', 0x00, b'i'];
assert_eq!(decode_filename(&data), "hi");
}
}