use std::cell::RefCell;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::path::Path;
use crate::Ad1Error;
pub(crate) const MARGIN: u64 = 512;
const MAX_SEGMENTS: u32 = 65_536;
#[derive(Debug)]
struct Seg {
handle: RefCell<File>,
data_len: u64,
}
#[derive(Debug)]
pub(crate) struct SegmentSet {
segs: Vec<Option<Seg>>,
stride: u64,
capacity: u64,
}
impl SegmentSet {
pub(crate) fn open(
first: &Path,
segment_count: u32,
fragments_size: u32,
) -> Result<Self, Ad1Error> {
if fragments_size == 0 {
return Err(Ad1Error::Malformed(
"segment header fragments_size is 0".into(),
));
}
if segment_count > MAX_SEGMENTS {
return Err(Ad1Error::Malformed(format!(
"segment header declares {segment_count} segments (> {MAX_SEGMENTS})"
)));
}
let stride = u64::from(fragments_size) * 65536 - MARGIN;
let first_str = first.to_string_lossy().to_string();
let mut base = first_str.clone();
base.pop();
let mut segs = Vec::new();
let mut capacity = 0u64;
for i in 1..=segment_count {
let path = if i == 1 {
first_str.clone()
} else {
format!("{base}{i}")
};
match File::open(&path) {
Ok(f) => {
let len = f.metadata().map_or(0, |m| m.len());
let data_len = len.saturating_sub(MARGIN);
capacity += data_len;
segs.push(Some(Seg {
handle: RefCell::new(f),
data_len,
}));
}
Err(_) => segs.push(None),
}
}
Ok(Self {
segs,
stride,
capacity,
})
}
pub(crate) fn capacity(&self) -> u64 {
self.capacity
}
pub(crate) fn missing(&self) -> Vec<u32> {
self.segs
.iter()
.enumerate()
.filter_map(|(i, s)| s.is_none().then_some(i as u32 + 1))
.collect()
}
pub(crate) fn read(&self, offset: u64, len: usize) -> Result<Vec<u8>, Ad1Error> {
if len == 0 {
return Ok(Vec::new());
}
if len as u64 > self.capacity {
return Err(Ad1Error::Malformed(format!(
"read of {len} bytes exceeds image data size {}",
self.capacity
)));
}
let mut out = vec![0u8; len];
let mut filled = 0usize;
let mut off = offset;
while filled < len {
let seg_idx = (off / self.stride) as usize;
let within = off % self.stride;
let seg = self
.segs
.get(seg_idx)
.and_then(|s| s.as_ref())
.ok_or_else(|| {
Ad1Error::Malformed(format!(
"logical offset {off} needs missing segment {}",
seg_idx + 1
))
})?;
if within >= seg.data_len {
return Err(Ad1Error::Malformed(format!(
"logical offset {off} past segment {} data ({} bytes)",
seg_idx + 1,
seg.data_len
)));
}
let avail = (seg.data_len - within) as usize;
let want = (len - filled).min(avail);
{
let mut fh = seg.handle.borrow_mut();
fh.seek(SeekFrom::Start(within + MARGIN))?;
fh.read_exact(&mut out[filled..filled + want])?;
}
filled += want;
off += want as u64;
}
Ok(out)
}
}