#![allow(clippy::unwrap_used, clippy::expect_used)]
use forensic_carve::{
sweep, CarveContext, CarveOptions, CarvedItem, Carver, RecoveryMethod, Region, RegionSource,
Signature,
};
const PAGE_SIZE: usize = 4096;
const MAGIC: &[u8] = b"CONFORM8";
const MAGIC_OFFSET: u64 = 5000;
struct DiskLikeSource(Vec<u8>);
impl RegionSource for DiskLikeSource {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> usize {
let off = offset as usize;
if off >= self.0.len() {
return 0;
}
let n = buf.len().min(self.0.len() - off);
buf[..n].copy_from_slice(&self.0[off..off + n]);
n
}
}
struct MemoryLikeSource {
pages: Vec<Vec<u8>>,
}
impl RegionSource for MemoryLikeSource {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> usize {
let start = offset as usize;
let mut written = 0;
while written < buf.len() {
let abs = start + written;
let page_idx = abs / PAGE_SIZE;
let intra = abs % PAGE_SIZE;
let Some(page) = self.pages.get(page_idx) else {
break;
};
if intra >= page.len() {
break;
}
let want = (buf.len() - written).min(page.len() - intra);
buf[written..written + want].copy_from_slice(&page[intra..intra + want]);
written += want;
}
written
}
}
struct ConformCarver;
impl Carver for ConformCarver {
fn format(&self) -> &'static str {
"conform"
}
fn signatures(&self) -> &[Signature] {
const SIGS: &[Signature] = &[Signature::new(MAGIC, 0)];
SIGS
}
fn max_window(&self) -> u64 {
16
}
fn carve(&self, window: &[u8], ctx: &CarveContext) -> Vec<CarvedItem> {
if window.starts_with(MAGIC) {
vec![CarvedItem::records(
"conform",
ctx.base_offset(),
0.95,
ctx.recovery_method(),
)]
} else {
Vec::new()
}
}
}
fn artifact_bytes() -> Vec<u8> {
let mut data = vec![b'.'; PAGE_SIZE * 3];
let start = MAGIC_OFFSET as usize;
data[start..start + MAGIC.len()].copy_from_slice(MAGIC);
data
}
fn split_pages(data: &[u8]) -> Vec<Vec<u8>> {
data.chunks(PAGE_SIZE).map(<[u8]>::to_vec).collect()
}
#[test]
fn same_carver_conforms_across_disk_and_memory_adapters() {
let data = artifact_bytes();
let disk = DiskLikeSource(data.clone());
let memory = MemoryLikeSource {
pages: split_pages(&data),
};
let mut d = [0u8; 8];
let mut m = [0u8; 8];
assert_eq!(disk.read_at(MAGIC_OFFSET, &mut d), 8);
assert_eq!(memory.read_at(MAGIC_OFFSET, &mut m), 8);
assert_eq!(
d, m,
"adapters must return identical bytes for the same offset"
);
let carvers: Vec<&dyn Carver> = vec![&ConformCarver];
let region = |tag: &'static str| {
vec![Region {
start: 0,
len: data.len() as u64,
tag,
}]
};
let disk_opts = CarveOptions {
recovery_method: RecoveryMethod::UnallocatedCarve,
..CarveOptions::default()
};
let disk_items = sweep(&disk, region("disk"), &carvers, &disk_opts);
let mem_opts = CarveOptions {
recovery_method: RecoveryMethod::MemoryCarve,
..CarveOptions::default()
};
let mem_items = sweep(&memory, region("memory"), &carvers, &mem_opts);
assert_eq!(disk_items.len(), 1, "disk sweep must find the artifact");
assert_eq!(mem_items.len(), 1, "memory sweep must find the artifact");
let disk_item = &disk_items[0].item;
let mem_item = &mem_items[0].item;
assert_eq!(disk_items[0].offset, MAGIC_OFFSET);
assert_eq!(mem_items[0].offset, MAGIC_OFFSET);
assert_eq!(disk_item.image_offset(), mem_item.image_offset());
assert_eq!(disk_item.format(), mem_item.format());
assert_eq!(disk_item.format(), "conform");
assert_eq!(
disk_item.confidence().to_bits(),
mem_item.confidence().to_bits()
);
assert_eq!(disk_item.payload(), mem_item.payload());
assert_eq!(
disk_item.recovery_method(),
RecoveryMethod::UnallocatedCarve
);
assert_eq!(mem_item.recovery_method(), RecoveryMethod::MemoryCarve);
assert_ne!(disk_item.recovery_method(), mem_item.recovery_method());
}