use crate::error::{Error, Result};
use crate::sector::SectorSource;
const MAX_FILE_BYTES: u64 = 64 * 1024 * 1024;
const MAX_DIR_BYTES: u32 = 1024 * 1024;
#[derive(Debug)]
pub struct UdfFs {
pub root: DirEntry,
pub volume_id: String,
partition_start: u32,
metadata_start: u32,
metadata_sectors: u32,
}
#[derive(Debug, Clone)]
pub struct DirEntry {
pub name: String,
pub is_dir: bool,
pub meta_lba: u32,
pub size: u64,
pub entries: Vec<DirEntry>,
}
impl UdfFs {
pub fn partition_start(&self) -> u32 {
self.partition_start
}
pub fn metadata_start(&self) -> u32 {
self.metadata_start
}
pub(crate) fn metadata_sectors(&self) -> u32 {
self.metadata_sectors
}
pub fn find_dir(&self, path: &str) -> Option<&DirEntry> {
let parts: Vec<&str> = path.trim_matches('/').split('/').collect();
let mut current = &self.root;
for part in &parts {
current = current
.entries
.iter()
.find(|e| e.is_dir && e.name.eq_ignore_ascii_case(part))?;
}
Some(current)
}
pub fn file_start_lba(&self, reader: &mut dyn SectorSource, path: &str) -> Result<u32> {
let parts: Vec<&str> = path.trim_matches('/').split('/').collect();
let mut current = &self.root;
for part in &parts[..parts.len() - 1] {
current = current
.entries
.iter()
.find(|e| e.is_dir && e.name.eq_ignore_ascii_case(part))
.ok_or_else(|| Error::UdfNotFound {
path: part.to_string(),
})?;
}
let filename = match parts.last() {
Some(f) => f,
None => {
return Err(Error::UdfNotFound {
path: path.to_string(),
});
}
};
let entry = current
.entries
.iter()
.find(|e| !e.is_dir && e.name.eq_ignore_ascii_case(filename))
.ok_or_else(|| Error::UdfNotFound {
path: path.to_string(),
})?;
let (data_lba, _) = self.read_icb_extent(reader, entry.meta_lba)?;
self.partition_start
.checked_add(data_lba)
.ok_or(Error::DiscRead {
sector: self.partition_start as u64,
status: None,
sense: None,
})
}
pub fn read_file(&self, reader: &mut dyn SectorSource, path: &str) -> Result<Vec<u8>> {
let parts: Vec<&str> = path.trim_matches('/').split('/').collect();
let mut current = &self.root;
for part in &parts[..parts.len() - 1] {
current = current
.entries
.iter()
.find(|e| e.is_dir && e.name.eq_ignore_ascii_case(part))
.ok_or_else(|| Error::UdfNotFound {
path: part.to_string(),
})?;
}
let filename = match parts.last() {
Some(f) => f,
None => {
return Err(Error::UdfNotFound {
path: path.to_string(),
});
}
};
let entry = current
.entries
.iter()
.find(|e| !e.is_dir && e.name.eq_ignore_ascii_case(filename))
.ok_or_else(|| Error::UdfNotFound {
path: path.to_string(),
})?;
let extents = self.read_icb_extents(reader, entry.meta_lba)?;
if entry.size > MAX_FILE_BYTES {
return Err(Error::DiscRead {
sector: self.partition_start as u64,
status: None,
sense: None,
});
}
let mut data = Vec::with_capacity(entry.size as usize);
let mut sector = [0u8; 2048];
for (data_lba, data_len) in extents {
if data.len() as u64 + data_len as u64 > MAX_FILE_BYTES {
return Err(Error::DiscRead {
sector: self.partition_start as u64,
status: None,
sense: None,
});
}
if data_len as u64 > MAX_FILE_BYTES {
return Err(Error::DiscRead {
sector: self.partition_start as u64,
status: None,
sense: None,
});
}
let abs_start = self
.partition_start
.checked_add(data_lba)
.ok_or(Error::DiscRead {
sector: self.partition_start as u64,
status: None,
sense: None,
})?;
let sector_count = (data_len as u64).div_ceil(2048) as u32;
for i in 0..sector_count {
let abs = abs_start.checked_add(i).ok_or(Error::DiscRead {
sector: abs_start as u64,
status: None,
sense: None,
})?;
read_sector(reader, abs, &mut sector)?;
data.extend_from_slice(§or);
}
}
if data.len() > entry.size as usize {
data.truncate(entry.size as usize);
}
Ok(data)
}
pub fn metadata_sector_ranges(&self, reader: &mut dyn SectorSource) -> Result<Vec<(u32, u32)>> {
let mut ranges = Vec::new();
let meta_end = self.metadata_start.saturating_add(self.metadata_sectors);
ranges.push((0, meta_end));
self.collect_file_ranges(reader, &self.root, &mut ranges)?;
ranges.sort_by_key(|r| r.0);
let merged = merge_ranges(&ranges);
Ok(merged)
}
pub fn all_sector_ranges(&self, reader: &mut dyn SectorSource) -> Result<Vec<(u32, u32)>> {
let mut ranges = Vec::new();
let meta_end = self.metadata_start.saturating_add(self.metadata_sectors);
ranges.push((0, meta_end));
self.collect_all_file_ranges(reader, &self.root, &mut ranges)?;
ranges.sort_by_key(|r| r.0);
let merged = merge_ranges(&ranges);
Ok(merged)
}
fn collect_all_file_ranges(
&self,
reader: &mut dyn SectorSource,
entry: &DirEntry,
ranges: &mut Vec<(u32, u32)>,
) -> Result<()> {
for child in &entry.entries {
if child.is_dir {
self.collect_all_file_ranges(reader, child, ranges)?;
} else {
ranges.push((self.meta_to_abs(child.meta_lba)?, 1));
if let Ok(extents) = self.read_icb_extents(reader, child.meta_lba) {
for (data_lba, data_len) in extents {
let abs_start = match self.partition_start.checked_add(data_lba) {
Some(v) => v,
None => continue,
};
let sector_count = (data_len as u64).div_ceil(2048) as u32;
ranges.push((abs_start, sector_count));
}
}
}
}
Ok(())
}
fn collect_file_ranges(
&self,
reader: &mut dyn SectorSource,
entry: &DirEntry,
ranges: &mut Vec<(u32, u32)>,
) -> Result<()> {
for child in &entry.entries {
if child.is_dir {
if child.name.eq_ignore_ascii_case("STREAM") {
continue;
}
self.collect_file_ranges(reader, child, ranges)?;
} else {
ranges.push((self.meta_to_abs(child.meta_lba)?, 1));
if child.size > 50_000_000 {
continue;
}
if let Ok(extents) = self.read_icb_extents(reader, child.meta_lba) {
for (data_lba, data_len) in extents {
let abs_start = match self.partition_start.checked_add(data_lba) {
Some(v) => v,
None => continue,
};
let sector_count = (data_len as u64).div_ceil(2048) as u32;
ranges.push((abs_start, sector_count));
}
}
}
}
Ok(())
}
fn meta_to_abs(&self, meta_lba: u32) -> Result<u32> {
self.metadata_start
.checked_add(meta_lba)
.ok_or(Error::DiscRead {
sector: self.metadata_start as u64,
status: None,
sense: None,
})
}
fn read_icb_extent(&self, reader: &mut dyn SectorSource, meta_lba: u32) -> Result<(u32, u32)> {
let extents = self.read_icb_extents(reader, meta_lba)?;
extents.first().copied().ok_or(Error::DiscRead {
sector: self.meta_to_abs(meta_lba).unwrap_or(0) as u64,
status: None,
sense: None,
})
}
fn read_icb_extents(
&self,
reader: &mut dyn SectorSource,
meta_lba: u32,
) -> Result<Vec<(u32, u32)>> {
let icb_abs = self.meta_to_abs(meta_lba)?;
let mut icb = [0u8; 2048];
read_sector(reader, icb_abs, &mut icb)?;
let tag = u16::from_le_bytes([icb[0], icb[1]]);
let (ad_offset, l_ad) = match tag {
266 => {
let l_ea = u32::from_le_bytes([icb[208], icb[209], icb[210], icb[211]]) as usize;
let l_ad = u32::from_le_bytes([icb[212], icb[213], icb[214], icb[215]]) as usize;
let ad_offset = 216 + l_ea;
if ad_offset + l_ad > icb.len() {
return Err(Error::DiscRead {
sector: icb_abs as u64,
status: None,
sense: None,
});
}
(ad_offset, l_ad)
}
261 => {
let l_ea = u32::from_le_bytes([icb[168], icb[169], icb[170], icb[171]]) as usize;
let l_ad = u32::from_le_bytes([icb[172], icb[173], icb[174], icb[175]]) as usize;
let ad_offset = 176 + l_ea;
if ad_offset + l_ad > icb.len() {
return Err(Error::DiscRead {
sector: icb_abs as u64,
status: None,
sense: None,
});
}
(ad_offset, l_ad)
}
_ => {
return Err(Error::DiscRead {
sector: icb_abs as u64,
status: None,
sense: None,
});
}
};
let mut extents = Vec::new();
let mut block = icb;
let mut ad_start = ad_offset;
let mut ad_bytes = l_ad;
const MAX_AD_BLOCKS: usize = 256;
for _ in 0..MAX_AD_BLOCKS {
let num_descriptors = ad_bytes / 8; let mut next_block: Option<u32> = None;
for i in 0..num_descriptors {
let off = ad_start + i * 8;
if off + 8 > block.len() {
break;
}
let raw_len = u32::from_le_bytes([
block[off],
block[off + 1],
block[off + 2],
block[off + 3],
]);
let extent_type = raw_len >> 30;
let data_len = raw_len & 0x3FFF_FFFF;
let data_lba = u32::from_le_bytes([
block[off + 4],
block[off + 5],
block[off + 6],
block[off + 7],
]);
match extent_type {
0 if data_len == 0 => break,
0 => extents.push((data_lba, data_len)),
1 => {} 3 => {
if data_len > 0 {
next_block = Some(data_lba);
}
break;
}
_ => break,
}
}
match next_block {
Some(cont_lba) => {
read_sector(reader, self.meta_to_abs(cont_lba)?, &mut block)?;
ad_start = 0;
ad_bytes = block.len();
}
None => break,
}
}
Ok(extents)
}
pub fn file_extents(
&self,
reader: &mut dyn SectorSource,
path: &str,
) -> Result<Vec<(u32, u32)>> {
let parts: Vec<&str> = path.trim_matches('/').split('/').collect();
let mut current = &self.root;
for part in &parts[..parts.len() - 1] {
current = current
.entries
.iter()
.find(|e| e.is_dir && e.name.eq_ignore_ascii_case(part))
.ok_or_else(|| Error::UdfNotFound {
path: part.to_string(),
})?;
}
let filename = match parts.last() {
Some(f) => f,
None => {
return Err(Error::UdfNotFound {
path: path.to_string(),
});
}
};
let entry = current
.entries
.iter()
.find(|e| !e.is_dir && e.name.eq_ignore_ascii_case(filename))
.ok_or_else(|| Error::UdfNotFound {
path: path.to_string(),
})?;
let alloc_extents = self.read_icb_extents(reader, entry.meta_lba)?;
let mut disc_extents = Vec::with_capacity(alloc_extents.len());
for (lba, byte_len) in alloc_extents {
let abs_lba = self
.partition_start
.checked_add(lba)
.ok_or(Error::DiscRead {
sector: self.partition_start as u64,
status: None,
sense: None,
})?;
let sectors = (byte_len as u64).div_ceil(2048) as u32;
disc_extents.push((abs_lba, sectors));
}
Ok(disc_extents)
}
}
pub fn read_filesystem(reader: &mut dyn SectorSource) -> Result<UdfFs> {
let mut avdp = [0u8; 2048];
read_sector(reader, 256, &mut avdp)?;
let tag_id = u16::from_le_bytes([avdp[0], avdp[1]]);
if tag_id != 2 {
return Err(Error::DiscRead {
sector: 256,
status: None,
sense: None,
});
}
let mut partition_start: u32 = 0;
let mut num_partition_maps: u32 = 0;
let mut lvd_sector: Option<u32> = None;
let mut volume_id = String::new();
let mut metadata_size_bytes: u32 = 0;
for i in 32..64 {
let mut desc = [0u8; 2048];
read_sector(reader, i, &mut desc)?;
let desc_tag = u16::from_le_bytes([desc[0], desc[1]]);
match desc_tag {
1 => {
volume_id = parse_dstring(&desc[24..56]);
}
5 => {
partition_start = u32::from_le_bytes([desc[188], desc[189], desc[190], desc[191]]);
}
6 => {
num_partition_maps =
u32::from_le_bytes([desc[268], desc[269], desc[270], desc[271]]);
lvd_sector = Some(i);
}
8 => break,
_ => continue,
}
}
if partition_start == 0 {
return Err(Error::DiscRead {
sector: 0,
status: None,
sense: None,
});
}
let metadata_start = if num_partition_maps >= 2 {
let lvd_sec = lvd_sector.ok_or(Error::DiscRead {
sector: 0,
status: None,
sense: None,
})?;
let mut lvd = [0u8; 2048];
read_sector(reader, lvd_sec, &mut lvd)?;
let _pm1_type = lvd[440]; let pm1_len = lvd[441] as usize;
if pm1_len > 0 && 440 + pm1_len < 2048 {
let pm2_type = lvd[440 + pm1_len];
if pm2_type == 2 {
let meta_file_lba = partition_start; let mut meta_icb = [0u8; 2048];
read_sector(reader, meta_file_lba, &mut meta_icb)?;
let meta_tag = u16::from_le_bytes([meta_icb[0], meta_icb[1]]);
if meta_tag == 266 {
let l_ea = u32::from_le_bytes([
meta_icb[208],
meta_icb[209],
meta_icb[210],
meta_icb[211],
]) as usize;
let ad_off = 216 + l_ea;
if ad_off + 8 > meta_icb.len() {
return Err(Error::DiscRead {
sector: meta_file_lba as u64,
status: None,
sense: None,
});
}
let ad_len = u32::from_le_bytes([
meta_icb[ad_off],
meta_icb[ad_off + 1],
meta_icb[ad_off + 2],
meta_icb[ad_off + 3],
]) & 0x3FFF_FFFF;
metadata_size_bytes = ad_len;
let ad_pos = u32::from_le_bytes([
meta_icb[ad_off + 4],
meta_icb[ad_off + 5],
meta_icb[ad_off + 6],
meta_icb[ad_off + 7],
]);
partition_start.checked_add(ad_pos).ok_or(Error::DiscRead {
sector: partition_start as u64,
status: None,
sense: None,
})?
} else {
partition_start
}
} else {
partition_start
}
} else {
partition_start
}
} else {
partition_start
};
let mut fsd = [0u8; 2048];
read_sector(reader, metadata_start, &mut fsd)?;
let fsd_tag = u16::from_le_bytes([fsd[0], fsd[1]]);
if fsd_tag != 256 {
return Err(Error::DiscRead {
sector: metadata_start as u64,
status: None,
sense: None,
});
}
let root_lba = u32::from_le_bytes([fsd[404], fsd[405], fsd[406], fsd[407]]);
let root = read_directory(reader, partition_start, metadata_start, root_lba, "", 0)?;
let metadata_sectors = (metadata_size_bytes as u64).div_ceil(2048) as u32;
Ok(UdfFs {
root,
volume_id,
partition_start,
metadata_start,
metadata_sectors,
})
}
const MAX_DIR_DEPTH: u32 = 8;
#[allow(clippy::only_used_in_recursion)]
fn read_directory(
reader: &mut dyn SectorSource,
part_start: u32,
meta_start: u32,
meta_lba: u32,
name: &str,
depth: u32,
) -> Result<DirEntry> {
let icb_abs = meta_start.checked_add(meta_lba).ok_or(Error::DiscRead {
sector: meta_start as u64,
status: None,
sense: None,
})?;
let mut icb = [0u8; 2048];
read_sector(reader, icb_abs, &mut icb)?;
let tag = u16::from_le_bytes([icb[0], icb[1]]);
let (ad_len, ad_pos) = match tag {
266 => {
let l_ea = u32::from_le_bytes([icb[208], icb[209], icb[210], icb[211]]) as usize;
let ad_off = 216 + l_ea;
if ad_off + 8 > icb.len() {
return Err(Error::DiscRead {
sector: icb_abs as u64,
status: None,
sense: None,
});
}
let len = u32::from_le_bytes([
icb[ad_off],
icb[ad_off + 1],
icb[ad_off + 2],
icb[ad_off + 3],
]) & 0x3FFF_FFFF;
let pos = u32::from_le_bytes([
icb[ad_off + 4],
icb[ad_off + 5],
icb[ad_off + 6],
icb[ad_off + 7],
]);
(len, pos)
}
261 => {
let l_ea = u32::from_le_bytes([icb[168], icb[169], icb[170], icb[171]]) as usize;
let ad_off = 176 + l_ea;
if ad_off + 8 > icb.len() {
return Err(Error::DiscRead {
sector: icb_abs as u64,
status: None,
sense: None,
});
}
let len = u32::from_le_bytes([
icb[ad_off],
icb[ad_off + 1],
icb[ad_off + 2],
icb[ad_off + 3],
]) & 0x3FFF_FFFF;
let pos = u32::from_le_bytes([
icb[ad_off + 4],
icb[ad_off + 5],
icb[ad_off + 6],
icb[ad_off + 7],
]);
(len, pos)
}
_ => {
return Ok(DirEntry {
name: name.to_string(),
is_dir: true,
meta_lba,
size: 0,
entries: Vec::new(),
});
}
};
if ad_len > MAX_DIR_BYTES {
return Err(Error::DiscRead {
sector: meta_start as u64,
status: None,
sense: None,
});
}
let dir_abs = meta_start.checked_add(ad_pos).ok_or(Error::DiscRead {
sector: meta_start as u64,
status: None,
sense: None,
})?;
let sector_count = ad_len.div_ceil(2048);
let mut dir_data = vec![0u8; sector_count as usize * 2048];
for i in 0..sector_count {
let abs = dir_abs.checked_add(i).ok_or(Error::DiscRead {
sector: dir_abs as u64,
status: None,
sense: None,
})?;
read_sector(
reader,
abs,
&mut dir_data[(i as usize) * 2048..(i as usize + 1) * 2048],
)?;
}
let mut entries = Vec::new();
let mut pos = 0;
while pos + 38 < dir_data.len().min(ad_len as usize) {
let fid_tag = u16::from_le_bytes([dir_data[pos], dir_data[pos + 1]]);
if fid_tag != 257 {
break;
}
let file_chars = dir_data[pos + 18];
let l_fi = dir_data[pos + 19] as usize;
let icb_lba = u32::from_le_bytes([
dir_data[pos + 24],
dir_data[pos + 25],
dir_data[pos + 26],
dir_data[pos + 27],
]);
let l_iu = u16::from_le_bytes([dir_data[pos + 36], dir_data[pos + 37]]) as usize;
let is_dir = (file_chars & 0x02) != 0;
let is_parent = (file_chars & 0x08) != 0;
if !is_parent && l_fi > 0 {
let name_start = pos + 38 + l_iu;
let name_end = name_start + l_fi;
if name_end > dir_data.len() {
break;
}
let entry_name = parse_udf_name(&dir_data[name_start..name_end]);
if !entry_name.is_empty() {
let file_size = read_file_size(reader, meta_start, icb_lba).unwrap_or(0);
if is_dir && depth < MAX_DIR_DEPTH {
let subdir = read_directory(
reader,
part_start,
meta_start,
icb_lba,
&entry_name,
depth + 1,
)?;
entries.push(subdir);
} else {
entries.push(DirEntry {
name: entry_name,
is_dir,
meta_lba: icb_lba,
size: file_size,
entries: Vec::new(),
});
}
}
}
let fid_len = (38 + l_iu + l_fi + 3) & !3;
pos += fid_len;
}
Ok(DirEntry {
name: name.to_string(),
is_dir: true,
meta_lba,
size: ad_len as u64,
entries,
})
}
fn read_file_size(reader: &mut dyn SectorSource, meta_start: u32, meta_lba: u32) -> Result<u64> {
let abs = meta_start.checked_add(meta_lba).ok_or(Error::DiscRead {
sector: meta_start as u64,
status: None,
sense: None,
})?;
let mut icb = [0u8; 2048];
read_sector(reader, abs, &mut icb)?;
let tag = u16::from_le_bytes([icb[0], icb[1]]);
match tag {
261 | 266 => Ok(u64::from_le_bytes([
icb[56], icb[57], icb[58], icb[59], icb[60], icb[61], icb[62], icb[63],
])),
_ => Ok(0),
}
}
fn parse_udf_name(data: &[u8]) -> String {
if data.is_empty() {
return String::new();
}
match data[0] {
8 => {
String::from_utf8_lossy(&data[1..]).trim().to_string()
}
16 => {
let mut s = String::new();
let chars = &data[1..];
for i in (0..chars.len()).step_by(2) {
if i + 1 < chars.len() {
let c = ((chars[i] as u16) << 8) | chars[i + 1] as u16;
if let Some(ch) = char::from_u32(c as u32) {
s.push(ch);
}
}
}
s.trim().to_string()
}
_ => String::from_utf8_lossy(&data[1..]).trim().to_string(),
}
}
fn merge_ranges(ranges: &[(u32, u32)]) -> Vec<(u32, u32)> {
if ranges.is_empty() {
return Vec::new();
}
let mut result = vec![ranges[0]];
for &(start, count) in &ranges[1..] {
let last = result.last_mut().unwrap();
let last_end = last.0.saturating_add(last.1);
if start <= last_end.saturating_add(1) {
let new_end = start.saturating_add(count).max(last_end);
last.1 = new_end - last.0;
} else {
result.push((start, count));
}
}
result
}
fn parse_dstring(data: &[u8]) -> String {
if data.is_empty() {
return String::new();
}
let len = *data.last().unwrap() as usize;
if len == 0 || len > data.len() {
return String::new();
}
let content = &data[..len];
if content.is_empty() {
return String::new();
}
match content[0] {
8 => String::from_utf8_lossy(&content[1..])
.trim_end_matches('\0')
.trim()
.to_string(),
16 => {
let mut s = String::new();
let chars = &content[1..];
for i in (0..chars.len()).step_by(2) {
if i + 1 < chars.len() {
let c = ((chars[i] as u16) << 8) | chars[i + 1] as u16;
if c != 0 {
if let Some(ch) = char::from_u32(c as u32) {
s.push(ch);
}
}
}
}
s.trim().to_string()
}
_ => String::from_utf8_lossy(&content[1..])
.trim_end_matches('\0')
.trim()
.to_string(),
}
}
pub(crate) struct BufferedSectorReader<'a> {
inner: &'a mut dyn SectorSource,
cache_start: u32,
cache: Vec<u8>,
cache_sectors: u32,
batch: u16,
prefetched: std::collections::HashMap<u32, Vec<u8>>,
}
impl<'a> BufferedSectorReader<'a> {
pub(crate) fn new(inner: &'a mut dyn SectorSource, batch: u16) -> Self {
Self {
inner,
cache_start: u32::MAX,
cache: Vec::new(),
cache_sectors: 0,
batch,
prefetched: std::collections::HashMap::new(),
}
}
}
impl BufferedSectorReader<'_> {
pub(crate) fn prefetch(&mut self, start_lba: u32, count: u32) {
let total = count as usize * 2048;
self.cache.resize(total, 0);
let mut offset = 0u32;
while offset < count {
let batch = (count - offset).min(self.batch as u32) as u16;
let buf_off = offset as usize * 2048;
if self
.inner
.read_sectors(
start_lba + offset,
batch,
&mut self.cache[buf_off..buf_off + batch as usize * 2048],
true,
)
.is_err()
{
break;
}
offset += batch as u32;
}
self.cache_start = start_lba;
self.cache_sectors = offset;
}
pub(crate) fn prefetch_ranges(&mut self, ranges: &[(u32, u32)]) {
let mut tmp = vec![0u8; self.batch as usize * 2048];
for &(start, count) in ranges {
let mut offset = 0u32;
while offset < count {
let batch = (count - offset).min(self.batch as u32) as u16;
let bytes = batch as usize * 2048;
if self
.inner
.read_sectors(start + offset, batch, &mut tmp[..bytes], true)
.is_err()
{
break;
}
for i in 0..batch as u32 {
let s = i as usize * 2048;
self.prefetched
.insert(start + offset + i, tmp[s..s + 2048].to_vec());
}
offset += batch as u32;
}
}
}
}
impl SectorSource for BufferedSectorReader<'_> {
fn read_sectors(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
_recovery: bool,
) -> std::result::Result<usize, crate::error::Error> {
if count == 1 {
if buf.len() < 2048 {
return Err(crate::error::Error::UdfBufferTooSmall);
}
if let Some(data) = self.prefetched.get(&lba) {
buf[..2048].copy_from_slice(data);
return Ok(2048);
}
if lba >= self.cache_start && lba < self.cache_start + self.cache_sectors {
let offset = (lba - self.cache_start) as usize * 2048;
buf[..2048].copy_from_slice(&self.cache[offset..offset + 2048]);
return Ok(2048);
}
let block = self.batch;
self.cache.resize(block as usize * 2048, 0);
match self.inner.read_sectors(lba, block, &mut self.cache, true) {
Ok(_) => {
self.cache_start = lba;
self.cache_sectors = block as u32;
}
Err(_) => {
self.cache.resize(2048, 0);
self.inner.read_sectors(lba, 1, &mut self.cache, true)?;
self.cache_start = lba;
self.cache_sectors = 1;
}
}
buf[..2048].copy_from_slice(&self.cache[..2048]);
Ok(2048)
} else {
self.inner.read_sectors(lba, count, buf, true)
}
}
}
fn read_sector(reader: &mut dyn SectorSource, lba: u32, buf: &mut [u8]) -> Result<()> {
reader.read_sectors(lba, 1, buf, true)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
struct MapReader {
sectors: HashMap<u32, [u8; 2048]>,
}
impl MapReader {
fn new() -> Self {
Self {
sectors: HashMap::new(),
}
}
fn put(&mut self, lba: u32, data: [u8; 2048]) {
self.sectors.insert(lba, data);
}
}
impl SectorSource for MapReader {
fn read_sectors(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
_recovery: bool,
) -> Result<usize> {
let need = count as usize * 2048;
if buf.len() < need {
return Err(Error::UdfBufferTooSmall);
}
for i in 0..count as u32 {
let off = i as usize * 2048;
let s = self.sectors.get(&(lba + i)).copied().unwrap_or([0u8; 2048]);
buf[off..off + 2048].copy_from_slice(&s);
}
Ok(need)
}
}
fn build_efe(info_length: u64, ads: &[(u32, u32, u32)]) -> [u8; 2048] {
let mut s = [0u8; 2048];
s[0..2].copy_from_slice(&266u16.to_le_bytes()); s[56..64].copy_from_slice(&info_length.to_le_bytes()); let l_ea: u32 = 0;
let l_ad: u32 = (ads.len() * 8) as u32;
s[208..212].copy_from_slice(&l_ea.to_le_bytes());
s[212..216].copy_from_slice(&l_ad.to_le_bytes());
let mut off = 216 + l_ea as usize;
for &(etype, dlen, dlba) in ads {
let raw_len = (etype << 30) | (dlen & 0x3FFF_FFFF);
s[off..off + 4].copy_from_slice(&raw_len.to_le_bytes());
s[off + 4..off + 8].copy_from_slice(&dlba.to_le_bytes());
off += 8;
}
s
}
fn build_cont_block(ads: &[(u32, u32, u32)]) -> [u8; 2048] {
let mut s = [0u8; 2048];
let mut off = 0usize;
for &(etype, dlen, dlba) in ads {
let raw_len = (etype << 30) | (dlen & 0x3FFF_FFFF);
s[off..off + 4].copy_from_slice(&raw_len.to_le_bytes());
s[off + 4..off + 8].copy_from_slice(&dlba.to_le_bytes());
off += 8;
}
s
}
fn fs_with(part_start: u32, meta_start: u32, root: DirEntry) -> UdfFs {
UdfFs {
root,
volume_id: String::new(),
partition_start: part_start,
metadata_start: meta_start,
metadata_sectors: 0,
}
}
fn file_entry(name: &str, meta_lba: u32, size: u64) -> DirEntry {
DirEntry {
name: name.to_string(),
is_dir: false,
meta_lba,
size,
entries: Vec::new(),
}
}
#[test]
fn icb_extents_follow_type3_continuation() {
let part_start = 1000;
let meta_start = 100;
let icb = build_efe(
6144,
&[
(0, 4096, 10), (3, 2048, 50), ],
);
let cont = build_cont_block(&[(0, 2048, 20)]);
let mut reader = MapReader::new();
reader.put(meta_start + 5, icb);
reader.put(meta_start + 50, cont);
let fs = fs_with(part_start, meta_start, file_entry("X", 5, 6144));
let extents = fs.read_icb_extents(&mut reader, 5).expect("extents");
assert_eq!(extents, vec![(10, 4096), (20, 2048)]);
}
#[test]
fn read_file_spans_multiple_extents() {
let part_start = 0;
let meta_start = 0;
let icb = build_efe(4096, &[(0, 2048, 10), (0, 2048, 30)]);
let mut reader = MapReader::new();
reader.put(5, icb);
reader.put(10, [0xAA; 2048]);
reader.put(30, [0xBB; 2048]);
let root = DirEntry {
name: String::new(),
is_dir: true,
meta_lba: 0,
size: 0,
entries: vec![file_entry("F", 5, 4096)],
};
let fs = fs_with(part_start, meta_start, root);
let data = fs.read_file(&mut reader, "/F").expect("read");
assert_eq!(data.len(), 4096);
assert!(data[..2048].iter().all(|&b| b == 0xAA));
assert!(data[2048..].iter().all(|&b| b == 0xBB));
}
#[test]
fn merge_ranges_saturates_near_u32_max() {
let ranges = [(u32::MAX - 1, 2), (u32::MAX, 5)];
let merged = merge_ranges(&ranges);
assert_eq!(merged.len(), 1);
assert_eq!(merged[0].0, u32::MAX - 1);
}
#[test]
fn buffered_reader_short_buf_errors_not_panics() {
let mut inner = MapReader::new();
inner.put(0, [0u8; 2048]);
let mut br = BufferedSectorReader::new(&mut inner, 8);
let mut tiny = [0u8; 100];
let err = br.read_sectors(0, 1, &mut tiny, true);
assert!(matches!(err, Err(Error::UdfBufferTooSmall)));
}
struct MemReader {
sectors: HashMap<u32, [u8; 2048]>,
}
impl MemReader {
fn new() -> Self {
Self {
sectors: HashMap::new(),
}
}
fn put(&mut self, lba: u32, sector: [u8; 2048]) {
self.sectors.insert(lba, sector);
}
}
impl SectorSource for MemReader {
fn read_sectors(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
_recovery: bool,
) -> Result<usize> {
for i in 0..count as u32 {
let off = i as usize * 2048;
let dst = &mut buf[off..off + 2048];
match self.sectors.get(&(lba + i)) {
Some(s) => dst.copy_from_slice(s),
None => dst.fill(0),
}
}
Ok(count as usize * 2048)
}
}
fn build_efe_icb(info_len: u64, data_len: u32, data_lba: u32) -> [u8; 2048] {
let mut icb = [0u8; 2048];
icb[0..2].copy_from_slice(&266u16.to_le_bytes());
icb[56..64].copy_from_slice(&info_len.to_le_bytes());
icb[208..212].copy_from_slice(&0u32.to_le_bytes());
icb[212..216].copy_from_slice(&8u32.to_le_bytes());
icb[216..220].copy_from_slice(&(data_len & 0x3FFF_FFFF).to_le_bytes());
icb[220..224].copy_from_slice(&data_lba.to_le_bytes());
icb
}
fn fs_with_file(meta_lba: u32, size: u64) -> UdfFs {
UdfFs {
root: DirEntry {
name: String::new(),
is_dir: true,
meta_lba: 0,
size: 0,
entries: vec![DirEntry {
name: "F".to_string(),
is_dir: false,
meta_lba,
size,
entries: Vec::new(),
}],
},
volume_id: String::new(),
partition_start: 0,
metadata_start: 0,
metadata_sectors: 0,
}
}
#[test]
fn read_file_rejects_oversized_extent_before_allocating() {
let oversized = MAX_FILE_BYTES as u32 + 2048;
let icb = build_efe_icb(oversized as u64, oversized, 100);
let mut reader = MemReader::new();
reader.put(10, icb);
let fs = fs_with_file(10, oversized as u64);
let err = fs.read_file(&mut reader, "/F").unwrap_err();
assert!(matches!(err, Error::DiscRead { .. }));
}
fn build_efe_icb_multi(info_len: u64, ads: &[(u32, u32)]) -> [u8; 2048] {
let mut icb = [0u8; 2048];
icb[0..2].copy_from_slice(&266u16.to_le_bytes());
icb[56..64].copy_from_slice(&info_len.to_le_bytes());
let l_ad = (ads.len() * 8) as u32;
icb[208..212].copy_from_slice(&0u32.to_le_bytes());
icb[212..216].copy_from_slice(&l_ad.to_le_bytes());
for (i, (data_len, data_lba)) in ads.iter().enumerate() {
let off = 216 + i * 8;
icb[off..off + 4].copy_from_slice(&(data_len & 0x3FFF_FFFF).to_le_bytes());
icb[off + 4..off + 8].copy_from_slice(&data_lba.to_le_bytes());
}
icb
}
#[test]
fn read_file_rejects_cumulative_extents_over_cap() {
let big = MAX_FILE_BYTES as u32;
let icb = build_efe_icb_multi(MAX_FILE_BYTES * 2, &[(2048, 100), (big, 200_000)]);
let mut reader = MemReader::new();
reader.put(10, icb);
let mut data_sector = [0u8; 2048];
data_sector[0] = 0xCD;
reader.put(100, data_sector);
let fs = fs_with_file(10, 2048);
let err = fs.read_file(&mut reader, "/F").unwrap_err();
assert!(matches!(err, Error::DiscRead { .. }));
}
#[test]
fn read_file_rejects_oversized_info_length() {
let icb = build_efe_icb(0, 2048, 100);
let mut reader = MemReader::new();
reader.put(10, icb);
let fs = fs_with_file(10, MAX_FILE_BYTES + 1);
let err = fs.read_file(&mut reader, "/F").unwrap_err();
assert!(matches!(err, Error::DiscRead { .. }));
}
#[test]
fn read_file_accepts_small_file() {
let icb = build_efe_icb(2048, 2048, 100);
let mut reader = MemReader::new();
reader.put(10, icb);
let mut data_sector = [0u8; 2048];
data_sector[0] = 0xAB;
reader.put(100, data_sector);
let fs = fs_with_file(10, 2048);
let data = fs
.read_file(&mut reader, "/F")
.expect("small file should read");
assert_eq!(data.len(), 2048);
assert_eq!(data[0], 0xAB);
}
#[test]
fn read_directory_rejects_oversized_dir_before_allocating() {
let oversized = MAX_DIR_BYTES + 2048;
let icb = build_efe_icb(oversized as u64, oversized, 50);
let mut reader = MemReader::new();
reader.put(5, icb);
let err = read_directory(&mut reader, 0, 0, 5, "DIR", 0).unwrap_err();
assert!(matches!(err, Error::DiscRead { .. }));
}
#[test]
fn read_directory_accepts_small_empty_dir() {
let icb = build_efe_icb(2048, 2048, 50);
let mut reader = MemReader::new();
reader.put(5, icb);
let dir = read_directory(&mut reader, 0, 0, 5, "DIR", 0).expect("small dir parses");
assert!(dir.entries.is_empty());
assert!(dir.is_dir);
}
}