Skip to main content

hadris_iso/
susp.rs

1//! System Use Sharing Protocol
2
3#[cfg(feature = "std")]
4use super::io::{self, Read, ReadExt, Writable};
5
6use crate::types::U32LsbMsb;
7
8#[repr(C)]
9#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
10/// Represents SystemUseHeader.
11pub struct SystemUseHeader {
12    /// The `sig` field.
13    pub sig: [u8; 2],
14    /// The `length` field.
15    pub length: u8,
16    /// The `version` field.
17    pub version: u8,
18}
19
20/// SPEC: SUSP 5.1 Description of the CE System Use Field
21///
22/// If this field is specified, then the System Use area is specified in another sector
23#[repr(C)]
24#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
25pub struct ContinuationArea {
26    /// The Sector of the Continuation System Use area
27    pub sector: U32LsbMsb,
28    /// The offset within the sector for the start of the SU
29    pub offset: U32LsbMsb,
30    /// The `length` field.
31    pub length: U32LsbMsb,
32}
33
34#[cfg(feature = "std")]
35impl ContinuationArea {
36    /// Performs the `header` operation.
37    pub fn header(&self) -> SystemUseHeader {
38        SystemUseHeader {
39            sig: *b"CE",
40            length: 28,
41            version: 1,
42        }
43    }
44}
45
46#[derive(Debug, Clone, Copy)]
47/// Represents PaddingField.
48pub struct PaddingField {
49    /// The `length` field.
50    pub length: u8,
51}
52
53#[cfg(feature = "std")]
54impl PaddingField {
55    /// Performs the `header` operation.
56    pub fn header(&self) -> SystemUseHeader {
57        SystemUseHeader {
58            sig: *b"PD",
59            length: 4 + self.length,
60            version: 1,
61        }
62    }
63}
64
65#[repr(C)]
66#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
67/// Represents SuspIdentifier.
68pub struct SuspIdentifier {
69    /// The `check_bytes` field.
70    pub check_bytes: [u8; 2],
71    /// The `bytes_skipped` field.
72    pub bytes_skipped: u8,
73}
74
75impl SuspIdentifier {
76    /// Performs the `new` operation.
77    pub fn new(bytes_skipped: u8) -> Self {
78        Self {
79            check_bytes: [0xBE, 0xEF],
80            bytes_skipped,
81        }
82    }
83
84    /// Performs the `is_valid` operation.
85    pub fn is_valid(&self) -> bool {
86        self.check_bytes == [0xBE, 0xEF]
87    }
88}
89
90#[cfg(feature = "std")]
91impl SuspIdentifier {
92    /// Performs the `header` operation.
93    pub fn header(&self) -> SystemUseHeader {
94        SystemUseHeader {
95            sig: *b"SP",
96            length: 7,
97            version: 1,
98        }
99    }
100}
101
102#[derive(Debug, Clone, Copy)]
103/// Represents SuspTerminator.
104pub struct SuspTerminator;
105
106#[cfg(feature = "std")]
107impl SuspTerminator {
108    /// Performs the `header` operation.
109    pub fn header(&self) -> SystemUseHeader {
110        SystemUseHeader {
111            sig: *b"ST",
112            length: 4,
113            version: 1,
114        }
115    }
116}
117
118#[repr(C)]
119#[derive(Debug, Clone, Copy)]
120/// Represents ExtensionReference.
121pub struct ExtensionReference {
122    /// The `version` field.
123    pub version: u8,
124    /// The `identifier_len` field.
125    pub identifier_len: u8,
126    /// The `descriptor_len` field.
127    pub descriptor_len: u8,
128    /// The `source_len` field.
129    pub source_len: u8,
130    /// The `buf` field.
131    pub buf: [u8; 252],
132}
133
134#[cfg(feature = "std")]
135impl ExtensionReference {
136    /// Performs the `header` operation.
137    pub fn header(&self) -> SystemUseHeader {
138        SystemUseHeader {
139            sig: *b"ER",
140            length: 8 + self.identifier_len + self.descriptor_len + self.source_len,
141            version: 1,
142        }
143    }
144}
145
146io_transform! {
147#[cfg(feature = "std")]
148impl Writable for SystemUseHeader {
149    async fn write<R: io::Write>(&self, writer: &mut R) -> io::Result<()> {
150        writer.write_all(bytemuck::bytes_of(self)).await?;
151        Ok(())
152    }
153}
154
155#[cfg(feature = "std")]
156impl Writable for ContinuationArea {
157    async fn write<R: io::Write>(&self, writer: &mut R) -> io::Result<()> {
158        self.header().write(writer).await?;
159        writer.write_all(bytemuck::bytes_of(self)).await?;
160        Ok(())
161    }
162}
163
164#[cfg(feature = "std")]
165impl ContinuationArea {
166    /// Performs the `parse_data` operation.
167    pub async fn parse_data<R: Read>(header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
168        assert_eq!(&header.sig, b"CE");
169        data.read_struct().await
170    }
171}
172
173#[cfg(feature = "std")]
174impl Writable for PaddingField {
175    async fn write<R: io::Write>(&self, writer: &mut R) -> io::Result<()> {
176        self.header().write(writer).await?;
177        let zeros = alloc::vec![0u8; self.length as usize];
178        writer.write_all(&zeros).await?;
179        Ok(())
180    }
181}
182
183#[cfg(feature = "std")]
184impl PaddingField {
185    /// Performs the `parse_data` operation.
186    pub async fn parse_data<R: Read>(header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
187        let len = header.length - 4;
188        let mut buf = [0];
189        for _ in 0..len {
190            data.read_exact(&mut buf).await?;
191        }
192        Ok(Self { length: len })
193    }
194}
195
196#[cfg(feature = "std")]
197impl Writable for SuspIdentifier {
198    async fn write<R: io::Write>(&self, writer: &mut R) -> io::Result<()> {
199        self.header().write(writer).await?;
200        writer.write_all(bytemuck::bytes_of(self)).await?;
201        Ok(())
202    }
203}
204
205#[cfg(feature = "std")]
206impl SuspIdentifier {
207    /// Performs the `parse_data` operation.
208    pub async fn parse_data<R: Read>(header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
209        assert_eq!(&header.sig, b"SP");
210        data.read_struct().await
211    }
212}
213
214#[cfg(feature = "std")]
215impl Writable for SuspTerminator {
216    async fn write<R: io::Write>(&self, writer: &mut R) -> io::Result<()> {
217        self.header().write(writer).await
218    }
219}
220
221#[cfg(feature = "std")]
222impl SuspTerminator {
223    /// Performs the `parse_data` operation.
224    pub async fn parse_data<R: Read>(_header: SystemUseHeader, _data: &mut R) -> io::Result<Self> {
225        Ok(Self)
226    }
227}
228
229#[cfg(feature = "std")]
230impl Writable for ExtensionReference {
231    async fn write<R: io::Write>(&self, writer: &mut R) -> io::Result<()> {
232        self.header().write(writer).await?;
233        let meta = [
234            self.identifier_len,
235            self.descriptor_len,
236            self.source_len,
237            self.version,
238        ];
239        writer.write_all(&meta).await?;
240        let total_len = self.identifier_len + self.descriptor_len + self.source_len;
241        writer.write_all(&self.buf[0..total_len as usize]).await?;
242        Ok(())
243    }
244}
245
246#[cfg(feature = "std")]
247impl ExtensionReference {
248    /// Performs the `parse_data` operation.
249    pub async fn parse_data<R: Read>(header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
250        assert_eq!(&header.sig, b"ER");
251        let mut buf = [0u8; 252];
252        let meta: [u8; 4] = data.read_struct().await?;
253        data.read_exact(&mut buf[0..header.length as usize - size_of::<SystemUseHeader>()]).await?;
254        Ok(Self {
255            identifier_len: meta[0],
256            descriptor_len: meta[1],
257            source_len: meta[2],
258            version: meta[3],
259            buf,
260        })
261    }
262}
263} // io_transform!
264
265/// A parsed system use field
266#[derive(Debug, Clone)]
267pub enum SystemUseField {
268    /// SP - SUSP Identifier entry
269    SuspIdentifier(SuspIdentifier),
270    /// ST - Terminator entry
271    Terminator,
272    /// CE - Continuation Area entry
273    ContinuationArea(ContinuationArea),
274    /// PD - Padding entry
275    Padding(PaddingField),
276    /// ER - Extension Reference entry
277    ExtensionReference(ExtensionReference),
278
279    // --- RRIP variants ---
280    /// PX - POSIX file attributes
281    PosixAttributes(super::rrip::PxEntry),
282    /// PN - POSIX device numbers
283    DeviceNumber(super::rrip::PnEntry),
284    /// NM - Alternate name (real filename)
285    AlternateName(super::rrip::NmEntry),
286    /// SL - Symbolic link
287    SymbolicLink(super::rrip::SlEntry),
288    /// TF - Timestamps
289    Timestamps(super::rrip::TfEntry),
290    /// CL - Child link (relocated directory)
291    ChildLink(super::rrip::ClEntry),
292    /// PL - Parent link (relocated directory)
293    ParentLink(super::rrip::PlEntry),
294    /// RE - Relocated directory marker
295    Relocated,
296
297    /// ES - Extension Selector
298    ExtensionSelector {
299        /// Sequence number of the selected extension.
300        extension_sequence: u8,
301    },
302
303    /// Unknown entry (not recognized)
304    Unknown(SystemUseHeader, [u8; 252]),
305}
306
307impl SystemUseField {
308    /// Returns the 2-byte signature for this field.
309    pub fn signature(&self) -> [u8; 2] {
310        match self {
311            Self::SuspIdentifier(_) => *b"SP",
312            Self::Terminator => *b"ST",
313            Self::ContinuationArea(_) => *b"CE",
314            Self::Padding(_) => *b"PD",
315            Self::ExtensionReference(_) => *b"ER",
316            Self::PosixAttributes(_) => *b"PX",
317            Self::DeviceNumber(_) => *b"PN",
318            Self::AlternateName(_) => *b"NM",
319            Self::SymbolicLink(_) => *b"SL",
320            Self::Timestamps(_) => *b"TF",
321            Self::ChildLink(_) => *b"CL",
322            Self::ParentLink(_) => *b"PL",
323            Self::Relocated => *b"RE",
324            Self::ExtensionSelector { .. } => *b"ES",
325            Self::Unknown(header, _) => header.sig,
326        }
327    }
328
329    /// Returns the PX entry if this is a `PosixAttributes` variant.
330    pub fn as_posix_attributes(&self) -> Option<&super::rrip::PxEntry> {
331        match self {
332            Self::PosixAttributes(px) => Some(px),
333            _ => None,
334        }
335    }
336
337    /// Returns the NM entry if this is an `AlternateName` variant.
338    pub fn as_alternate_name(&self) -> Option<&super::rrip::NmEntry> {
339        match self {
340            Self::AlternateName(nm) => Some(nm),
341            _ => None,
342        }
343    }
344
345    /// Returns the TF entry if this is a `Timestamps` variant.
346    pub fn as_timestamps(&self) -> Option<&super::rrip::TfEntry> {
347        match self {
348            Self::Timestamps(tf) => Some(tf),
349            _ => None,
350        }
351    }
352
353    /// Returns the SL entry if this is a `SymbolicLink` variant.
354    pub fn as_symbolic_link(&self) -> Option<&super::rrip::SlEntry> {
355        match self {
356            Self::SymbolicLink(sl) => Some(sl),
357            _ => None,
358        }
359    }
360}
361
362/// Iterator over system use entries in a byte slice
363pub struct SystemUseIter<'a> {
364    data: &'a [u8],
365    offset: usize,
366}
367
368impl<'a> SystemUseIter<'a> {
369    /// Create a new iterator over system use entries
370    ///
371    /// # Arguments
372    /// * `data` - The system use area bytes
373    /// * `bytes_to_skip` - Number of bytes to skip at the start (from SP entry)
374    pub fn new(data: &'a [u8], bytes_to_skip: usize) -> Self {
375        Self {
376            data,
377            offset: bytes_to_skip,
378        }
379    }
380}
381
382impl Iterator for SystemUseIter<'_> {
383    type Item = SystemUseField;
384
385    fn next(&mut self) -> Option<Self::Item> {
386        // Need at least 4 bytes for the header
387        if self.offset + 4 > self.data.len() {
388            return None;
389        }
390
391        let header_bytes = &self.data[self.offset..self.offset + 4];
392        let header: SystemUseHeader = *bytemuck::from_bytes(header_bytes);
393
394        // Zero length means end of entries
395        if header.length == 0 {
396            return None;
397        }
398
399        // A SUSP entry is at least its 4-byte header (BP1-2 signature, BP3
400        // length, BP4 version). A nonzero length below that is malformed and
401        // would make the `entry_data` slice below have start > end (panic) —
402        // treat it as the end of entries.
403        if (header.length as usize) < 4 {
404            return None;
405        }
406
407        // Validate we have enough data
408        let entry_end = self.offset + header.length as usize;
409        if entry_end > self.data.len() {
410            return None;
411        }
412
413        let entry_data = &self.data[self.offset + 4..entry_end];
414        self.offset = entry_end;
415
416        let field = match &header.sig {
417            b"SP" if entry_data.len() >= 3 => {
418                let sp: SuspIdentifier = *bytemuck::from_bytes(&entry_data[0..3]);
419                SystemUseField::SuspIdentifier(sp)
420            }
421            b"ST" => SystemUseField::Terminator,
422            b"CE" if entry_data.len() >= 24 => {
423                let ce: ContinuationArea = *bytemuck::from_bytes(&entry_data[0..24]);
424                SystemUseField::ContinuationArea(ce)
425            }
426            b"PD" => {
427                let len = header.length.saturating_sub(4);
428                SystemUseField::Padding(PaddingField { length: len })
429            }
430            b"ER" if entry_data.len() >= 4 => {
431                let mut buf = [0u8; 252];
432                let copy_len = entry_data.len().min(252);
433                buf[..copy_len].copy_from_slice(&entry_data[..copy_len]);
434                SystemUseField::ExtensionReference(ExtensionReference {
435                    identifier_len: entry_data[0],
436                    descriptor_len: entry_data[1],
437                    source_len: entry_data[2],
438                    version: entry_data[3],
439                    buf,
440                })
441            }
442            // --- RRIP entries ---
443            b"PX" if entry_data.len() >= 32 => {
444                let len = entry_data.len().min(40);
445                let px = super::rrip::PxEntry {
446                    file_mode: *bytemuck::from_bytes(&entry_data[0..8]),
447                    file_links: *bytemuck::from_bytes(&entry_data[8..16]),
448                    file_uid: *bytemuck::from_bytes(&entry_data[16..24]),
449                    file_gid: *bytemuck::from_bytes(&entry_data[24..32]),
450                    file_serial: if len >= 40 {
451                        *bytemuck::from_bytes(&entry_data[32..40])
452                    } else {
453                        crate::types::U32LsbMsb::new(0)
454                    },
455                };
456                SystemUseField::PosixAttributes(px)
457            }
458            b"PN" if entry_data.len() >= 16 => {
459                let pn = super::rrip::PnEntry {
460                    dev_high: *bytemuck::from_bytes(&entry_data[0..8]),
461                    dev_low: *bytemuck::from_bytes(&entry_data[8..16]),
462                };
463                SystemUseField::DeviceNumber(pn)
464            }
465            b"NM" if !entry_data.is_empty() => {
466                let flags = super::rrip::NmFlags::from_bits_truncate(entry_data[0]);
467                let name = entry_data[1..].to_vec();
468                SystemUseField::AlternateName(super::rrip::NmEntry { flags, name })
469            }
470            b"SL" if !entry_data.is_empty() => {
471                let flags = entry_data[0];
472                let mut components = alloc::vec::Vec::new();
473                let mut pos = 1;
474                while pos + 2 <= entry_data.len() {
475                    let comp_flags =
476                        super::rrip::SlComponentFlags::from_bits_truncate(entry_data[pos]);
477                    let comp_len = entry_data[pos + 1] as usize;
478                    pos += 2;
479                    let end = (pos + comp_len).min(entry_data.len());
480                    let content = entry_data[pos..end].to_vec();
481                    pos = end;
482                    components.push(super::rrip::SlComponent {
483                        flags: comp_flags,
484                        content,
485                    });
486                }
487                SystemUseField::SymbolicLink(super::rrip::SlEntry { flags, components })
488            }
489            b"TF" if !entry_data.is_empty() => {
490                let flags = super::rrip::TfFlags::from_bits_truncate(entry_data[0]);
491                let timestamps = entry_data[1..].to_vec();
492                SystemUseField::Timestamps(super::rrip::TfEntry { flags, timestamps })
493            }
494            b"CL" if entry_data.len() >= 8 => {
495                let cl = super::rrip::ClEntry {
496                    child_directory_location: *bytemuck::from_bytes(&entry_data[0..8]),
497                };
498                SystemUseField::ChildLink(cl)
499            }
500            b"PL" if entry_data.len() >= 8 => {
501                let pl = super::rrip::PlEntry {
502                    parent_directory_location: *bytemuck::from_bytes(&entry_data[0..8]),
503                };
504                SystemUseField::ParentLink(pl)
505            }
506            b"RE" => SystemUseField::Relocated,
507            b"ES" if !entry_data.is_empty() => SystemUseField::ExtensionSelector {
508                extension_sequence: entry_data[0],
509            },
510            _ => {
511                let mut buf = [0u8; 252];
512                let copy_len = entry_data.len().min(252);
513                buf[..copy_len].copy_from_slice(&entry_data[..copy_len]);
514                SystemUseField::Unknown(header, buf)
515            }
516        };
517
518        Some(field)
519    }
520}
521
522/// Builder for constructing system use areas
523#[cfg(feature = "alloc")]
524pub struct SystemUseBuilder {
525    entries: alloc::vec::Vec<alloc::vec::Vec<u8>>,
526}
527
528#[cfg(feature = "alloc")]
529impl SystemUseBuilder {
530    /// Create a new empty builder
531    pub fn new() -> Self {
532        Self {
533            entries: alloc::vec::Vec::new(),
534        }
535    }
536
537    /// Add the SP (SUSP Identifier) entry
538    ///
539    /// This should be the first entry in the root directory's system use area
540    pub fn add_sp(&mut self, bytes_skipped: u8) -> &mut Self {
541        let sp = SuspIdentifier::new(bytes_skipped);
542        let mut buf = alloc::vec![0u8; 7];
543        buf[0..2].copy_from_slice(b"SP");
544        buf[2] = 7; // length
545        buf[3] = 1; // version
546        buf[4..7].copy_from_slice(bytemuck::bytes_of(&sp));
547        self.entries.push(buf);
548        self
549    }
550
551    /// Add the ST (Terminator) entry
552    pub fn add_st(&mut self) -> &mut Self {
553        let buf = alloc::vec![b'S', b'T', 4, 1];
554        self.entries.push(buf);
555        self
556    }
557
558    /// Add a CE (Continuation Area) entry
559    pub fn add_ce(&mut self, ce: ContinuationArea) -> &mut Self {
560        let mut buf = alloc::vec![0u8; 28];
561        buf[0..2].copy_from_slice(b"CE");
562        buf[2] = 28;
563        buf[3] = 1;
564        buf[4..28].copy_from_slice(bytemuck::bytes_of(&ce));
565        self.entries.push(buf);
566        self
567    }
568
569    /// Add padding to align the system use area
570    pub fn add_padding(&mut self, length: u8) -> &mut Self {
571        let mut buf = alloc::vec![0u8; 4 + length as usize];
572        buf[0..2].copy_from_slice(b"PD");
573        buf[2] = 4 + length;
574        buf[3] = 1;
575        self.entries.push(buf);
576        self
577    }
578
579    /// Add an ER (Extension Reference) entry
580    pub fn add_er(
581        &mut self,
582        identifier: &str,
583        descriptor: &str,
584        source: &str,
585        version: u8,
586    ) -> &mut Self {
587        let id_bytes = identifier.as_bytes();
588        let desc_bytes = descriptor.as_bytes();
589        let src_bytes = source.as_bytes();
590
591        let len = 8 + id_bytes.len() + desc_bytes.len() + src_bytes.len();
592        let mut buf = alloc::vec![0u8; len];
593        buf[0..2].copy_from_slice(b"ER");
594        buf[2] = len as u8;
595        buf[3] = 1;
596        buf[4] = id_bytes.len() as u8;
597        buf[5] = desc_bytes.len() as u8;
598        buf[6] = src_bytes.len() as u8;
599        buf[7] = version;
600
601        let mut offset = 8;
602        buf[offset..offset + id_bytes.len()].copy_from_slice(id_bytes);
603        offset += id_bytes.len();
604        buf[offset..offset + desc_bytes.len()].copy_from_slice(desc_bytes);
605        offset += desc_bytes.len();
606        buf[offset..offset + src_bytes.len()].copy_from_slice(src_bytes);
607
608        self.entries.push(buf);
609        self
610    }
611
612    /// Add raw bytes for a custom entry
613    pub fn add_raw(&mut self, data: alloc::vec::Vec<u8>) -> &mut Self {
614        self.entries.push(data);
615        self
616    }
617
618    /// Calculate the total size of the system use area
619    pub fn size(&self) -> usize {
620        self.entries.iter().map(|e| e.len()).sum()
621    }
622
623    /// Build the system use area as a byte vector
624    pub fn build(&self) -> alloc::vec::Vec<u8> {
625        let total_size = self.size();
626        let mut result = alloc::vec::Vec::with_capacity(total_size);
627        for entry in &self.entries {
628            result.extend_from_slice(entry);
629        }
630        result
631    }
632
633    /// Check if the builder is empty
634    pub fn is_empty(&self) -> bool {
635        self.entries.is_empty()
636    }
637
638    /// Split entries across inline and overflow areas.
639    ///
640    /// Walks entries in order, keeping as many complete entries inline as
641    /// possible. Remaining entries go to a continuation area, and a CE
642    /// pointer is appended to the inline portion.
643    pub fn build_split(&self, max_inline: usize) -> SplitSu {
644        let total = self.size();
645        if total <= max_inline {
646            return SplitSu {
647                inline: self.build(),
648                overflow: alloc::vec::Vec::new(),
649                ce_offset: None,
650            };
651        }
652
653        const CE_SIZE: usize = 28;
654        let budget = max_inline.saturating_sub(CE_SIZE);
655
656        // Find split point: largest prefix of entries fitting in budget
657        let mut accumulated = 0;
658        let mut split_at = 0;
659        for (i, entry) in self.entries.iter().enumerate() {
660            if accumulated + entry.len() > budget {
661                split_at = i;
662                break;
663            }
664            accumulated += entry.len();
665            split_at = i + 1;
666        }
667
668        // Build inline = entries[..split_at] + CE placeholder
669        let mut inline = alloc::vec::Vec::with_capacity(accumulated + CE_SIZE);
670        for entry in &self.entries[..split_at] {
671            inline.extend_from_slice(entry);
672        }
673        let ce_offset = inline.len();
674        // CE placeholder: header (sig+len+ver) + 24 zeroed bytes for sector/offset/length
675        let mut ce_buf = alloc::vec![0u8; CE_SIZE];
676        ce_buf[0..2].copy_from_slice(b"CE");
677        ce_buf[2] = CE_SIZE as u8;
678        ce_buf[3] = 1;
679        inline.extend_from_slice(&ce_buf);
680
681        // Build overflow = entries[split_at..]
682        let overflow_size: usize = self.entries[split_at..].iter().map(|e| e.len()).sum();
683        let mut overflow = alloc::vec::Vec::with_capacity(overflow_size);
684        for entry in &self.entries[split_at..] {
685            overflow.extend_from_slice(entry);
686        }
687
688        SplitSu {
689            inline,
690            overflow,
691            ce_offset: Some(ce_offset),
692        }
693    }
694}
695
696/// System Use data (RRIP) split into inline and overflow parts.
697///
698/// RRIP (Rock Ridge) extensions store additional metadata (permissions, symlinks,
699/// device nodes, etc.) in the System Use area of directory records. However, the
700/// System Use area is limited to the remaining space in the directory record.
701///
702/// When the RRIP data exceeds the available inline space, the excess is written
703/// to a separate continuation area, and a `CE` (Continuation Area) entry is
704/// placed in the inline part to point to it.
705#[cfg(feature = "alloc")]
706#[derive(Debug, Clone)]
707pub struct SplitSu {
708    /// Bytes that fit inline in the directory record's SU area.
709    pub inline: alloc::vec::Vec<u8>,
710    /// Bytes that go in the continuation area (may be empty).
711    pub overflow: alloc::vec::Vec<u8>,
712    /// Byte offset of the CE entry within `inline`, for patching.
713    ce_offset: Option<usize>,
714}
715
716#[cfg(feature = "alloc")]
717impl SplitSu {
718    /// Create an empty SplitSu with no inline or overflow data.
719    pub fn empty() -> Self {
720        Self {
721            inline: alloc::vec::Vec::new(),
722            overflow: alloc::vec::Vec::new(),
723            ce_offset: None,
724        }
725    }
726
727    /// Returns true if there is overflow data requiring a continuation area.
728    pub fn has_overflow(&self) -> bool {
729        !self.overflow.is_empty()
730    }
731
732    /// Patch the CE entry with the actual continuation area location.
733    pub fn patch_ce(&mut self, sector: u32, byte_offset: u32) {
734        if let Some(off) = self.ce_offset {
735            let data_start = off + 4; // skip header (sig+len+ver)
736            let length = self.overflow.len() as u32;
737            // Write sector as U32LsbMsb (4 LE + 4 BE = 8 bytes)
738            self.inline[data_start..data_start + 4].copy_from_slice(&sector.to_le_bytes());
739            self.inline[data_start + 4..data_start + 8].copy_from_slice(&sector.to_be_bytes());
740            // Write offset as U32LsbMsb
741            self.inline[data_start + 8..data_start + 12]
742                .copy_from_slice(&byte_offset.to_le_bytes());
743            self.inline[data_start + 12..data_start + 16]
744                .copy_from_slice(&byte_offset.to_be_bytes());
745            // Write length as U32LsbMsb
746            self.inline[data_start + 16..data_start + 20].copy_from_slice(&length.to_le_bytes());
747            self.inline[data_start + 20..data_start + 24].copy_from_slice(&length.to_be_bytes());
748        }
749    }
750}
751
752#[cfg(feature = "alloc")]
753impl Default for SystemUseBuilder {
754    fn default() -> Self {
755        Self::new()
756    }
757}
758
759sync_only! {
760#[cfg(all(feature = "std", test))]
761mod tests {
762    use super::*;
763    use alloc::vec;
764    use alloc::vec::Vec;
765
766    static_assertions::const_assert_eq!(size_of::<SystemUseHeader>(), 4);
767    static_assertions::const_assert_eq!(size_of::<ContinuationArea>(), 24);
768    static_assertions::const_assert_eq!(size_of::<SuspIdentifier>(), 3);
769
770    #[test]
771    fn short_entry_length_does_not_panic() {
772        // A nonzero length below the 4-byte header size used to make the
773        // entry_data slice have start (offset+4) > end (offset+length) and
774        // panic. The iterator must terminate cleanly instead. (fuzz regression)
775        for len in 1u8..=3 {
776            let data = [b'A', b'A', len, 1];
777            let mut iter = SystemUseIter::new(&data, 0);
778            assert!(iter.next().is_none(), "length {len} should terminate");
779        }
780    }
781
782    #[test]
783    fn test_susp_identifier_valid() {
784        let sp = SuspIdentifier::new(0);
785        assert!(sp.is_valid());
786        assert_eq!(sp.check_bytes, [0xBE, 0xEF]);
787        assert_eq!(sp.bytes_skipped, 0);
788    }
789
790    #[test]
791    fn test_susp_identifier_invalid() {
792        let sp = SuspIdentifier {
793            check_bytes: [0x00, 0x00],
794            bytes_skipped: 0,
795        };
796        assert!(!sp.is_valid());
797    }
798
799    #[test]
800    fn test_system_use_iter_empty() {
801        let data: &[u8] = &[];
802        let mut iter = SystemUseIter::new(data, 0);
803        assert!(iter.next().is_none());
804    }
805
806    #[test]
807    fn test_system_use_iter_sp_entry() {
808        // SP entry: sig='SP', length=7, version=1, check_bytes=[0xBE, 0xEF], bytes_skipped=0
809        let data: &[u8] = &[
810            b'S', b'P', 7, 1, // header
811            0xBE, 0xEF, 0, // data
812        ];
813        let mut iter = SystemUseIter::new(data, 0);
814        match iter.next() {
815            Some(SystemUseField::SuspIdentifier(sp)) => {
816                assert!(sp.is_valid());
817                assert_eq!(sp.bytes_skipped, 0);
818            }
819            other => panic!("expected SP entry, got {other:?}"),
820        }
821        assert!(iter.next().is_none());
822    }
823
824    #[test]
825    fn test_system_use_iter_st_entry() {
826        // ST entry: sig='ST', length=4, version=1
827        let data: &[u8] = &[b'S', b'T', 4, 1];
828        let mut iter = SystemUseIter::new(data, 0);
829        match iter.next() {
830            Some(SystemUseField::Terminator) => {}
831            other => panic!("expected ST entry, got {other:?}"),
832        }
833        assert!(iter.next().is_none());
834    }
835
836    #[test]
837    fn test_system_use_iter_ce_entry() {
838        // CE entry: sig='CE', length=28, version=1, then 24 bytes of data
839        let mut data = vec![b'C', b'E', 28, 1];
840        // sector (8 bytes), offset (8 bytes), length (8 bytes)
841        data.extend_from_slice(&[10, 0, 0, 0, 10, 0, 0, 0]); // sector = 10
842        data.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0]); // offset = 0
843        data.extend_from_slice(&[100, 0, 0, 0, 100, 0, 0, 0]); // length = 100
844
845        let mut iter = SystemUseIter::new(&data, 0);
846        match iter.next() {
847            Some(SystemUseField::ContinuationArea(ce)) => {
848                assert_eq!(ce.sector.read(), 10);
849                assert_eq!(ce.offset.read(), 0);
850                assert_eq!(ce.length.read(), 100);
851            }
852            other => panic!("expected CE entry, got {other:?}"),
853        }
854        assert!(iter.next().is_none());
855    }
856
857    #[test]
858    fn test_system_use_iter_pd_entry() {
859        // PD entry: sig='PD', length=8, version=1, then 4 bytes of padding
860        let data: &[u8] = &[b'P', b'D', 8, 1, 0, 0, 0, 0];
861        let mut iter = SystemUseIter::new(data, 0);
862        match iter.next() {
863            Some(SystemUseField::Padding(pd)) => {
864                assert_eq!(pd.length, 4);
865            }
866            other => panic!("expected PD entry, got {other:?}"),
867        }
868        assert!(iter.next().is_none());
869    }
870
871    #[test]
872    fn test_system_use_iter_unknown_entry() {
873        // Unknown entry: sig='XX', length=6, version=1, data=[0x42, 0x43]
874        let data: &[u8] = &[b'X', b'X', 6, 1, 0x42, 0x43];
875        let mut iter = SystemUseIter::new(data, 0);
876        match iter.next() {
877            Some(SystemUseField::Unknown(header, buf)) => {
878                assert_eq!(&header.sig, b"XX");
879                assert_eq!(header.length, 6);
880                assert_eq!(&buf[0..2], &[0x42, 0x43]);
881            }
882            other => panic!("expected Unknown entry, got {other:?}"),
883        }
884        assert!(iter.next().is_none());
885    }
886
887    #[test]
888    fn test_system_use_iter_multiple_entries() {
889        // SP followed by ST
890        let data: &[u8] = &[
891            b'S', b'P', 7, 1, 0xBE, 0xEF, 0, // SP
892            b'S', b'T', 4, 1, // ST
893        ];
894        let mut iter = SystemUseIter::new(data, 0);
895        assert!(matches!(
896            iter.next(),
897            Some(SystemUseField::SuspIdentifier(_))
898        ));
899        assert!(matches!(iter.next(), Some(SystemUseField::Terminator)));
900        assert!(iter.next().is_none());
901    }
902
903    #[test]
904    fn test_system_use_iter_with_skip() {
905        // First 3 bytes should be skipped
906        let data: &[u8] = &[
907            0xFF, 0xFF, 0xFF, // skip these
908            b'S', b'T', 4, 1, // ST
909        ];
910        let mut iter = SystemUseIter::new(data, 3);
911        assert!(matches!(iter.next(), Some(SystemUseField::Terminator)));
912        assert!(iter.next().is_none());
913    }
914
915    #[test]
916    fn test_system_use_iter_zero_length_terminates() {
917        let data: &[u8] = &[0, 0, 0, 0]; // zero-length header
918        let mut iter = SystemUseIter::new(data, 0);
919        assert!(iter.next().is_none());
920    }
921
922    #[test]
923    fn test_system_use_builder_empty() {
924        let builder = SystemUseBuilder::new();
925        assert!(builder.is_empty());
926        assert_eq!(builder.size(), 0);
927        assert_eq!(builder.build(), Vec::<u8>::new());
928    }
929
930    #[test]
931    fn test_system_use_builder_sp() {
932        let mut builder = SystemUseBuilder::new();
933        builder.add_sp(0);
934        assert!(!builder.is_empty());
935        assert_eq!(builder.size(), 7);
936
937        let data = builder.build();
938        assert_eq!(&data[0..2], b"SP");
939        assert_eq!(data[2], 7); // length
940        assert_eq!(data[3], 1); // version
941        assert_eq!(&data[4..6], &[0xBE, 0xEF]); // check bytes
942        assert_eq!(data[6], 0); // bytes_skipped
943    }
944
945    #[test]
946    fn test_system_use_builder_st() {
947        let mut builder = SystemUseBuilder::new();
948        builder.add_st();
949        assert_eq!(builder.size(), 4);
950
951        let data = builder.build();
952        assert_eq!(&data, &[b'S', b'T', 4, 1]);
953    }
954
955    #[test]
956    fn test_system_use_builder_padding() {
957        let mut builder = SystemUseBuilder::new();
958        builder.add_padding(4);
959        assert_eq!(builder.size(), 8);
960
961        let data = builder.build();
962        assert_eq!(&data[0..2], b"PD");
963        assert_eq!(data[2], 8); // length = 4 + padding
964        assert_eq!(data[3], 1); // version
965    }
966
967    #[test]
968    fn test_system_use_builder_er() {
969        let mut builder = SystemUseBuilder::new();
970        builder.add_er("RRIP", "Rock Ridge", "TEST", 1);
971
972        let data = builder.build();
973        assert_eq!(&data[0..2], b"ER");
974        let len = data[2] as usize;
975        assert_eq!(len, 8 + 4 + 10 + 4); // header(8) + id(4) + desc(10) + src(4)
976        assert_eq!(data[3], 1); // version
977        assert_eq!(data[4], 4); // id_len
978        assert_eq!(data[5], 10); // desc_len
979        assert_eq!(data[6], 4); // src_len
980        assert_eq!(data[7], 1); // ext_version
981
982        let id = &data[8..12];
983        assert_eq!(id, b"RRIP");
984    }
985
986    #[test]
987    fn test_system_use_builder_roundtrip() {
988        let mut builder = SystemUseBuilder::new();
989        builder.add_sp(0).add_st();
990
991        let data = builder.build();
992        let mut iter = SystemUseIter::new(&data, 0);
993
994        assert!(matches!(
995            iter.next(),
996            Some(SystemUseField::SuspIdentifier(_))
997        ));
998        assert!(matches!(iter.next(), Some(SystemUseField::Terminator)));
999        assert!(iter.next().is_none());
1000    }
1001
1002    #[test]
1003    fn test_build_split_no_overflow() {
1004        // SP(7) + ST(4) = 11 bytes, fits in 100 bytes inline
1005        let mut builder = SystemUseBuilder::new();
1006        builder.add_sp(0).add_st();
1007        let split = builder.build_split(100);
1008        assert!(!split.has_overflow());
1009        assert!(split.overflow.is_empty());
1010        assert_eq!(split.inline, builder.build());
1011    }
1012
1013    #[test]
1014    fn test_build_split_with_overflow() {
1015        // SP(7) + a large ER entry. SP should stay inline, ER should overflow.
1016        let mut builder = SystemUseBuilder::new();
1017        builder.add_sp(0);
1018        builder.add_er(
1019            "RRIP_1991A",
1020            "THE ROCK RIDGE INTERCHANGE PROTOCOL PROVIDES SUPPORT FOR POSIX FILE SYSTEM SEMANTICS",
1021            "PLEASE CONTACT DISC PUBLISHER FOR SPECIFICATION SOURCE. SEE PUBLISHER IDENTIFIER IN PRIMARY VOLUME DESCRIPTOR FOR CONTACT INFORMATION.",
1022            1,
1023        );
1024        let total = builder.size();
1025        assert!(total > 100); // ER is ~236 bytes
1026
1027        // Give enough budget for SP(7) + CE(28) = 35 bytes inline
1028        let mut split = builder.build_split(40);
1029        assert!(split.has_overflow());
1030        // Inline should be: SP(7) + CE(28) = 35 bytes
1031        assert_eq!(split.inline.len(), 7 + 28);
1032        // Overflow should contain the ER entry
1033        assert_eq!(&split.overflow[0..2], b"ER");
1034        // Inline starts with SP
1035        assert_eq!(&split.inline[0..2], b"SP");
1036        // CE is at offset 7
1037        assert_eq!(&split.inline[7..9], b"CE");
1038        assert_eq!(split.inline[9], 28); // CE length
1039
1040        // Patch CE and verify the fields are set correctly
1041        split.patch_ce(42, 100);
1042        let ce_data = &split.inline[7..35];
1043        // Header is 4 bytes (sig+len+ver), then sector(8), offset(8), length(8)
1044        let sector_le = u32::from_le_bytes(ce_data[4..8].try_into().unwrap());
1045        let sector_be = u32::from_be_bytes(ce_data[8..12].try_into().unwrap());
1046        assert_eq!(sector_le, 42);
1047        assert_eq!(sector_be, 42);
1048        let offset_le = u32::from_le_bytes(ce_data[12..16].try_into().unwrap());
1049        let offset_be = u32::from_be_bytes(ce_data[16..20].try_into().unwrap());
1050        assert_eq!(offset_le, 100);
1051        assert_eq!(offset_be, 100);
1052        let length_le = u32::from_le_bytes(ce_data[20..24].try_into().unwrap());
1053        let length_be = u32::from_be_bytes(ce_data[24..28].try_into().unwrap());
1054        assert_eq!(length_le, split.overflow.len() as u32);
1055        assert_eq!(length_be, split.overflow.len() as u32);
1056    }
1057
1058    // ── RRIP parsing tests ──
1059
1060    /// Helper: build a U32LsbMsb as 8 raw bytes (4 LE + 4 BE).
1061    fn u32_lsb_msb_bytes(v: u32) -> [u8; 8] {
1062        let mut buf = [0u8; 8];
1063        buf[0..4].copy_from_slice(&v.to_le_bytes());
1064        buf[4..8].copy_from_slice(&v.to_be_bytes());
1065        buf
1066    }
1067
1068    #[test]
1069    fn test_iter_px_entry() {
1070        // PX entry: 44 bytes total (header 4 + 40 data bytes)
1071        let mut data = vec![b'P', b'X', 44, 1];
1072        data.extend_from_slice(&u32_lsb_msb_bytes(0o100644)); // file_mode
1073        data.extend_from_slice(&u32_lsb_msb_bytes(1)); // file_links
1074        data.extend_from_slice(&u32_lsb_msb_bytes(1000)); // file_uid
1075        data.extend_from_slice(&u32_lsb_msb_bytes(2000)); // file_gid
1076        data.extend_from_slice(&u32_lsb_msb_bytes(42)); // file_serial
1077
1078        let mut iter = SystemUseIter::new(&data, 0);
1079        match iter.next() {
1080            Some(SystemUseField::PosixAttributes(px)) => {
1081                assert_eq!(px.file_mode.read(), 0o100644);
1082                assert_eq!(px.file_links.read(), 1);
1083                assert_eq!(px.file_uid.read(), 1000);
1084                assert_eq!(px.file_gid.read(), 2000);
1085                assert_eq!(px.file_serial.read(), 42);
1086            }
1087            other => panic!("expected PX entry, got {other:?}"),
1088        }
1089        assert!(iter.next().is_none());
1090    }
1091
1092    #[test]
1093    fn test_iter_nm_entry() {
1094        // NM entry: header(4) + flags(1) + name bytes
1095        let name = b"hello.txt";
1096        let total_len = 4 + 1 + name.len();
1097        let mut data = vec![b'N', b'M', total_len as u8, 1];
1098        data.push(0x00); // flags: no special flags
1099        data.extend_from_slice(name);
1100
1101        let mut iter = SystemUseIter::new(&data, 0);
1102        match iter.next() {
1103            Some(SystemUseField::AlternateName(nm)) => {
1104                assert!(nm.flags.is_empty());
1105                assert_eq!(nm.name, b"hello.txt");
1106            }
1107            other => panic!("expected NM entry, got {other:?}"),
1108        }
1109        assert!(iter.next().is_none());
1110    }
1111
1112    #[test]
1113    fn test_iter_tf_entry() {
1114        // TF with MODIFY(0x02) + ACCESS(0x04) flags = 0x06, short form (7 bytes each)
1115        let total_len = 4 + 1 + 14; // header + flags + 2*7 timestamps
1116        let mut data = vec![b'T', b'F', total_len as u8, 1];
1117        data.push(0x06); // MODIFY | ACCESS
1118        // Modify timestamp: 2026-01-15 10:30:00 UTC
1119        data.extend_from_slice(&[126, 1, 15, 10, 30, 0, 0]);
1120        // Access timestamp: 2026-01-15 12:00:00 UTC
1121        data.extend_from_slice(&[126, 1, 15, 12, 0, 0, 0]);
1122
1123        let mut iter = SystemUseIter::new(&data, 0);
1124        match iter.next() {
1125            Some(SystemUseField::Timestamps(tf)) => {
1126                use super::super::rrip::TfFlags;
1127                assert!(tf.flags.contains(TfFlags::MODIFY));
1128                assert!(tf.flags.contains(TfFlags::ACCESS));
1129                assert!(!tf.flags.contains(TfFlags::LONG_FORM));
1130                assert_eq!(tf.timestamps.len(), 14);
1131            }
1132            other => panic!("expected TF entry, got {other:?}"),
1133        }
1134        assert!(iter.next().is_none());
1135    }
1136
1137    #[test]
1138    fn test_iter_sl_entry() {
1139        // SL for "/usr/bin": root component + "usr" + "bin"
1140        let mut data = vec![b'S', b'L', 0, 1]; // length patched below
1141        data.push(0x00); // SL flags
1142        // Root component: flags=ROOT(0x08), len=0
1143        data.push(0x08);
1144        data.push(0x00);
1145        // "usr" component: flags=0, len=3
1146        data.push(0x00);
1147        data.push(0x03);
1148        data.extend_from_slice(b"usr");
1149        // "bin" component: flags=0, len=3
1150        data.push(0x00);
1151        data.push(0x03);
1152        data.extend_from_slice(b"bin");
1153        // Patch total length
1154        data[2] = data.len() as u8;
1155
1156        let mut iter = SystemUseIter::new(&data, 0);
1157        match iter.next() {
1158            Some(SystemUseField::SymbolicLink(sl)) => {
1159                assert_eq!(sl.flags, 0);
1160                assert_eq!(sl.components.len(), 3);
1161                assert!(
1162                    sl.components[0]
1163                        .flags
1164                        .contains(super::super::rrip::SlComponentFlags::ROOT)
1165                );
1166                assert!(sl.components[0].content.is_empty());
1167                assert_eq!(sl.components[1].content, b"usr");
1168                assert_eq!(sl.components[2].content, b"bin");
1169            }
1170            other => panic!("expected SL entry, got {other:?}"),
1171        }
1172        assert!(iter.next().is_none());
1173    }
1174
1175    #[test]
1176    fn test_iter_cl_pl_re() {
1177        let mut data = Vec::new();
1178        // CL entry: header(4) + location(8) = 12
1179        data.extend_from_slice(&[b'C', b'L', 12, 1]);
1180        data.extend_from_slice(&u32_lsb_msb_bytes(100));
1181        // PL entry: header(4) + location(8) = 12
1182        data.extend_from_slice(&[b'P', b'L', 12, 1]);
1183        data.extend_from_slice(&u32_lsb_msb_bytes(50));
1184        // RE entry: header(4) = 4
1185        data.extend_from_slice(&[b'R', b'E', 4, 1]);
1186
1187        let mut iter = SystemUseIter::new(&data, 0);
1188        match iter.next() {
1189            Some(SystemUseField::ChildLink(cl)) => {
1190                assert_eq!(cl.child_directory_location.read(), 100);
1191            }
1192            other => panic!("expected CL entry, got {other:?}"),
1193        }
1194        match iter.next() {
1195            Some(SystemUseField::ParentLink(pl)) => {
1196                assert_eq!(pl.parent_directory_location.read(), 50);
1197            }
1198            other => panic!("expected PL entry, got {other:?}"),
1199        }
1200        match iter.next() {
1201            Some(SystemUseField::Relocated) => {}
1202            other => panic!("expected RE entry, got {other:?}"),
1203        }
1204        assert!(iter.next().is_none());
1205    }
1206
1207    #[test]
1208    fn test_iter_pn_entry() {
1209        // PN entry: header(4) + dev_high(8) + dev_low(8) = 20
1210        let mut data = vec![b'P', b'N', 20, 1];
1211        data.extend_from_slice(&u32_lsb_msb_bytes(8)); // major
1212        data.extend_from_slice(&u32_lsb_msb_bytes(1)); // minor
1213
1214        let mut iter = SystemUseIter::new(&data, 0);
1215        match iter.next() {
1216            Some(SystemUseField::DeviceNumber(pn)) => {
1217                assert_eq!(pn.dev_high.read(), 8);
1218                assert_eq!(pn.dev_low.read(), 1);
1219            }
1220            other => panic!("expected PN entry, got {other:?}"),
1221        }
1222        assert!(iter.next().is_none());
1223    }
1224
1225    #[test]
1226    fn test_iter_mixed_susp_rrip() {
1227        let mut data = Vec::new();
1228        // SP entry
1229        data.extend_from_slice(&[b'S', b'P', 7, 1, 0xBE, 0xEF, 0]);
1230        // PX entry (44 bytes)
1231        data.extend_from_slice(&[b'P', b'X', 44, 1]);
1232        data.extend_from_slice(&u32_lsb_msb_bytes(0o040755)); // dir mode
1233        data.extend_from_slice(&u32_lsb_msb_bytes(2));
1234        data.extend_from_slice(&u32_lsb_msb_bytes(0));
1235        data.extend_from_slice(&u32_lsb_msb_bytes(0));
1236        data.extend_from_slice(&u32_lsb_msb_bytes(1));
1237        // NM entry for "mydir"
1238        data.extend_from_slice(&[b'N', b'M', 10, 1, 0x00]);
1239        data.extend_from_slice(b"mydir");
1240        // TF entry with MODIFY flag (7 bytes timestamp)
1241        data.extend_from_slice(&[b'T', b'F', 12, 1, 0x02]);
1242        data.extend_from_slice(&[126, 2, 16, 8, 0, 0, 0]);
1243        // ST terminator
1244        data.extend_from_slice(&[b'S', b'T', 4, 1]);
1245
1246        let fields: Vec<_> = SystemUseIter::new(&data, 0).collect();
1247        assert_eq!(fields.len(), 5);
1248        assert!(matches!(fields[0], SystemUseField::SuspIdentifier(_)));
1249        assert!(matches!(fields[1], SystemUseField::PosixAttributes(_)));
1250        assert!(matches!(fields[2], SystemUseField::AlternateName(_)));
1251        assert!(matches!(fields[3], SystemUseField::Timestamps(_)));
1252        assert!(matches!(fields[4], SystemUseField::Terminator));
1253
1254        // Verify signature() method
1255        assert_eq!(fields[0].signature(), *b"SP");
1256        assert_eq!(fields[1].signature(), *b"PX");
1257        assert_eq!(fields[2].signature(), *b"NM");
1258        assert_eq!(fields[3].signature(), *b"TF");
1259        assert_eq!(fields[4].signature(), *b"ST");
1260    }
1261}
1262}