use std::sync::Arc;
use forensic_vfs::adapters::SubRange;
use forensic_vfs::{
DynSource, ImageSource, VfsError, VfsResult, VolumeDesc, VolumeKind, VolumeScheme, VolumeSystem,
};
fn fill(src: &dyn ImageSource, mut off: u64, mut buf: &mut [u8]) -> VfsResult<()> {
while !buf.is_empty() {
let n = src.read_at(off, buf)?;
if n == 0 {
break; }
off = off.saturating_add(n as u64);
let Some(rest) = buf.get_mut(n..) else {
break; };
buf = rest;
}
Ok(())
}
pub struct MbrVolumes {
parent: DynSource,
volumes: Vec<VolumeDesc>,
}
impl MbrVolumes {
pub fn open(parent: DynSource) -> VfsResult<Self> {
let sector_size = crate::SECTOR_SIZE as u64;
let mut sector = vec![0u8; crate::SECTOR_SIZE];
fill(&*parent, 0, &mut sector)?;
let mbr = crate::parse_mbr_sector(§or).map_err(|_| VfsError::Unsupported {
layer: "MbrVolumes",
scheme: "MBR".to_string(),
})?;
let volumes = mbr
.entries
.iter()
.filter(|e| !e.is_empty())
.enumerate()
.map(|(i, e)| VolumeDesc {
index: i,
kind: VolumeKind::Partition,
start: u64::from(e.lba_start).saturating_mul(sector_size),
len: u64::from(e.lba_count).saturating_mul(sector_size),
type_hint: Some(format!("0x{:02X}", e.type_code.0)),
label: None,
})
.collect();
Ok(Self { parent, volumes })
}
}
impl VolumeSystem for MbrVolumes {
fn scheme(&self) -> VolumeScheme {
VolumeScheme::Mbr
}
fn volumes(&self) -> &[VolumeDesc] {
&self.volumes
}
fn open_volume(&self, index: usize) -> VfsResult<DynSource> {
let v = self.volumes.get(index).ok_or(VfsError::OutOfRange {
what: "mbr volume index",
offset: index as u64,
len: 1,
bound: self.volumes.len() as u64,
})?;
Ok(Arc::new(SubRange::new(self.parent.clone(), v.start, v.len)))
}
}
#[cfg(test)]
mod tests {
use super::MbrVolumes;
use forensic_vfs::adapters::{FileSource, SeekPoolSource};
use forensic_vfs::{DynSource, VfsError, VolumeKind, VolumeScheme, VolumeSystem};
use std::io::Cursor;
use std::sync::Arc;
fn mem_source(bytes: Vec<u8>, advertised_len: u64) -> DynSource {
Arc::new(SeekPoolSource::single(Cursor::new(bytes), advertised_len))
}
fn real_mbr() -> DynSource {
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../tests/data/dftt_mmls_1_mbr.dd"
);
Arc::new(FileSource::open(path).expect("open real MBR fixture"))
}
#[test]
fn mbr_volumes_match_mmls_fdisk_oracle() {
let vs = MbrVolumes::open(real_mbr()).expect("real MBR must parse");
assert_eq!(vs.scheme(), VolumeScheme::Mbr);
let vols = vs.volumes();
assert_eq!(vols.len(), 2, "two used primaries (slots 2,3 empty)");
assert_eq!(vols[0].kind, VolumeKind::Partition);
assert_eq!(vols[0].start, 128 * 512);
assert_eq!(vols[0].len, 55296 * 512);
assert_eq!(vols[0].type_hint.as_deref(), Some("0x07"));
assert_eq!(vols[1].start, 55424 * 512);
assert_eq!(vols[1].len, 61440 * 512);
assert_eq!(vols[1].type_hint.as_deref(), Some("0x07"));
}
#[test]
fn open_volume_ok_for_valid_err_for_invalid() {
let vs = MbrVolumes::open(real_mbr()).expect("parse");
assert!(vs.open_volume(0).is_ok());
assert!(vs.open_volume(99).is_err());
}
#[test]
fn open_rejects_non_mbr_sector_zero() {
let vs = MbrVolumes::open(mem_source(vec![0u8; 512], 512));
assert!(matches!(vs, Err(VfsError::Unsupported { scheme, .. }) if scheme == "MBR"));
}
#[test]
fn open_tolerates_truncated_source_then_rejects() {
let vs = MbrVolumes::open(mem_source(vec![0u8; 100], 512));
assert!(matches!(vs, Err(VfsError::Unsupported { .. })));
}
}