use alloc::string::String;
use alloc::vec::Vec;
use super::super::directory::{DirectoryRecord, DirectoryRef};
use super::super::io::{self, IsoCursor, LogicalSector, Read, Seek, SeekFrom};
use super::super::rrip::{NmFlags, PnEntry, PxEntry, SlComponentFlags, TfFlags};
use super::super::susp::{ContinuationArea, SystemUseField, SystemUseIter};
use super::IsoImage;
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct SuspInfo {
pub detected: bool,
pub bytes_skipped: u8,
pub rrip_detected: bool,
}
io_transform! {
pub(crate) async fn detect_susp_rrip<DATA: Read + Seek>(
data: &mut IsoCursor<DATA>,
root_extent: LogicalSector,
) -> io::Result<SuspInfo> {
let mut info = SuspInfo::default();
data.seek(SeekFrom::Start(root_extent.0 as u64 * 2048)).await?;
let dot_record = DirectoryRecord::parse(data).await?;
let su = dot_record.system_use();
if su.is_empty() {
return Ok(info);
}
let mut ce_entry: Option<ContinuationArea> = None;
for field in SystemUseIter::new(su, 0) {
match &field {
SystemUseField::SuspIdentifier(sp) => {
if sp.is_valid() {
info.detected = true;
info.bytes_skipped = sp.bytes_skipped;
}
}
SystemUseField::ExtensionReference(er) => {
if is_rrip_identifier(er) {
info.rrip_detected = true;
return Ok(info);
}
}
SystemUseField::ContinuationArea(ce) => {
ce_entry = Some(*ce);
}
SystemUseField::Terminator => break,
_ => {}
}
}
if info.detected && !info.rrip_detected {
let mut depth = 0;
while let Some(ce) = ce_entry.take() {
depth += 1;
if depth > 16 {
break;
}
let ce_offset = ce.sector.read() as u64 * 2048 + ce.offset.read() as u64;
let ce_len = ce.length.read() as usize;
if ce_len == 0 || ce_len > 1024 * 1024 {
break;
}
let mut ce_buf = alloc::vec![0u8; ce_len];
data.seek(SeekFrom::Start(ce_offset)).await?;
data.read_exact(&mut ce_buf).await?;
for field in SystemUseIter::new(&ce_buf, 0) {
match &field {
SystemUseField::ExtensionReference(er) => {
if is_rrip_identifier(er) {
info.rrip_detected = true;
return Ok(info);
}
}
SystemUseField::ContinuationArea(next_ce) => {
ce_entry = Some(*next_ce);
}
SystemUseField::Terminator => break,
_ => {}
}
}
}
}
Ok(info)
}
}
fn is_rrip_identifier(er: &super::super::susp::ExtensionReference) -> bool {
let id_len = er.identifier_len as usize;
if id_len == 0 || 4 + id_len > er.buf.len() {
return false;
}
let identifier = &er.buf[4..4 + id_len];
matches!(identifier, b"RRIP_1991A" | b"IEEE_P1282" | b"IEEE_1282")
}
io_transform! {
pub async fn collect_su_entries<DATA: Read + Seek>(
record: &DirectoryRecord,
image: &IsoImage<DATA>,
bytes_to_skip: u8,
) -> io::Result<Vec<SystemUseField>> {
let su = record.system_use();
let mut fields = Vec::new();
let mut ce_entry: Option<ContinuationArea> = None;
for field in SystemUseIter::new(su, bytes_to_skip as usize) {
match &field {
SystemUseField::ContinuationArea(ce) => {
ce_entry = Some(*ce);
}
SystemUseField::Terminator => {
fields.push(field);
return Ok(fields);
}
_ => {}
}
fields.push(field);
}
let mut depth = 0;
while let Some(ce) = ce_entry.take() {
depth += 1;
if depth > 16 {
break;
}
let ce_offset = ce.sector.read() as u64 * 2048 + ce.offset.read() as u64;
let ce_len = ce.length.read() as usize;
if ce_len == 0 || ce_len > 1024 * 1024 {
break;
}
let mut ce_buf = alloc::vec![0u8; ce_len];
image.read_bytes_at(ce_offset, &mut ce_buf).await?;
for field in SystemUseIter::new(&ce_buf, 0) {
match &field {
SystemUseField::ContinuationArea(next_ce) => {
ce_entry = Some(*next_ce);
}
SystemUseField::Terminator => {
fields.push(field);
return Ok(fields);
}
_ => {}
}
fields.push(field);
}
}
Ok(fields)
}
#[derive(Debug, Clone, Copy)]
pub struct RripDateTime {
pub year: u16,
pub month: u8,
pub day: u8,
pub hour: u8,
pub minute: u8,
pub second: u8,
pub gmt_offset: i8,
}
#[derive(Debug, Clone, Default)]
pub struct RripTimestamps {
pub creation: Option<RripDateTime>,
pub modify: Option<RripDateTime>,
pub access: Option<RripDateTime>,
pub attributes: Option<RripDateTime>,
pub backup: Option<RripDateTime>,
pub expiration: Option<RripDateTime>,
pub effective: Option<RripDateTime>,
}
#[derive(Debug, Clone, Default)]
pub struct RripMetadata {
pub posix_attributes: Option<PxEntry>,
pub device_number: Option<PnEntry>,
pub alternate_name: Option<String>,
pub symlink_target: Option<String>,
pub timestamps: Option<RripTimestamps>,
pub child_link: Option<u32>,
pub parent_link: Option<u32>,
pub is_relocated: bool,
}
impl RripMetadata {
pub fn from_fields(fields: &[SystemUseField]) -> Self {
let mut meta = Self::default();
let mut nm_parts: Vec<&[u8]> = Vec::new();
let mut nm_is_current = false;
let mut nm_is_parent = false;
let mut sl_components: Vec<&super::super::rrip::SlComponent> = Vec::new();
let mut has_sl = false;
for field in fields {
match field {
SystemUseField::PosixAttributes(px) => {
meta.posix_attributes = Some(*px);
}
SystemUseField::DeviceNumber(pn) => {
meta.device_number = Some(*pn);
}
SystemUseField::AlternateName(nm) => {
if nm.flags.contains(NmFlags::CURRENT) {
nm_is_current = true;
} else if nm.flags.contains(NmFlags::PARENT) {
nm_is_parent = true;
} else if !nm.name.is_empty() {
nm_parts.push(&nm.name);
}
}
SystemUseField::SymbolicLink(sl) => {
has_sl = true;
for comp in &sl.components {
sl_components.push(comp);
}
}
SystemUseField::Timestamps(tf) => {
meta.timestamps = Some(parse_tf_timestamps(tf));
}
SystemUseField::ChildLink(cl) => {
meta.child_link = Some(cl.child_directory_location.read());
}
SystemUseField::ParentLink(pl) => {
meta.parent_link = Some(pl.parent_directory_location.read());
}
SystemUseField::Relocated => {
meta.is_relocated = true;
}
_ => {}
}
}
if nm_is_current {
meta.alternate_name = Some(String::from("."));
} else if nm_is_parent {
meta.alternate_name = Some(String::from(".."));
} else if !nm_parts.is_empty() {
let total_len: usize = nm_parts.iter().map(|p| p.len()).sum();
let mut name_bytes = Vec::with_capacity(total_len);
for part in &nm_parts {
name_bytes.extend_from_slice(part);
}
meta.alternate_name = Some(String::from_utf8_lossy(&name_bytes).into_owned());
}
if has_sl {
meta.symlink_target = Some(assemble_symlink_path(&sl_components));
}
meta
}
}
fn parse_tf_timestamps(tf: &super::super::rrip::TfEntry) -> RripTimestamps {
let mut ts = RripTimestamps::default();
let long_form = tf.flags.contains(TfFlags::LONG_FORM);
let stamp_size = if long_form { 17 } else { 7 };
let data = &tf.timestamps;
let mut offset = 0;
let flags_in_order = [
TfFlags::CREATION,
TfFlags::MODIFY,
TfFlags::ACCESS,
TfFlags::ATTRIBUTES,
TfFlags::BACKUP,
TfFlags::EXPIRATION,
TfFlags::EFFECTIVE,
];
let mut parsed: [Option<RripDateTime>; 7] = [None; 7];
for (i, flag) in flags_in_order.iter().enumerate() {
if tf.flags.contains(*flag)
&& offset + stamp_size <= data.len()
{
parsed[i] = Some(if long_form {
parse_long_timestamp(&data[offset..offset + 17])
} else {
parse_short_timestamp(&data[offset..offset + 7])
});
offset += stamp_size;
}
}
ts.creation = parsed[0];
ts.modify = parsed[1];
ts.access = parsed[2];
ts.attributes = parsed[3];
ts.backup = parsed[4];
ts.expiration = parsed[5];
ts.effective = parsed[6];
ts
}
fn parse_short_timestamp(data: &[u8]) -> RripDateTime {
RripDateTime {
year: 1900 + data[0] as u16,
month: data[1],
day: data[2],
hour: data[3],
minute: data[4],
second: data[5],
gmt_offset: data[6] as i8,
}
}
fn parse_long_timestamp(data: &[u8]) -> RripDateTime {
let parse_num = |start: usize, len: usize| -> u16 {
let s = core::str::from_utf8(&data[start..start + len]).unwrap_or("0");
s.parse().unwrap_or(0)
};
RripDateTime {
year: parse_num(0, 4),
month: parse_num(4, 2) as u8,
day: parse_num(6, 2) as u8,
hour: parse_num(8, 2) as u8,
minute: parse_num(10, 2) as u8,
second: parse_num(12, 2) as u8,
gmt_offset: data[16] as i8,
}
}
fn assemble_symlink_path(components: &[&super::super::rrip::SlComponent]) -> String {
let mut path = String::new();
let mut pending_content: Vec<u8> = Vec::new();
let mut first = true;
for comp in components {
if comp.flags.contains(SlComponentFlags::ROOT) {
path.push('/');
first = false;
} else if comp.flags.contains(SlComponentFlags::CURRENT) {
if !first {
path.push('/');
}
path.push('.');
first = false;
} else if comp.flags.contains(SlComponentFlags::PARENT) {
if !first {
path.push('/');
}
path.push_str("..");
first = false;
} else {
pending_content.extend_from_slice(&comp.content);
if comp.flags.contains(SlComponentFlags::CONTINUE) {
continue;
}
if !first && !path.ends_with('/') {
path.push('/');
}
path.push_str(&String::from_utf8_lossy(&pending_content));
pending_content.clear();
first = false;
}
}
if !pending_content.is_empty() {
if !first && !path.ends_with('/') {
path.push('/');
}
path.push_str(&String::from_utf8_lossy(&pending_content));
}
path
}
pub(crate) async fn read_dir_size<DATA: Read + Seek>(
image: &IsoImage<DATA>,
sector: LogicalSector,
) -> io::Result<DirectoryRef> {
let byte_offset = sector.0 as u64 * 2048;
let mut buf = [0u8; 34];
image.read_bytes_at(byte_offset, &mut buf).await?;
let header: &super::super::directory::DirectoryRecordHeader =
bytemuck::from_bytes(&buf[..core::mem::size_of::<super::super::directory::DirectoryRecordHeader>()]);
Ok(DirectoryRef {
extent: sector,
size: header.data_len.read() as usize,
})
}
}