Skip to main content

hadris_udf/write/
mod.rs

1//! UDF Write Support
2//!
3//! This module provides functionality to write UDF filesystem structures.
4//! It supports both low-level descriptor writing and high-level formatting.
5//!
6//! ## High-Level API
7//!
8//! Use [`UdfWriter::create`] to create a complete UDF filesystem:
9//!
10//! ```rust,no_run
11//! use hadris_udf::write::{UdfWriter, UdfWriteOptions, SimpleFile, SimpleDir};
12//! use std::io::Cursor;
13//!
14//! let mut buffer = vec![0u8; 10 * 1024 * 1024]; // 10MB
15//! let mut cursor = Cursor::new(&mut buffer[..]);
16//!
17//! let mut root = SimpleDir::new("");
18//! root.add_file(SimpleFile::new("readme.txt", b"Hello, World!".to_vec()));
19//!
20//! let mut subdir = SimpleDir::new("docs");
21//! subdir.add_file(SimpleFile::new("guide.txt", b"User guide content".to_vec()));
22//! root.add_dir(subdir);
23//!
24//! let options = UdfWriteOptions::default();
25//! UdfWriter::create(&mut cursor, &root, options).expect("Format failed");
26//! ```
27//!
28//! ## Low-Level API
29//!
30//! For fine-grained control (e.g., hybrid ISO+UDF images), use individual
31//! descriptor writing methods on [`UdfWriter`].
32
33use 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// =============================================================================
50// High-Level Types for Simple UDF Creation
51// =============================================================================
52
53/// A simple file for the high-level format API
54#[derive(Debug, Clone)]
55pub struct SimpleFile {
56    /// File name
57    pub name: String,
58    /// File content
59    pub data: Vec<u8>,
60}
61
62impl SimpleFile {
63    /// Create a new file with the given name and content
64    pub fn new(name: impl Into<String>, data: Vec<u8>) -> Self {
65        Self {
66            name: name.into(),
67            data,
68        }
69    }
70
71    /// Create an empty file
72    pub fn empty(name: impl Into<String>) -> Self {
73        Self::new(name, Vec::new())
74    }
75}
76
77/// A simple directory for the high-level format API
78#[derive(Debug, Clone, Default)]
79pub struct SimpleDir {
80    /// Directory name (empty for root)
81    pub name: String,
82    /// Files in this directory
83    pub files: Vec<SimpleFile>,
84    /// Subdirectories
85    pub subdirs: Vec<SimpleDir>,
86}
87
88impl SimpleDir {
89    /// Create a new empty directory
90    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    /// Create a root directory
99    pub fn root() -> Self {
100        Self::new("")
101    }
102
103    /// Add a file to this directory
104    pub fn add_file(&mut self, file: SimpleFile) {
105        self.files.push(file);
106    }
107
108    /// Add a subdirectory
109    pub fn add_dir(&mut self, dir: SimpleDir) {
110        self.subdirs.push(dir);
111    }
112
113    /// Count total files recursively
114    pub fn total_files(&self) -> usize {
115        self.files.len() + self.subdirs.iter().map(|d| d.total_files()).sum::<usize>()
116    }
117
118    /// Count total directories recursively (including self)
119    pub fn total_dirs(&self) -> usize {
120        1 + self.subdirs.iter().map(|d| d.total_dirs()).sum::<usize>()
121    }
122
123    /// Sort files and directories by name
124    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// Internal structure for tracking allocated items during format
134#[derive(Debug)]
135struct AllocatedFile {
136    name: String,
137    data_block: u32,  // Block where file data starts
138    data_length: u64, // File size in bytes
139    icb_block: u32,   // Block where File Entry lives
140    unique_id: u64,
141}
142
143#[derive(Debug)]
144struct AllocatedDir {
145    name: String,
146    icb_block: u32,        // Block where this dir's File Entry lives
147    fid_block: u32,        // Block where FIDs start
148    fid_bytes: usize,      // Unpadded information length of the FID stream
149    parent_icb_block: u32, // Parent directory's ICB block (self for root)
150    unique_id: u64,
151    files: Vec<AllocatedFile>,
152    subdirs: Vec<AllocatedDir>,
153}
154
155/// Options for UDF filesystem creation
156#[derive(Debug, Clone)]
157pub struct UdfWriteOptions {
158    /// Volume identifier (max 30 characters for dstring encoding)
159    pub volume_id: String,
160    /// UDF revision to write
161    pub revision: UdfRevision,
162    /// Partition starting sector (relative to volume start)
163    pub partition_start: u32,
164    /// Partition length in sectors
165    pub partition_length: u32,
166}
167
168/// Result of creating a complete UDF image.
169pub struct UdfCreateOutput<W> {
170    /// Recovered output target.
171    pub target: W,
172    /// Total number of sectors used by the image.
173    pub sectors_written: u32,
174}
175
176impl<W> UdfCreateOutput<W> {
177    /// Returns the output target, discarding creation metadata.
178    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, // After AVDP at 256
189            partition_length: 0,  // Will be calculated
190        }
191    }
192}
193
194/// A pre-allocated file extent for UDF
195#[derive(Debug, Clone, Copy)]
196pub struct UdfFileExtent {
197    /// Starting sector (logical block number within partition)
198    pub logical_block: u32,
199    /// Length in bytes
200    pub length: u64,
201}
202
203/// File entry information for writing
204#[derive(Debug, Clone)]
205pub struct UdfFileInfo {
206    /// File name
207    pub name: String,
208    /// Whether this is a directory
209    pub is_directory: bool,
210    /// File size in bytes
211    pub size: u64,
212    /// Pre-allocated extent (sector and length)
213    pub extent: UdfFileExtent,
214    /// Unique ID for this file
215    pub unique_id: u64,
216}
217
218/// Directory information for writing
219#[derive(Debug, Clone)]
220pub struct UdfDirInfo {
221    /// Directory name (empty for root)
222    pub name: String,
223    /// Files in this directory
224    pub files: Vec<UdfFileInfo>,
225    /// Subdirectories
226    pub subdirs: Vec<UdfDirInfo>,
227    /// ICB location for this directory (filled during write)
228    pub icb_location: u32,
229    /// Unique ID
230    pub unique_id: u64,
231}
232
233impl UdfDirInfo {
234    /// Create an empty root directory
235    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
246/// UDF Writer for creating UDF filesystem structures
247///
248/// This struct provides both a high-level API for standalone UDF images
249/// and low-level methods for integration with hybrid ISO+UDF writers.
250///
251/// ## High-Level API
252///
253/// Use [`UdfWriter::create`] for simple standalone UDF filesystems.
254///
255/// ## Low-Level API
256///
257/// For hybrid ISO+UDF images (like hadris-cd), use [`UdfWriter::new`] and
258/// the individual descriptor writing methods to control exact layout.
259pub struct UdfWriter<W: Write + Seek> {
260    writer: W,
261    options: UdfWriteOptions,
262    /// Current unique ID counter
263    unique_id_counter: u64,
264}
265
266impl<W: Write + Seek> UdfWriter<W> {
267    /// Create a new UDF writer for low-level descriptor writing
268    pub fn new(writer: W, options: UdfWriteOptions) -> Self {
269        Self {
270            writer,
271            options,
272            unique_id_counter: 16, // Start after reserved IDs
273        }
274    }
275
276    /// Get the underlying writer
277    pub fn into_inner(self) -> W {
278        self.writer
279    }
280
281    /// Creates a complete UDF filesystem and returns its target and size.
282    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
297/// Maximum `SimpleDir` nesting accepted by the formatter; deeper trees would
298/// overflow the stack in the recursive allocation and write passes.
299const MAX_DIRECTORY_DEPTH: usize = 128;
300
301/// Internal formatter that handles the full UDF format process
302struct 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, // UDF reserves IDs 0-15
316        }
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        // Phase 1: Plan the layout
337        //
338        // UDF disk layout:
339        // Sector 16-18:  VRS (BEA01, NSR02, TEA01)
340        // Sector 256:    AVDP
341        // Sector 257-272: Main VDS (six descriptors plus reserved extent)
342        // Sector 273-288: Reserve VDS
343        // Sector 289:     LVID
344        // Sector 290+:    Partition starts here
345        //   Block 0:     FSD
346        //   Block 1+:    Root dir File Entry, FIDs, subdirs, file data
347
348        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        // Phase 2: Allocate all structures within the partition
355        let fsd_block = self.allocate_block(); // 0
356        let allocated_root = self.allocate_directory(root, fsd_block, 0)?;
357
358        // Calculate partition length
359        let partition_length = self.next_block;
360
361        // Update options with calculated values
362        self.options.partition_start = partition_start;
363        self.options.partition_length = partition_length;
364
365        // Phase 3: Write all structures
366
367        // Write VRS
368        self.write_vrs()?;
369
370        // Write AVDP
371        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        // Write VDS
382        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        // Main VDS
394        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        // Reserve VDS (copy)
402        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        // Write LVID
410        self.write_lvid(lvid_location)?;
411
412        // Write FSD
413        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        // Write directory structures
422        self.write_directory(&allocated_root)?;
423
424        // Write file data
425        self.write_file_data(root, &allocated_root)?;
426
427        // Leave the partition before the two trailing anchor positions. The
428        // second anchor is 256 sectors before the final sector and must not
429        // overlap partition space.
430        let sector_count = partition_start + partition_length + 257;
431        let last_sector = sector_count - 1;
432        // UDF 1.02 records exactly two of the three candidate anchors. Use
433        // sector 256 and N-256, leaving sector N free of an anchor.
434        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    /// Allocate blocks for a directory and all its contents
442    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        // Every FID is 38 bytes plus the CS0 identifier, padded to four bytes.
455        // Plan from the actual encoded lengths so directory data cannot overlap
456        // the ICB or payload extent that follows it.
457        let mut fid_bytes = 40usize; // parent FID has an empty identifier
458        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        // Allocate additional FID sectors if needed
473        for _ in 1..fid_sectors {
474            self.allocate_block();
475        }
476
477        // Allocate files
478        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            // Allocate data blocks for non-empty files
484            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 // Empty file has no data block
493            };
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        // Recursively allocate subdirectories
505        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    /// Write a directory and all its contents
524    fn write_directory(&mut self, dir: &AllocatedDir) -> Result<()> {
525        // Calculate FID data size
526        // Write directory File Entry
527        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        // Build FID entries list
540        let mut entries: Vec<(String, LongAllocationDescriptor, bool)> = Vec::new();
541
542        // Add file entries
543        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        // Add subdirectory entries
554        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        // Write FIDs
565        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        // Write file File Entries and data
574        for (file, orig_file) in dir.files.iter().zip(
575            // We need to get the original file data - this is a bit awkward
576            // For now, we'll rely on the caller to ensure data is available
577            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            // Write file data (if any) - we need the original data here
597            // This is handled by passing it through the allocation
598            let _ = orig_file; // Placeholder - actual data writing happens below
599        }
600
601        // Recursively write subdirectories
602        for subdir in &dir.subdirs {
603            self.write_directory(subdir)?;
604        }
605
606        Ok(())
607    }
608
609    /// Write file data for all files in the tree
610    fn write_file_data(&mut self, dir: &SimpleDir, alloc_dir: &AllocatedDir) -> Result<()> {
611        // Write file data
612        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                // Pad to sector boundary
618                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        // Recursively write subdirectory file data
627        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    // Low-level descriptor writing methods (delegated to helper)
635    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()); // Closed
928
929        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        // UDF Logical Volume Integrity implementation use: file/dir counts,
945        // minimum read revision, minimum write revision, maximum write revision.
946        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        // Parent entry
1095        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        // Child entries
1104        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        // Pad to sector boundary
1116        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
1218// =============================================================================
1219// Low-Level UdfWriter Methods (for hadris-cd integration)
1220// =============================================================================
1221
1222impl<W: Write + Seek> UdfWriter<W> {
1223    /// Seek to a logical block within the partition
1224    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    /// Seek to an absolute sector
1232    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    /// Write Volume Recognition Sequence (VRS)
1239    ///
1240    /// Writes BEA01, NSR02/NSR03, TEA01 at sectors 16+
1241    pub fn write_vrs(&mut self) -> Result<()> {
1242        self.write_vrs_at(16)
1243    }
1244
1245    /// Write the Volume Recognition Sequence beginning at an explicit sector.
1246    ///
1247    /// Standalone UDF images use sector 16. Bridge writers can place the VRS
1248    /// after the ISO descriptor terminator to avoid overwriting either format.
1249    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        // NSR02/NSR03 at sector 17
1259        self.write_vrs_descriptor(nsr)?;
1260
1261        // TEA01 at sector 18
1262        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; // Structure type
1270        buffer[1..6].copy_from_slice(id);
1271        buffer[6] = 1; // Version
1272        self.writer.write_all(&buffer)?;
1273        Ok(())
1274    }
1275
1276    /// Write Anchor Volume Descriptor Pointer at sector 256
1277    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    /// Write an Anchor Volume Descriptor Pointer at an explicit sector.
1286    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        // Main VDS extent
1297        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        // Reserve VDS extent
1301        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        // Write tag at the beginning
1305        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    /// Write Primary Volume Descriptor
1317    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; // After tag
1322
1323        // VDS Number (4 bytes)
1324        buffer[offset..offset + 4].copy_from_slice(&vds_number.to_le_bytes());
1325        // PVD Number (4 bytes)
1326        buffer[offset + 4..offset + 8].copy_from_slice(&0u32.to_le_bytes());
1327
1328        // Volume Identifier (dstring, 32 bytes)
1329        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        // Volume Sequence Number
1336        let vsn_offset = vol_id_offset + 32;
1337        buffer[vsn_offset..vsn_offset + 2].copy_from_slice(&1u16.to_le_bytes());
1338        // Max Volume Sequence Number
1339        buffer[vsn_offset + 2..vsn_offset + 4].copy_from_slice(&1u16.to_le_bytes());
1340        // Interchange Level
1341        buffer[vsn_offset + 4..vsn_offset + 6].copy_from_slice(&2u16.to_le_bytes());
1342        // Max Interchange Level
1343        buffer[vsn_offset + 6..vsn_offset + 8].copy_from_slice(&3u16.to_le_bytes());
1344        // Character Set List
1345        buffer[vsn_offset + 8..vsn_offset + 12].copy_from_slice(&1u32.to_le_bytes());
1346        // Max Character Set List
1347        buffer[vsn_offset + 12..vsn_offset + 16].copy_from_slice(&1u32.to_le_bytes());
1348
1349        // Volume Set Identifier (dstring, 128 bytes)
1350        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        // Descriptor Character Set (64 bytes)
1357        let dcs_offset = vsi_offset + 128;
1358        write_osta_charspec(&mut buffer[dcs_offset..dcs_offset + 64]);
1359
1360        // Explanatory Character Set (64 bytes)
1361        let ecs_offset = dcs_offset + 64;
1362        write_osta_charspec(&mut buffer[ecs_offset..ecs_offset + 64]);
1363
1364        // Volume Abstract (8 bytes) - empty
1365        // Volume Copyright Notice (8 bytes) - empty
1366        let abs_offset = ecs_offset + 64;
1367
1368        // Application Identifier (32 bytes)
1369        let app_offset = abs_offset + 16;
1370        self.write_entity_identifier(&mut buffer[app_offset..app_offset + 32], b"*hadris-udf");
1371
1372        // Recording Date Time (12 bytes)
1373        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        // Implementation Identifier (32 bytes)
1378        let impl_offset = rdt_offset + 12;
1379        self.write_entity_identifier(&mut buffer[impl_offset..impl_offset + 32], b"*hadris-udf");
1380
1381        // Write tag
1382        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    /// Write Partition Descriptor
1394    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        // VDS Number
1401        buffer[offset..offset + 4].copy_from_slice(&vds_number.to_le_bytes());
1402        // Partition Flags (allocated = 1)
1403        buffer[offset + 4..offset + 6].copy_from_slice(&1u16.to_le_bytes());
1404        // Partition Number
1405        buffer[offset + 6..offset + 8].copy_from_slice(&0u16.to_le_bytes());
1406
1407        // Partition Contents (EntityIdentifier, 32 bytes)
1408        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        // Partition Contents Use (128 bytes) - empty for basic use
1416        // Access Type (4 bytes) - 1 = read-only
1417        let at_offset = pc_offset + 32 + 128;
1418        buffer[at_offset..at_offset + 4].copy_from_slice(&1u32.to_le_bytes());
1419
1420        // Partition Starting Location
1421        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        // Partition Length
1426        buffer[psl_offset + 4..psl_offset + 8]
1427            .copy_from_slice(&self.options.partition_length.to_le_bytes());
1428
1429        // Implementation Identifier (32 bytes)
1430        let impl_offset = psl_offset + 8;
1431        self.write_entity_identifier(&mut buffer[impl_offset..impl_offset + 32], b"*hadris-udf");
1432
1433        // Write tag
1434        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    /// Write Logical Volume Descriptor
1442    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        // VDS Number
1455        buffer[offset..offset + 4].copy_from_slice(&vds_number.to_le_bytes());
1456
1457        // Descriptor Character Set (64 bytes)
1458        let dcs_offset = offset + 4;
1459        write_osta_charspec(&mut buffer[dcs_offset..dcs_offset + 64]);
1460
1461        // Logical Volume Identifier (dstring, 128 bytes)
1462        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        // Logical Block Size (4 bytes)
1469        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        // Domain Identifier (32 bytes)
1473        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        // Set UDF revision in domain identifier suffix
1480        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        // Logical Volume Contents Use (16 bytes) - Long Allocation Descriptor to FSD
1484        let lvcu_offset = di_offset + 32;
1485        buffer[lvcu_offset..lvcu_offset + 16].copy_from_slice(bytemuck::bytes_of(&fsd_location));
1486
1487        // Map Table Length (4 bytes)
1488        let mtl_offset = lvcu_offset + 16;
1489        buffer[mtl_offset..mtl_offset + 4].copy_from_slice(&6u32.to_le_bytes()); // Type 1 map is 6 bytes
1490
1491        // Number of Partition Maps (4 bytes)
1492        buffer[mtl_offset + 4..mtl_offset + 8].copy_from_slice(&1u32.to_le_bytes());
1493
1494        // Implementation Identifier (32 bytes)
1495        let impl_offset = mtl_offset + 8;
1496        self.write_entity_identifier(&mut buffer[impl_offset..impl_offset + 32], b"*hadris-udf");
1497
1498        // Implementation Use (128 bytes) - skip
1499        let iu_offset = impl_offset + 32;
1500
1501        // Integrity Sequence Extent (8 bytes)
1502        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        // Partition Maps - Type 1 (6 bytes)
1508        let pm_offset = ise_offset + 8;
1509        buffer[pm_offset] = 1; // Type 1
1510        buffer[pm_offset + 1] = 6; // Length
1511        buffer[pm_offset + 2..pm_offset + 4].copy_from_slice(&1u16.to_le_bytes()); // Volume Sequence Number
1512        buffer[pm_offset + 4..pm_offset + 6].copy_from_slice(&0u16.to_le_bytes()); // Partition Number
1513
1514        // Write tag
1515        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    /// Write Unallocated Space Descriptor
1527    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        // VDS Number
1534        buffer[offset..offset + 4].copy_from_slice(&vds_number.to_le_bytes());
1535        // Number of Allocation Descriptors (0 for read-only)
1536        buffer[offset + 4..offset + 8].copy_from_slice(&0u32.to_le_bytes());
1537
1538        // Write tag
1539        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    /// Write Implementation Use Volume Descriptor
1551    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        // VDS Number
1558        buffer[offset..offset + 4].copy_from_slice(&vds_number.to_le_bytes());
1559
1560        // Implementation Identifier (32 bytes)
1561        let impl_offset = offset + 4;
1562        self.write_entity_identifier(&mut buffer[impl_offset..impl_offset + 32], b"*UDF LV Info");
1563
1564        // Implementation Use - LVInformation
1565        let iu_offset = impl_offset + 32;
1566        // LVI Character Set (64 bytes)
1567        write_osta_charspec(&mut buffer[iu_offset..iu_offset + 64]);
1568
1569        // Logical Volume Identifier (dstring, 128 bytes)
1570        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        // Write tag
1577        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    /// Write Terminating Descriptor
1589    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        // Write tag (content is empty for terminating descriptor)
1595        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    /// Write File Set Descriptor
1603    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        // Recording Date and Time (12 bytes)
1614        let now = UdfTimestamp::now();
1615        buffer[offset..offset + 12].copy_from_slice(bytemuck::bytes_of(&now));
1616
1617        // Interchange Level (2 bytes)
1618        buffer[offset + 12..offset + 14].copy_from_slice(&3u16.to_le_bytes());
1619        // Maximum Interchange Level (2 bytes)
1620        buffer[offset + 14..offset + 16].copy_from_slice(&3u16.to_le_bytes());
1621        // Character Set List (4 bytes)
1622        buffer[offset + 16..offset + 20].copy_from_slice(&1u32.to_le_bytes());
1623        // Maximum Character Set List (4 bytes)
1624        buffer[offset + 20..offset + 24].copy_from_slice(&1u32.to_le_bytes());
1625        // File Set Number (4 bytes)
1626        buffer[offset + 24..offset + 28].copy_from_slice(&0u32.to_le_bytes());
1627        // File Set Descriptor Number (4 bytes)
1628        buffer[offset + 28..offset + 32].copy_from_slice(&0u32.to_le_bytes());
1629
1630        // Logical Volume Identifier Character Set (64 bytes)
1631        let lvics_offset = offset + 32;
1632        write_osta_charspec(&mut buffer[lvics_offset..lvics_offset + 64]);
1633
1634        // Logical Volume Identifier (dstring, 128 bytes)
1635        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        // File Set Character Set (64 bytes)
1642        let fscs_offset = lvi_offset + 128;
1643        write_osta_charspec(&mut buffer[fscs_offset..fscs_offset + 64]);
1644
1645        // File Set Identifier (dstring, 32 bytes)
1646        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        // Copyright/Abstract File Identifiers (32 bytes each) - empty
1653        // Root Directory ICB (16 bytes)
1654        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        // Domain Identifier (32 bytes)
1658        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        // Write tag (location is relative to partition)
1667        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    /// Write a File Entry for a file or directory
1675    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; // After tag
1687
1688        // ICB Tag (20 bytes)
1689        let icb_offset = offset;
1690        // Prior Recorded Number of Direct Entries (4 bytes) - 0
1691        // Strategy Type (2 bytes) - 4 (sequential)
1692        buffer[icb_offset + 4..icb_offset + 6].copy_from_slice(&4u16.to_le_bytes());
1693        // Strategy Parameters (2 bytes) - 0
1694        // Maximum Number of Entries (2 bytes) - 1
1695        buffer[icb_offset + 8..icb_offset + 10].copy_from_slice(&1u16.to_le_bytes());
1696        // Reserved (1 byte)
1697        // File Type (1 byte)
1698        buffer[icb_offset + 11] = file_type as u8;
1699        // Parent ICB Location (6 bytes) - 0
1700        // Flags (2 bytes) - 0 = short allocation descriptors
1701        buffer[icb_offset + 18..icb_offset + 20].copy_from_slice(&0u16.to_le_bytes());
1702
1703        // UID (4 bytes) - 0xFFFFFFFF = not specified
1704        let uid_offset = icb_offset + 20;
1705        buffer[uid_offset..uid_offset + 4].copy_from_slice(&0xFFFFFFFFu32.to_le_bytes());
1706        // GID (4 bytes) - 0xFFFFFFFF = not specified
1707        buffer[uid_offset + 4..uid_offset + 8].copy_from_slice(&0xFFFFFFFFu32.to_le_bytes());
1708        // Permissions (4 bytes) - 0x7FFF = all permissions
1709        buffer[uid_offset + 8..uid_offset + 12].copy_from_slice(&0x7FFFu32.to_le_bytes());
1710        // File Link Count (2 bytes) - 1
1711        buffer[uid_offset + 12..uid_offset + 14].copy_from_slice(&1u16.to_le_bytes());
1712        // Record Format (1 byte) - 0
1713        // Record Display Attributes (1 byte) - 0
1714        // Record Length (4 bytes) - 0
1715
1716        // Information Length (8 bytes)
1717        let il_offset = uid_offset + 20;
1718        buffer[il_offset..il_offset + 8].copy_from_slice(&info_length.to_le_bytes());
1719
1720        // Logical Blocks Recorded (8 bytes)
1721        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        // Access/Modification/Attribute Times (12 bytes each)
1725        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        // Checkpoint (4 bytes) - 1
1732        let cp_offset = time_offset + 36;
1733        buffer[cp_offset..cp_offset + 4].copy_from_slice(&1u32.to_le_bytes());
1734
1735        // Extended Attribute ICB (16 bytes) - 0
1736        // Implementation Identifier (32 bytes)
1737        let impl_offset = cp_offset + 4 + 16;
1738        self.write_entity_identifier(&mut buffer[impl_offset..impl_offset + 32], b"*hadris-udf");
1739
1740        // Unique ID (8 bytes)
1741        let uid_offset2 = impl_offset + 32;
1742        buffer[uid_offset2..uid_offset2 + 8].copy_from_slice(&unique_id.to_le_bytes());
1743
1744        // Length of Extended Attributes (4 bytes) - 0
1745        let lea_offset = uid_offset2 + 8;
1746        buffer[lea_offset..lea_offset + 4].copy_from_slice(&0u32.to_le_bytes());
1747
1748        // Length of Allocation Descriptors (4 bytes)
1749        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        // Allocation Descriptors
1753        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        // Write tag
1763        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    /// Write File Identifier Descriptors for a directory
1776    pub fn write_fids(
1777        &mut self,
1778        location: u32,
1779        parent_icb: LongAllocationDescriptor,
1780        entries: &[(String, LongAllocationDescriptor, bool)], // (name, icb, is_dir)
1781    ) -> Result<usize> {
1782        self.seek_to_partition_block(location)?;
1783
1784        let mut buffer = Vec::new();
1785
1786        // Parent directory entry
1787        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        // Child entries
1796        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        // Pad to sector boundary
1808        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; // FID base size
1823        let total_size = (base_size + encoded_name.len() + 3) & !3; // Pad to 4 bytes
1824        let mut buffer = vec![0u8; total_size];
1825
1826        // File Version Number (2 bytes) - 1
1827        buffer[16..18].copy_from_slice(&1u16.to_le_bytes());
1828        // File Characteristics (1 byte)
1829        buffer[18] = characteristics.bits();
1830        // Length of File Identifier (1 byte)
1831        buffer[19] = encoded_name.len() as u8;
1832        // ICB (16 bytes)
1833        buffer[20..36].copy_from_slice(bytemuck::bytes_of(icb));
1834        // Length of Implementation Use (2 bytes) - 0
1835        buffer[36..38].copy_from_slice(&0u16.to_le_bytes());
1836        // File Identifier
1837        if !encoded_name.is_empty() {
1838            buffer[38..38 + encoded_name.len()].copy_from_slice(encoded_name);
1839        }
1840
1841        // Create and write tag
1842        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    /// Write Logical Volume Integrity Descriptor
1853    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        // Recording Date and Time (12 bytes)
1860        let now = UdfTimestamp::now();
1861        buffer[offset..offset + 12].copy_from_slice(bytemuck::bytes_of(&now));
1862
1863        // Integrity Type (4 bytes) - 0 = open, 1 = close
1864        let integrity = if close { 1u32 } else { 0u32 };
1865        buffer[offset + 12..offset + 16].copy_from_slice(&integrity.to_le_bytes());
1866
1867        // Next Integrity Extent (8 bytes) - 0 (none)
1868
1869        // Logical Volume Contents Use (32 bytes)
1870        let lvcu_offset = offset + 24;
1871        // Unique ID (8 bytes)
1872        buffer[lvcu_offset..lvcu_offset + 8].copy_from_slice(&self.unique_id_counter.to_le_bytes());
1873
1874        // Number of Partitions (4 bytes) - 1
1875        let np_offset = lvcu_offset + 32;
1876        buffer[np_offset..np_offset + 4].copy_from_slice(&1u32.to_le_bytes());
1877
1878        // Length of Implementation Use (4 bytes)
1879        buffer[np_offset + 4..np_offset + 8].copy_from_slice(&46u32.to_le_bytes());
1880
1881        // Free Space Table (4 bytes per partition)
1882        let fst_offset = np_offset + 8;
1883        buffer[fst_offset..fst_offset + 4].copy_from_slice(&0u32.to_le_bytes()); // No free space (read-only)
1884
1885        // Size Table (4 bytes per partition)
1886        buffer[fst_offset + 4..fst_offset + 8]
1887            .copy_from_slice(&self.options.partition_length.to_le_bytes());
1888
1889        // Implementation Use
1890        let iu_offset = fst_offset + 8;
1891        // Implementation ID (32 bytes)
1892        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        // Write tag
1899        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    /// Create a descriptor tag
1911    fn create_tag(&self, identifier: TagIdentifier, location: u32, data: &[u8]) -> DescriptorTag {
1912        let crc_length = data.len().min(496) as u16; // Max CRC length
1913        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        // Calculate tag checksum
1927        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    /// Write a dstring (OSTA Compressed Unicode)
1940    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; // Reserve 1 byte for compression ID, 1 for length
1946        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; // Length including compression ID
1962    }
1963
1964    /// Write an entity identifier
1965    fn write_entity_identifier(&self, buffer: &mut [u8], id: &[u8]) {
1966        // Flags (1 byte) - 0
1967        // Identifier (23 bytes)
1968        let len = id.len().min(23);
1969        buffer[1..1 + len].copy_from_slice(&id[..len]);
1970        // Suffix (8 bytes) - version info
1971        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    /// Encode a filename for UDF
1978    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
2024/// CRC-16-ITU (CCITT) used by UDF
2025fn 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    /// Create a timestamp for the current time (or default if no std)
2037    #[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        // Calculate date/time from Unix timestamp
2049        // This is a simplified calculation
2050        let days = (secs / 86400) as i64;
2051        let day_secs = (secs % 86400) as u32;
2052
2053        // Calculate year, month, day from days since 1970
2054        let (year, month, day) = days_to_ymd(days + 719468); // Days since year 0
2055
2056        Self {
2057            type_and_tz: 0x1000, // Local time, offset 0
2058            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/// Convert days since year 0 to (year, month, day)
2088#[cfg(feature = "std")]
2089fn days_to_ymd(days: i64) -> (i32, u32, u32) {
2090    // Algorithm from Howard Hinnant
2091    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        // Empty data should produce 0
2116        assert_eq!(crc16_itu(&[]), 0);
2117
2118        // The CRC algorithm used by UDF is a variant
2119        // Just verify consistency for now
2120        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]; // 2MB
2143        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]; // 2MB
2193        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        // Verify VRS is written at sector 16
2207        let bea01 = &buffer[16 * 2048..16 * 2048 + 6];
2208        assert_eq!(&bea01[1..6], b"BEA01", "VRS should start with BEA01");
2209
2210        // Verify AVDP at sector 256
2211        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]; // 4MB
2218        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]; // 2MB
2243        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        // Test UDF 1.02 uses NSR02
2258        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        // Test UDF 2.01 uses NSR03
2270        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        // Write → open → list → read_file roundtrip.
2310
2311        let mut buffer = vec![0u8; 4 * 1024 * 1024]; // 4MB
2312        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        // Structural sanity checks
2328        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        // Full reader roundtrip
2336        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]; // 8MB
2372        let cursor = Cursor::new(&mut buffer[..]);
2373
2374        let mut root = SimpleDir::root();
2375        // Create a file larger than one sector
2376        let large_data = vec![0x55; 10000]; // ~10KB, spans multiple sectors
2377        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        // Verify the data was written somewhere in the image
2384        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}