// 可扩展规则接口。
//
// 内置的 100 多条签名只是「默认规则集」,不是硬编码的判定逻辑:
// 调用方可以构造 `Registry`,追加自己的规则或检测器,
// 用 `detect_with` 在同一套流程里生效。
//
// 优先级:用户规则 → 内置签名与容器消歧 → 用户检测器 → 文本启发式。
///|
/// 规则匹配器。
///
/// 组合子让规则可以表达「强魔数 + 掩码」「多条件与/或」,
/// 而不是为每种变体在签名表里堆一条硬编码。
pub enum Matcher {
/// 在固定偏移处匹配字节串
MagicAt(Int, Bytes)
/// 从文件尾部倒数 `back` 字节处匹配(例如 ZIP 的 EOCD)
MagicAtEnd(Int, Bytes)
/// 在固定偏移处按位掩码匹配:`(data & mask) == (magic & mask)`
MaskedAt(Int, Bytes, Bytes)
/// 在固定偏移处匹配 ASCII 文本
AsciiAt(Int, String)
/// 全部子匹配器命中
All(Array[Matcher])
/// 任一子匹配器命中
Any(Array[Matcher])
}
///|
/// 具名构造器。比起直接写枚举变体,调用方更容易读写,也让文档示例更短。
pub fn Matcher::magic_at(offset : Int, magic : Bytes) -> Matcher {
MagicAt(offset, magic)
}
///|
pub fn Matcher::magic_at_end(back : Int, magic : Bytes) -> Matcher {
MagicAtEnd(back, magic)
}
///|
pub fn Matcher::masked_at(offset : Int, magic : Bytes, mask : Bytes) -> Matcher {
MaskedAt(offset, magic, mask)
}
///|
pub fn Matcher::ascii_at(offset : Int, text : String) -> Matcher {
AsciiAt(offset, text)
}
///|
pub fn Matcher::all(items : Array[Matcher]) -> Matcher {
All(items)
}
///|
pub fn Matcher::any(items : Array[Matcher]) -> Matcher {
Any(items)
}
///|
/// 一条声明式规则。
pub struct Rule {
id : String
mime : String
ext : String
category : Category
/// 证据描述
label : String
/// 命中后贡献的分值
weight : Int
matcher : Matcher
}
///|
pub fn Rule::new(
id : String,
mime : String,
ext : String,
category : Category,
label : String,
weight : Int,
matcher : Matcher,
) -> Rule {
{ id, mime, ext, category, label, weight, matcher }
}
///|
/// 用户扩展入口。
pub struct Registry {
rules : Array[Rule]
detectors : Array[(Bytes) -> Detection?]
}
///|
pub fn Registry::new() -> Registry {
{ rules: [], detectors: [] }
}
///|
/// 追加一条规则。用户规则优先于内置签名表。
pub fn Registry::add_rule(self : Registry, rule : Rule) -> Unit {
self.rules.push(rule)
}
///|
/// 追加一个检测器。它只在规则与内置签名都没命中时被调用,
/// 返回 `Some` 即作为最终结果。
pub fn Registry::add_detector(
self : Registry,
detector : (Bytes) -> Detection?,
) -> Unit {
self.detectors.push(detector)
}
///|
pub fn Registry::rule_count(self : Registry) -> Int {
self.rules.length()
}
///|
pub fn Registry::detector_count(self : Registry) -> Int {
self.detectors.length()
}
///|
fn string_bytes(text : String) -> Bytes {
let out : Array[Byte] = []
for c in text {
out.push(c.to_int().to_byte())
}
Bytes::from_array(out.exact_view())
}
///|
fn match_masked(
data : Bytes,
offset : Int,
magic : Bytes,
mask : Bytes,
) -> Bool {
let n = data.length()
let m = magic.length()
if offset < 0 || m == 0 || mask.length() != m || offset + m > n {
return false
}
let mut i = 0
while i < m {
let mask_byte = mask[i].to_int()
let actual = data[offset + i].to_int() & mask_byte
let expected = magic[i].to_int() & mask_byte
if actual != expected {
return false
}
i = i + 1
}
true
}
///|
/// 求值匹配器,命中返回证据偏移。
pub fn eval_matcher(matcher : Matcher, data : Bytes) -> Int? {
match matcher {
MagicAt(offset, magic) =>
if match_at(data, offset, magic) {
Some(offset)
} else {
None
}
MagicAtEnd(back, magic) => {
let offset = data.length() - magic.length() - back
if match_at(data, offset, magic) {
Some(offset)
} else {
None
}
}
MaskedAt(offset, magic, mask) =>
if match_masked(data, offset, magic, mask) {
Some(offset)
} else {
None
}
AsciiAt(offset, text) =>
if match_at(data, offset, string_bytes(text)) {
Some(offset)
} else {
None
}
All(items) => {
let mut first : Int? = None
let mut i = 0
let mut ok = true
while i < items.length() && ok {
match eval_matcher(items[i], data) {
Some(offset) =>
match first {
Some(_) => ()
None => first = Some(offset)
}
None => ok = false
}
i = i + 1
}
if ok {
first
} else {
None
}
}
Any(items) => {
let mut found : Int? = None
let mut i = 0
let mut go = true
while i < items.length() && go {
match eval_matcher(items[i], data) {
Some(offset) => {
found = Some(offset)
go = false
}
None => ()
}
i = i + 1
}
found
}
}
}
///|
/// 命中规则时构造证据。
pub fn rule_evidence(rule : Rule, offset : Int) -> Evidence {
{
kind: "rule",
label: rule.label,
offset,
matched_len: 0,
weight: rule.weight,
}
}