1use alloc::string::String;
4use alloc::vec::Vec;
5
6use super::descriptor::{DescriptorTag, LongAllocationDescriptor, TagIdentifier};
7use crate::error::{Error, Result};
8
9#[derive(Debug, Clone)]
11pub struct UdfDirEntry {
12 pub name: String,
14 pub is_directory: bool,
16 pub size: u64,
18 pub icb: LongAllocationDescriptor,
20 pub characteristics: FileCharacteristics,
22}
23
24impl UdfDirEntry {
25 pub fn name(&self) -> &str {
27 &self.name
28 }
29
30 pub fn is_dir(&self) -> bool {
32 self.is_directory
33 }
34
35 pub fn is_file(&self) -> bool {
37 !self.is_directory
38 }
39
40 pub fn is_hidden(&self) -> bool {
42 self.characteristics.contains(FileCharacteristics::HIDDEN)
43 }
44
45 pub fn is_parent(&self) -> bool {
47 self.characteristics.contains(FileCharacteristics::PARENT)
48 }
49}
50
51#[repr(C)]
56#[derive(Debug, Clone, Copy)]
57pub struct FileIdentifierDescriptor {
58 pub tag: DescriptorTag,
60 pub file_version_number: u16,
62 pub file_characteristics: u8,
64 pub file_identifier_length: u8,
66 pub icb: LongAllocationDescriptor,
68 pub implementation_use_length: u16,
70 }
75
76unsafe impl bytemuck::Zeroable for FileIdentifierDescriptor {}
77unsafe impl bytemuck::Pod for FileIdentifierDescriptor {}
78
79impl FileIdentifierDescriptor {
80 pub const BASE_SIZE: usize = 38;
84
85 pub fn total_size(&self) -> usize {
87 let base = Self::BASE_SIZE;
88 let variable =
89 self.implementation_use_length as usize + self.file_identifier_length as usize;
90 (base + variable + 3) & !3
92 }
93
94 pub fn from_bytes(data: &[u8]) -> Result<(Self, &[u8])> {
96 if data.len() < Self::BASE_SIZE {
97 return Err(Error::Io(hadris_io::Error::new(
98 hadris_io::ErrorKind::UnexpectedEof,
99 "buffer too small for FID",
100 )));
101 }
102
103 let tag = DescriptorTag::from_disk_bytes(&data[0..16])?;
106 let file_version_number = u16::from_le_bytes([data[16], data[17]]);
107 let file_characteristics = data[18];
108 let file_identifier_length = data[19];
109 let icb =
112 bytemuck::pod_read_unaligned::<LongAllocationDescriptor>(&data[20..36]).into_native();
113 let implementation_use_length = u16::from_le_bytes([data[36], data[37]]);
114
115 let fid = Self {
116 tag,
117 file_version_number,
118 file_characteristics,
119 file_identifier_length,
120 icb,
121 implementation_use_length,
122 };
123
124 if fid.tag.identifier() != TagIdentifier::FileIdentifierDescriptor {
125 return Err(Error::InvalidTag {
126 expected: TagIdentifier::FileIdentifierDescriptor.to_u16(),
127 found: fid.tag.tag_identifier,
128 });
129 }
130
131 let total_size = fid.total_size();
132 if data.len() < total_size {
133 return Err(Error::Io(hadris_io::Error::new(
134 hadris_io::ErrorKind::UnexpectedEof,
135 "buffer too small for FID data",
136 )));
137 }
138
139 Ok((fid, &data[Self::BASE_SIZE..total_size]))
140 }
141}
142
143bitflags::bitflags! {
144 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
146 pub struct FileCharacteristics: u8 {
147 const EXISTENCE = 0x01;
149 const DIRECTORY = 0x02;
151 const DELETED = 0x04;
153 const PARENT = 0x08;
155 const METADATA = 0x10;
157 const HIDDEN = 0x20;
159 }
160}
161
162pub struct UdfDir {
164 entries: Vec<UdfDirEntry>,
166}
167
168impl UdfDir {
169 pub(crate) fn new(entries: Vec<UdfDirEntry>) -> Self {
171 Self { entries }
172 }
173
174 pub fn entries(&self) -> impl Iterator<Item = &UdfDirEntry> {
176 self.entries.iter().filter(|e| !e.is_parent())
177 }
178
179 pub fn all_entries(&self) -> impl Iterator<Item = &UdfDirEntry> {
181 self.entries.iter()
182 }
183
184 pub fn find(&self, name: &str) -> Option<&UdfDirEntry> {
186 self.entries.iter().find(|e| e.name == name)
187 }
188
189 pub fn len(&self) -> usize {
191 self.entries.iter().filter(|e| !e.is_parent()).count()
192 }
193
194 pub fn is_empty(&self) -> bool {
196 self.len() == 0
197 }
198}
199
200pub fn decode_filename(data: &[u8]) -> String {
202 if data.is_empty() {
203 return String::new();
204 }
205
206 let compression_id = data[0];
207 let content = &data[1..];
208
209 match compression_id {
210 8 => {
211 content.iter().map(|byte| char::from(*byte)).collect()
213 }
214 16 => {
215 let mut result = String::new();
217 for chunk in content.chunks(2) {
218 if chunk.len() == 2 {
219 let code_unit = u16::from_be_bytes([chunk[0], chunk[1]]);
220 if let Some(c) = char::from_u32(code_unit as u32) {
221 result.push(c);
222 }
223 }
224 }
225 result
226 }
227 _ => String::new(),
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 static_assertions::const_assert_eq!(size_of::<FileIdentifierDescriptor>(), 40);
237
238 #[test]
239 fn test_file_characteristics() {
240 let chars = FileCharacteristics::DIRECTORY | FileCharacteristics::EXISTENCE;
241 assert!(chars.contains(FileCharacteristics::DIRECTORY));
242 assert!(!chars.contains(FileCharacteristics::HIDDEN));
243 }
244
245 #[test]
246 fn test_decode_filename_8bit() {
247 let data = [8, b'h', b'e', b'l', b'l', b'o'];
248 assert_eq!(decode_filename(&data), "hello");
249 }
250
251 #[test]
252 fn test_decode_filename_16bit() {
253 let data = [16, 0x00, b'h', 0x00, b'i'];
255 assert_eq!(decode_filename(&data), "hi");
256 }
257}