use crate::types::*;
pub fn is_within_hex_context(data: &str, match_start: usize, match_end: usize) -> bool {
if !valid_match_bounds(data, match_start, match_end) {
return false;
}
let matched = &data[match_start..match_end];
if matched.len() < MIN_HEX_MATCH_LEN {
return false;
}
if !has_at_least_n_hex_digits(matched, MIN_HEX_DIGITS_IN_MATCH) {
return false;
}
let (before, after) = surrounding_hex_context(data, match_start, match_end);
let hex_before = formatted_hex_run(before.chars().rev());
let hex_after = formatted_hex_run(after.chars());
hex_before >= MIN_HEX_CONTEXT_DIGITS && hex_after >= MIN_HEX_CONTEXT_DIGITS
}
fn has_at_least_n_hex_digits(s: &str, n: usize) -> bool {
if n == 0 {
return true;
}
let mut seen = 0usize;
for &b in s.as_bytes() {
if b.is_ascii_hexdigit() {
seen += 1;
if seen >= n {
return true;
}
}
}
false
}
fn valid_match_bounds(data: &str, match_start: usize, match_end: usize) -> bool {
match_end > match_start
&& data.is_char_boundary(match_start)
&& data.is_char_boundary(match_end)
}
fn surrounding_hex_context(data: &str, match_start: usize, match_end: usize) -> (&str, &str) {
let context_start = crate::engine::floor_char_boundary(
data,
match_start.saturating_sub(HEX_CONTEXT_RADIUS_CHARS),
);
let context_end = {
let mut end = (match_end + HEX_CONTEXT_RADIUS_CHARS).min(data.len());
while end < data.len() && !data.is_char_boundary(end) {
end += 1;
}
end.min(data.len())
};
(
&data[context_start..match_start],
&data[match_end..context_end],
)
}
fn formatted_hex_run(iter: impl Iterator<Item = char>) -> usize {
let mut hex_digits = 0usize;
let mut separators = 0usize;
let mut seen_hex = false;
for ch in iter {
if ch.is_ascii_hexdigit() {
hex_digits += 1;
seen_hex = true;
continue;
}
if matches!(ch, ' ' | '\t' | ':' | '-')
&& (!seen_hex || separators < MAX_HEX_CONTEXT_SEPARATORS)
{
separators += 1;
continue;
}
break;
}
hex_digits
}
pub fn match_entropy(data: &[u8]) -> f64 {
#[cfg(feature = "entropy")]
{
crate::entropy::shannon_entropy(data)
}
#[cfg(not(feature = "entropy"))]
{
fallback_entropy(data)
}
}
#[cfg(not(feature = "entropy"))]
fn fallback_entropy(data: &[u8]) -> f64 {
if data.is_empty() {
return 0.0;
}
let mut c0 = [0u32; 256];
let mut c1 = [0u32; 256];
let mut c2 = [0u32; 256];
let mut c3 = [0u32; 256];
let chunks = data.chunks_exact(4);
let remainder = chunks.remainder();
for chunk in chunks {
c0[chunk[0] as usize] += 1;
c1[chunk[1] as usize] += 1;
c2[chunk[2] as usize] += 1;
c3[chunk[3] as usize] += 1;
}
for &byte in remainder {
c0[byte as usize] += 1;
}
let mut counts = [0u32; 256];
for j in 0..256 {
counts[j] = c0[j] + c1[j] + c2[j] + c3[j];
}
let len = data.len() as f64;
let mut entropy = 0.0;
for &count in &counts {
if count > 0 {
let p = count as f64 / len;
entropy -= p * p.log2();
}
}
entropy
}