1#[cfg(feature = "std")]
2use super::io::Parsable;
3#[cfg(any(feature = "std", feature = "alloc"))]
4use super::io::{self, Read, Write};
5#[cfg(feature = "alloc")]
6use alloc::vec::Vec;
7use core::{ffi::CStr, fmt::Debug};
8
9use super::directory::RootDirectoryEntry;
10use crate::types::{
11 BigEndian, Charset, DecDateTime, Endian, IsoStr, IsoStrA, IsoStrD, LittleEndian, U16LsbMsb,
12 U32, U32LsbMsb,
13};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum VolumeError {
18 Io,
20 InvalidHeader,
22 PrimaryNotFound,
24}
25
26impl core::fmt::Display for VolumeError {
27 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
28 match self {
29 Self::Io => write!(f, "I/O error"),
30 Self::InvalidHeader => write!(
31 f,
32 "invalid volume descriptor header (missing CD001 signature)"
33 ),
34 Self::PrimaryNotFound => write!(f, "primary volume descriptor not found"),
35 }
36 }
37}
38
39#[cfg(feature = "std")]
40impl std::error::Error for VolumeError {}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum VolumeDescriptorType {
45 BootRecord,
47 PrimaryVolumeDescriptor,
49 SupplementaryVolumeDescriptor,
51 VolumePartitionDescriptor,
53 VolumeSetTerminator,
55 Unknown(u8),
57}
58
59impl VolumeDescriptorType {
60 pub fn to_u8(self) -> u8 {
62 match self {
63 Self::BootRecord => 0x00,
64 Self::PrimaryVolumeDescriptor => 0x01,
65 Self::SupplementaryVolumeDescriptor => 0x02,
66 Self::VolumePartitionDescriptor => 0x03,
67 Self::VolumeSetTerminator => 0xFF,
68 Self::Unknown(value) => value,
69 }
70 }
71 pub fn from_u8(value: u8) -> Self {
73 match value {
74 0x00 => Self::BootRecord,
75 0x01 => Self::PrimaryVolumeDescriptor,
76 0x02 => Self::SupplementaryVolumeDescriptor,
77 0x03 => Self::VolumePartitionDescriptor,
78 0xFF => Self::VolumeSetTerminator,
79 value => Self::Unknown(value),
80 }
81 }
82}
83
84#[derive(Debug, Clone, Copy)]
85pub enum VolumeDescriptor {
87 BootRecord(BootRecordVolumeDescriptor),
89 Primary(PrimaryVolumeDescriptor),
91 Supplementary(SupplementaryVolumeDescriptor),
93 End(VolumeDescriptorSetTerminator),
95 Unknown(UnknownVolumeDescriptor),
97}
98
99io_transform! {
100#[cfg(feature = "std")]
101impl Parsable for VolumeDescriptor {
102 async fn parse<R: Read>(reader: &mut R) -> io::Result<Self> {
103 let mut buf = [0u8; 2048];
104 reader.read_exact(&mut buf).await?;
105 let header = VolumeDescriptorHeader::from_bytes(&buf[0..7]);
106 if !header.is_valid() {
107 return Err(io::Error::new(
110 io::ErrorKind::InvalidData,
111 "Invalid volume descriptor header (missing CD001 signature)",
112 ));
113 }
114
115 Ok(VolumeDescriptor::new(buf))
116 }
117}
118} impl VolumeDescriptor {
121 pub fn as_bytes(&self) -> &[u8] {
123 match self {
124 VolumeDescriptor::BootRecord(entry) => bytemuck::bytes_of(entry),
125 VolumeDescriptor::Primary(entry) => bytemuck::bytes_of(entry),
126 VolumeDescriptor::Supplementary(entry) => bytemuck::bytes_of(entry),
127 VolumeDescriptor::End(entry) => bytemuck::bytes_of(entry),
128 VolumeDescriptor::Unknown(entry) => bytemuck::bytes_of(entry),
129 }
130 }
131
132 pub fn header(&self) -> VolumeDescriptorHeader {
134 match self {
135 VolumeDescriptor::BootRecord(entry) => entry.header,
136 VolumeDescriptor::Primary(entry) => entry.header,
137 VolumeDescriptor::Supplementary(entry) => entry.header,
138 VolumeDescriptor::End(entry) => entry.header,
139 VolumeDescriptor::Unknown(entry) => entry.header,
140 }
141 }
142
143 pub fn new(data: [u8; 2048]) -> Self {
145 let ty = VolumeDescriptorType::from_u8(data[0]);
146 match ty {
147 VolumeDescriptorType::BootRecord => VolumeDescriptor::BootRecord(bytemuck::cast(data)),
148 VolumeDescriptorType::PrimaryVolumeDescriptor => {
149 VolumeDescriptor::Primary(bytemuck::cast(data))
150 }
151 VolumeDescriptorType::SupplementaryVolumeDescriptor => {
152 VolumeDescriptor::Supplementary(bytemuck::cast(data))
153 }
154 VolumeDescriptorType::VolumeSetTerminator => {
155 VolumeDescriptor::End(bytemuck::cast(data))
156 }
157 _ => VolumeDescriptor::Unknown(bytemuck::cast(data)),
158 }
159 }
160}
161
162#[cfg(feature = "alloc")]
163#[derive(Debug, Clone)]
164pub struct VolumeDescriptorList {
166 pub descriptors: Vec<VolumeDescriptor>,
168}
169
170#[cfg(feature = "alloc")]
171impl VolumeDescriptorList {
172 pub fn empty() -> Self {
174 Self {
175 descriptors: Vec::new(),
176 }
177 }
178
179 pub fn primary(&self) -> &PrimaryVolumeDescriptor {
181 self.try_primary()
182 .expect("Primary volume descriptor not found")
183 }
184
185 pub fn try_primary(&self) -> Option<&PrimaryVolumeDescriptor> {
187 self.descriptors.iter().find_map(|d| match d {
188 VolumeDescriptor::Primary(d) => Some(d),
189 _ => None,
190 })
191 }
192
193 pub fn primary_mut(&mut self) -> &mut PrimaryVolumeDescriptor {
195 self.descriptors
196 .iter_mut()
197 .find_map(|d| match d {
198 VolumeDescriptor::Primary(d) => Some(d),
199 _ => None,
200 })
201 .expect("Primary volume descriptor not found")
202 }
203
204 pub fn supplementary(&self) -> impl Iterator<Item = &SupplementaryVolumeDescriptor> {
206 self.descriptors.iter().filter_map(|d| match d {
207 VolumeDescriptor::Supplementary(d) => Some(d),
208 _ => None,
209 })
210 }
211
212 pub fn supplementary_mut(
214 &mut self,
215 ) -> impl Iterator<Item = &mut SupplementaryVolumeDescriptor> {
216 self.descriptors.iter_mut().filter_map(|d| match d {
217 VolumeDescriptor::Supplementary(d) => Some(d),
218 _ => None,
219 })
220 }
221
222 pub fn boot_record(&self) -> Option<&BootRecordVolumeDescriptor> {
224 self.descriptors.iter().find_map(|d| match d {
225 VolumeDescriptor::BootRecord(d) => Some(d),
226 _ => None,
227 })
228 }
229
230 pub fn boot_record_mut(&mut self) -> Option<&mut BootRecordVolumeDescriptor> {
232 self.descriptors.iter_mut().find_map(|d| match d {
233 VolumeDescriptor::BootRecord(d) => Some(d),
234 _ => None,
235 })
236 }
237
238 pub fn push(&mut self, descriptor: VolumeDescriptor) {
240 self.descriptors.push(descriptor);
241 }
242
243 pub fn insert(&mut self, index: usize, descriptor: VolumeDescriptor) {
245 self.descriptors.insert(index, descriptor);
246 }
247
248 pub fn size_required(&self) -> usize {
250 (self.descriptors.len() + 1) * 2048
251 }
252}
253
254io_transform! {
255#[cfg(feature = "alloc")]
256impl VolumeDescriptorList {
257 pub async fn parse<T: Read>(reader: &mut T) -> Result<Self, io::Error> {
266 let mut descriptors = Vec::new();
267 let mut buffer = [0u8; 2048];
268 loop {
269 reader.read_exact(&mut buffer).await?;
270 let header = VolumeDescriptorHeader::from_bytes(&buffer[0..7]);
271 if !header.is_valid() {
272 return Err(io::Error::new(
273 io::ErrorKind::InvalidData,
274 "invalid volume descriptor identifier or version",
275 ));
276 }
277 let ty = VolumeDescriptorType::from_u8(header.descriptor_type);
278 if let VolumeDescriptorType::VolumeSetTerminator = ty {
279 if buffer[7..].iter().any(|byte| *byte != 0) {
280 return Err(io::Error::new(
281 io::ErrorKind::InvalidData,
282 "volume descriptor set terminator body is not zero-filled",
283 ));
284 }
285 break;
286 }
287
288 descriptors.push(VolumeDescriptor::new(buffer));
289 }
290
291 Ok(Self { descriptors })
292 }
293
294 pub async fn write<W: Write>(&self, writer: &mut W) -> io::Result<usize> {
296 let mut written = 0;
297 for descriptor in &self.descriptors {
298 writer.write_all(descriptor.as_bytes()).await?;
299 written += 2048;
300 }
301 writer.write_all(VolumeDescriptorSetTerminator::new().to_bytes()).await?;
302 written += 2048;
303 Ok(written)
304 }
305}
306} #[repr(C)]
309#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
310pub struct VolumeDescriptorHeader {
312 pub descriptor_type: u8,
314 pub standard_identifier: IsoStrA<5>,
316 pub version: u8,
318}
319
320impl Debug for VolumeDescriptorHeader {
321 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
322 f.debug_struct("VolumeDescriptorHeader")
323 .field(
324 "descriptor_type",
325 &VolumeDescriptorType::from_u8(self.descriptor_type),
326 )
327 .field("standard_identifier", &self.standard_identifier)
328 .field("version", &self.version)
329 .finish()
330 }
331}
332
333impl VolumeDescriptorHeader {
334 const IDENTIFIER: IsoStrA<5> = IsoStrA::from_bytes_exact(*b"CD001");
335 pub fn new(ty: VolumeDescriptorType) -> Self {
337 Self {
338 descriptor_type: ty.to_u8(),
339 standard_identifier: Self::IDENTIFIER,
340 version: 1,
341 }
342 }
343
344 pub fn is_valid(&self) -> bool {
346 self.standard_identifier == Self::IDENTIFIER
347 && (self.version == 1
348 || (self.descriptor_type
349 == VolumeDescriptorType::SupplementaryVolumeDescriptor.to_u8()
350 && self.version == 2))
351 }
352
353 pub fn from_bytes(bytes: &[u8]) -> &Self {
355 bytemuck::from_bytes(bytes)
356 }
357}
358
359#[repr(C)]
360#[derive(Clone, Copy)]
361pub struct UnknownVolumeDescriptor {
363 pub header: VolumeDescriptorHeader,
365 pub data: [u8; 2041],
367}
368
369impl Debug for UnknownVolumeDescriptor {
370 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
371 f.debug_struct("UnknownVolumeDescriptor")
372 .field("header", &self.header)
373 .finish_non_exhaustive()
374 }
375}
376
377unsafe impl bytemuck::Zeroable for UnknownVolumeDescriptor {}
378unsafe impl bytemuck::Pod for UnknownVolumeDescriptor {}
379
380fn lossy_identifier<C: Charset, const N: usize>(s: &str) -> IsoStr<C, N> {
383 let mut chars = [b' '; N];
384 let len = s.len().min(N);
385 chars[..len].copy_from_slice(&s.as_bytes()[..len]);
386 C::substitute_invalid(chars[..len].iter_mut());
387 IsoStr::from_bytes_exact(chars)
388}
389
390#[repr(C)]
398#[derive(Clone, Copy)]
399pub struct PrimaryVolumeDescriptor {
400 pub header: VolumeDescriptorHeader,
402 pub unused0: u8,
404 pub system_identifier: IsoStrA<32>,
406 pub volume_identifier: IsoStrD<32>,
408 pub unused1: [u8; 8],
410 pub volume_space_size: U32LsbMsb,
412 pub unused2: [u8; 32],
414 pub volume_set_size: U16LsbMsb,
416 pub volume_sequence_number: U16LsbMsb,
418 pub logical_block_size: U16LsbMsb,
420 pub path_table_size: U32LsbMsb,
422 pub type_l_path_table: U32<LittleEndian>,
424 pub opt_type_l_path_table: U32<LittleEndian>,
426 pub type_m_path_table: U32<BigEndian>,
428 pub opt_type_m_path_table: U32<BigEndian>,
430 pub dir_record: RootDirectoryEntry,
432 pub volume_set_identifier: IsoStrD<128>,
434 pub publisher_identifier: IsoStrA<128>,
436 pub preparer_identifier: IsoStrA<128>,
438 pub application_identifier: IsoStrA<128>,
440 pub copyright_file_identifier: IsoStrD<37>,
442 pub abstract_file_identifier: IsoStrD<37>,
444 pub bibliographic_file_identifier: IsoStrD<37>,
446 pub creation_date: DecDateTime,
448 pub modification_date: DecDateTime,
450 pub expiration_date: DecDateTime,
452 pub effective_date: DecDateTime,
454 pub file_structure_version: u8,
456 pub unused3: u8,
458 pub app_data: [u8; 512],
460 pub reserved: [u8; 653],
462}
463
464impl Debug for PrimaryVolumeDescriptor {
465 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
466 f.debug_struct("PrimaryVolumeDescriptor")
467 .field("header", &self.header)
468 .field("system_identifier", &self.system_identifier)
469 .field("volume_identifier", &self.volume_identifier)
470 .field("volume_space_size", &self.volume_space_size)
471 .field("volume_set_size", &self.volume_set_size)
472 .field("volume_sequence_number", &self.volume_sequence_number)
473 .field("logical_block_size", &self.logical_block_size)
474 .field("path_table_size", &self.path_table_size)
475 .field("type_l_path_table", &self.type_l_path_table)
476 .field("opt_type_l_path_table", &self.opt_type_l_path_table)
477 .field("type_m_path_table", &self.type_m_path_table)
478 .field("opt_type_m_path_table", &self.opt_type_m_path_table)
479 .field("dir_record", &self.dir_record)
480 .field("volume_set_identifier", &self.volume_set_identifier)
481 .field("publisher_identifier", &self.publisher_identifier)
482 .field("preparer_identifier", &self.preparer_identifier)
483 .field("application_identifier", &self.application_identifier)
484 .field("copyright_file_identifier", &self.copyright_file_identifier)
485 .field("abstract_file_identifier", &self.abstract_file_identifier)
486 .field(
487 "bibliographic_file_identifier",
488 &self.bibliographic_file_identifier,
489 )
490 .field("creation_date", &self.creation_date)
491 .field("modification_date", &self.modification_date)
492 .field("expiration_date", &self.expiration_date)
493 .field("effective_date", &self.effective_date)
494 .field("file_structure_version", &self.file_structure_version)
495 .finish_non_exhaustive()
496 }
497}
498
499impl PrimaryVolumeDescriptor {
500 pub fn new(name: &str, sectors: u32) -> Self {
502 Self {
503 header: VolumeDescriptorHeader {
504 descriptor_type: VolumeDescriptorType::PrimaryVolumeDescriptor.to_u8(),
505 standard_identifier: IsoStrA::from_str("CD001").unwrap(),
506 version: 1,
507 },
508 unused0: 0,
509 system_identifier: IsoStrA::empty(),
510 volume_identifier: lossy_identifier(name),
511 unused1: [0; 8],
512 volume_space_size: U32LsbMsb::new(sectors),
513 unused2: [0; 32],
514 volume_set_size: U16LsbMsb::new(1),
515 volume_sequence_number: U16LsbMsb::new(1),
516 logical_block_size: U16LsbMsb::new(2048),
517 path_table_size: U32LsbMsb::new(0),
518 type_l_path_table: U32::<LittleEndian>::new(0),
519 opt_type_l_path_table: U32::<LittleEndian>::new(0),
520 type_m_path_table: U32::<BigEndian>::new(0),
521 opt_type_m_path_table: U32::<BigEndian>::new(0),
522 dir_record: RootDirectoryEntry::default(),
523 volume_set_identifier: IsoStrD::empty(),
524 publisher_identifier: IsoStrA::empty(),
525 preparer_identifier: IsoStrA::empty(),
526 application_identifier: IsoStrA::from_str("HADRIS-ISO").unwrap(),
527 copyright_file_identifier: IsoStrD::empty(),
528 abstract_file_identifier: IsoStrD::empty(),
529 bibliographic_file_identifier: IsoStrD::empty(),
530 creation_date: DecDateTime::now(),
531 modification_date: DecDateTime::now(),
532 expiration_date: DecDateTime::default(),
533 effective_date: DecDateTime::default(),
534 file_structure_version: 1,
535 unused3: 0,
536 app_data: [0; 512],
537 reserved: [0; 653],
538 }
539 }
540}
541
542unsafe impl bytemuck::Zeroable for PrimaryVolumeDescriptor {}
543unsafe impl bytemuck::Pod for PrimaryVolumeDescriptor {}
544
545#[repr(C)]
554#[derive(Clone, Copy)]
555pub struct BootRecordVolumeDescriptor {
556 pub header: VolumeDescriptorHeader,
558 pub boot_system_identifier: [u8; 32],
560 pub unused0: [u8; 32],
562 pub catalog_ptr: U32<LittleEndian>,
564 pub unused1: [u8; 1973],
566}
567
568impl BootRecordVolumeDescriptor {
569 pub fn new(catalog_sector: u32) -> Self {
571 const BOOT_SYSTEM_IDENTIFIER: [u8; 32] = *b"EL TORITO SPECIFICATION\0\0\0\0\0\0\0\0\0";
572 Self {
573 header: VolumeDescriptorHeader::new(VolumeDescriptorType::BootRecord),
574 boot_system_identifier: BOOT_SYSTEM_IDENTIFIER,
575 unused0: [0; 32],
576 catalog_ptr: U32::<LittleEndian>::new(catalog_sector),
577 unused1: [0; 1973],
578 }
579 }
580}
581
582impl Debug for BootRecordVolumeDescriptor {
583 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
584 let system_identifier = CStr::from_bytes_until_nul(&self.boot_system_identifier);
585 f.debug_struct("BootRecordVolumeDescriptor")
586 .field("header", &self.header)
587 .field("boot_system_identifier", &system_identifier)
588 .field("catalog_ptr", &self.catalog_ptr)
589 .finish_non_exhaustive()
590 }
591}
592
593unsafe impl bytemuck::Zeroable for BootRecordVolumeDescriptor {}
594unsafe impl bytemuck::Pod for BootRecordVolumeDescriptor {}
595
596#[repr(C)]
604#[derive(Clone, Copy)]
605pub struct SupplementaryVolumeDescriptor {
606 pub header: VolumeDescriptorHeader,
608 pub flags: u8,
610 pub system_identifier: IsoStrA<32>,
612 pub volume_identifier: IsoStrD<32>,
614 pub unused1: [u8; 8],
616 pub volume_space_size: U32LsbMsb,
618 pub escape_sequences: [u8; 32],
620 pub volume_set_size: U16LsbMsb,
622 pub volume_sequence_number: U16LsbMsb,
624 pub logical_block_size: U16LsbMsb,
626 pub path_table_size: U32LsbMsb,
628 pub type_l_path_table: U32<LittleEndian>,
630 pub opt_type_l_path_table: U32<LittleEndian>,
632 pub type_m_path_table: U32<BigEndian>,
634 pub opt_type_m_path_table: U32<BigEndian>,
636 pub dir_record: RootDirectoryEntry,
638 pub volume_set_identifier: IsoStrD<128>,
640 pub publisher_identifier: IsoStrA<128>,
642 pub preparer_identifier: IsoStrA<128>,
644 pub application_identifier: IsoStrA<128>,
646 pub copyright_file_identifier: IsoStrD<37>,
648 pub abstract_file_identifier: IsoStrD<37>,
650 pub bibliographic_file_identifier: IsoStrD<37>,
652 pub creation_date: DecDateTime,
654 pub modification_date: DecDateTime,
656 pub expiration_date: DecDateTime,
658 pub effective_date: DecDateTime,
660 pub file_structure_version: u8,
663 pub unused3: u8,
665 pub app_data: [u8; 512],
667 pub reserved: [u8; 653],
669}
670
671impl SupplementaryVolumeDescriptor {
672 pub fn utf16be_str<C: Charset, const N: usize>(s: &str) -> IsoStr<C, N> {
674 let mut bytes = [0u8; N];
675 let paired = N & !1; for i in (0..paired).step_by(2) {
678 bytes[i] = 0x00;
679 bytes[i + 1] = 0x20;
680 }
681 let mut pos = 0;
683 for c in s.encode_utf16() {
684 if pos + 2 > paired {
685 break;
686 }
687 let be = c.to_be_bytes();
688 bytes[pos] = be[0];
689 bytes[pos + 1] = be[1];
690 pos += 2;
691 }
692 IsoStr::from_bytes_exact(bytes)
693 }
694
695 pub fn utf16be_empty<C: Charset, const N: usize>() -> IsoStr<C, N> {
697 let mut bytes = [0u8; N];
698 let paired = N & !1;
699 for i in (0..paired).step_by(2) {
700 bytes[i] = 0x00;
701 bytes[i + 1] = 0x20;
702 }
703 IsoStr::from_bytes_exact(bytes)
704 }
705
706 pub fn new_svd(name: &str, sectors: u32, escape_sequences: [u8; 32]) -> Self {
708 Self {
709 header: VolumeDescriptorHeader {
710 descriptor_type: VolumeDescriptorType::SupplementaryVolumeDescriptor.to_u8(),
711 standard_identifier: IsoStrA::from_str("CD001").unwrap(),
712 version: 1,
713 },
714 flags: 0,
715 system_identifier: Self::utf16be_empty(),
716 volume_identifier: Self::utf16be_str(name),
717 unused1: [0; 8],
718 volume_space_size: U32LsbMsb::new(sectors),
719 escape_sequences,
720 volume_set_size: U16LsbMsb::new(1),
721 volume_sequence_number: U16LsbMsb::new(1),
722 logical_block_size: U16LsbMsb::new(2048),
723 path_table_size: U32LsbMsb::new(0),
724 type_l_path_table: U32::<LittleEndian>::new(0),
725 opt_type_l_path_table: U32::<LittleEndian>::new(0),
726 type_m_path_table: U32::<BigEndian>::new(0),
727 opt_type_m_path_table: U32::<BigEndian>::new(0),
728 dir_record: RootDirectoryEntry::default(),
729 volume_set_identifier: Self::utf16be_empty(),
730 publisher_identifier: Self::utf16be_empty(),
731 preparer_identifier: Self::utf16be_empty(),
732 application_identifier: Self::utf16be_str("HADRIS-ISO"),
733 copyright_file_identifier: Self::utf16be_empty(),
734 abstract_file_identifier: Self::utf16be_empty(),
735 bibliographic_file_identifier: Self::utf16be_empty(),
736 creation_date: DecDateTime::now(),
737 modification_date: DecDateTime::now(),
738 expiration_date: DecDateTime::default(),
739 effective_date: DecDateTime::default(),
740 file_structure_version: 1,
741 unused3: 0,
742 app_data: [0; 512],
743 reserved: [0; 653],
744 }
745 }
746
747 pub fn new_evd(name: &str, sectors: u32) -> Self {
749 Self {
750 header: VolumeDescriptorHeader {
751 descriptor_type: VolumeDescriptorType::SupplementaryVolumeDescriptor.to_u8(),
752 standard_identifier: IsoStrA::from_str("CD001").unwrap(),
753 version: 2,
754 },
755 flags: 0,
756 system_identifier: IsoStrA::empty(),
757 volume_identifier: lossy_identifier(name),
758 unused1: [0; 8],
759 volume_space_size: U32LsbMsb::new(sectors),
760 escape_sequences: [b' '; 32],
761 volume_set_size: U16LsbMsb::new(1),
762 volume_sequence_number: U16LsbMsb::new(1),
763 logical_block_size: U16LsbMsb::new(2048),
764 path_table_size: U32LsbMsb::new(0),
765 type_l_path_table: U32::<LittleEndian>::new(0),
766 opt_type_l_path_table: U32::<LittleEndian>::new(0),
767 type_m_path_table: U32::<BigEndian>::new(0),
768 opt_type_m_path_table: U32::<BigEndian>::new(0),
769 dir_record: RootDirectoryEntry::default(),
770 volume_set_identifier: IsoStrD::empty(),
771 publisher_identifier: IsoStrA::empty(),
772 preparer_identifier: IsoStrA::empty(),
773 application_identifier: IsoStrA::from_str("HADRIS-ISO").unwrap(),
774 copyright_file_identifier: IsoStrD::empty(),
775 abstract_file_identifier: IsoStrD::empty(),
776 bibliographic_file_identifier: IsoStrD::empty(),
777 creation_date: DecDateTime::now(),
778 modification_date: DecDateTime::now(),
779 expiration_date: DecDateTime::default(),
780 effective_date: DecDateTime::default(),
781 file_structure_version: 2,
782 unused3: 0,
783 app_data: [0; 512],
784 reserved: [0; 653],
785 }
786 }
787}
788
789unsafe impl bytemuck::Zeroable for SupplementaryVolumeDescriptor {}
790unsafe impl bytemuck::Pod for SupplementaryVolumeDescriptor {}
791
792impl Debug for SupplementaryVolumeDescriptor {
793 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
794 f.debug_struct("SupplementaryVolumeDescriptor")
795 .field("header", &self.header)
796 .field("flags", &self.flags)
797 .field("system_identifier", &self.system_identifier)
798 .field("volume_identifier", &self.volume_identifier)
799 .field("volume_space_size", &self.volume_space_size)
800 .field("escape_sequences", &self.escape_sequences)
801 .field("volume_set_size", &self.volume_set_size)
802 .field("volume_sequence_number", &self.volume_sequence_number)
803 .field("logical_block_size", &self.logical_block_size)
804 .field("path_table_size", &self.path_table_size)
805 .field("type_l_path_table", &self.type_l_path_table)
806 .field("opt_type_l_path_table", &self.opt_type_l_path_table)
807 .field("type_m_path_table", &self.type_m_path_table)
808 .field("opt_type_m_path_table", &self.opt_type_m_path_table)
809 .field("dir_record", &self.dir_record)
810 .field("volume_set_identifier", &self.volume_set_identifier)
811 .field("publisher_identifier", &self.publisher_identifier)
812 .field("preparer_identifier", &self.preparer_identifier)
813 .field("application_identifier", &self.application_identifier)
814 .field("copyright_file_identifier", &self.copyright_file_identifier)
815 .field("abstract_file_identifier", &self.abstract_file_identifier)
816 .field(
817 "bibliographic_file_identifier",
818 &self.bibliographic_file_identifier,
819 )
820 .field("creation_date", &self.creation_date)
821 .field("modification_date", &self.modification_date)
822 .field("expiration_date", &self.expiration_date)
823 .field("effective_date", &self.effective_date)
824 .field("file_structure_version", &self.file_structure_version)
825 .finish_non_exhaustive()
826 }
827}
828
829#[repr(C)]
837#[derive(Clone, Copy)]
838pub struct VolumeDescriptorSetTerminator {
839 header: VolumeDescriptorHeader,
840 padding: [u8; 2041],
841}
842
843impl Default for VolumeDescriptorSetTerminator {
844 fn default() -> Self {
845 Self::new()
846 }
847}
848
849impl VolumeDescriptorSetTerminator {
850 pub fn new() -> Self {
852 Self {
853 header: VolumeDescriptorHeader {
854 descriptor_type: VolumeDescriptorType::VolumeSetTerminator.to_u8(),
855 standard_identifier: VolumeDescriptorHeader::IDENTIFIER,
856 version: 1,
857 },
858 padding: [0; 2041],
859 }
860 }
861
862 pub fn to_bytes(&self) -> &[u8] {
864 bytemuck::bytes_of(self)
865 }
866}
867
868impl Debug for VolumeDescriptorSetTerminator {
869 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
870 f.debug_struct("VolumeDescriptorSetTerminator")
871 .field("header", &self.header)
872 .finish_non_exhaustive()
873 }
874}
875
876unsafe impl bytemuck::Zeroable for VolumeDescriptorSetTerminator {}
877unsafe impl bytemuck::Pod for VolumeDescriptorSetTerminator {}
878
879#[cfg(test)]
880mod tests {
881 use super::*;
882
883 static_assertions::assert_eq_size!(PrimaryVolumeDescriptor, [u8; 2048]);
884 static_assertions::assert_eq_size!(VolumeDescriptorSetTerminator, [u8; 2048]);
885 static_assertions::assert_eq_size!(BootRecordVolumeDescriptor, [u8; 2048]);
886 static_assertions::assert_eq_size!(UnknownVolumeDescriptor, [u8; 2048]);
887
888 static_assertions::assert_eq_align!(PrimaryVolumeDescriptor, u8);
889 static_assertions::assert_eq_align!(VolumeDescriptorSetTerminator, u8);
890 static_assertions::assert_eq_align!(BootRecordVolumeDescriptor, u8);
891
892 #[test]
893 fn pvd_new_truncates_overlong_volume_name_without_panicking() {
894 let name = "A".repeat(40);
895 let pvd = PrimaryVolumeDescriptor::new(&name, 0);
896 assert_eq!(pvd.volume_identifier.to_str(), "A".repeat(32));
897 }
898
899 #[test]
900 fn evd_new_truncates_overlong_volume_name_without_panicking() {
901 let name = "B".repeat(40);
902 let evd = SupplementaryVolumeDescriptor::new_evd(&name, 0);
903 assert_eq!(evd.volume_identifier.to_str(), "B".repeat(32));
904 }
905
906 sync_only! {
907 use std::io::Cursor;
908
909 #[test]
910 fn descriptor_parser_rejects_invalid_terminator_header() {
911 let mut bytes = [0_u8; 2048];
912 bytes[0] = VolumeDescriptorType::VolumeSetTerminator.to_u8();
913 bytes[1..6].copy_from_slice(b"WRONG");
914 bytes[6] = 1;
915 let error = VolumeDescriptorList::parse(&mut Cursor::new(bytes)).unwrap_err();
916 assert_eq!(error.kind(), io::ErrorKind::InvalidData);
917 }
918
919 #[test]
920 fn descriptor_parser_rejects_nonzero_terminator_body() {
921 let mut bytes = [0_u8; 2048];
922 bytes.copy_from_slice(VolumeDescriptorSetTerminator::new().to_bytes());
923 bytes[7] = 1;
924 let error = VolumeDescriptorList::parse(&mut Cursor::new(bytes)).unwrap_err();
925 assert_eq!(error.kind(), io::ErrorKind::InvalidData);
926 }
927 }
928}