#[cfg(feature = "std")]
use super::io::Writable;
use super::io::{self, Read, ReadExt};
use crate::types::U32LsbMsb;
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
pub struct SystemUseHeader {
pub sig: [u8; 2],
pub length: u8,
pub version: u8,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
pub struct ContinuationArea {
pub sector: U32LsbMsb,
pub offset: U32LsbMsb,
pub length: U32LsbMsb,
}
#[cfg(feature = "std")]
impl ContinuationArea {
pub fn header(&self) -> SystemUseHeader {
SystemUseHeader {
sig: *b"CE",
length: 28,
version: 1,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct PaddingField {
pub length: u8,
}
#[cfg(feature = "std")]
impl PaddingField {
pub fn header(&self) -> SystemUseHeader {
SystemUseHeader {
sig: *b"PD",
length: 4 + self.length,
version: 1,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
pub struct SuspIdentifier {
pub check_bytes: [u8; 2],
pub bytes_skipped: u8,
}
impl SuspIdentifier {
pub fn new(bytes_skipped: u8) -> Self {
Self {
check_bytes: [0xBE, 0xEF],
bytes_skipped,
}
}
pub fn is_valid(&self) -> bool {
self.check_bytes == [0xBE, 0xEF]
}
}
#[cfg(feature = "std")]
impl SuspIdentifier {
pub fn header(&self) -> SystemUseHeader {
SystemUseHeader {
sig: *b"SP",
length: 7,
version: 1,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct SuspTerminator;
#[cfg(feature = "std")]
impl SuspTerminator {
pub fn header(&self) -> SystemUseHeader {
SystemUseHeader {
sig: *b"ST",
length: 4,
version: 1,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ExtensionReference {
pub version: u8,
pub identifier_len: u8,
pub descriptor_len: u8,
pub source_len: u8,
pub buf: [u8; 252],
}
#[cfg(feature = "std")]
impl ExtensionReference {
pub fn header(&self) -> SystemUseHeader {
SystemUseHeader {
sig: *b"ER",
length: 8 + self.identifier_len + self.descriptor_len + self.source_len,
version: 1,
}
}
}
io_transform! {
#[cfg(feature = "std")]
impl Writable for SystemUseHeader {
async fn write<R: io::Write>(&self, writer: &mut R) -> io::Result<()> {
writer.write_all(bytemuck::bytes_of(self)).await?;
Ok(())
}
}
#[cfg(feature = "std")]
impl Writable for ContinuationArea {
async fn write<R: io::Write>(&self, writer: &mut R) -> io::Result<()> {
self.header().write(writer).await?;
writer.write_all(bytemuck::bytes_of(self)).await?;
Ok(())
}
}
#[cfg(feature = "std")]
impl ContinuationArea {
pub async fn parse_data<R: Read>(header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
assert_eq!(&header.sig, b"CE");
data.read_struct().await
}
}
#[cfg(feature = "std")]
impl Writable for PaddingField {
async fn write<R: io::Write>(&self, writer: &mut R) -> io::Result<()> {
self.header().write(writer).await?;
let zeros = alloc::vec![0u8; self.length as usize];
writer.write_all(&zeros).await?;
Ok(())
}
}
#[cfg(feature = "std")]
impl PaddingField {
pub async fn parse_data<R: Read>(header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
let len = header.length - 4;
let mut buf = [0];
for _ in 0..len {
data.read_exact(&mut buf).await?;
}
Ok(Self { length: len })
}
}
#[cfg(feature = "std")]
impl Writable for SuspIdentifier {
async fn write<R: io::Write>(&self, writer: &mut R) -> io::Result<()> {
self.header().write(writer).await?;
writer.write_all(bytemuck::bytes_of(self)).await?;
Ok(())
}
}
#[cfg(feature = "std")]
impl SuspIdentifier {
pub async fn parse_data<R: Read>(header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
assert_eq!(&header.sig, b"SP");
data.read_struct().await
}
}
#[cfg(feature = "std")]
impl Writable for SuspTerminator {
async fn write<R: io::Write>(&self, writer: &mut R) -> io::Result<()> {
self.header().write(writer).await
}
}
#[cfg(feature = "std")]
impl SuspTerminator {
pub async fn parse_data<R: Read>(_header: SystemUseHeader, _data: &mut R) -> io::Result<Self> {
Ok(Self)
}
}
#[cfg(feature = "std")]
impl Writable for ExtensionReference {
async fn write<R: io::Write>(&self, writer: &mut R) -> io::Result<()> {
self.header().write(writer).await?;
let meta = [
self.identifier_len,
self.descriptor_len,
self.source_len,
self.version,
];
writer.write_all(&meta).await?;
let total_len = self.identifier_len + self.descriptor_len + self.source_len;
writer.write_all(&self.buf[0..total_len as usize]).await?;
Ok(())
}
}
#[cfg(feature = "std")]
impl ExtensionReference {
pub async fn parse_data<R: Read>(header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
assert_eq!(&header.sig, b"ER");
let mut buf = [0u8; 252];
let meta: [u8; 4] = data.read_struct().await?;
data.read_exact(&mut buf[0..header.length as usize - size_of::<SystemUseHeader>()]).await?;
Ok(Self {
identifier_len: meta[0],
descriptor_len: meta[1],
source_len: meta[2],
version: meta[3],
buf,
})
}
}
}
#[derive(Debug, Clone)]
pub enum SystemUseField {
SuspIdentifier(SuspIdentifier),
Terminator,
ContinuationArea(ContinuationArea),
Padding(PaddingField),
ExtensionReference(ExtensionReference),
PosixAttributes(super::rrip::PxEntry),
DeviceNumber(super::rrip::PnEntry),
AlternateName(super::rrip::NmEntry),
SymbolicLink(super::rrip::SlEntry),
Timestamps(super::rrip::TfEntry),
ChildLink(super::rrip::ClEntry),
ParentLink(super::rrip::PlEntry),
Relocated,
ExtensionSelector { extension_sequence: u8 },
Unknown(SystemUseHeader, [u8; 252]),
}
impl SystemUseField {
pub fn signature(&self) -> [u8; 2] {
match self {
Self::SuspIdentifier(_) => *b"SP",
Self::Terminator => *b"ST",
Self::ContinuationArea(_) => *b"CE",
Self::Padding(_) => *b"PD",
Self::ExtensionReference(_) => *b"ER",
Self::PosixAttributes(_) => *b"PX",
Self::DeviceNumber(_) => *b"PN",
Self::AlternateName(_) => *b"NM",
Self::SymbolicLink(_) => *b"SL",
Self::Timestamps(_) => *b"TF",
Self::ChildLink(_) => *b"CL",
Self::ParentLink(_) => *b"PL",
Self::Relocated => *b"RE",
Self::ExtensionSelector { .. } => *b"ES",
Self::Unknown(header, _) => header.sig,
}
}
pub fn as_posix_attributes(&self) -> Option<&super::rrip::PxEntry> {
match self {
Self::PosixAttributes(px) => Some(px),
_ => None,
}
}
pub fn as_alternate_name(&self) -> Option<&super::rrip::NmEntry> {
match self {
Self::AlternateName(nm) => Some(nm),
_ => None,
}
}
pub fn as_timestamps(&self) -> Option<&super::rrip::TfEntry> {
match self {
Self::Timestamps(tf) => Some(tf),
_ => None,
}
}
pub fn as_symbolic_link(&self) -> Option<&super::rrip::SlEntry> {
match self {
Self::SymbolicLink(sl) => Some(sl),
_ => None,
}
}
}
pub struct SystemUseIter<'a> {
data: &'a [u8],
offset: usize,
}
impl<'a> SystemUseIter<'a> {
pub fn new(data: &'a [u8], bytes_to_skip: usize) -> Self {
Self {
data,
offset: bytes_to_skip,
}
}
}
impl Iterator for SystemUseIter<'_> {
type Item = SystemUseField;
fn next(&mut self) -> Option<Self::Item> {
if self.offset + 4 > self.data.len() {
return None;
}
let header_bytes = &self.data[self.offset..self.offset + 4];
let header: SystemUseHeader = *bytemuck::from_bytes(header_bytes);
if header.length == 0 {
return None;
}
let entry_end = self.offset + header.length as usize;
if entry_end > self.data.len() {
return None;
}
let entry_data = &self.data[self.offset + 4..entry_end];
self.offset = entry_end;
let field = match &header.sig {
b"SP" if entry_data.len() >= 3 => {
let sp: SuspIdentifier = *bytemuck::from_bytes(&entry_data[0..3]);
SystemUseField::SuspIdentifier(sp)
}
b"ST" => SystemUseField::Terminator,
b"CE" if entry_data.len() >= 24 => {
let ce: ContinuationArea = *bytemuck::from_bytes(&entry_data[0..24]);
SystemUseField::ContinuationArea(ce)
}
b"PD" => {
let len = header.length.saturating_sub(4);
SystemUseField::Padding(PaddingField { length: len })
}
b"ER" if entry_data.len() >= 4 => {
let mut buf = [0u8; 252];
let copy_len = entry_data.len().min(252);
buf[..copy_len].copy_from_slice(&entry_data[..copy_len]);
SystemUseField::ExtensionReference(ExtensionReference {
identifier_len: entry_data[0],
descriptor_len: entry_data[1],
source_len: entry_data[2],
version: entry_data[3],
buf,
})
}
b"PX" if entry_data.len() >= 32 => {
let len = entry_data.len().min(40);
let px = super::rrip::PxEntry {
file_mode: *bytemuck::from_bytes(&entry_data[0..8]),
file_links: *bytemuck::from_bytes(&entry_data[8..16]),
file_uid: *bytemuck::from_bytes(&entry_data[16..24]),
file_gid: *bytemuck::from_bytes(&entry_data[24..32]),
file_serial: if len >= 40 {
*bytemuck::from_bytes(&entry_data[32..40])
} else {
crate::types::U32LsbMsb::new(0)
},
};
SystemUseField::PosixAttributes(px)
}
b"PN" if entry_data.len() >= 16 => {
let pn = super::rrip::PnEntry {
dev_high: *bytemuck::from_bytes(&entry_data[0..8]),
dev_low: *bytemuck::from_bytes(&entry_data[8..16]),
};
SystemUseField::DeviceNumber(pn)
}
b"NM" if !entry_data.is_empty() => {
let flags = super::rrip::NmFlags::from_bits_truncate(entry_data[0]);
let name = entry_data[1..].to_vec();
SystemUseField::AlternateName(super::rrip::NmEntry { flags, name })
}
b"SL" if !entry_data.is_empty() => {
let flags = entry_data[0];
let mut components = alloc::vec::Vec::new();
let mut pos = 1;
while pos + 2 <= entry_data.len() {
let comp_flags =
super::rrip::SlComponentFlags::from_bits_truncate(entry_data[pos]);
let comp_len = entry_data[pos + 1] as usize;
pos += 2;
let end = (pos + comp_len).min(entry_data.len());
let content = entry_data[pos..end].to_vec();
pos = end;
components.push(super::rrip::SlComponent {
flags: comp_flags,
content,
});
}
SystemUseField::SymbolicLink(super::rrip::SlEntry { flags, components })
}
b"TF" if !entry_data.is_empty() => {
let flags = super::rrip::TfFlags::from_bits_truncate(entry_data[0]);
let timestamps = entry_data[1..].to_vec();
SystemUseField::Timestamps(super::rrip::TfEntry { flags, timestamps })
}
b"CL" if entry_data.len() >= 8 => {
let cl = super::rrip::ClEntry {
child_directory_location: *bytemuck::from_bytes(&entry_data[0..8]),
};
SystemUseField::ChildLink(cl)
}
b"PL" if entry_data.len() >= 8 => {
let pl = super::rrip::PlEntry {
parent_directory_location: *bytemuck::from_bytes(&entry_data[0..8]),
};
SystemUseField::ParentLink(pl)
}
b"RE" => SystemUseField::Relocated,
b"ES" if !entry_data.is_empty() => SystemUseField::ExtensionSelector {
extension_sequence: entry_data[0],
},
_ => {
let mut buf = [0u8; 252];
let copy_len = entry_data.len().min(252);
buf[..copy_len].copy_from_slice(&entry_data[..copy_len]);
SystemUseField::Unknown(header, buf)
}
};
Some(field)
}
}
#[cfg(feature = "alloc")]
pub struct SystemUseBuilder {
entries: alloc::vec::Vec<alloc::vec::Vec<u8>>,
}
#[cfg(feature = "alloc")]
impl SystemUseBuilder {
pub fn new() -> Self {
Self {
entries: alloc::vec::Vec::new(),
}
}
pub fn add_sp(&mut self, bytes_skipped: u8) -> &mut Self {
let sp = SuspIdentifier::new(bytes_skipped);
let mut buf = alloc::vec![0u8; 7];
buf[0..2].copy_from_slice(b"SP");
buf[2] = 7; buf[3] = 1; buf[4..7].copy_from_slice(bytemuck::bytes_of(&sp));
self.entries.push(buf);
self
}
pub fn add_st(&mut self) -> &mut Self {
let buf = alloc::vec![b'S', b'T', 4, 1];
self.entries.push(buf);
self
}
pub fn add_ce(&mut self, ce: ContinuationArea) -> &mut Self {
let mut buf = alloc::vec![0u8; 28];
buf[0..2].copy_from_slice(b"CE");
buf[2] = 28;
buf[3] = 1;
buf[4..28].copy_from_slice(bytemuck::bytes_of(&ce));
self.entries.push(buf);
self
}
pub fn add_padding(&mut self, length: u8) -> &mut Self {
let mut buf = alloc::vec![0u8; 4 + length as usize];
buf[0..2].copy_from_slice(b"PD");
buf[2] = 4 + length;
buf[3] = 1;
self.entries.push(buf);
self
}
pub fn add_er(
&mut self,
identifier: &str,
descriptor: &str,
source: &str,
version: u8,
) -> &mut Self {
let id_bytes = identifier.as_bytes();
let desc_bytes = descriptor.as_bytes();
let src_bytes = source.as_bytes();
let len = 8 + id_bytes.len() + desc_bytes.len() + src_bytes.len();
let mut buf = alloc::vec![0u8; len];
buf[0..2].copy_from_slice(b"ER");
buf[2] = len as u8;
buf[3] = 1;
buf[4] = id_bytes.len() as u8;
buf[5] = desc_bytes.len() as u8;
buf[6] = src_bytes.len() as u8;
buf[7] = version;
let mut offset = 8;
buf[offset..offset + id_bytes.len()].copy_from_slice(id_bytes);
offset += id_bytes.len();
buf[offset..offset + desc_bytes.len()].copy_from_slice(desc_bytes);
offset += desc_bytes.len();
buf[offset..offset + src_bytes.len()].copy_from_slice(src_bytes);
self.entries.push(buf);
self
}
pub fn add_raw(&mut self, data: alloc::vec::Vec<u8>) -> &mut Self {
self.entries.push(data);
self
}
pub fn size(&self) -> usize {
self.entries.iter().map(|e| e.len()).sum()
}
pub fn build(&self) -> alloc::vec::Vec<u8> {
let total_size = self.size();
let mut result = alloc::vec::Vec::with_capacity(total_size);
for entry in &self.entries {
result.extend_from_slice(entry);
}
result
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn build_split(&self, max_inline: usize) -> SplitSu {
let total = self.size();
if total <= max_inline {
return SplitSu {
inline: self.build(),
overflow: alloc::vec::Vec::new(),
ce_offset: None,
};
}
const CE_SIZE: usize = 28;
let budget = max_inline.saturating_sub(CE_SIZE);
let mut accumulated = 0;
let mut split_at = 0;
for (i, entry) in self.entries.iter().enumerate() {
if accumulated + entry.len() > budget {
split_at = i;
break;
}
accumulated += entry.len();
split_at = i + 1;
}
let mut inline = alloc::vec::Vec::with_capacity(accumulated + CE_SIZE);
for entry in &self.entries[..split_at] {
inline.extend_from_slice(entry);
}
let ce_offset = inline.len();
let mut ce_buf = alloc::vec![0u8; CE_SIZE];
ce_buf[0..2].copy_from_slice(b"CE");
ce_buf[2] = CE_SIZE as u8;
ce_buf[3] = 1;
inline.extend_from_slice(&ce_buf);
let overflow_size: usize = self.entries[split_at..].iter().map(|e| e.len()).sum();
let mut overflow = alloc::vec::Vec::with_capacity(overflow_size);
for entry in &self.entries[split_at..] {
overflow.extend_from_slice(entry);
}
SplitSu {
inline,
overflow,
ce_offset: Some(ce_offset),
}
}
}
#[cfg(feature = "alloc")]
pub struct SplitSu {
pub inline: alloc::vec::Vec<u8>,
pub overflow: alloc::vec::Vec<u8>,
ce_offset: Option<usize>,
}
#[cfg(feature = "alloc")]
impl SplitSu {
pub fn empty() -> Self {
Self {
inline: alloc::vec::Vec::new(),
overflow: alloc::vec::Vec::new(),
ce_offset: None,
}
}
pub fn has_overflow(&self) -> bool {
!self.overflow.is_empty()
}
pub fn patch_ce(&mut self, sector: u32, byte_offset: u32) {
if let Some(off) = self.ce_offset {
let data_start = off + 4; let length = self.overflow.len() as u32;
self.inline[data_start..data_start + 4].copy_from_slice(§or.to_le_bytes());
self.inline[data_start + 4..data_start + 8].copy_from_slice(§or.to_be_bytes());
self.inline[data_start + 8..data_start + 12]
.copy_from_slice(&byte_offset.to_le_bytes());
self.inline[data_start + 12..data_start + 16]
.copy_from_slice(&byte_offset.to_be_bytes());
self.inline[data_start + 16..data_start + 20].copy_from_slice(&length.to_le_bytes());
self.inline[data_start + 20..data_start + 24].copy_from_slice(&length.to_be_bytes());
}
}
}
#[cfg(feature = "alloc")]
impl Default for SystemUseBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(all(feature = "std", test))]
mod tests {
use super::*;
use alloc::vec;
use alloc::vec::Vec;
static_assertions::const_assert_eq!(size_of::<SystemUseHeader>(), 4);
static_assertions::const_assert_eq!(size_of::<ContinuationArea>(), 24);
static_assertions::const_assert_eq!(size_of::<SuspIdentifier>(), 3);
#[test]
fn test_susp_identifier_valid() {
let sp = SuspIdentifier::new(0);
assert!(sp.is_valid());
assert_eq!(sp.check_bytes, [0xBE, 0xEF]);
assert_eq!(sp.bytes_skipped, 0);
}
#[test]
fn test_susp_identifier_invalid() {
let sp = SuspIdentifier {
check_bytes: [0x00, 0x00],
bytes_skipped: 0,
};
assert!(!sp.is_valid());
}
#[test]
fn test_system_use_iter_empty() {
let data: &[u8] = &[];
let mut iter = SystemUseIter::new(data, 0);
assert!(iter.next().is_none());
}
#[test]
fn test_system_use_iter_sp_entry() {
let data: &[u8] = &[
b'S', b'P', 7, 1, 0xBE, 0xEF, 0, ];
let mut iter = SystemUseIter::new(data, 0);
match iter.next() {
Some(SystemUseField::SuspIdentifier(sp)) => {
assert!(sp.is_valid());
assert_eq!(sp.bytes_skipped, 0);
}
other => panic!("expected SP entry, got {:?}", other),
}
assert!(iter.next().is_none());
}
#[test]
fn test_system_use_iter_st_entry() {
let data: &[u8] = &[b'S', b'T', 4, 1];
let mut iter = SystemUseIter::new(data, 0);
match iter.next() {
Some(SystemUseField::Terminator) => {}
other => panic!("expected ST entry, got {:?}", other),
}
assert!(iter.next().is_none());
}
#[test]
fn test_system_use_iter_ce_entry() {
let mut data = vec![b'C', b'E', 28, 1];
data.extend_from_slice(&[10, 0, 0, 0, 10, 0, 0, 0]); data.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0]); data.extend_from_slice(&[100, 0, 0, 0, 100, 0, 0, 0]);
let mut iter = SystemUseIter::new(&data, 0);
match iter.next() {
Some(SystemUseField::ContinuationArea(ce)) => {
assert_eq!(ce.sector.read(), 10);
assert_eq!(ce.offset.read(), 0);
assert_eq!(ce.length.read(), 100);
}
other => panic!("expected CE entry, got {:?}", other),
}
assert!(iter.next().is_none());
}
#[test]
fn test_system_use_iter_pd_entry() {
let data: &[u8] = &[b'P', b'D', 8, 1, 0, 0, 0, 0];
let mut iter = SystemUseIter::new(data, 0);
match iter.next() {
Some(SystemUseField::Padding(pd)) => {
assert_eq!(pd.length, 4);
}
other => panic!("expected PD entry, got {:?}", other),
}
assert!(iter.next().is_none());
}
#[test]
fn test_system_use_iter_unknown_entry() {
let data: &[u8] = &[b'X', b'X', 6, 1, 0x42, 0x43];
let mut iter = SystemUseIter::new(data, 0);
match iter.next() {
Some(SystemUseField::Unknown(header, buf)) => {
assert_eq!(&header.sig, b"XX");
assert_eq!(header.length, 6);
assert_eq!(&buf[0..2], &[0x42, 0x43]);
}
other => panic!("expected Unknown entry, got {:?}", other),
}
assert!(iter.next().is_none());
}
#[test]
fn test_system_use_iter_multiple_entries() {
let data: &[u8] = &[
b'S', b'P', 7, 1, 0xBE, 0xEF, 0, b'S', b'T', 4, 1, ];
let mut iter = SystemUseIter::new(data, 0);
assert!(matches!(
iter.next(),
Some(SystemUseField::SuspIdentifier(_))
));
assert!(matches!(iter.next(), Some(SystemUseField::Terminator)));
assert!(iter.next().is_none());
}
#[test]
fn test_system_use_iter_with_skip() {
let data: &[u8] = &[
0xFF, 0xFF, 0xFF, b'S', b'T', 4, 1, ];
let mut iter = SystemUseIter::new(data, 3);
assert!(matches!(iter.next(), Some(SystemUseField::Terminator)));
assert!(iter.next().is_none());
}
#[test]
fn test_system_use_iter_zero_length_terminates() {
let data: &[u8] = &[0, 0, 0, 0]; let mut iter = SystemUseIter::new(data, 0);
assert!(iter.next().is_none());
}
#[test]
fn test_system_use_builder_empty() {
let builder = SystemUseBuilder::new();
assert!(builder.is_empty());
assert_eq!(builder.size(), 0);
assert_eq!(builder.build(), Vec::<u8>::new());
}
#[test]
fn test_system_use_builder_sp() {
let mut builder = SystemUseBuilder::new();
builder.add_sp(0);
assert!(!builder.is_empty());
assert_eq!(builder.size(), 7);
let data = builder.build();
assert_eq!(&data[0..2], b"SP");
assert_eq!(data[2], 7); assert_eq!(data[3], 1); assert_eq!(&data[4..6], &[0xBE, 0xEF]); assert_eq!(data[6], 0); }
#[test]
fn test_system_use_builder_st() {
let mut builder = SystemUseBuilder::new();
builder.add_st();
assert_eq!(builder.size(), 4);
let data = builder.build();
assert_eq!(&data, &[b'S', b'T', 4, 1]);
}
#[test]
fn test_system_use_builder_padding() {
let mut builder = SystemUseBuilder::new();
builder.add_padding(4);
assert_eq!(builder.size(), 8);
let data = builder.build();
assert_eq!(&data[0..2], b"PD");
assert_eq!(data[2], 8); assert_eq!(data[3], 1); }
#[test]
fn test_system_use_builder_er() {
let mut builder = SystemUseBuilder::new();
builder.add_er("RRIP", "Rock Ridge", "TEST", 1);
let data = builder.build();
assert_eq!(&data[0..2], b"ER");
let len = data[2] as usize;
assert_eq!(len, 8 + 4 + 10 + 4); assert_eq!(data[3], 1); assert_eq!(data[4], 4); assert_eq!(data[5], 10); assert_eq!(data[6], 4); assert_eq!(data[7], 1);
let id = &data[8..12];
assert_eq!(id, b"RRIP");
}
#[test]
fn test_system_use_builder_roundtrip() {
let mut builder = SystemUseBuilder::new();
builder.add_sp(0).add_st();
let data = builder.build();
let mut iter = SystemUseIter::new(&data, 0);
assert!(matches!(
iter.next(),
Some(SystemUseField::SuspIdentifier(_))
));
assert!(matches!(iter.next(), Some(SystemUseField::Terminator)));
assert!(iter.next().is_none());
}
#[test]
fn test_build_split_no_overflow() {
let mut builder = SystemUseBuilder::new();
builder.add_sp(0).add_st();
let split = builder.build_split(100);
assert!(!split.has_overflow());
assert!(split.overflow.is_empty());
assert_eq!(split.inline, builder.build());
}
#[test]
fn test_build_split_with_overflow() {
let mut builder = SystemUseBuilder::new();
builder.add_sp(0);
builder.add_er(
"RRIP_1991A",
"THE ROCK RIDGE INTERCHANGE PROTOCOL PROVIDES SUPPORT FOR POSIX FILE SYSTEM SEMANTICS",
"PLEASE CONTACT DISC PUBLISHER FOR SPECIFICATION SOURCE. SEE PUBLISHER IDENTIFIER IN PRIMARY VOLUME DESCRIPTOR FOR CONTACT INFORMATION.",
1,
);
let total = builder.size();
assert!(total > 100);
let mut split = builder.build_split(40);
assert!(split.has_overflow());
assert_eq!(split.inline.len(), 7 + 28);
assert_eq!(&split.overflow[0..2], b"ER");
assert_eq!(&split.inline[0..2], b"SP");
assert_eq!(&split.inline[7..9], b"CE");
assert_eq!(split.inline[9], 28);
split.patch_ce(42, 100);
let ce_data = &split.inline[7..35];
let sector_le = u32::from_le_bytes(ce_data[4..8].try_into().unwrap());
let sector_be = u32::from_be_bytes(ce_data[8..12].try_into().unwrap());
assert_eq!(sector_le, 42);
assert_eq!(sector_be, 42);
let offset_le = u32::from_le_bytes(ce_data[12..16].try_into().unwrap());
let offset_be = u32::from_be_bytes(ce_data[16..20].try_into().unwrap());
assert_eq!(offset_le, 100);
assert_eq!(offset_be, 100);
let length_le = u32::from_le_bytes(ce_data[20..24].try_into().unwrap());
let length_be = u32::from_be_bytes(ce_data[24..28].try_into().unwrap());
assert_eq!(length_le, split.overflow.len() as u32);
assert_eq!(length_be, split.overflow.len() as u32);
}
fn u32_lsb_msb_bytes(v: u32) -> [u8; 8] {
let mut buf = [0u8; 8];
buf[0..4].copy_from_slice(&v.to_le_bytes());
buf[4..8].copy_from_slice(&v.to_be_bytes());
buf
}
#[test]
fn test_iter_px_entry() {
let mut data = vec![b'P', b'X', 44, 1];
data.extend_from_slice(&u32_lsb_msb_bytes(0o100644)); data.extend_from_slice(&u32_lsb_msb_bytes(1)); data.extend_from_slice(&u32_lsb_msb_bytes(1000)); data.extend_from_slice(&u32_lsb_msb_bytes(2000)); data.extend_from_slice(&u32_lsb_msb_bytes(42));
let mut iter = SystemUseIter::new(&data, 0);
match iter.next() {
Some(SystemUseField::PosixAttributes(px)) => {
assert_eq!(px.file_mode.read(), 0o100644);
assert_eq!(px.file_links.read(), 1);
assert_eq!(px.file_uid.read(), 1000);
assert_eq!(px.file_gid.read(), 2000);
assert_eq!(px.file_serial.read(), 42);
}
other => panic!("expected PX entry, got {:?}", other),
}
assert!(iter.next().is_none());
}
#[test]
fn test_iter_nm_entry() {
let name = b"hello.txt";
let total_len = 4 + 1 + name.len();
let mut data = vec![b'N', b'M', total_len as u8, 1];
data.push(0x00); data.extend_from_slice(name);
let mut iter = SystemUseIter::new(&data, 0);
match iter.next() {
Some(SystemUseField::AlternateName(nm)) => {
assert!(nm.flags.is_empty());
assert_eq!(nm.name, b"hello.txt");
}
other => panic!("expected NM entry, got {:?}", other),
}
assert!(iter.next().is_none());
}
#[test]
fn test_iter_tf_entry() {
let total_len = 4 + 1 + 14; let mut data = vec![b'T', b'F', total_len as u8, 1];
data.push(0x06); data.extend_from_slice(&[126, 1, 15, 10, 30, 0, 0]);
data.extend_from_slice(&[126, 1, 15, 12, 0, 0, 0]);
let mut iter = SystemUseIter::new(&data, 0);
match iter.next() {
Some(SystemUseField::Timestamps(tf)) => {
use super::super::rrip::TfFlags;
assert!(tf.flags.contains(TfFlags::MODIFY));
assert!(tf.flags.contains(TfFlags::ACCESS));
assert!(!tf.flags.contains(TfFlags::LONG_FORM));
assert_eq!(tf.timestamps.len(), 14);
}
other => panic!("expected TF entry, got {:?}", other),
}
assert!(iter.next().is_none());
}
#[test]
fn test_iter_sl_entry() {
let mut data = vec![b'S', b'L', 0, 1]; data.push(0x00); data.push(0x08);
data.push(0x00);
data.push(0x00);
data.push(0x03);
data.extend_from_slice(b"usr");
data.push(0x00);
data.push(0x03);
data.extend_from_slice(b"bin");
data[2] = data.len() as u8;
let mut iter = SystemUseIter::new(&data, 0);
match iter.next() {
Some(SystemUseField::SymbolicLink(sl)) => {
assert_eq!(sl.flags, 0);
assert_eq!(sl.components.len(), 3);
assert!(
sl.components[0]
.flags
.contains(super::super::rrip::SlComponentFlags::ROOT)
);
assert!(sl.components[0].content.is_empty());
assert_eq!(sl.components[1].content, b"usr");
assert_eq!(sl.components[2].content, b"bin");
}
other => panic!("expected SL entry, got {:?}", other),
}
assert!(iter.next().is_none());
}
#[test]
fn test_iter_cl_pl_re() {
let mut data = Vec::new();
data.extend_from_slice(&[b'C', b'L', 12, 1]);
data.extend_from_slice(&u32_lsb_msb_bytes(100));
data.extend_from_slice(&[b'P', b'L', 12, 1]);
data.extend_from_slice(&u32_lsb_msb_bytes(50));
data.extend_from_slice(&[b'R', b'E', 4, 1]);
let mut iter = SystemUseIter::new(&data, 0);
match iter.next() {
Some(SystemUseField::ChildLink(cl)) => {
assert_eq!(cl.child_directory_location.read(), 100);
}
other => panic!("expected CL entry, got {:?}", other),
}
match iter.next() {
Some(SystemUseField::ParentLink(pl)) => {
assert_eq!(pl.parent_directory_location.read(), 50);
}
other => panic!("expected PL entry, got {:?}", other),
}
match iter.next() {
Some(SystemUseField::Relocated) => {}
other => panic!("expected RE entry, got {:?}", other),
}
assert!(iter.next().is_none());
}
#[test]
fn test_iter_pn_entry() {
let mut data = vec![b'P', b'N', 20, 1];
data.extend_from_slice(&u32_lsb_msb_bytes(8)); data.extend_from_slice(&u32_lsb_msb_bytes(1));
let mut iter = SystemUseIter::new(&data, 0);
match iter.next() {
Some(SystemUseField::DeviceNumber(pn)) => {
assert_eq!(pn.dev_high.read(), 8);
assert_eq!(pn.dev_low.read(), 1);
}
other => panic!("expected PN entry, got {:?}", other),
}
assert!(iter.next().is_none());
}
#[test]
fn test_iter_mixed_susp_rrip() {
let mut data = Vec::new();
data.extend_from_slice(&[b'S', b'P', 7, 1, 0xBE, 0xEF, 0]);
data.extend_from_slice(&[b'P', b'X', 44, 1]);
data.extend_from_slice(&u32_lsb_msb_bytes(0o040755)); data.extend_from_slice(&u32_lsb_msb_bytes(2));
data.extend_from_slice(&u32_lsb_msb_bytes(0));
data.extend_from_slice(&u32_lsb_msb_bytes(0));
data.extend_from_slice(&u32_lsb_msb_bytes(1));
data.extend_from_slice(&[b'N', b'M', 10, 1, 0x00]);
data.extend_from_slice(b"mydir");
data.extend_from_slice(&[b'T', b'F', 12, 1, 0x02]);
data.extend_from_slice(&[126, 2, 16, 8, 0, 0, 0]);
data.extend_from_slice(&[b'S', b'T', 4, 1]);
let fields: Vec<_> = SystemUseIter::new(&data, 0).collect();
assert_eq!(fields.len(), 5);
assert!(matches!(fields[0], SystemUseField::SuspIdentifier(_)));
assert!(matches!(fields[1], SystemUseField::PosixAttributes(_)));
assert!(matches!(fields[2], SystemUseField::AlternateName(_)));
assert!(matches!(fields[3], SystemUseField::Timestamps(_)));
assert!(matches!(fields[4], SystemUseField::Terminator));
assert_eq!(fields[0].signature(), *b"SP");
assert_eq!(fields[1].signature(), *b"PX");
assert_eq!(fields[2].signature(), *b"NM");
assert_eq!(fields[3].signature(), *b"TF");
assert_eq!(fields[4].signature(), *b"ST");
}
}