#[derive(Debug, Clone, PartialEq)]
pub enum ScreenResult {
Skip {
reason: &'static str,
entropy_bits: f64,
},
Proceed {
entropy_bits: f64,
},
}
impl ScreenResult {
#[must_use]
pub fn is_skip(&self) -> bool {
matches!(self, Self::Skip { .. })
}
#[must_use]
pub fn entropy_bits(&self) -> f64 {
match *self {
Self::Skip { entropy_bits, .. } | Self::Proceed { entropy_bits } => entropy_bits,
}
}
}
#[must_use]
pub fn quick_screen(data: &[u8], threshold: f64) -> ScreenResult {
let entropy_bits = byte_entropy(data);
if entropy_bits <= threshold {
ScreenResult::Skip {
reason: "near-deterministic",
entropy_bits,
}
} else {
ScreenResult::Proceed { entropy_bits }
}
}
#[must_use]
pub(crate) fn byte_entropy(data: &[u8]) -> f64 {
if data.is_empty() {
return 0.0;
}
let mut freq = [0u64; 256];
for &b in data {
freq[b as usize] += 1;
}
let n = data.len() as f64;
freq.iter()
.filter(|&&c| c > 0)
.map(|&c| {
let p = c as f64 / n;
-p * p.log2()
})
.sum()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constant_stream_is_skipped() {
let data = vec![42u8; 1000];
let r = quick_screen(&data, 0.5);
assert!(r.is_skip(), "constant stream must be skipped");
assert!((r.entropy_bits() - 0.0).abs() < 1e-10);
}
#[test]
fn uniform_byte_stream_proceeds() {
let data: Vec<u8> = (0u8..=255).cycle().take(2048).collect();
let r = quick_screen(&data, 0.5);
assert!(!r.is_skip(), "uniform stream must proceed");
assert!((r.entropy_bits() - 8.0).abs() < 0.01);
}
#[test]
fn empty_input_is_skipped() {
let r = quick_screen(&[], 0.5);
assert!(r.is_skip());
assert_eq!(r.entropy_bits(), 0.0);
}
#[test]
fn zero_threshold_only_skips_constant() {
let data = vec![0u8; 100];
assert!(quick_screen(&data, 0.0).is_skip());
let data2 = vec![0u8, 1u8, 0u8, 1u8];
assert!(!quick_screen(&data2, 0.0).is_skip());
}
#[test]
fn binary_alternating_entropy_is_one_bit() {
let data: Vec<u8> = (0u8..2).cycle().take(1000).collect();
let h = byte_entropy(&data);
assert!((h - 1.0).abs() < 0.01, "H([0,1]) = 1 bit, got {h}");
}
#[test]
fn entropy_bits_accessor_consistent() {
let data: Vec<u8> = (0u8..=255).cycle().take(512).collect();
let r = quick_screen(&data, 4.0);
let h = byte_entropy(&data);
assert!((r.entropy_bits() - h).abs() < 1e-10);
}
#[test]
fn screen_result_is_skip_helper() {
let skip = ScreenResult::Skip {
reason: "near-deterministic",
entropy_bits: 0.1,
};
let proceed = ScreenResult::Proceed { entropy_bits: 5.0 };
assert!(skip.is_skip());
assert!(!proceed.is_skip());
}
}