#[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: crate::types::LazyRegex,
pub(crate) ac_map_index: usize,
}
pub(crate) fn build_hot_pattern_validator(
detector: &keyhog_core::DetectorSpec,
) -> crate::error::Result<regex::Regex> {
let source = hot_pattern_validator_source_parts(&detector.id, &detector.patterns)?;
compile_hot_pattern_validator(&detector.id, &source)
}
pub(crate) fn build_hot_pattern_slot_validator(
detector: &keyhog_core::DetectorSpec,
) -> crate::error::Result<crate::types::LazyRegex> {
let source = hot_pattern_validator_source_parts(&detector.id, &detector.patterns)?;
drop(compile_hot_pattern_validator(&detector.id, &source)?);
Ok(crate::types::LazyRegex::detector(source))
}
pub(crate) fn hydrate_hot_pattern_validator(
detector: &crate::execution_pack::detector_plan::DetectorPlanRecord,
) -> crate::error::Result<crate::types::LazyRegex> {
let source = hot_pattern_validator_source_parts(&detector.id, &detector.patterns)?;
Ok(crate::types::LazyRegex::detector(source))
}
fn hot_pattern_validator_source_parts(
detector_id: &str,
patterns: &[keyhog_core::PatternSpec],
) -> crate::error::Result<String> {
if patterns.is_empty() {
return Err(crate::error::ScanError::Config(format!(
"detector {} declares simdsieve prefixes but has no regex patterns",
detector_id
)));
}
let source_bytes = patterns
.iter()
.map(|pattern| pattern.regex.len().saturating_add(4))
.sum::<usize>();
let mut combined = String::with_capacity(source_bytes.saturating_add(5));
combined.push_str("^(?:");
for (index, pattern) in patterns.iter().enumerate() {
if index != 0 {
combined.push('|');
}
combined.push_str("(?:");
combined.push_str(&pattern.regex);
combined.push(')');
}
combined.push(')');
Ok(combined)
}
fn compile_hot_pattern_validator(
detector_id: &str,
source: &str,
) -> crate::error::Result<regex::Regex> {
regex::RegexBuilder::new(source)
.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.to_owned(),
index: 0,
source,
})
}
#[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);