use crate::interpret::{identify_bytes, Candidate};
pub struct CarveHit {
pub offset: usize,
pub lane: String,
pub reading: Candidate,
}
const MAX_CARVE_BYTES: usize = 4096;
#[must_use]
pub fn carve(bytes: &[u8], min_score: f64, window: Option<(i128, i128)>) -> Vec<CarveHit> {
let n = bytes.len().min(MAX_CARVE_BYTES);
let in_window = |i: i128| window.is_none_or(|(lo, hi)| i >= lo && i < hi);
let mut hits = Vec::new();
for off in 0..n {
for (lane, cands) in identify_bytes(&bytes[off..n]) {
for c in cands {
if c.score >= min_score && in_window(c.instant.0) {
hits.push(CarveHit {
offset: off,
lane: lane.clone(),
reading: c,
});
}
}
}
}
hits.sort_by(|a, b| {
a.offset.cmp(&b.offset).then(
b.reading
.score
.partial_cmp(&a.reading.score)
.unwrap_or(std::cmp::Ordering::Equal),
)
});
hits
}
fn lane_size(lane: &str) -> usize {
if lane.contains("SYSTEMTIME") {
16
} else if lane.contains("u32") || lane.contains("FAT") {
4
} else {
8
}
}
#[must_use]
pub fn to_jsonl(hits: &[CarveHit]) -> String {
hits.iter()
.map(|h| {
serde_json::json!({
"offset": h.offset,
"size": lane_size(&h.lane),
"lane": h.lane,
"format": h.reading.format_id,
"rendered": h.reading.rendered,
"instant_ns": h.reading.instant.0.to_string(),
"score": h.reading.score,
"citation": h.reading.citation,
})
.to_string()
})
.collect::<Vec<_>>()
.join("\n")
}
#[must_use]
pub fn to_imhex_bookmarks(hits: &[CarveHit]) -> String {
let bookmarks: Vec<serde_json::Value> = hits
.iter()
.map(|h| {
serde_json::json!({
"region": { "address": h.offset, "size": lane_size(&h.lane) },
"name": h.reading.format_id,
"comment": format!(
"{} — {} (score {:.2})",
h.reading.rendered.as_deref().unwrap_or("?"),
h.lane,
h.reading.score
),
"color": 0x5000_ff00_u32, "locked": false,
})
})
.collect();
serde_json::json!({ "bookmarks": bookmarks }).to_string()
}
#[must_use]
pub fn to_timesketch_jsonl(hits: &[CarveHit]) -> String {
hits.iter()
.filter_map(|h| {
let dt = h.reading.rendered.as_deref()?;
Some(
serde_json::json!({
"datetime": dt,
"message": format!(
"timeglyph carve: {} at offset {} ({}), score {:.2}",
h.reading.format_id, h.offset, h.lane, h.reading.score
),
"timestamp_desc": h.reading.format_id,
"data_type": "timeglyph:carve:hit",
"offset": h.offset,
})
.to_string(),
)
})
.collect::<Vec<_>>()
.join("\n")
}