const TRAILER_START: usize = 0xFF0;
fn shannon_entropy(bytes: &[u8]) -> f64 {
let mut hist = [0u32; 256];
for &b in bytes {
hist[b as usize] += 1;
}
let total = bytes.len() as f64;
if total == 0.0 {
return 0.0;
}
let mut h = 0.0;
for &c in &hist {
if c == 0 {
continue;
}
let p = c as f64 / total;
h -= p * p.log2();
}
h
}
pub const OPAQUE_ENTROPY_THRESHOLD: f64 = 7.5;
pub fn is_opaque_high_entropy(raw_page: &[u8]) -> bool {
if raw_page.len() < TRAILER_START {
return false;
}
shannon_entropy(&raw_page[..TRAILER_START]) >= OPAQUE_ENTROPY_THRESHOLD
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_zeros_is_not_opaque() {
let raw = [0u8; 4096];
assert!(!is_opaque_high_entropy(&raw));
}
#[test]
fn pseudo_random_is_opaque() {
let mut raw = [0u8; 4096];
let mut x: u32 = 0xdeadbeef;
for b in raw.iter_mut() {
x = x.wrapping_mul(1_103_515_245).wrapping_add(12345);
*b = (x >> 16) as u8;
}
assert!(is_opaque_high_entropy(&raw));
}
#[test]
fn sparse_page_with_anchor_is_not_opaque() {
let mut raw = [0u8; 4096];
for i in (0..0xFF0).step_by(64) {
raw[i] = 0x07;
raw[i + 1] = 0x00;
raw[i + 2] = 0xD5;
raw[i + 3] = 0x0B;
}
assert!(!is_opaque_high_entropy(&raw));
}
#[test]
fn short_slice_does_not_panic() {
let raw = [0u8; 100];
assert!(!is_opaque_high_entropy(&raw));
}
#[test]
fn threshold_is_seventy_five_tenths() {
assert!((OPAQUE_ENTROPY_THRESHOLD - 7.5).abs() < 1e-9);
}
}