#![allow(clippy::result_large_err)]
use std::collections::HashMap;
use bumpalo::Bump;
use thiserror::Error;
use super::manifest::WevtManifestError;
#[derive(Debug, Error)]
pub enum WevtCacheError {
#[error(
"TEMP slice out of bounds (crim_index={crim_index}, offset={temp_offset}, size={temp_size}, len={len})"
)]
TempSliceOutOfBounds {
crim_index: usize,
temp_offset: u32,
temp_size: u32,
len: usize,
},
#[error("failed to parse CRIM/WEVT blob: {source}")]
CrimParse { source: WevtManifestError },
#[error("template GUID `{guid}` not found in cache")]
TemplateNotFound { guid: String },
#[error(
"TEMP too small to contain BinXML fragment for template_guid={guid} (len={len}, need >= {need})"
)]
TempTooSmall {
guid: String,
len: usize,
need: usize,
},
}
#[derive(Debug)]
enum TemplateSource {
TempBytes(Vec<u8>),
CrimSlice {
crim_index: usize,
temp_offset: u32,
temp_size: u32,
},
}
#[derive(Debug, Default)]
pub struct WevtCache {
crim_blobs: Vec<Vec<u8>>,
sources_by_guid: HashMap<String, TemplateSource>,
}
impl WevtCache {
pub fn new() -> Self {
WevtCache::default()
}
pub fn insert_temp_bytes(&mut self, template_guid: &str, temp_bytes: Vec<u8>) {
let guid = normalize_guid(template_guid);
self.sources_by_guid
.insert(guid, TemplateSource::TempBytes(temp_bytes));
}
pub fn add_wevt_blob(&mut self, bytes: Vec<u8>) -> Result<usize, WevtCacheError> {
let templates = crate::wevt_templates::extract_temp_templates_from_wevt_blob(&bytes)
.map_err(|source| WevtCacheError::CrimParse { source })?;
let crim_index = self.crim_blobs.len();
self.crim_blobs.push(bytes);
let mut inserted = 0usize;
for t in templates {
let g = normalize_guid(&t.header.guid.to_string());
let entry = self.sources_by_guid.entry(g);
if let std::collections::hash_map::Entry::Vacant(v) = entry {
v.insert(TemplateSource::CrimSlice {
crim_index,
temp_offset: t.temp_offset,
temp_size: t.temp_size,
});
inserted = inserted.saturating_add(1);
}
}
Ok(inserted)
}
pub(crate) fn load_temp_binxml_fragment_in<'a>(
&self,
template_guid: &str,
arena: &'a Bump,
) -> Result<&'a [u8], WevtCacheError> {
const TEMP_BINXML_OFFSET: usize = 40;
let guid = normalize_guid(template_guid);
let temp = self.get_temp_bytes_for_guid(&guid)?;
if temp.len() < TEMP_BINXML_OFFSET {
return Err(WevtCacheError::TempTooSmall {
guid,
len: temp.len(),
need: TEMP_BINXML_OFFSET,
});
}
Ok(arena.alloc_slice_copy(&temp[TEMP_BINXML_OFFSET..]))
}
fn get_temp_bytes_for_guid(&self, guid: &str) -> Result<&[u8], WevtCacheError> {
let Some(src) = self.sources_by_guid.get(guid) else {
return Err(WevtCacheError::TemplateNotFound {
guid: guid.to_string(),
});
};
match *src {
TemplateSource::TempBytes(ref bytes) => Ok(bytes),
TemplateSource::CrimSlice {
crim_index,
temp_offset,
temp_size,
} => {
let bytes = self
.crim_blobs
.get(crim_index)
.expect("crim_index out of bounds");
let start = temp_offset as usize;
let end = start.saturating_add(temp_size as usize);
if end > bytes.len() {
return Err(WevtCacheError::TempSliceOutOfBounds {
crim_index,
temp_offset,
temp_size,
len: bytes.len(),
});
}
Ok(&bytes[start..end])
}
}
}
}
pub fn normalize_guid(s: &str) -> String {
s.trim()
.trim_start_matches('{')
.trim_end_matches('}')
.to_ascii_lowercase()
}