use aho_corasick::AhoCorasick;
use crate::{CarveContext, CarvedItem, Carver, ConfidencePolicy, RecoveryMethod};
pub trait RegionSource {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> usize;
}
#[derive(Debug, Clone)]
pub struct Region<R> {
pub start: u64,
pub len: u64,
pub tag: R,
}
#[derive(Debug, Clone)]
pub struct SweptItem<R> {
pub region: R,
pub offset: u64,
pub item: CarvedItem,
}
#[derive(Debug, Clone)]
pub struct CarveOptions {
pub chunk_size: usize,
pub max_window: u64,
pub confidence_policy: ConfidencePolicy,
pub recovery_method: RecoveryMethod,
}
impl Default for CarveOptions {
fn default() -> Self {
Self {
chunk_size: 1 << 20, max_window: 256 << 20, confidence_policy: ConfidencePolicy::KeepAll,
recovery_method: RecoveryMethod::UnallocatedCarve,
}
}
}
pub fn sweep<S, R>(
source: &S,
regions: impl IntoIterator<Item = Region<R>>,
carvers: &[&dyn Carver],
opts: &CarveOptions,
) -> Vec<SweptItem<R>>
where
S: RegionSource,
R: Clone,
{
let mut patterns: Vec<&[u8]> = Vec::new();
let mut meta: Vec<(usize, crate::Signature)> = Vec::new();
for (ci, c) in carvers.iter().enumerate() {
for sig in c.signatures() {
patterns.push(sig.magic());
meta.push((ci, *sig));
}
}
if patterns.is_empty() {
return Vec::new();
}
let Ok(ac) = AhoCorasick::new(&patterns) else {
return Vec::new(); };
let longest = patterns.iter().map(|p| p.len()).max().unwrap_or(0);
let overlap = longest.saturating_sub(1);
let chunk_size = opts.chunk_size.max(longest).max(1);
let mut out: Vec<SweptItem<R>> = Vec::new();
for region in regions {
let region_end = region.start.saturating_add(region.len);
let mut carry: Vec<u8> = Vec::new();
let mut pos = region.start;
while pos < region_end {
let want = chunk_size.min(usize_saturating(region_end - pos));
if want == 0 {
break; }
let mut chunk = vec![0u8; want];
let n = source.read_at(pos, &mut chunk);
if n == 0 {
break;
}
chunk.truncate(n);
let chunk_new_start = pos;
let buffer_base = chunk_new_start - carry.len() as u64;
let mut buffer = Vec::with_capacity(carry.len() + chunk.len());
buffer.extend_from_slice(&carry);
buffer.extend_from_slice(&chunk);
for m in ac.find_overlapping_iter(&buffer) {
let abs_start = buffer_base + m.start() as u64;
let abs_end = buffer_base + m.end() as u64;
if abs_end <= chunk_new_start {
continue;
}
let (cidx, sig) = &meta[m.pattern().as_usize()];
let Some(artifact_start) = abs_start.checked_sub(sig.offset() as u64) else {
continue;
};
let Some(carver) = carvers.get(*cidx) else {
continue; };
let window_len = usize_saturating(carver.max_window().min(opts.max_window));
if window_len == 0 {
continue;
}
let mut window = vec![0u8; window_len];
let got = source.read_at(artifact_start, &mut window);
window.truncate(got);
let ctx = CarveContext::at(artifact_start)
.with_method(opts.recovery_method)
.with_policy(opts.confidence_policy);
for item in carver.carve(&window, &ctx) {
if keeps(opts.confidence_policy, item.confidence()) {
out.push(SweptItem {
region: region.tag.clone(),
offset: item.image_offset(),
item,
});
}
}
}
let ov = overlap.min(chunk.len());
carry = chunk[chunk.len() - ov..].to_vec();
pos += n as u64;
}
}
out
}
fn keeps(policy: ConfidencePolicy, confidence: f32) -> bool {
match policy {
ConfidencePolicy::KeepAll => true,
ConfidencePolicy::Minimum(floor) => confidence >= floor,
}
}
fn usize_saturating(v: u64) -> usize {
usize::try_from(v).unwrap_or(usize::MAX)
}