#[inline]
pub(crate) fn hot_pattern_index_at(
slots: &[HotPatternSlot],
text_bytes: &[u8],
offset: usize,
) -> Option<usize> {
let rest = text_bytes.get(offset..)?;
slots
.iter()
.enumerate()
.find_map(|(idx, slot)| rest.starts_with(&slot.prefix).then_some(idx))
}
#[derive(Debug)]
pub(crate) struct HotPatternSlot {
pub(crate) prefix: Box<[u8]>,
pub(crate) validator: regex::Regex,
pub(crate) ac_map_index: usize,
}
pub(crate) fn build_hot_pattern_validator(
detector: &keyhog_core::DetectorSpec,
) -> crate::error::Result<regex::Regex> {
let alts: Vec<String> = detector
.patterns
.iter()
.map(|p| format!("(?:{})", p.regex))
.collect();
if alts.is_empty() {
return Err(crate::error::ScanError::Config(format!(
"detector {} declares simdsieve prefixes but has no regex patterns",
detector.id
)));
}
let combined = format!("^(?:{})", alts.join("|"));
let re = regex::RegexBuilder::new(&combined)
.case_insensitive(true)
.size_limit(crate::types::REGEX_SIZE_LIMIT_BYTES)
.dfa_size_limit(crate::types::regex_dfa_limit())
.crlf(true)
.build()
.map_err(|source| crate::error::ScanError::RegexCompile {
detector_id: detector.id.clone(),
index: 0,
source,
})?;
Ok(re)
}
#[cfg(feature = "simdsieve")]
pub(crate) fn build_hot_pattern_validators(
detectors: &[keyhog_core::DetectorSpec],
) -> crate::error::Result<Vec<Option<regex::Regex>>> {
detectors
.iter()
.map(|detector| {
if detector.simdsieve_prefixes.is_empty() {
Ok(None)
} else {
build_hot_pattern_validator(detector).map(Some)
}
})
.collect()
}
#[cfg(feature = "simdsieve")]
static HOT_PATTERN_DATA: std::sync::OnceLock<(&'static [&'static [u8]], &'static [&'static str])> =
std::sync::OnceLock::new();
#[cfg(feature = "simdsieve")]
fn compute_hot_pattern_data() -> (&'static [&'static [u8]], &'static [&'static str]) {
let detectors = keyhog_core::embedded_detector_specs();
let mut prefixes: Vec<&'static [u8]> = Vec::new();
let mut detector_ids: Vec<&'static str> = Vec::new();
for detector in detectors {
for prefix in &detector.simdsieve_prefixes {
let static_prefix: &'static [u8] =
Box::leak(prefix.clone().into_bytes().into_boxed_slice());
prefixes.push(static_prefix);
detector_ids.push(detector.id.as_str());
}
}
let static_prefixes: &'static [&'static [u8]] = Box::leak(prefixes.into_boxed_slice());
let static_ids: &'static [&'static str] = Box::leak(detector_ids.into_boxed_slice());
(static_prefixes, static_ids)
}
#[cfg(feature = "simdsieve")]
pub(crate) static HOT_PATTERNS: std::sync::LazyLock<&'static [&'static [u8]]> =
std::sync::LazyLock::new(|| HOT_PATTERN_DATA.get_or_init(compute_hot_pattern_data).0);
#[cfg(feature = "simdsieve")]
pub(crate) static HOT_PATTERN_DETECTOR_IDS: std::sync::LazyLock<&'static [&'static str]> =
std::sync::LazyLock::new(|| HOT_PATTERN_DATA.get_or_init(compute_hot_pattern_data).1);