use crate::integrity::{AnalysisProgress, ComputedHashes, EwfIntegrity, EwfIntegrityAnomaly};
use memmap2::Mmap;
use std::fs::File;
use std::io;
use std::path::{Path, PathBuf};
pub struct EwfIntegrityPath {
segment_paths: Vec<PathBuf>,
expected_md5: Option<[u8; 16]>,
expected_sha1: Option<[u8; 20]>,
expected_sha256: Option<[u8; 32]>,
}
impl EwfIntegrityPath {
pub fn from_path(path: impl AsRef<Path>) -> Self {
let base = path.as_ref();
Self {
segment_paths: discover_segments(base),
expected_md5: None,
expected_sha1: None,
expected_sha256: None,
}
}
pub fn from_paths(paths: &[impl AsRef<Path>]) -> Self {
Self {
segment_paths: paths.iter().map(|p| p.as_ref().to_path_buf()).collect(),
expected_md5: None,
expected_sha1: None,
expected_sha256: None,
}
}
pub fn with_expected_md5(mut self, hash: [u8; 16]) -> Self {
self.expected_md5 = Some(hash);
self
}
pub fn with_expected_sha1(mut self, hash: [u8; 20]) -> Self {
self.expected_sha1 = Some(hash);
self
}
pub fn with_expected_sha256(mut self, hash: [u8; 32]) -> Self {
self.expected_sha256 = Some(hash);
self
}
pub fn compute_hashes(&self) -> io::Result<Option<ComputedHashes>> {
let mmaps = self
.segment_paths
.iter()
.map(|p| {
let file = File::open(p)?;
unsafe { Mmap::map(&file) }
})
.collect::<io::Result<Vec<Mmap>>>()?;
let seg_refs: Vec<&[u8]> = mmaps.iter().map(|m| m.as_ref()).collect();
Ok(EwfIntegrity::from_segments(&seg_refs).compute_hashes())
}
pub fn analyse(&self) -> io::Result<Vec<EwfIntegrityAnomaly>> {
let mmaps = self
.segment_paths
.iter()
.map(|p| {
let file = File::open(p)?;
unsafe { Mmap::map(&file) }
})
.collect::<io::Result<Vec<Mmap>>>()?;
let seg_refs: Vec<&[u8]> = mmaps.iter().map(|m| m.as_ref()).collect();
let mut checker = EwfIntegrity::from_segments(&seg_refs);
if let Some(h) = self.expected_md5 {
checker = checker.with_expected_md5(h);
}
if let Some(h) = self.expected_sha1 {
checker = checker.with_expected_sha1(h);
}
if let Some(h) = self.expected_sha256 {
checker = checker.with_expected_sha256(h);
}
Ok(checker.analyse())
}
pub fn analyse_and_compute_hashes(
&self,
) -> io::Result<(Vec<EwfIntegrityAnomaly>, ComputedHashes)> {
let mmaps = self
.segment_paths
.iter()
.map(|p| {
let file = File::open(p)?;
unsafe { Mmap::map(&file) }
})
.collect::<io::Result<Vec<Mmap>>>()?;
let seg_refs: Vec<&[u8]> = mmaps.iter().map(|m| m.as_ref()).collect();
let mut checker = EwfIntegrity::from_segments(&seg_refs);
if let Some(h) = self.expected_md5 {
checker = checker.with_expected_md5(h);
}
if let Some(h) = self.expected_sha1 {
checker = checker.with_expected_sha1(h);
}
if let Some(h) = self.expected_sha256 {
checker = checker.with_expected_sha256(h);
}
let anomalies = checker.analyse();
let hashes = EwfIntegrity::from_segments(&seg_refs)
.compute_hashes()
.unwrap_or(ComputedHashes { md5: [0u8; 16], sha1: [0u8; 20], sha256: [0u8; 32] });
Ok((anomalies, hashes))
}
pub fn analyse_with_progress(
&self,
mut progress: impl FnMut(AnalysisProgress),
) -> io::Result<(Vec<EwfIntegrityAnomaly>, ())> {
let mmaps = self
.segment_paths
.iter()
.map(|p| {
let file = File::open(p)?;
unsafe { Mmap::map(&file) }
})
.collect::<io::Result<Vec<Mmap>>>()?;
let seg_refs: Vec<&[u8]> = mmaps.iter().map(|m| m.as_ref()).collect();
let mut checker = EwfIntegrity::from_segments(&seg_refs);
if let Some(h) = self.expected_md5 {
checker = checker.with_expected_md5(h);
}
if let Some(h) = self.expected_sha1 {
checker = checker.with_expected_sha1(h);
}
if let Some(h) = self.expected_sha256 {
checker = checker.with_expected_sha256(h);
}
let anomalies = checker.analyse_with_progress(&mut progress);
Ok((anomalies, ()))
}
}
fn discover_segments(base: &Path) -> Vec<PathBuf> {
let ext = match base.extension().and_then(|e| e.to_str()) {
Some(e) => e,
None => return vec![base.to_path_buf()],
};
let (prefix_char, has_x, digits) = match parse_ewf_extension(ext) {
Some(v) => v,
None => return vec![base.to_path_buf()],
};
let stem = match base.file_stem().and_then(|s| s.to_str()) {
Some(s) => s,
None => return vec![base.to_path_buf()],
};
let dir = base.parent().unwrap_or(Path::new("."));
let mut segments = Vec::new();
for n in 1u32.. {
let ext_str = make_ewf_extension(prefix_char, has_x, digits, n);
let candidate = dir.join(format!("{stem}.{ext_str}"));
if candidate.exists() {
segments.push(candidate);
} else {
break;
}
if n >= 999 {
break;
}
}
if segments.is_empty() {
vec![base.to_path_buf()]
} else {
segments
}
}
fn parse_ewf_extension(ext: &str) -> Option<(char, bool, usize)> {
let mut chars = ext.chars();
let prefix = chars.next()?;
if !prefix.is_ascii_alphabetic() {
return None;
}
let rest: String = chars.collect();
let has_x = rest.starts_with('x') || rest.starts_with('X');
let rest = rest.trim_start_matches(|c| c == 'x' || c == 'X');
if rest.chars().all(|c| c.is_ascii_digit()) && !rest.is_empty() {
Some((prefix, has_x, rest.len()))
} else {
None
}
}
fn make_ewf_extension(prefix: char, has_x: bool, digit_count: usize, n: u32) -> String {
let width = digit_count.max(2);
let x = if has_x { "x" } else { "" };
format!("{}{}{:0width$}", prefix, x, n, width = width)
}