1use alloc::string::String;
34use alloc::vec;
35use alloc::vec::Vec;
36use core::mem::size_of;
37
38use super::super::{Seek, SeekFrom, Write};
39use super::descriptor::{
40 DescriptorTag, ExtentDescriptor, LongAllocationDescriptor, ShortAllocationDescriptor,
41 TagIdentifier,
42};
43use crate::dir::FileCharacteristics;
44use crate::error::Result;
45use crate::file::FileType;
46use crate::time::UdfTimestamp;
47use crate::{AVDP_LOCATION, SECTOR_SIZE, UdfRevision};
48
49#[derive(Debug, Clone)]
55pub struct SimpleFile {
56 pub name: String,
58 pub data: Vec<u8>,
60}
61
62impl SimpleFile {
63 pub fn new(name: impl Into<String>, data: Vec<u8>) -> Self {
65 Self {
66 name: name.into(),
67 data,
68 }
69 }
70
71 pub fn empty(name: impl Into<String>) -> Self {
73 Self::new(name, Vec::new())
74 }
75}
76
77#[derive(Debug, Clone, Default)]
79pub struct SimpleDir {
80 pub name: String,
82 pub files: Vec<SimpleFile>,
84 pub subdirs: Vec<SimpleDir>,
86}
87
88impl SimpleDir {
89 pub fn new(name: impl Into<String>) -> Self {
91 Self {
92 name: name.into(),
93 files: Vec::new(),
94 subdirs: Vec::new(),
95 }
96 }
97
98 pub fn root() -> Self {
100 Self::new("")
101 }
102
103 pub fn add_file(&mut self, file: SimpleFile) {
105 self.files.push(file);
106 }
107
108 pub fn add_dir(&mut self, dir: SimpleDir) {
110 self.subdirs.push(dir);
111 }
112
113 pub fn total_files(&self) -> usize {
115 self.files.len() + self.subdirs.iter().map(|d| d.total_files()).sum::<usize>()
116 }
117
118 pub fn total_dirs(&self) -> usize {
120 1 + self.subdirs.iter().map(|d| d.total_dirs()).sum::<usize>()
121 }
122
123 pub fn sort(&mut self) {
125 self.files.sort_by(|a, b| a.name.cmp(&b.name));
126 self.subdirs.sort_by(|a, b| a.name.cmp(&b.name));
127 for subdir in &mut self.subdirs {
128 subdir.sort();
129 }
130 }
131}
132
133#[derive(Debug)]
135struct AllocatedFile {
136 name: String,
137 data_block: u32, data_length: u64, icb_block: u32, unique_id: u64,
141}
142
143#[derive(Debug)]
144struct AllocatedDir {
145 name: String,
146 icb_block: u32, fid_block: u32, fid_bytes: usize, parent_icb_block: u32, unique_id: u64,
151 files: Vec<AllocatedFile>,
152 subdirs: Vec<AllocatedDir>,
153}
154
155#[derive(Debug, Clone)]
157pub struct UdfWriteOptions {
158 pub volume_id: String,
160 pub revision: UdfRevision,
162 pub partition_start: u32,
164 pub partition_length: u32,
166}
167
168pub struct UdfCreateOutput<W> {
170 pub target: W,
172 pub sectors_written: u32,
174}
175
176impl<W> UdfCreateOutput<W> {
177 pub fn into_inner(self) -> W {
179 self.target
180 }
181}
182
183impl Default for UdfWriteOptions {
184 fn default() -> Self {
185 Self {
186 volume_id: String::from("UDF_VOLUME"),
187 revision: UdfRevision::V1_02,
188 partition_start: 257, partition_length: 0, }
191 }
192}
193
194#[derive(Debug, Clone, Copy)]
196pub struct UdfFileExtent {
197 pub logical_block: u32,
199 pub length: u64,
201}
202
203#[derive(Debug, Clone)]
205pub struct UdfFileInfo {
206 pub name: String,
208 pub is_directory: bool,
210 pub size: u64,
212 pub extent: UdfFileExtent,
214 pub unique_id: u64,
216}
217
218#[derive(Debug, Clone)]
220pub struct UdfDirInfo {
221 pub name: String,
223 pub files: Vec<UdfFileInfo>,
225 pub subdirs: Vec<UdfDirInfo>,
227 pub icb_location: u32,
229 pub unique_id: u64,
231}
232
233impl UdfDirInfo {
234 pub fn root() -> Self {
236 Self {
237 name: String::new(),
238 files: Vec::new(),
239 subdirs: Vec::new(),
240 icb_location: 0,
241 unique_id: 0,
242 }
243 }
244}
245
246pub struct UdfWriter<W: Write + Seek> {
260 writer: W,
261 options: UdfWriteOptions,
262 unique_id_counter: u64,
264}
265
266impl<W: Write + Seek> UdfWriter<W> {
267 pub fn new(writer: W, options: UdfWriteOptions) -> Self {
269 Self {
270 writer,
271 options,
272 unique_id_counter: 16, }
274 }
275
276 pub fn into_inner(self) -> W {
278 self.writer
279 }
280
281 pub fn create(
283 writer: W,
284 root: &SimpleDir,
285 options: UdfWriteOptions,
286 ) -> Result<UdfCreateOutput<W>> {
287 let mut formatter = UdfFormatter::new(writer, options);
288 let sectors_written = formatter.format(root)?;
289 Ok(UdfCreateOutput {
290 target: formatter.into_inner(),
291 sectors_written,
292 })
293 }
294
295}
296
297const MAX_DIRECTORY_DEPTH: usize = 128;
300
301struct UdfFormatter<W: Write + Seek> {
303 writer: W,
304 options: UdfWriteOptions,
305 next_block: u32,
306 unique_id_counter: u64,
307}
308
309impl<W: Write + Seek> UdfFormatter<W> {
310 fn new(writer: W, options: UdfWriteOptions) -> Self {
311 Self {
312 writer,
313 options,
314 next_block: 0,
315 unique_id_counter: 16, }
317 }
318
319 fn into_inner(self) -> W {
320 self.writer
321 }
322
323 fn allocate_block(&mut self) -> u32 {
324 let block = self.next_block;
325 self.next_block += 1;
326 block
327 }
328
329 fn next_unique_id(&mut self) -> u64 {
330 let id = self.unique_id_counter;
331 self.unique_id_counter += 1;
332 id
333 }
334
335 fn format(&mut self, root: &SimpleDir) -> Result<u32> {
336 let vds_start = 257u32;
349 let vds_length = 16u32;
350 let reserve_vds_start = vds_start + vds_length;
351 let lvid_location = reserve_vds_start + vds_length;
352 let partition_start = lvid_location + 1;
353
354 let fsd_block = self.allocate_block(); let allocated_root = self.allocate_directory(root, fsd_block, 0)?;
357
358 let partition_length = self.next_block;
360
361 self.options.partition_start = partition_start;
363 self.options.partition_length = partition_length;
364
365 self.write_vrs()?;
369
370 let main_vds = ExtentDescriptor {
372 length: vds_length * SECTOR_SIZE as u32,
373 location: vds_start,
374 };
375 let reserve_vds = ExtentDescriptor {
376 length: vds_length * SECTOR_SIZE as u32,
377 location: reserve_vds_start,
378 };
379 self.write_avdp(main_vds, reserve_vds)?;
380
381 let fsd_icb = LongAllocationDescriptor {
383 extent_length: SECTOR_SIZE as u32,
384 logical_block_num: fsd_block,
385 partition_ref_num: 0,
386 impl_use: [0; 6],
387 };
388 let integrity_extent = ExtentDescriptor {
389 length: SECTOR_SIZE as u32,
390 location: lvid_location,
391 };
392
393 self.write_pvd(vds_start, 0)?;
395 self.write_iuvd(vds_start + 1, 1)?;
396 self.write_partition_descriptor(vds_start + 2, 2)?;
397 self.write_lvd(vds_start + 3, 3, fsd_icb, integrity_extent)?;
398 self.write_usd(vds_start + 4, 4)?;
399 self.write_terminating_descriptor(vds_start + 5)?;
400
401 self.write_pvd(reserve_vds_start, 0)?;
403 self.write_iuvd(reserve_vds_start + 1, 1)?;
404 self.write_partition_descriptor(reserve_vds_start + 2, 2)?;
405 self.write_lvd(reserve_vds_start + 3, 3, fsd_icb, integrity_extent)?;
406 self.write_usd(reserve_vds_start + 4, 4)?;
407 self.write_terminating_descriptor(reserve_vds_start + 5)?;
408
409 self.write_lvid(lvid_location)?;
411
412 let root_icb = LongAllocationDescriptor {
414 extent_length: SECTOR_SIZE as u32,
415 logical_block_num: allocated_root.icb_block,
416 partition_ref_num: 0,
417 impl_use: [0; 6],
418 };
419 self.write_fsd(fsd_block, root_icb)?;
420
421 self.write_directory(&allocated_root)?;
423
424 self.write_file_data(root, &allocated_root)?;
426
427 let sector_count = partition_start + partition_length + 257;
431 let last_sector = sector_count - 1;
432 if last_sector > 256 {
435 self.write_avdp_at(last_sector - 256, main_vds, reserve_vds)?;
436 }
437
438 Ok(sector_count)
439 }
440
441 fn allocate_directory(
443 &mut self,
444 dir: &SimpleDir,
445 parent_icb: u32,
446 depth: usize,
447 ) -> Result<AllocatedDir> {
448 if depth >= MAX_DIRECTORY_DEPTH {
449 return Err(crate::error::Error::DirectoryNestingTooDeep);
450 }
451 let icb_block = self.allocate_block();
452 let unique_id = self.next_unique_id();
453
454 let mut fid_bytes = 40usize; for name in dir
459 .files
460 .iter()
461 .map(|file| file.name.as_str())
462 .chain(dir.subdirs.iter().map(|subdir| subdir.name.as_str()))
463 {
464 let encoded_len = self.encode_filename(name)?.len();
465 fid_bytes = fid_bytes
466 .checked_add((38 + encoded_len + 3) & !3)
467 .ok_or(crate::error::Error::PathTooLong)?;
468 }
469 let fid_sectors = fid_bytes.div_ceil(SECTOR_SIZE) as u32;
470
471 let fid_block = self.allocate_block();
472 for _ in 1..fid_sectors {
474 self.allocate_block();
475 }
476
477 let mut allocated_files = Vec::new();
479 for file in &dir.files {
480 let file_icb_block = self.allocate_block();
481 let file_unique_id = self.next_unique_id();
482
483 let data_block = if !file.data.is_empty() {
485 let block = self.allocate_block();
486 let data_sectors = file.data.len().div_ceil(SECTOR_SIZE) as u32;
487 for _ in 1..data_sectors {
488 self.allocate_block();
489 }
490 block
491 } else {
492 0 };
494
495 allocated_files.push(AllocatedFile {
496 name: file.name.clone(),
497 data_block,
498 data_length: file.data.len() as u64,
499 icb_block: file_icb_block,
500 unique_id: file_unique_id,
501 });
502 }
503
504 let mut allocated_subdirs = Vec::new();
506 for subdir in &dir.subdirs {
507 let allocated_subdir = self.allocate_directory(subdir, icb_block, depth + 1)?;
508 allocated_subdirs.push(allocated_subdir);
509 }
510
511 Ok(AllocatedDir {
512 name: dir.name.clone(),
513 icb_block,
514 fid_block,
515 fid_bytes,
516 parent_icb_block: parent_icb,
517 unique_id,
518 files: allocated_files,
519 subdirs: allocated_subdirs,
520 })
521 }
522
523 fn write_directory(&mut self, dir: &AllocatedDir) -> Result<()> {
525 let dir_alloc = vec![ShortAllocationDescriptor {
528 extent_length: dir.fid_bytes as u32,
529 extent_position: dir.fid_block,
530 }];
531 self.write_file_entry(
532 dir.icb_block,
533 FileType::Directory,
534 dir.fid_bytes as u64,
535 &dir_alloc,
536 dir.unique_id,
537 )?;
538
539 let mut entries: Vec<(String, LongAllocationDescriptor, bool)> = Vec::new();
541
542 for file in &dir.files {
544 let file_icb = LongAllocationDescriptor {
545 extent_length: SECTOR_SIZE as u32,
546 logical_block_num: file.icb_block,
547 partition_ref_num: 0,
548 impl_use: [0; 6],
549 };
550 entries.push((file.name.clone(), file_icb, false));
551 }
552
553 for subdir in &dir.subdirs {
555 let subdir_icb = LongAllocationDescriptor {
556 extent_length: SECTOR_SIZE as u32,
557 logical_block_num: subdir.icb_block,
558 partition_ref_num: 0,
559 impl_use: [0; 6],
560 };
561 entries.push((subdir.name.clone(), subdir_icb, true));
562 }
563
564 let parent_icb = LongAllocationDescriptor {
566 extent_length: SECTOR_SIZE as u32,
567 logical_block_num: dir.parent_icb_block,
568 partition_ref_num: 0,
569 impl_use: [0; 6],
570 };
571 self.write_fids(dir.fid_block, parent_icb, &entries)?;
572
573 for (file, orig_file) in dir.files.iter().zip(
575 core::iter::repeat(&Vec::<u8>::new()),
578 ) {
579 let file_alloc = if file.data_length > 0 {
580 vec![ShortAllocationDescriptor {
581 extent_length: file.data_length as u32,
582 extent_position: file.data_block,
583 }]
584 } else {
585 vec![]
586 };
587
588 self.write_file_entry(
589 file.icb_block,
590 FileType::RegularFile,
591 file.data_length,
592 &file_alloc,
593 file.unique_id,
594 )?;
595
596 let _ = orig_file; }
600
601 for subdir in &dir.subdirs {
603 self.write_directory(subdir)?;
604 }
605
606 Ok(())
607 }
608
609 fn write_file_data(&mut self, dir: &SimpleDir, alloc_dir: &AllocatedDir) -> Result<()> {
611 for (file, alloc_file) in dir.files.iter().zip(&alloc_dir.files) {
613 if !file.data.is_empty() {
614 self.seek_to_partition_block(alloc_file.data_block)?;
615 self.writer.write_all(&file.data)?;
616
617 let padded = file.data.len().div_ceil(SECTOR_SIZE) * SECTOR_SIZE;
619 if padded > file.data.len() {
620 let padding = vec![0u8; padded - file.data.len()];
621 self.writer.write_all(&padding)?;
622 }
623 }
624 }
625
626 for (subdir, alloc_subdir) in dir.subdirs.iter().zip(&alloc_dir.subdirs) {
628 self.write_file_data(subdir, alloc_subdir)?;
629 }
630
631 Ok(())
632 }
633
634 fn seek_to_partition_block(&mut self, block: u32) -> Result<()> {
636 let sector = self.options.partition_start + block;
637 self.writer
638 .seek(SeekFrom::Start((sector as u64) * SECTOR_SIZE as u64))?;
639 Ok(())
640 }
641
642 fn seek_to_sector(&mut self, sector: u32) -> Result<()> {
643 self.writer
644 .seek(SeekFrom::Start((sector as u64) * SECTOR_SIZE as u64))?;
645 Ok(())
646 }
647
648 fn write_vrs(&mut self) -> Result<()> {
649 let nsr = match self.options.revision {
650 r if r >= UdfRevision::V2_00 => b"NSR03",
651 _ => b"NSR02",
652 };
653
654 self.seek_to_sector(16)?;
655 self.write_vrs_descriptor(b"BEA01")?;
656 self.write_vrs_descriptor(nsr)?;
657 self.write_vrs_descriptor(b"TEA01")?;
658 Ok(())
659 }
660
661 fn write_vrs_descriptor(&mut self, id: &[u8; 5]) -> Result<()> {
662 let mut buffer = [0u8; SECTOR_SIZE];
663 buffer[0] = 0;
664 buffer[1..6].copy_from_slice(id);
665 buffer[6] = 1;
666 self.writer.write_all(&buffer)?;
667 Ok(())
668 }
669
670 fn write_avdp(
671 &mut self,
672 main_vds: ExtentDescriptor,
673 reserve_vds: ExtentDescriptor,
674 ) -> Result<()> {
675 self.write_avdp_at(AVDP_LOCATION, main_vds, reserve_vds)
676 }
677
678 fn write_avdp_at(
679 &mut self,
680 location: u32,
681 main_vds: ExtentDescriptor,
682 reserve_vds: ExtentDescriptor,
683 ) -> Result<()> {
684 self.seek_to_sector(location)?;
685 let mut buffer = [0u8; SECTOR_SIZE];
686
687 buffer[16..20].copy_from_slice(&main_vds.length.to_le_bytes());
688 buffer[20..24].copy_from_slice(&main_vds.location.to_le_bytes());
689 buffer[24..28].copy_from_slice(&reserve_vds.length.to_le_bytes());
690 buffer[28..32].copy_from_slice(&reserve_vds.location.to_le_bytes());
691
692 let tag = self.create_tag(
693 TagIdentifier::AnchorVolumeDescriptorPointer,
694 location,
695 &buffer[16..],
696 );
697 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
698
699 self.writer.write_all(&buffer)?;
700 Ok(())
701 }
702
703 fn write_pvd(&mut self, location: u32, vds_number: u32) -> Result<()> {
704 self.seek_to_sector(location)?;
705 let mut buffer = [0u8; 512];
706 let offset = 16;
707
708 buffer[offset..offset + 4].copy_from_slice(&vds_number.to_le_bytes());
709 buffer[offset + 4..offset + 8].copy_from_slice(&0u32.to_le_bytes());
710
711 let vol_id_offset = offset + 8;
712 self.write_dstring(
713 &mut buffer[vol_id_offset..vol_id_offset + 32],
714 &self.options.volume_id,
715 );
716
717 let vsn_offset = vol_id_offset + 32;
718 buffer[vsn_offset..vsn_offset + 2].copy_from_slice(&1u16.to_le_bytes());
719 buffer[vsn_offset + 2..vsn_offset + 4].copy_from_slice(&1u16.to_le_bytes());
720 buffer[vsn_offset + 4..vsn_offset + 6].copy_from_slice(&2u16.to_le_bytes());
721 buffer[vsn_offset + 6..vsn_offset + 8].copy_from_slice(&3u16.to_le_bytes());
722 buffer[vsn_offset + 8..vsn_offset + 12].copy_from_slice(&1u32.to_le_bytes());
723 buffer[vsn_offset + 12..vsn_offset + 16].copy_from_slice(&1u32.to_le_bytes());
724
725 let vsi_offset = vsn_offset + 16;
726 self.write_dstring(
727 &mut buffer[vsi_offset..vsi_offset + 128],
728 &self.options.volume_id,
729 );
730
731 let dcs_offset = vsi_offset + 128;
732 write_osta_charspec(&mut buffer[dcs_offset..dcs_offset + 64]);
733
734 let ecs_offset = dcs_offset + 64;
735 write_osta_charspec(&mut buffer[ecs_offset..ecs_offset + 64]);
736
737 let abs_offset = ecs_offset + 64;
738 let app_offset = abs_offset + 16;
739 self.write_entity_identifier(&mut buffer[app_offset..app_offset + 32], b"*hadris-udf");
740
741 let rdt_offset = app_offset + 32;
742 let now = UdfTimestamp::now();
743 buffer[rdt_offset..rdt_offset + 12].copy_from_slice(bytemuck::bytes_of(&now));
744
745 let impl_offset = rdt_offset + 12;
746 self.write_entity_identifier(&mut buffer[impl_offset..impl_offset + 32], b"*hadris-udf");
747
748 let tag = self.create_tag(
749 TagIdentifier::PrimaryVolumeDescriptor,
750 location,
751 &buffer[16..],
752 );
753 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
754
755 self.writer.write_all(&buffer)?;
756 Ok(())
757 }
758
759 fn write_partition_descriptor(&mut self, location: u32, vds_number: u32) -> Result<()> {
760 self.seek_to_sector(location)?;
761 let mut buffer = [0u8; 512];
762 let offset = 16;
763
764 buffer[offset..offset + 4].copy_from_slice(&vds_number.to_le_bytes());
765 buffer[offset + 4..offset + 6].copy_from_slice(&1u16.to_le_bytes());
766 buffer[offset + 6..offset + 8].copy_from_slice(&0u16.to_le_bytes());
767
768 let nsr = match self.options.revision {
769 r if r >= UdfRevision::V2_00 => b"+NSR03",
770 _ => b"+NSR02",
771 };
772 let pc_offset = offset + 8;
773 self.write_entity_identifier(&mut buffer[pc_offset..pc_offset + 32], nsr);
774
775 let at_offset = pc_offset + 32 + 128;
776 buffer[at_offset..at_offset + 4].copy_from_slice(&1u32.to_le_bytes());
777
778 let psl_offset = at_offset + 4;
779 buffer[psl_offset..psl_offset + 4]
780 .copy_from_slice(&self.options.partition_start.to_le_bytes());
781 buffer[psl_offset + 4..psl_offset + 8]
782 .copy_from_slice(&self.options.partition_length.to_le_bytes());
783
784 let impl_offset = psl_offset + 8;
785 self.write_entity_identifier(&mut buffer[impl_offset..impl_offset + 32], b"*hadris-udf");
786
787 let tag = self.create_tag(TagIdentifier::PartitionDescriptor, location, &buffer[16..]);
788 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
789
790 self.writer.write_all(&buffer)?;
791 Ok(())
792 }
793
794 fn write_lvd(
795 &mut self,
796 location: u32,
797 vds_number: u32,
798 fsd_location: LongAllocationDescriptor,
799 integrity_extent: ExtentDescriptor,
800 ) -> Result<()> {
801 self.seek_to_sector(location)?;
802 let mut buffer = [0u8; 512];
803 let offset = 16;
804
805 buffer[offset..offset + 4].copy_from_slice(&vds_number.to_le_bytes());
806
807 let dcs_offset = offset + 4;
808 buffer[dcs_offset] = 0;
809
810 let lvi_offset = dcs_offset + 64;
811 self.write_dstring(
812 &mut buffer[lvi_offset..lvi_offset + 128],
813 &self.options.volume_id,
814 );
815
816 let lbs_offset = lvi_offset + 128;
817 buffer[lbs_offset..lbs_offset + 4].copy_from_slice(&(SECTOR_SIZE as u32).to_le_bytes());
818
819 let di_offset = lbs_offset + 4;
820 self.write_entity_identifier(
821 &mut buffer[di_offset..di_offset + 32],
822 b"*OSTA UDF Compliant",
823 );
824 buffer[di_offset + 24] = (self.options.revision.to_raw() & 0xFF) as u8;
825 buffer[di_offset + 25] = ((self.options.revision.to_raw() >> 8) & 0xFF) as u8;
826
827 let lvcu_offset = di_offset + 32;
828 buffer[lvcu_offset..lvcu_offset + 16].copy_from_slice(bytemuck::bytes_of(&fsd_location));
829
830 let mtl_offset = lvcu_offset + 16;
831 buffer[mtl_offset..mtl_offset + 4].copy_from_slice(&6u32.to_le_bytes());
832 buffer[mtl_offset + 4..mtl_offset + 8].copy_from_slice(&1u32.to_le_bytes());
833
834 let impl_offset = mtl_offset + 8;
835 self.write_entity_identifier(&mut buffer[impl_offset..impl_offset + 32], b"*hadris-udf");
836
837 let iu_offset = impl_offset + 32;
838 let ise_offset = iu_offset + 128;
839 buffer[ise_offset..ise_offset + 4].copy_from_slice(&integrity_extent.length.to_le_bytes());
840 buffer[ise_offset + 4..ise_offset + 8]
841 .copy_from_slice(&integrity_extent.location.to_le_bytes());
842
843 let pm_offset = ise_offset + 8;
844 buffer[pm_offset] = 1;
845 buffer[pm_offset + 1] = 6;
846 buffer[pm_offset + 2..pm_offset + 4].copy_from_slice(&1u16.to_le_bytes());
847 buffer[pm_offset + 4..pm_offset + 6].copy_from_slice(&0u16.to_le_bytes());
848
849 let tag = self.create_tag(
850 TagIdentifier::LogicalVolumeDescriptor,
851 location,
852 &buffer[16..],
853 );
854 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
855
856 self.writer.write_all(&buffer)?;
857 Ok(())
858 }
859
860 fn write_usd(&mut self, location: u32, vds_number: u32) -> Result<()> {
861 self.seek_to_sector(location)?;
862 let mut buffer = [0u8; 512];
863 let offset = 16;
864
865 buffer[offset..offset + 4].copy_from_slice(&vds_number.to_le_bytes());
866 buffer[offset + 4..offset + 8].copy_from_slice(&0u32.to_le_bytes());
867
868 let tag = self.create_tag(
869 TagIdentifier::UnallocatedSpaceDescriptor,
870 location,
871 &buffer[16..],
872 );
873 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
874
875 self.writer.write_all(&buffer)?;
876 Ok(())
877 }
878
879 fn write_iuvd(&mut self, location: u32, vds_number: u32) -> Result<()> {
880 self.seek_to_sector(location)?;
881 let mut buffer = [0u8; 512];
882 let offset = 16;
883
884 buffer[offset..offset + 4].copy_from_slice(&vds_number.to_le_bytes());
885
886 let impl_offset = offset + 4;
887 self.write_entity_identifier(&mut buffer[impl_offset..impl_offset + 32], b"*UDF LV Info");
888
889 let iu_offset = impl_offset + 32;
890 buffer[iu_offset] = 0;
891
892 let lvi_offset = iu_offset + 64;
893 self.write_dstring(
894 &mut buffer[lvi_offset..lvi_offset + 128],
895 &self.options.volume_id,
896 );
897
898 let tag = self.create_tag(
899 TagIdentifier::ImplementationUseVolumeDescriptor,
900 location,
901 &buffer[16..],
902 );
903 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
904
905 self.writer.write_all(&buffer)?;
906 Ok(())
907 }
908
909 fn write_terminating_descriptor(&mut self, location: u32) -> Result<()> {
910 self.seek_to_sector(location)?;
911 let mut buffer = [0u8; 512];
912
913 let tag = self.create_tag(TagIdentifier::TerminatingDescriptor, location, &[]);
914 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
915
916 self.writer.write_all(&buffer)?;
917 Ok(())
918 }
919
920 fn write_lvid(&mut self, location: u32) -> Result<()> {
921 self.seek_to_sector(location)?;
922 let mut buffer = [0u8; 512];
923 let offset = 16;
924
925 let now = UdfTimestamp::now();
926 buffer[offset..offset + 12].copy_from_slice(bytemuck::bytes_of(&now));
927 buffer[offset + 12..offset + 16].copy_from_slice(&1u32.to_le_bytes()); let lvcu_offset = offset + 24;
930 buffer[lvcu_offset..lvcu_offset + 8].copy_from_slice(&self.unique_id_counter.to_le_bytes());
931
932 let np_offset = lvcu_offset + 32;
933 buffer[np_offset..np_offset + 4].copy_from_slice(&1u32.to_le_bytes());
934 buffer[np_offset + 4..np_offset + 8].copy_from_slice(&46u32.to_le_bytes());
935
936 let fst_offset = np_offset + 8;
937 buffer[fst_offset..fst_offset + 4].copy_from_slice(&0u32.to_le_bytes());
938 buffer[fst_offset + 4..fst_offset + 8]
939 .copy_from_slice(&self.options.partition_length.to_le_bytes());
940
941 let iu_offset = fst_offset + 8;
942 self.write_entity_identifier(&mut buffer[iu_offset..iu_offset + 32], b"*hadris-udf");
943 let revision = self.options.revision.to_raw().to_le_bytes();
944 buffer[iu_offset + 40..iu_offset + 42].copy_from_slice(&revision);
947 buffer[iu_offset + 42..iu_offset + 44].copy_from_slice(&revision);
948 buffer[iu_offset + 44..iu_offset + 46].copy_from_slice(&revision);
949
950 let tag = self.create_tag(
951 TagIdentifier::LogicalVolumeIntegrityDescriptor,
952 location,
953 &buffer[16..],
954 );
955 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
956
957 self.writer.write_all(&buffer)?;
958 Ok(())
959 }
960
961 fn write_fsd(&mut self, location: u32, root_icb: LongAllocationDescriptor) -> Result<()> {
962 self.seek_to_partition_block(location)?;
963 let mut buffer = [0u8; 512];
964 let offset = 16;
965
966 let now = UdfTimestamp::now();
967 buffer[offset..offset + 12].copy_from_slice(bytemuck::bytes_of(&now));
968
969 buffer[offset + 12..offset + 14].copy_from_slice(&3u16.to_le_bytes());
970 buffer[offset + 14..offset + 16].copy_from_slice(&3u16.to_le_bytes());
971 buffer[offset + 16..offset + 20].copy_from_slice(&1u32.to_le_bytes());
972 buffer[offset + 20..offset + 24].copy_from_slice(&1u32.to_le_bytes());
973 buffer[offset + 24..offset + 28].copy_from_slice(&0u32.to_le_bytes());
974 buffer[offset + 28..offset + 32].copy_from_slice(&0u32.to_le_bytes());
975
976 let lvics_offset = offset + 32;
977 write_osta_charspec(&mut buffer[lvics_offset..lvics_offset + 64]);
978
979 let lvi_offset = lvics_offset + 64;
980 self.write_dstring(
981 &mut buffer[lvi_offset..lvi_offset + 128],
982 &self.options.volume_id,
983 );
984
985 let fscs_offset = lvi_offset + 128;
986 write_osta_charspec(&mut buffer[fscs_offset..fscs_offset + 64]);
987
988 let fsi_offset = fscs_offset + 64;
989 self.write_dstring(
990 &mut buffer[fsi_offset..fsi_offset + 32],
991 &self.options.volume_id,
992 );
993
994 let root_offset = fsi_offset + 32 + 32 + 32;
995 buffer[root_offset..root_offset + 16].copy_from_slice(bytemuck::bytes_of(&root_icb));
996
997 let di_offset = root_offset + 16;
998 self.write_entity_identifier(
999 &mut buffer[di_offset..di_offset + 32],
1000 b"*OSTA UDF Compliant",
1001 );
1002 buffer[di_offset + 24] = (self.options.revision.to_raw() & 0xFF) as u8;
1003 buffer[di_offset + 25] = ((self.options.revision.to_raw() >> 8) & 0xFF) as u8;
1004
1005 let tag = self.create_tag(TagIdentifier::FileSetDescriptor, location, &buffer[16..]);
1006 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
1007
1008 self.writer.write_all(&buffer)?;
1009 Ok(())
1010 }
1011
1012 fn write_file_entry(
1013 &mut self,
1014 location: u32,
1015 file_type: FileType,
1016 info_length: u64,
1017 allocation_descriptors: &[ShortAllocationDescriptor],
1018 unique_id: u64,
1019 ) -> Result<()> {
1020 self.seek_to_partition_block(location)?;
1021 let mut buffer = [0u8; SECTOR_SIZE];
1022 let offset = 16;
1023
1024 let icb_offset = offset;
1025 buffer[icb_offset + 4..icb_offset + 6].copy_from_slice(&4u16.to_le_bytes());
1026 buffer[icb_offset + 8..icb_offset + 10].copy_from_slice(&1u16.to_le_bytes());
1027 buffer[icb_offset + 11] = file_type as u8;
1028 buffer[icb_offset + 18..icb_offset + 20].copy_from_slice(&0u16.to_le_bytes());
1029
1030 let uid_offset = icb_offset + 20;
1031 buffer[uid_offset..uid_offset + 4].copy_from_slice(&0xFFFFFFFFu32.to_le_bytes());
1032 buffer[uid_offset + 4..uid_offset + 8].copy_from_slice(&0xFFFFFFFFu32.to_le_bytes());
1033 buffer[uid_offset + 8..uid_offset + 12].copy_from_slice(&0x7FFFu32.to_le_bytes());
1034 buffer[uid_offset + 12..uid_offset + 14].copy_from_slice(&1u16.to_le_bytes());
1035
1036 let il_offset = uid_offset + 20;
1037 buffer[il_offset..il_offset + 8].copy_from_slice(&info_length.to_le_bytes());
1038
1039 let blocks = info_length.div_ceil(SECTOR_SIZE as u64);
1040 buffer[il_offset + 8..il_offset + 16].copy_from_slice(&blocks.to_le_bytes());
1041
1042 let now = UdfTimestamp::now();
1043 let time_offset = il_offset + 16;
1044 buffer[time_offset..time_offset + 12].copy_from_slice(bytemuck::bytes_of(&now));
1045 buffer[time_offset + 12..time_offset + 24].copy_from_slice(bytemuck::bytes_of(&now));
1046 buffer[time_offset + 24..time_offset + 36].copy_from_slice(bytemuck::bytes_of(&now));
1047
1048 let cp_offset = time_offset + 36;
1049 buffer[cp_offset..cp_offset + 4].copy_from_slice(&1u32.to_le_bytes());
1050
1051 let impl_offset = cp_offset + 4 + 16;
1052 self.write_entity_identifier(&mut buffer[impl_offset..impl_offset + 32], b"*hadris-udf");
1053
1054 let uid_offset2 = impl_offset + 32;
1055 buffer[uid_offset2..uid_offset2 + 8].copy_from_slice(&unique_id.to_le_bytes());
1056
1057 let lea_offset = uid_offset2 + 8;
1058 buffer[lea_offset..lea_offset + 4].copy_from_slice(&0u32.to_le_bytes());
1059
1060 let ad_len = core::mem::size_of_val(allocation_descriptors);
1061 buffer[lea_offset + 4..lea_offset + 8].copy_from_slice(&(ad_len as u32).to_le_bytes());
1062
1063 let ad_offset = lea_offset + 8;
1064 if ad_offset + ad_len > buffer.len() {
1065 return Err(crate::error::Error::TooManyAllocationDescriptors);
1066 }
1067 for (i, ad) in allocation_descriptors.iter().enumerate() {
1068 let start = ad_offset + i * size_of::<ShortAllocationDescriptor>();
1069 buffer[start..start + 8].copy_from_slice(bytemuck::bytes_of(ad));
1070 }
1071
1072 let descriptor_end = ad_offset + ad_len;
1073 let tag = self.create_tag(
1074 TagIdentifier::FileEntry,
1075 location,
1076 &buffer[16..descriptor_end],
1077 );
1078 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
1079
1080 self.writer.write_all(&buffer)?;
1081 Ok(())
1082 }
1083
1084 fn write_fids(
1085 &mut self,
1086 location: u32,
1087 parent_icb: LongAllocationDescriptor,
1088 entries: &[(String, LongAllocationDescriptor, bool)],
1089 ) -> Result<usize> {
1090 self.seek_to_partition_block(location)?;
1091
1092 let mut buffer = Vec::new();
1093
1094 let parent_fid = self.create_fid(
1096 location,
1097 &parent_icb,
1098 FileCharacteristics::PARENT | FileCharacteristics::DIRECTORY,
1099 &[],
1100 );
1101 buffer.extend_from_slice(&parent_fid);
1102
1103 for (name, icb, is_dir) in entries {
1105 let chars = if *is_dir {
1106 FileCharacteristics::DIRECTORY
1107 } else {
1108 FileCharacteristics::empty()
1109 };
1110 let encoded_name = self.encode_filename(name)?;
1111 let fid = self.create_fid(location, icb, chars, &encoded_name);
1112 buffer.extend_from_slice(&fid);
1113 }
1114
1115 let padded_len = buffer.len().div_ceil(SECTOR_SIZE) * SECTOR_SIZE;
1117 buffer.resize(padded_len, 0);
1118
1119 self.writer.write_all(&buffer)?;
1120 Ok(padded_len / SECTOR_SIZE)
1121 }
1122
1123 fn create_fid(
1124 &self,
1125 dir_location: u32,
1126 icb: &LongAllocationDescriptor,
1127 characteristics: FileCharacteristics,
1128 encoded_name: &[u8],
1129 ) -> Vec<u8> {
1130 let base_size = 38;
1131 let total_size = (base_size + encoded_name.len() + 3) & !3;
1132 let mut buffer = vec![0u8; total_size];
1133
1134 buffer[16..18].copy_from_slice(&1u16.to_le_bytes());
1135 buffer[18] = characteristics.bits();
1136 buffer[19] = encoded_name.len() as u8;
1137 buffer[20..36].copy_from_slice(bytemuck::bytes_of(icb));
1138 buffer[36..38].copy_from_slice(&0u16.to_le_bytes());
1139 if !encoded_name.is_empty() {
1140 buffer[38..38 + encoded_name.len()].copy_from_slice(encoded_name);
1141 }
1142
1143 let tag = self.create_tag(
1144 TagIdentifier::FileIdentifierDescriptor,
1145 dir_location,
1146 &buffer[16..],
1147 );
1148 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
1149
1150 buffer
1151 }
1152
1153 fn create_tag(&self, identifier: TagIdentifier, location: u32, data: &[u8]) -> DescriptorTag {
1154 let crc_length = data.len().min(496) as u16;
1155 let crc = crc16_itu(&data[..crc_length as usize]);
1156
1157 let mut tag = DescriptorTag {
1158 tag_identifier: identifier.to_u16(),
1159 descriptor_version: 2,
1160 tag_checksum: 0,
1161 reserved: 0,
1162 tag_serial_number: 0,
1163 descriptor_crc: crc,
1164 descriptor_crc_length: crc_length,
1165 tag_location: location,
1166 };
1167
1168 let bytes = bytemuck::bytes_of(&tag);
1169 let mut sum: u8 = 0;
1170 for (i, &byte) in bytes.iter().enumerate() {
1171 if i != 4 {
1172 sum = sum.wrapping_add(byte);
1173 }
1174 }
1175 tag.tag_checksum = sum;
1176
1177 tag
1178 }
1179
1180 fn write_dstring(&self, buffer: &mut [u8], s: &str) {
1181 if s.is_empty() || buffer.is_empty() {
1182 return;
1183 }
1184
1185 let max_content = buffer.len() - 2;
1186 let mut encoded = Vec::new();
1187 if s.chars().all(|ch| (ch as u32) <= 0xff) {
1188 buffer[0] = 8;
1189 encoded.extend(s.chars().map(|ch| ch as u8));
1190 } else {
1191 buffer[0] = 16;
1192 for unit in s.encode_utf16() {
1193 if encoded.len() + 2 > max_content {
1194 break;
1195 }
1196 encoded.extend_from_slice(&unit.to_be_bytes());
1197 }
1198 }
1199 let content_len = encoded.len().min(max_content);
1200 buffer[1..1 + content_len].copy_from_slice(&encoded[..content_len]);
1201 buffer[buffer.len() - 1] = (content_len + 1) as u8;
1202 }
1203
1204 fn write_entity_identifier(&self, buffer: &mut [u8], id: &[u8]) {
1205 let len = id.len().min(23);
1206 buffer[1..1 + len].copy_from_slice(&id[..len]);
1207 if id.starts_with(b"*OSTA UDF") {
1208 buffer[24] = (self.options.revision.to_raw() & 0xFF) as u8;
1209 buffer[25] = ((self.options.revision.to_raw() >> 8) & 0xFF) as u8;
1210 }
1211 }
1212
1213 fn encode_filename(&self, name: &str) -> Result<Vec<u8>> {
1214 encode_cs0_filename(name)
1215 }
1216}
1217
1218impl<W: Write + Seek> UdfWriter<W> {
1223 fn seek_to_partition_block(&mut self, block: u32) -> Result<()> {
1225 let sector = self.options.partition_start + block;
1226 self.writer
1227 .seek(SeekFrom::Start((sector as u64) * SECTOR_SIZE as u64))?;
1228 Ok(())
1229 }
1230
1231 fn seek_to_sector(&mut self, sector: u32) -> Result<()> {
1233 self.writer
1234 .seek(SeekFrom::Start((sector as u64) * SECTOR_SIZE as u64))?;
1235 Ok(())
1236 }
1237
1238 pub fn write_vrs(&mut self) -> Result<()> {
1242 self.write_vrs_at(16)
1243 }
1244
1245 pub fn write_vrs_at(&mut self, start_sector: u32) -> Result<()> {
1250 let nsr = match self.options.revision {
1251 r if r >= UdfRevision::V2_00 => b"NSR03",
1252 _ => b"NSR02",
1253 };
1254
1255 self.seek_to_sector(start_sector)?;
1256 self.write_vrs_descriptor(b"BEA01")?;
1257
1258 self.write_vrs_descriptor(nsr)?;
1260
1261 self.write_vrs_descriptor(b"TEA01")?;
1263
1264 Ok(())
1265 }
1266
1267 fn write_vrs_descriptor(&mut self, id: &[u8; 5]) -> Result<()> {
1268 let mut buffer = [0u8; SECTOR_SIZE];
1269 buffer[0] = 0; buffer[1..6].copy_from_slice(id);
1271 buffer[6] = 1; self.writer.write_all(&buffer)?;
1273 Ok(())
1274 }
1275
1276 pub fn write_avdp(
1278 &mut self,
1279 main_vds_extent: ExtentDescriptor,
1280 reserve_vds_extent: ExtentDescriptor,
1281 ) -> Result<()> {
1282 self.write_avdp_at(AVDP_LOCATION, main_vds_extent, reserve_vds_extent)
1283 }
1284
1285 pub fn write_avdp_at(
1287 &mut self,
1288 location: u32,
1289 main_vds_extent: ExtentDescriptor,
1290 reserve_vds_extent: ExtentDescriptor,
1291 ) -> Result<()> {
1292 self.seek_to_sector(location)?;
1293
1294 let mut buffer = [0u8; SECTOR_SIZE];
1295
1296 buffer[16..20].copy_from_slice(&main_vds_extent.length.to_le_bytes());
1298 buffer[20..24].copy_from_slice(&main_vds_extent.location.to_le_bytes());
1299
1300 buffer[24..28].copy_from_slice(&reserve_vds_extent.length.to_le_bytes());
1302 buffer[28..32].copy_from_slice(&reserve_vds_extent.location.to_le_bytes());
1303
1304 let tag = self.create_tag(
1306 TagIdentifier::AnchorVolumeDescriptorPointer,
1307 location,
1308 &buffer[16..],
1309 );
1310 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
1311
1312 self.writer.write_all(&buffer)?;
1313 Ok(())
1314 }
1315
1316 pub fn write_pvd(&mut self, location: u32, vds_number: u32) -> Result<()> {
1318 self.seek_to_sector(location)?;
1319
1320 let mut buffer = [0u8; 512];
1321 let offset = 16; buffer[offset..offset + 4].copy_from_slice(&vds_number.to_le_bytes());
1325 buffer[offset + 4..offset + 8].copy_from_slice(&0u32.to_le_bytes());
1327
1328 let vol_id_offset = offset + 8;
1330 self.write_dstring(
1331 &mut buffer[vol_id_offset..vol_id_offset + 32],
1332 &self.options.volume_id,
1333 );
1334
1335 let vsn_offset = vol_id_offset + 32;
1337 buffer[vsn_offset..vsn_offset + 2].copy_from_slice(&1u16.to_le_bytes());
1338 buffer[vsn_offset + 2..vsn_offset + 4].copy_from_slice(&1u16.to_le_bytes());
1340 buffer[vsn_offset + 4..vsn_offset + 6].copy_from_slice(&2u16.to_le_bytes());
1342 buffer[vsn_offset + 6..vsn_offset + 8].copy_from_slice(&3u16.to_le_bytes());
1344 buffer[vsn_offset + 8..vsn_offset + 12].copy_from_slice(&1u32.to_le_bytes());
1346 buffer[vsn_offset + 12..vsn_offset + 16].copy_from_slice(&1u32.to_le_bytes());
1348
1349 let vsi_offset = vsn_offset + 16;
1351 self.write_dstring(
1352 &mut buffer[vsi_offset..vsi_offset + 128],
1353 &self.options.volume_id,
1354 );
1355
1356 let dcs_offset = vsi_offset + 128;
1358 write_osta_charspec(&mut buffer[dcs_offset..dcs_offset + 64]);
1359
1360 let ecs_offset = dcs_offset + 64;
1362 write_osta_charspec(&mut buffer[ecs_offset..ecs_offset + 64]);
1363
1364 let abs_offset = ecs_offset + 64;
1367
1368 let app_offset = abs_offset + 16;
1370 self.write_entity_identifier(&mut buffer[app_offset..app_offset + 32], b"*hadris-udf");
1371
1372 let rdt_offset = app_offset + 32;
1374 let now = UdfTimestamp::now();
1375 buffer[rdt_offset..rdt_offset + 12].copy_from_slice(bytemuck::bytes_of(&now));
1376
1377 let impl_offset = rdt_offset + 12;
1379 self.write_entity_identifier(&mut buffer[impl_offset..impl_offset + 32], b"*hadris-udf");
1380
1381 let tag = self.create_tag(
1383 TagIdentifier::PrimaryVolumeDescriptor,
1384 location,
1385 &buffer[16..],
1386 );
1387 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
1388
1389 self.writer.write_all(&buffer)?;
1390 Ok(())
1391 }
1392
1393 pub fn write_partition_descriptor(&mut self, location: u32, vds_number: u32) -> Result<()> {
1395 self.seek_to_sector(location)?;
1396
1397 let mut buffer = [0u8; 512];
1398 let offset = 16;
1399
1400 buffer[offset..offset + 4].copy_from_slice(&vds_number.to_le_bytes());
1402 buffer[offset + 4..offset + 6].copy_from_slice(&1u16.to_le_bytes());
1404 buffer[offset + 6..offset + 8].copy_from_slice(&0u16.to_le_bytes());
1406
1407 let nsr = match self.options.revision {
1409 r if r >= UdfRevision::V2_00 => b"+NSR03",
1410 _ => b"+NSR02",
1411 };
1412 let pc_offset = offset + 8;
1413 self.write_entity_identifier(&mut buffer[pc_offset..pc_offset + 32], nsr);
1414
1415 let at_offset = pc_offset + 32 + 128;
1418 buffer[at_offset..at_offset + 4].copy_from_slice(&1u32.to_le_bytes());
1419
1420 let psl_offset = at_offset + 4;
1422 buffer[psl_offset..psl_offset + 4]
1423 .copy_from_slice(&self.options.partition_start.to_le_bytes());
1424
1425 buffer[psl_offset + 4..psl_offset + 8]
1427 .copy_from_slice(&self.options.partition_length.to_le_bytes());
1428
1429 let impl_offset = psl_offset + 8;
1431 self.write_entity_identifier(&mut buffer[impl_offset..impl_offset + 32], b"*hadris-udf");
1432
1433 let tag = self.create_tag(TagIdentifier::PartitionDescriptor, location, &buffer[16..]);
1435 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
1436
1437 self.writer.write_all(&buffer)?;
1438 Ok(())
1439 }
1440
1441 pub fn write_lvd(
1443 &mut self,
1444 location: u32,
1445 vds_number: u32,
1446 fsd_location: LongAllocationDescriptor,
1447 integrity_extent: ExtentDescriptor,
1448 ) -> Result<()> {
1449 self.seek_to_sector(location)?;
1450
1451 let mut buffer = [0u8; 512];
1452 let offset = 16;
1453
1454 buffer[offset..offset + 4].copy_from_slice(&vds_number.to_le_bytes());
1456
1457 let dcs_offset = offset + 4;
1459 write_osta_charspec(&mut buffer[dcs_offset..dcs_offset + 64]);
1460
1461 let lvi_offset = dcs_offset + 64;
1463 self.write_dstring(
1464 &mut buffer[lvi_offset..lvi_offset + 128],
1465 &self.options.volume_id,
1466 );
1467
1468 let lbs_offset = lvi_offset + 128;
1470 buffer[lbs_offset..lbs_offset + 4].copy_from_slice(&(SECTOR_SIZE as u32).to_le_bytes());
1471
1472 let di_offset = lbs_offset + 4;
1474 self.write_entity_identifier(
1475 &mut buffer[di_offset..di_offset + 32],
1476 b"*OSTA UDF Compliant",
1477 );
1478
1479 buffer[di_offset + 24] = (self.options.revision.to_raw() & 0xFF) as u8;
1481 buffer[di_offset + 25] = ((self.options.revision.to_raw() >> 8) & 0xFF) as u8;
1482
1483 let lvcu_offset = di_offset + 32;
1485 buffer[lvcu_offset..lvcu_offset + 16].copy_from_slice(bytemuck::bytes_of(&fsd_location));
1486
1487 let mtl_offset = lvcu_offset + 16;
1489 buffer[mtl_offset..mtl_offset + 4].copy_from_slice(&6u32.to_le_bytes()); buffer[mtl_offset + 4..mtl_offset + 8].copy_from_slice(&1u32.to_le_bytes());
1493
1494 let impl_offset = mtl_offset + 8;
1496 self.write_entity_identifier(&mut buffer[impl_offset..impl_offset + 32], b"*hadris-udf");
1497
1498 let iu_offset = impl_offset + 32;
1500
1501 let ise_offset = iu_offset + 128;
1503 buffer[ise_offset..ise_offset + 4].copy_from_slice(&integrity_extent.length.to_le_bytes());
1504 buffer[ise_offset + 4..ise_offset + 8]
1505 .copy_from_slice(&integrity_extent.location.to_le_bytes());
1506
1507 let pm_offset = ise_offset + 8;
1509 buffer[pm_offset] = 1; buffer[pm_offset + 1] = 6; buffer[pm_offset + 2..pm_offset + 4].copy_from_slice(&1u16.to_le_bytes()); buffer[pm_offset + 4..pm_offset + 6].copy_from_slice(&0u16.to_le_bytes()); let tag = self.create_tag(
1516 TagIdentifier::LogicalVolumeDescriptor,
1517 location,
1518 &buffer[16..],
1519 );
1520 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
1521
1522 self.writer.write_all(&buffer)?;
1523 Ok(())
1524 }
1525
1526 pub fn write_usd(&mut self, location: u32, vds_number: u32) -> Result<()> {
1528 self.seek_to_sector(location)?;
1529
1530 let mut buffer = [0u8; 512];
1531 let offset = 16;
1532
1533 buffer[offset..offset + 4].copy_from_slice(&vds_number.to_le_bytes());
1535 buffer[offset + 4..offset + 8].copy_from_slice(&0u32.to_le_bytes());
1537
1538 let tag = self.create_tag(
1540 TagIdentifier::UnallocatedSpaceDescriptor,
1541 location,
1542 &buffer[16..],
1543 );
1544 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
1545
1546 self.writer.write_all(&buffer)?;
1547 Ok(())
1548 }
1549
1550 pub fn write_iuvd(&mut self, location: u32, vds_number: u32) -> Result<()> {
1552 self.seek_to_sector(location)?;
1553
1554 let mut buffer = [0u8; 512];
1555 let offset = 16;
1556
1557 buffer[offset..offset + 4].copy_from_slice(&vds_number.to_le_bytes());
1559
1560 let impl_offset = offset + 4;
1562 self.write_entity_identifier(&mut buffer[impl_offset..impl_offset + 32], b"*UDF LV Info");
1563
1564 let iu_offset = impl_offset + 32;
1566 write_osta_charspec(&mut buffer[iu_offset..iu_offset + 64]);
1568
1569 let lvi_offset = iu_offset + 64;
1571 self.write_dstring(
1572 &mut buffer[lvi_offset..lvi_offset + 128],
1573 &self.options.volume_id,
1574 );
1575
1576 let tag = self.create_tag(
1578 TagIdentifier::ImplementationUseVolumeDescriptor,
1579 location,
1580 &buffer[16..],
1581 );
1582 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
1583
1584 self.writer.write_all(&buffer)?;
1585 Ok(())
1586 }
1587
1588 pub fn write_terminating_descriptor(&mut self, location: u32) -> Result<()> {
1590 self.seek_to_sector(location)?;
1591
1592 let mut buffer = [0u8; 512];
1593
1594 let tag = self.create_tag(TagIdentifier::TerminatingDescriptor, location, &[]);
1596 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
1597
1598 self.writer.write_all(&buffer)?;
1599 Ok(())
1600 }
1601
1602 pub fn write_fsd(
1604 &mut self,
1605 location: u32,
1606 root_icb: LongAllocationDescriptor,
1607 ) -> Result<()> {
1608 self.seek_to_partition_block(location)?;
1609
1610 let mut buffer = [0u8; 512];
1611 let offset = 16;
1612
1613 let now = UdfTimestamp::now();
1615 buffer[offset..offset + 12].copy_from_slice(bytemuck::bytes_of(&now));
1616
1617 buffer[offset + 12..offset + 14].copy_from_slice(&3u16.to_le_bytes());
1619 buffer[offset + 14..offset + 16].copy_from_slice(&3u16.to_le_bytes());
1621 buffer[offset + 16..offset + 20].copy_from_slice(&1u32.to_le_bytes());
1623 buffer[offset + 20..offset + 24].copy_from_slice(&1u32.to_le_bytes());
1625 buffer[offset + 24..offset + 28].copy_from_slice(&0u32.to_le_bytes());
1627 buffer[offset + 28..offset + 32].copy_from_slice(&0u32.to_le_bytes());
1629
1630 let lvics_offset = offset + 32;
1632 write_osta_charspec(&mut buffer[lvics_offset..lvics_offset + 64]);
1633
1634 let lvi_offset = lvics_offset + 64;
1636 self.write_dstring(
1637 &mut buffer[lvi_offset..lvi_offset + 128],
1638 &self.options.volume_id,
1639 );
1640
1641 let fscs_offset = lvi_offset + 128;
1643 write_osta_charspec(&mut buffer[fscs_offset..fscs_offset + 64]);
1644
1645 let fsi_offset = fscs_offset + 64;
1647 self.write_dstring(
1648 &mut buffer[fsi_offset..fsi_offset + 32],
1649 &self.options.volume_id,
1650 );
1651
1652 let root_offset = fsi_offset + 32 + 32 + 32;
1655 buffer[root_offset..root_offset + 16].copy_from_slice(bytemuck::bytes_of(&root_icb));
1656
1657 let di_offset = root_offset + 16;
1659 self.write_entity_identifier(
1660 &mut buffer[di_offset..di_offset + 32],
1661 b"*OSTA UDF Compliant",
1662 );
1663 buffer[di_offset + 24] = (self.options.revision.to_raw() & 0xFF) as u8;
1664 buffer[di_offset + 25] = ((self.options.revision.to_raw() >> 8) & 0xFF) as u8;
1665
1666 let tag = self.create_tag(TagIdentifier::FileSetDescriptor, location, &buffer[16..]);
1668 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
1669
1670 self.writer.write_all(&buffer)?;
1671 Ok(())
1672 }
1673
1674 pub fn write_file_entry(
1676 &mut self,
1677 location: u32,
1678 file_type: FileType,
1679 info_length: u64,
1680 allocation_descriptors: &[ShortAllocationDescriptor],
1681 unique_id: u64,
1682 ) -> Result<()> {
1683 self.seek_to_partition_block(location)?;
1684
1685 let mut buffer = [0u8; SECTOR_SIZE];
1686 let offset = 16; let icb_offset = offset;
1690 buffer[icb_offset + 4..icb_offset + 6].copy_from_slice(&4u16.to_le_bytes());
1693 buffer[icb_offset + 8..icb_offset + 10].copy_from_slice(&1u16.to_le_bytes());
1696 buffer[icb_offset + 11] = file_type as u8;
1699 buffer[icb_offset + 18..icb_offset + 20].copy_from_slice(&0u16.to_le_bytes());
1702
1703 let uid_offset = icb_offset + 20;
1705 buffer[uid_offset..uid_offset + 4].copy_from_slice(&0xFFFFFFFFu32.to_le_bytes());
1706 buffer[uid_offset + 4..uid_offset + 8].copy_from_slice(&0xFFFFFFFFu32.to_le_bytes());
1708 buffer[uid_offset + 8..uid_offset + 12].copy_from_slice(&0x7FFFu32.to_le_bytes());
1710 buffer[uid_offset + 12..uid_offset + 14].copy_from_slice(&1u16.to_le_bytes());
1712 let il_offset = uid_offset + 20;
1718 buffer[il_offset..il_offset + 8].copy_from_slice(&info_length.to_le_bytes());
1719
1720 let blocks = info_length.div_ceil(SECTOR_SIZE as u64);
1722 buffer[il_offset + 8..il_offset + 16].copy_from_slice(&blocks.to_le_bytes());
1723
1724 let now = UdfTimestamp::now();
1726 let time_offset = il_offset + 16;
1727 buffer[time_offset..time_offset + 12].copy_from_slice(bytemuck::bytes_of(&now));
1728 buffer[time_offset + 12..time_offset + 24].copy_from_slice(bytemuck::bytes_of(&now));
1729 buffer[time_offset + 24..time_offset + 36].copy_from_slice(bytemuck::bytes_of(&now));
1730
1731 let cp_offset = time_offset + 36;
1733 buffer[cp_offset..cp_offset + 4].copy_from_slice(&1u32.to_le_bytes());
1734
1735 let impl_offset = cp_offset + 4 + 16;
1738 self.write_entity_identifier(&mut buffer[impl_offset..impl_offset + 32], b"*hadris-udf");
1739
1740 let uid_offset2 = impl_offset + 32;
1742 buffer[uid_offset2..uid_offset2 + 8].copy_from_slice(&unique_id.to_le_bytes());
1743
1744 let lea_offset = uid_offset2 + 8;
1746 buffer[lea_offset..lea_offset + 4].copy_from_slice(&0u32.to_le_bytes());
1747
1748 let ad_len = core::mem::size_of_val(allocation_descriptors);
1750 buffer[lea_offset + 4..lea_offset + 8].copy_from_slice(&(ad_len as u32).to_le_bytes());
1751
1752 let ad_offset = lea_offset + 8;
1754 if ad_offset + ad_len > buffer.len() {
1755 return Err(crate::error::Error::TooManyAllocationDescriptors);
1756 }
1757 for (i, ad) in allocation_descriptors.iter().enumerate() {
1758 let start = ad_offset + i * size_of::<ShortAllocationDescriptor>();
1759 buffer[start..start + 8].copy_from_slice(bytemuck::bytes_of(ad));
1760 }
1761
1762 let descriptor_end = ad_offset + ad_len;
1764 let tag = self.create_tag(
1765 TagIdentifier::FileEntry,
1766 location,
1767 &buffer[16..descriptor_end],
1768 );
1769 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
1770
1771 self.writer.write_all(&buffer)?;
1772 Ok(())
1773 }
1774
1775 pub fn write_fids(
1777 &mut self,
1778 location: u32,
1779 parent_icb: LongAllocationDescriptor,
1780 entries: &[(String, LongAllocationDescriptor, bool)], ) -> Result<usize> {
1782 self.seek_to_partition_block(location)?;
1783
1784 let mut buffer = Vec::new();
1785
1786 let parent_fid = self.create_fid(
1788 location,
1789 &parent_icb,
1790 FileCharacteristics::PARENT | FileCharacteristics::DIRECTORY,
1791 &[],
1792 );
1793 buffer.extend_from_slice(&parent_fid);
1794
1795 for (name, icb, is_dir) in entries {
1797 let chars = if *is_dir {
1798 FileCharacteristics::DIRECTORY
1799 } else {
1800 FileCharacteristics::empty()
1801 };
1802 let encoded_name = self.encode_filename(name)?;
1803 let fid = self.create_fid(location, icb, chars, &encoded_name);
1804 buffer.extend_from_slice(&fid);
1805 }
1806
1807 let padded_len = buffer.len().div_ceil(SECTOR_SIZE) * SECTOR_SIZE;
1809 buffer.resize(padded_len, 0);
1810
1811 self.writer.write_all(&buffer)?;
1812 Ok(padded_len / SECTOR_SIZE)
1813 }
1814
1815 fn create_fid(
1816 &self,
1817 dir_location: u32,
1818 icb: &LongAllocationDescriptor,
1819 characteristics: FileCharacteristics,
1820 encoded_name: &[u8],
1821 ) -> Vec<u8> {
1822 let base_size = 38; let total_size = (base_size + encoded_name.len() + 3) & !3; let mut buffer = vec![0u8; total_size];
1825
1826 buffer[16..18].copy_from_slice(&1u16.to_le_bytes());
1828 buffer[18] = characteristics.bits();
1830 buffer[19] = encoded_name.len() as u8;
1832 buffer[20..36].copy_from_slice(bytemuck::bytes_of(icb));
1834 buffer[36..38].copy_from_slice(&0u16.to_le_bytes());
1836 if !encoded_name.is_empty() {
1838 buffer[38..38 + encoded_name.len()].copy_from_slice(encoded_name);
1839 }
1840
1841 let tag = self.create_tag(
1843 TagIdentifier::FileIdentifierDescriptor,
1844 dir_location,
1845 &buffer[16..],
1846 );
1847 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
1848
1849 buffer
1850 }
1851
1852 pub fn write_lvid(&mut self, location: u32, close: bool) -> Result<()> {
1854 self.seek_to_sector(location)?;
1855
1856 let mut buffer = [0u8; 512];
1857 let offset = 16;
1858
1859 let now = UdfTimestamp::now();
1861 buffer[offset..offset + 12].copy_from_slice(bytemuck::bytes_of(&now));
1862
1863 let integrity = if close { 1u32 } else { 0u32 };
1865 buffer[offset + 12..offset + 16].copy_from_slice(&integrity.to_le_bytes());
1866
1867 let lvcu_offset = offset + 24;
1871 buffer[lvcu_offset..lvcu_offset + 8].copy_from_slice(&self.unique_id_counter.to_le_bytes());
1873
1874 let np_offset = lvcu_offset + 32;
1876 buffer[np_offset..np_offset + 4].copy_from_slice(&1u32.to_le_bytes());
1877
1878 buffer[np_offset + 4..np_offset + 8].copy_from_slice(&46u32.to_le_bytes());
1880
1881 let fst_offset = np_offset + 8;
1883 buffer[fst_offset..fst_offset + 4].copy_from_slice(&0u32.to_le_bytes()); buffer[fst_offset + 4..fst_offset + 8]
1887 .copy_from_slice(&self.options.partition_length.to_le_bytes());
1888
1889 let iu_offset = fst_offset + 8;
1891 self.write_entity_identifier(&mut buffer[iu_offset..iu_offset + 32], b"*hadris-udf");
1893 let revision = self.options.revision.to_raw().to_le_bytes();
1894 buffer[iu_offset + 40..iu_offset + 42].copy_from_slice(&revision);
1895 buffer[iu_offset + 42..iu_offset + 44].copy_from_slice(&revision);
1896 buffer[iu_offset + 44..iu_offset + 46].copy_from_slice(&revision);
1897
1898 let tag = self.create_tag(
1900 TagIdentifier::LogicalVolumeIntegrityDescriptor,
1901 location,
1902 &buffer[16..],
1903 );
1904 buffer[0..16].copy_from_slice(bytemuck::bytes_of(&tag));
1905
1906 self.writer.write_all(&buffer)?;
1907 Ok(())
1908 }
1909
1910 fn create_tag(&self, identifier: TagIdentifier, location: u32, data: &[u8]) -> DescriptorTag {
1912 let crc_length = data.len().min(496) as u16; let crc = crc16_itu(&data[..crc_length as usize]);
1914
1915 let mut tag = DescriptorTag {
1916 tag_identifier: identifier.to_u16(),
1917 descriptor_version: 2,
1918 tag_checksum: 0,
1919 reserved: 0,
1920 tag_serial_number: 0,
1921 descriptor_crc: crc,
1922 descriptor_crc_length: crc_length,
1923 tag_location: location,
1924 };
1925
1926 let bytes = bytemuck::bytes_of(&tag);
1928 let mut sum: u8 = 0;
1929 for (i, &byte) in bytes.iter().enumerate() {
1930 if i != 4 {
1931 sum = sum.wrapping_add(byte);
1932 }
1933 }
1934 tag.tag_checksum = sum;
1935
1936 tag
1937 }
1938
1939 fn write_dstring(&self, buffer: &mut [u8], s: &str) {
1941 if s.is_empty() || buffer.is_empty() {
1942 return;
1943 }
1944
1945 let max_content = buffer.len() - 2; let mut encoded = Vec::new();
1947 if s.chars().all(|ch| (ch as u32) <= 0xff) {
1948 buffer[0] = 8;
1949 encoded.extend(s.chars().map(|ch| ch as u8));
1950 } else {
1951 buffer[0] = 16;
1952 for unit in s.encode_utf16() {
1953 if encoded.len() + 2 > max_content {
1954 break;
1955 }
1956 encoded.extend_from_slice(&unit.to_be_bytes());
1957 }
1958 }
1959 let content_len = encoded.len().min(max_content);
1960 buffer[1..1 + content_len].copy_from_slice(&encoded[..content_len]);
1961 buffer[buffer.len() - 1] = (content_len + 1) as u8; }
1963
1964 fn write_entity_identifier(&self, buffer: &mut [u8], id: &[u8]) {
1966 let len = id.len().min(23);
1969 buffer[1..1 + len].copy_from_slice(&id[..len]);
1970 if id.starts_with(b"*OSTA UDF") {
1972 buffer[24] = (self.options.revision.to_raw() & 0xFF) as u8;
1973 buffer[25] = ((self.options.revision.to_raw() >> 8) & 0xFF) as u8;
1974 }
1975 }
1976
1977 fn encode_filename(&self, name: &str) -> Result<Vec<u8>> {
1979 encode_cs0_filename(name)
1980 }
1981}
1982
1983fn encode_cs0_filename(name: &str) -> Result<Vec<u8>> {
1984 let mut result = if name.chars().all(|ch| (ch as u32) <= 0xff) {
1985 let mut encoded = Vec::with_capacity(name.chars().count() + 1);
1986 encoded.push(8);
1987 encoded.extend(name.chars().map(|ch| ch as u8));
1988 encoded
1989 } else {
1990 let mut encoded = Vec::with_capacity(name.encode_utf16().count() * 2 + 1);
1991 encoded.push(16);
1992 for unit in name.encode_utf16() {
1993 encoded.extend_from_slice(&unit.to_be_bytes());
1994 }
1995 encoded
1996 };
1997 if result.len() > u8::MAX as usize {
1998 result.clear();
1999 return Err(crate::error::Error::InvalidEncoding);
2000 }
2001 Ok(result)
2002}
2003
2004#[cfg(test)]
2005mod cs0_tests {
2006 use super::encode_cs0_filename;
2007
2008 #[test]
2009 fn selects_eight_bit_for_latin1() {
2010 assert_eq!(encode_cs0_filename("café").unwrap(), b"\x08caf\xe9");
2011 }
2012
2013 #[test]
2014 fn selects_sixteen_bit_for_wide_unicode() {
2015 assert_eq!(encode_cs0_filename("文").unwrap(), [16, 0x65, 0x87]);
2016 }
2017
2018 #[test]
2019 fn rejects_fid_identifiers_over_255_bytes() {
2020 assert!(encode_cs0_filename(&"文".repeat(128)).is_err());
2021 }
2022}
2023
2024fn crc16_itu(data: &[u8]) -> u16 {
2026 let mut crc: u16 = 0;
2027 for &byte in data {
2028 let mut x = ((crc >> 8) ^ (byte as u16)) & 0xFF;
2029 x ^= x >> 4;
2030 crc = (crc << 8) ^ (x << 12) ^ (x << 5) ^ x;
2031 }
2032 crc
2033}
2034
2035impl UdfTimestamp {
2036 #[cfg(feature = "std")]
2038 pub fn now() -> Self {
2039 use std::time::{SystemTime, UNIX_EPOCH};
2040
2041 let duration = SystemTime::now()
2042 .duration_since(UNIX_EPOCH)
2043 .unwrap_or_default();
2044
2045 let secs = duration.as_secs();
2046 let subsec_nanos = duration.subsec_nanos();
2047
2048 let days = (secs / 86400) as i64;
2051 let day_secs = (secs % 86400) as u32;
2052
2053 let (year, month, day) = days_to_ymd(days + 719468); Self {
2057 type_and_tz: 0x1000, year: year as u16,
2059 month: month as u8,
2060 day: day as u8,
2061 hour: (day_secs / 3600) as u8,
2062 minute: ((day_secs % 3600) / 60) as u8,
2063 second: (day_secs % 60) as u8,
2064 centiseconds: (subsec_nanos / 10_000_000) as u8,
2065 hundreds_of_microseconds: ((subsec_nanos / 100_000) % 100) as u8,
2066 microseconds: ((subsec_nanos / 1000) % 100) as u8,
2067 }
2068 }
2069
2070 #[cfg(not(feature = "std"))]
2071 pub fn now() -> Self {
2072 Self {
2073 type_and_tz: 0x1000,
2074 year: 2024,
2075 month: 1,
2076 day: 1,
2077 hour: 0,
2078 minute: 0,
2079 second: 0,
2080 centiseconds: 0,
2081 hundreds_of_microseconds: 0,
2082 microseconds: 0,
2083 }
2084 }
2085}
2086
2087#[cfg(feature = "std")]
2089fn days_to_ymd(days: i64) -> (i32, u32, u32) {
2090 let era = if days >= 0 { days } else { days - 146096 } / 146097;
2092 let doe = (days - era * 146097) as u32;
2093 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
2094 let y = yoe as i64 + era * 400;
2095 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
2096 let mp = (5 * doy + 2) / 153;
2097 let d = doy - (153 * mp + 2) / 5 + 1;
2098 let m = if mp < 10 { mp + 3 } else { mp - 9 };
2099 let y = if m <= 2 { y + 1 } else { y };
2100 (y as i32, m, d)
2101}
2102
2103fn write_osta_charspec(buffer: &mut [u8]) {
2104 buffer.fill(0);
2105 buffer[1..24].copy_from_slice(b"OSTA Compressed Unicode");
2106}
2107
2108#[cfg(test)]
2109mod tests {
2110 use super::*;
2111 use std::io::Cursor;
2112
2113 #[test]
2114 fn test_crc16_itu() {
2115 assert_eq!(crc16_itu(&[]), 0);
2117
2118 let data = b"test";
2121 let crc1 = crc16_itu(data);
2122 let crc2 = crc16_itu(data);
2123 assert_eq!(crc1, crc2);
2124 }
2125
2126 #[test]
2127 fn test_simple_dir_creation() {
2128 let mut root = SimpleDir::root();
2129 root.add_file(SimpleFile::new("test.txt", b"Hello".to_vec()));
2130 root.add_file(SimpleFile::empty("empty.txt"));
2131
2132 let mut subdir = SimpleDir::new("docs");
2133 subdir.add_file(SimpleFile::new("guide.txt", b"Guide content".to_vec()));
2134 root.add_dir(subdir);
2135
2136 assert_eq!(root.total_files(), 3);
2137 assert_eq!(root.total_dirs(), 2);
2138 }
2139
2140 #[test]
2141 fn test_format_empty_filesystem() {
2142 let mut buffer = vec![0u8; 2 * 1024 * 1024]; let cursor = Cursor::new(&mut buffer[..]);
2144
2145 let root = SimpleDir::root();
2146 let options = UdfWriteOptions::default();
2147
2148 let result = UdfWriter::create(cursor, &root, options);
2149 assert!(result.is_ok(), "Format should succeed for empty filesystem");
2150
2151 let sectors = result.unwrap().sectors_written;
2152 assert!(
2153 sectors > 270,
2154 "Should have written at least partition start sectors"
2155 );
2156 let anchor_locations = [256, sectors - 257];
2157 for location in anchor_locations {
2158 let offset = location as usize * SECTOR_SIZE;
2159 assert_eq!(
2160 u16::from_le_bytes([buffer[offset], buffer[offset + 1]]),
2161 TagIdentifier::AnchorVolumeDescriptorPointer.to_u16(),
2162 "missing anchor at sector {location}"
2163 );
2164 }
2165
2166 let last_sector = sectors - 1;
2167 let last_offset = last_sector as usize * SECTOR_SIZE;
2168 assert_ne!(
2169 u16::from_le_bytes([buffer[last_offset], buffer[last_offset + 1]]),
2170 TagIdentifier::AnchorVolumeDescriptorPointer.to_u16(),
2171 "UDF 1.02 records exactly two of the three candidate anchors"
2172 );
2173
2174 let avdp_offset = 256 * SECTOR_SIZE;
2175 let main_length =
2176 u32::from_le_bytes(buffer[avdp_offset + 16..avdp_offset + 20].try_into().unwrap());
2177 let main_location =
2178 u32::from_le_bytes(buffer[avdp_offset + 20..avdp_offset + 24].try_into().unwrap());
2179 let reserve_length =
2180 u32::from_le_bytes(buffer[avdp_offset + 24..avdp_offset + 28].try_into().unwrap());
2181 let reserve_location =
2182 u32::from_le_bytes(buffer[avdp_offset + 28..avdp_offset + 32].try_into().unwrap());
2183
2184 assert_eq!(main_length, 16 * SECTOR_SIZE as u32);
2185 assert_eq!(reserve_length, 16 * SECTOR_SIZE as u32);
2186 assert_eq!(main_location, 257);
2187 assert_eq!(reserve_location, main_location + 16);
2188 }
2189
2190 #[test]
2191 fn test_format_with_single_file() {
2192 let mut buffer = vec![0u8; 2 * 1024 * 1024]; let cursor = Cursor::new(&mut buffer[..]);
2194
2195 let mut root = SimpleDir::root();
2196 root.add_file(SimpleFile::new("readme.txt", b"Hello, World!".to_vec()));
2197
2198 let options = UdfWriteOptions {
2199 volume_id: String::from("TEST_VOL"),
2200 ..Default::default()
2201 };
2202
2203 let result = UdfWriter::create(cursor, &root, options);
2204 assert!(result.is_ok(), "Format should succeed with single file");
2205
2206 let bea01 = &buffer[16 * 2048..16 * 2048 + 6];
2208 assert_eq!(&bea01[1..6], b"BEA01", "VRS should start with BEA01");
2209
2210 let avdp_tag = u16::from_le_bytes([buffer[256 * 2048], buffer[256 * 2048 + 1]]);
2212 assert_eq!(avdp_tag, 2, "AVDP tag should be 2");
2213 }
2214
2215 #[test]
2216 fn test_format_with_subdirectory() {
2217 let mut buffer = vec![0u8; 4 * 1024 * 1024]; let cursor = Cursor::new(&mut buffer[..]);
2219
2220 let mut root = SimpleDir::root();
2221 root.add_file(SimpleFile::new("root.txt", b"Root file".to_vec()));
2222
2223 let mut docs = SimpleDir::new("docs");
2224 docs.add_file(SimpleFile::new(
2225 "manual.txt",
2226 b"User manual content here".to_vec(),
2227 ));
2228 docs.add_file(SimpleFile::new("changelog.txt", b"Version 1.0".to_vec()));
2229 root.add_dir(docs);
2230
2231 let options = UdfWriteOptions {
2232 volume_id: String::from("SUBDIR_TEST"),
2233 ..Default::default()
2234 };
2235
2236 let result = UdfWriter::create(cursor, &root, options);
2237 assert!(result.is_ok(), "Format should succeed with subdirectory");
2238 }
2239
2240 #[test]
2241 fn test_format_with_empty_file() {
2242 let mut buffer = vec![0u8; 2 * 1024 * 1024]; let cursor = Cursor::new(&mut buffer[..]);
2244
2245 let mut root = SimpleDir::root();
2246 root.add_file(SimpleFile::empty("empty.txt"));
2247 root.add_file(SimpleFile::new("notempty.txt", b"content".to_vec()));
2248
2249 let options = UdfWriteOptions::default();
2250
2251 let result = UdfWriter::create(cursor, &root, options);
2252 assert!(result.is_ok(), "Format should handle empty files");
2253 }
2254
2255 #[test]
2256 fn test_format_vrs_nsr_version() {
2257 let mut buffer = vec![0u8; 2 * 1024 * 1024];
2259 let cursor = Cursor::new(&mut buffer[..]);
2260 let root = SimpleDir::root();
2261 let options = UdfWriteOptions {
2262 revision: crate::UdfRevision::V1_02,
2263 ..Default::default()
2264 };
2265 UdfWriter::create(cursor, &root, options).unwrap();
2266 let nsr = &buffer[17 * 2048 + 1..17 * 2048 + 6];
2267 assert_eq!(nsr, b"NSR02", "UDF 1.02 should use NSR02");
2268
2269 let mut buffer2 = vec![0u8; 2 * 1024 * 1024];
2271 let cursor2 = Cursor::new(&mut buffer2[..]);
2272 let root2 = SimpleDir::root();
2273 let options2 = UdfWriteOptions {
2274 revision: crate::UdfRevision::V2_01,
2275 ..Default::default()
2276 };
2277 UdfWriter::create(cursor2, &root2, options2).unwrap();
2278 let nsr2 = &buffer2[17 * 2048 + 1..17 * 2048 + 6];
2279 assert_eq!(nsr2, b"NSR03", "UDF 2.01 should use NSR03");
2280 }
2281
2282 #[test]
2283 fn mastered_revision_roundtrips_exactly() {
2284 for revision in [
2285 crate::UdfRevision::V1_02,
2286 crate::UdfRevision::V1_50,
2287 crate::UdfRevision::V2_00,
2288 crate::UdfRevision::V2_01,
2289 crate::UdfRevision::V2_50,
2290 crate::UdfRevision::V2_60,
2291 ] {
2292 let mut buffer = vec![0u8; 2 * 1024 * 1024];
2293 UdfWriter::create(
2294 Cursor::new(&mut buffer[..]),
2295 &SimpleDir::root(),
2296 UdfWriteOptions {
2297 revision,
2298 ..Default::default()
2299 },
2300 )
2301 .unwrap();
2302 let volume = crate::UdfVolume::open(Cursor::new(&buffer[..])).unwrap();
2303 assert_eq!(volume.info().udf_revision, revision);
2304 }
2305 }
2306
2307 #[test]
2308 fn test_roundtrip_basic_verification() {
2309 let mut buffer = vec![0u8; 4 * 1024 * 1024]; let payload = b"Hello, UDF!";
2313
2314 {
2315 let cursor = Cursor::new(&mut buffer[..]);
2316 let mut root = SimpleDir::root();
2317 root.add_file(SimpleFile::new("hello.txt", payload.to_vec()));
2318
2319 let options = UdfWriteOptions {
2320 volume_id: String::from("ROUNDTRIP"),
2321 ..Default::default()
2322 };
2323
2324 UdfWriter::create(cursor, &root, options).expect("Format should succeed");
2325 }
2326
2327 assert_eq!(&buffer[16 * 2048 + 1..16 * 2048 + 6], b"BEA01", "VRS BEA01");
2329 assert_eq!(&buffer[17 * 2048 + 1..17 * 2048 + 6], b"NSR02", "VRS NSR02");
2330 assert_eq!(&buffer[18 * 2048 + 1..18 * 2048 + 6], b"TEA01", "VRS TEA01");
2331
2332 let avdp_tag = u16::from_le_bytes([buffer[256 * 2048], buffer[256 * 2048 + 1]]);
2333 assert_eq!(avdp_tag, 2, "AVDP tag ID should be 2");
2334
2335 let udf = crate::UdfVolume::open(Cursor::new(&buffer[..])).expect("open hadris-written image");
2337 let root = udf.root_dir().expect("root_dir");
2338 let entry = root
2339 .entries()
2340 .find(|e| e.is_file() && e.name() == "hello.txt")
2341 .expect("hello.txt should be listed");
2342 assert_eq!(entry.size, payload.len() as u64);
2343 let bytes = udf.read_file(entry).expect("read_file");
2344 assert_eq!(bytes, payload);
2345 }
2346
2347 #[test]
2348 fn test_roundtrip_large_file_read() {
2349 let mut buffer = vec![0u8; 8 * 1024 * 1024];
2350 let large_data = vec![0x55; 10000];
2351
2352 {
2353 let cursor = Cursor::new(&mut buffer[..]);
2354 let mut root = SimpleDir::root();
2355 root.add_file(SimpleFile::new("large.bin", large_data.clone()));
2356 UdfWriter::create(cursor, &root, UdfWriteOptions::default()).unwrap();
2357 }
2358
2359 let udf = crate::UdfVolume::open(Cursor::new(&buffer[..])).unwrap();
2360 let root = udf.root_dir().unwrap();
2361 let entry = root
2362 .entries()
2363 .find(|e| e.name() == "large.bin")
2364 .expect("large.bin");
2365 assert_eq!(entry.size, large_data.len() as u64);
2366 assert_eq!(udf.read_file(entry).unwrap(), large_data);
2367 }
2368
2369 #[test]
2370 fn test_format_large_file() {
2371 let mut buffer = vec![0u8; 8 * 1024 * 1024]; let cursor = Cursor::new(&mut buffer[..]);
2373
2374 let mut root = SimpleDir::root();
2375 let large_data = vec![0x55; 10000]; root.add_file(SimpleFile::new("large.bin", large_data.clone()));
2378
2379 let options = UdfWriteOptions::default();
2380 let result = UdfWriter::create(cursor, &root, options);
2381 assert!(result.is_ok(), "Format should succeed with large file");
2382
2383 let pattern_found = buffer.windows(100).any(|w| w == &large_data[..100]);
2385 assert!(pattern_found, "Large file data should be in the image");
2386 }
2387}