#![forbid(unsafe_code)]
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SemanticMode {
None,
Extension,
ByteSketch,
History,
Combined,
}
impl SemanticMode {
pub fn enabled(self) -> bool {
!matches!(self, SemanticMode::None)
}
}
pub const SEMANTIC_WEIGHT: f64 = 0.3;
pub const SKETCH_BYTES: usize = 4096;
pub const PRIOR_MAX_KEYS: usize = 4096;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SemanticContext {
pub extension_class: u8,
pub parent_class: u8,
pub basename_shape: u8,
pub magic_class: u8,
pub printable_ratio: u8,
pub entropy_class: u8,
pub lifecycle: u8,
}
impl SemanticContext {
pub fn from_name(name: &[u8], parent_class: u8) -> Self {
let mut ctx = SemanticContext {
parent_class,
..SemanticContext::default()
};
let name = &name[..name.len().min(256)];
if let Some(dot) = name.iter().rposition(|&b| b == b'.') {
if dot > 0 && dot + 1 < name.len() {
let ext = &name[dot + 1..];
ctx.extension_class = (class_hash(ext) % 64) as u8;
}
}
let len_bucket = (name.len().min(256) / 16).min(15) as u8;
let mut alpha = 0u32;
let mut digit = 0u32;
for &b in name {
if b.is_ascii_alphabetic() {
alpha += 1;
} else if b.is_ascii_digit() {
digit += 1;
}
}
let n = name.len().max(1) as f64;
let mix = if alpha as f64 / n > 0.7 {
0
} else if digit as f64 / n > 0.3 {
1
} else if alpha as f64 / n > 0.3 {
2
} else {
3
};
ctx.basename_shape = (len_bucket << 4) | mix;
ctx
}
pub fn from_bytes(bytes: &[u8]) -> Self {
let mut ctx = SemanticContext::default();
let sample = &bytes[..bytes.len().min(SKETCH_BYTES)];
if sample.len() >= 8 {
ctx.magic_class = (class_hash(&sample[..8]) % 16) as u8;
}
let mut printable = 0u32;
let mut counted = 0u32;
let mut distinct = [false; 256];
let step = (sample.len() / 2048).max(1);
let mut i = 0usize;
while i < sample.len() {
let b = sample[i];
distinct[b as usize] = true;
if b.is_ascii_graphic() || b == b' ' || b == b'\t' || b == b'\n' || b == b'\r' {
printable += 1;
}
counted += 1;
i += step;
}
if counted > 0 {
ctx.printable_ratio = ((printable as f64 / counted as f64) * 20.0) as u8;
}
let distinct_count = distinct.iter().filter(|&&d| d).count();
ctx.entropy_class = ((distinct_count as f64 / 256.0) * 20.0) as u8;
ctx
}
pub fn key_for(self, mode: SemanticMode) -> Option<u64> {
match mode {
SemanticMode::None => None,
SemanticMode::Extension => Some(class_hash(&[
self.extension_class,
self.parent_class,
self.basename_shape,
])),
SemanticMode::ByteSketch => Some(class_hash(&[
self.magic_class,
self.printable_ratio,
self.entropy_class,
])),
SemanticMode::History => Some(class_hash(&[self.lifecycle])),
SemanticMode::Combined => Some(class_hash(&[
self.extension_class,
self.parent_class,
self.basename_shape,
self.magic_class,
self.printable_ratio,
self.entropy_class,
self.lifecycle,
])),
}
}
}
#[derive(Debug, Default)]
pub struct SemanticPrior {
table: HashMap<u64, Vec<u64>>,
}
impl SemanticPrior {
pub fn observe(&mut self, key: u64, channel: crate::dsfb::features::Channel) {
let row = self
.table
.entry(key)
.or_insert_with(|| vec![0; crate::dsfb::features::Channel::ALL.len()]);
row[channel as usize] = row[channel as usize].saturating_add(1);
if self.table.len() > PRIOR_MAX_KEYS {
if let Some(k) = self.table.keys().next().copied() {
self.table.remove(&k);
}
}
}
pub fn prior(&self, key: u64, channel: crate::dsfb::features::Channel) -> f64 {
let Some(row) = self.table.get(&key) else {
return 0.0;
};
let total: u64 = row.iter().sum();
if total == 0 {
return 0.0;
}
row[channel as usize] as f64 / total as f64
}
pub fn count(&self, key: u64) -> u64 {
self.table
.get(&key)
.map(|row| row.iter().sum())
.unwrap_or(0)
}
}
fn class_hash(bytes: &[u8]) -> u64 {
let mut h = 0xcbf2_9ce4_8422_2325u64;
for &b in bytes {
h ^= b as u64;
h = h.wrapping_mul(0x0000_0100_0000_01b3);
}
h
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn context_is_bounded_and_stable() {
let c1 = SemanticContext::from_name(b"photo.jpg", 3);
let c2 = SemanticContext::from_name(b"photo.jpg", 3);
assert_eq!(c1, c2, "same name+parent must give the same classes");
assert_eq!(c1.extension_class, c2.extension_class);
assert_eq!(c1.basename_shape >> 4, 0); let no_ext = SemanticContext::from_name(b"README", 0);
assert_eq!(no_ext.extension_class, 0, "no extension -> class 0");
let text = vec![b'a'; 4096];
let noise: Vec<u8> = (0..4096u32)
.map(|i| (i.wrapping_mul(2654435761) >> 24) as u8)
.collect();
let t = SemanticContext::from_bytes(&text);
let n = SemanticContext::from_bytes(&noise);
assert!(
t.printable_ratio > n.printable_ratio,
"text is more printable"
);
assert!(
n.entropy_class >= t.entropy_class,
"noise has higher entropy"
);
let zeros4k = vec![0u8; 4096];
let big = vec![0u8; 1024 * 1024];
assert_eq!(
SemanticContext::from_bytes(&big),
SemanticContext::from_bytes(&zeros4k)
);
}
#[test]
fn prior_learns_and_bounds() {
let mut p = SemanticPrior::default();
let key = 7u64;
assert_eq!(p.prior(key, crate::dsfb::features::Channel::Raw), 0.0);
for _ in 0..9 {
p.observe(key, crate::dsfb::features::Channel::Raw);
}
p.observe(key, crate::dsfb::features::Channel::PrevVersion);
let raw = p.prior(key, crate::dsfb::features::Channel::Raw);
let pv = p.prior(key, crate::dsfb::features::Channel::PrevVersion);
assert!((raw - 0.9).abs() < 1e-9, "raw share {raw}");
assert!((pv - 0.1).abs() < 1e-9, "prev-version share {pv}");
for i in 0..(PRIOR_MAX_KEYS + 100) {
p.observe(i as u64, crate::dsfb::features::Channel::Raw);
}
assert!(p.table.len() <= PRIOR_MAX_KEYS);
}
#[test]
fn modes_select_class_groups() {
let ctx = SemanticContext::from_name(b"data.bin", 5);
let ctx = SemanticContext {
printable_ratio: 3,
entropy_class: 19,
magic_class: 9,
lifecycle: 2,
..ctx
};
assert!(ctx.key_for(SemanticMode::None).is_none());
let k_ext = ctx.key_for(SemanticMode::Extension).unwrap();
let mut other = ctx;
other.magic_class = 0;
assert_eq!(other.key_for(SemanticMode::Extension).unwrap(), k_ext);
let mut other2 = ctx;
other2.extension_class = 1;
assert_ne!(other2.key_for(SemanticMode::Extension).unwrap(), k_ext);
let k_sketch = ctx.key_for(SemanticMode::ByteSketch).unwrap();
let mut other3 = ctx;
other3.extension_class = 1;
assert_eq!(other3.key_for(SemanticMode::ByteSketch).unwrap(), k_sketch);
let mut other4 = ctx;
other4.printable_ratio = 0;
assert_ne!(other4.key_for(SemanticMode::ByteSketch).unwrap(), k_sketch);
}
}