use crate::entropy::{self, HIGH_ENTROPY_THRESHOLD};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum FillPattern {
Zeros,
Ones,
Uniform(u8),
Alternating(u8, u8),
HighEntropy,
Mixed,
}
impl FillPattern {
#[must_use]
pub fn is_deliberate_wipe(self) -> bool {
matches!(
self,
FillPattern::Ones | FillPattern::Uniform(_) | FillPattern::Alternating(_, _)
)
}
#[must_use]
pub fn label(self) -> String {
match self {
FillPattern::Zeros => "all 0x00".to_string(),
FillPattern::Ones => "all 0xFF".to_string(),
FillPattern::Uniform(b) => format!("uniform {b:#04X}"),
FillPattern::Alternating(a, b) => format!("alternating {a:#04X}/{b:#04X}"),
FillPattern::HighEntropy => "high-entropy (random/encrypted)".to_string(),
FillPattern::Mixed => "mixed".to_string(),
}
}
}
#[must_use]
pub fn classify(data: &[u8]) -> FillPattern {
if data.is_empty() {
return FillPattern::Mixed;
}
let first = data[0];
if data.iter().all(|&b| b == first) {
return match first {
0x00 => FillPattern::Zeros,
0xFF => FillPattern::Ones,
other => FillPattern::Uniform(other),
};
}
if data.len() >= 2 {
let (a, b) = (data[0], data[1]);
if a != b
&& data
.iter()
.enumerate()
.all(|(i, &x)| x == if i % 2 == 0 { a } else { b })
{
return FillPattern::Alternating(a, b);
}
}
if entropy::shannon(data) > HIGH_ENTROPY_THRESHOLD {
return FillPattern::HighEntropy;
}
FillPattern::Mixed
}