use crate::parser::ast::{MagicRule, OffsetSpec, Operator, StrengthModifier, TypeKind, Value};
pub const MAX_STRENGTH: i32 = 255;
pub const MIN_STRENGTH: i32 = 0;
#[must_use]
pub fn calculate_default_strength(rule: &MagicRule) -> i32 {
let mut strength: i32 = 0;
strength += match &rule.typ {
TypeKind::String { max_length } | TypeKind::PString { max_length, .. } => {
let base = 20;
if max_length.is_some() { base + 5 } else { base }
}
TypeKind::String16 { .. } => 20,
TypeKind::Regex { count, .. } => {
use crate::parser::ast::RegexCount;
match count {
RegexCount::Default | RegexCount::Lines(None) => 20,
RegexCount::Bytes(_) | RegexCount::Lines(Some(_)) => 25,
}
}
TypeKind::Search { .. } => 25,
TypeKind::Quad { .. } | TypeKind::Double { .. } | TypeKind::QDate { .. } => 16,
TypeKind::Long { .. } | TypeKind::Float { .. } | TypeKind::Date { .. } => 15,
TypeKind::Short { .. } => 10,
TypeKind::Byte { .. } => 5,
#[allow(clippy::match_same_arms)]
TypeKind::Meta(meta) => match meta {
crate::parser::ast::MetaType::Default => 0,
crate::parser::ast::MetaType::Clear => 0,
crate::parser::ast::MetaType::Name(_) => 0,
crate::parser::ast::MetaType::Use(_) => 5,
crate::parser::ast::MetaType::Indirect => 5,
crate::parser::ast::MetaType::Offset => 0,
},
};
strength += match &rule.op {
Operator::Equal => 10,
Operator::NotEqual => 5,
Operator::LessThan
| Operator::GreaterThan
| Operator::LessEqual
| Operator::GreaterEqual => 6,
Operator::BitwiseAndMask(_) => 7,
Operator::BitwiseAnd => 3,
Operator::BitwiseXor | Operator::BitwiseNot => 4,
Operator::AnyValue => 1,
};
strength += match &rule.offset {
OffsetSpec::Absolute(_) => 10,
OffsetSpec::FromEnd(_) => 8,
OffsetSpec::Indirect { .. } => 5,
OffsetSpec::Relative(_) => 3,
};
let value_length_bonus = match &rule.value {
Value::String(s) => {
i32::try_from(s.len()).unwrap_or(20).min(20)
}
Value::Bytes(b) => {
i32::try_from(b.len()).unwrap_or(20).min(20)
}
Value::Uint(_) | Value::Int(_) | Value::Float(_) => 0,
};
strength += value_length_bonus;
strength.clamp(MIN_STRENGTH, MAX_STRENGTH)
}
#[must_use]
pub fn apply_strength_modifier(base_strength: i32, modifier: &StrengthModifier) -> i32 {
let result = match modifier {
StrengthModifier::Add(n) => base_strength.saturating_add(*n),
StrengthModifier::Subtract(n) => base_strength.saturating_sub(*n),
StrengthModifier::Multiply(n) => base_strength.saturating_mul(*n),
StrengthModifier::Divide(n) => {
if *n == 0 {
base_strength
} else {
base_strength / n
}
}
StrengthModifier::Set(n) => *n,
};
result.clamp(MIN_STRENGTH, MAX_STRENGTH)
}
#[must_use]
pub fn calculate_rule_strength(rule: &MagicRule) -> i32 {
let base_strength = calculate_default_strength(rule);
if let Some(ref modifier) = rule.strength_modifier {
apply_strength_modifier(base_strength, modifier)
} else {
base_strength
}
}
pub fn sort_rules_by_strength(rules: &mut [MagicRule]) {
rules.sort_by_cached_key(|rule| calculate_rule_strength(rule).saturating_neg());
}
pub fn sort_rules_by_strength_recursive(rules: &mut [MagicRule]) {
sort_rules_by_strength(rules);
for rule in rules.iter_mut() {
sort_rules_by_strength_recursive(&mut rule.children);
}
}
#[must_use]
pub fn into_sorted_by_strength(mut rules: Vec<MagicRule>) -> Vec<MagicRule> {
sort_rules_by_strength(&mut rules);
rules
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::ast::{Endianness, IndirectAdjustmentOp};
fn make_rule(typ: TypeKind, op: Operator, offset: OffsetSpec, value: Value) -> MagicRule {
MagicRule {
offset,
typ,
op,
value,
message: "test".to_string(),
children: vec![],
level: 0,
strength_modifier: None,
value_transform: None,
}
}
#[test]
#[allow(clippy::too_many_lines)]
fn test_calculate_default_strength_table() {
type Case = (fn() -> MagicRule, i32, &'static str);
let cases: &[Case] = &[
(
|| {
make_rule(
TypeKind::Byte { signed: true },
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Uint(0),
)
},
25, "type=byte",
),
(
|| {
make_rule(
TypeKind::Short {
endian: Endianness::Little,
signed: false,
},
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Uint(0),
)
},
30, "type=short",
),
(
|| {
make_rule(
TypeKind::Long {
endian: Endianness::Big,
signed: false,
},
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Uint(0),
)
},
35, "type=long",
),
(
|| {
make_rule(
TypeKind::Quad {
endian: Endianness::Little,
signed: false,
},
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Uint(0),
)
},
36, "type=quad",
),
(
|| {
make_rule(
TypeKind::Date {
endian: Endianness::Big,
utc: true,
},
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Uint(0),
)
},
35, "type=date",
),
(
|| {
make_rule(
TypeKind::QDate {
endian: Endianness::Little,
utc: false,
},
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Uint(0),
)
},
36, "type=qdate",
),
(
|| {
make_rule(
TypeKind::String { max_length: None },
Operator::Equal,
OffsetSpec::Absolute(0),
Value::String("ELF".to_string()),
)
},
43, "type=string len=3",
),
(
|| {
make_rule(
TypeKind::String {
max_length: Some(10),
},
Operator::Equal,
OffsetSpec::Absolute(0),
Value::String("TEST".to_string()),
)
},
49, "type=string max_length=10",
),
(
|| {
make_rule(
TypeKind::Byte { signed: true },
Operator::NotEqual,
OffsetSpec::Absolute(0),
Value::Uint(0),
)
},
20, "op=not_equal",
),
(
|| {
make_rule(
TypeKind::Byte { signed: true },
Operator::BitwiseAnd,
OffsetSpec::Absolute(0),
Value::Uint(0),
)
},
18, "op=bitwise_and",
),
(
|| {
make_rule(
TypeKind::Byte { signed: true },
Operator::BitwiseAndMask(0xFF),
OffsetSpec::Absolute(0),
Value::Uint(0),
)
},
22, "op=bitwise_and_mask",
),
(
|| {
make_rule(
TypeKind::Byte { signed: true },
Operator::LessThan,
OffsetSpec::Absolute(0),
Value::Uint(0),
)
},
21, "op=less_than",
),
(
|| {
make_rule(
TypeKind::Byte { signed: true },
Operator::GreaterThan,
OffsetSpec::Absolute(0),
Value::Uint(0),
)
},
21,
"op=greater_than",
),
(
|| {
make_rule(
TypeKind::Byte { signed: true },
Operator::LessEqual,
OffsetSpec::Absolute(0),
Value::Uint(0),
)
},
21,
"op=less_equal",
),
(
|| {
make_rule(
TypeKind::Byte { signed: true },
Operator::GreaterEqual,
OffsetSpec::Absolute(0),
Value::Uint(0),
)
},
21,
"op=greater_equal",
),
(
|| {
make_rule(
TypeKind::Byte { signed: true },
Operator::Equal,
OffsetSpec::Indirect {
base_offset: 0,
base_relative: false,
pointer_type: TypeKind::Long {
endian: Endianness::Little,
signed: false,
},
adjustment: 0,
adjustment_op: IndirectAdjustmentOp::Add,
result_relative: false,
endian: Endianness::Little,
},
Value::Uint(0),
)
},
20, "offset=indirect",
),
(
|| {
make_rule(
TypeKind::Byte { signed: true },
Operator::Equal,
OffsetSpec::Relative(4),
Value::Uint(0),
)
},
18, "offset=relative",
),
(
|| {
make_rule(
TypeKind::Byte { signed: true },
Operator::Equal,
OffsetSpec::FromEnd(-4),
Value::Uint(0),
)
},
23, "offset=from_end",
),
(
|| {
make_rule(
TypeKind::Byte { signed: true },
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]),
)
},
29, "value=bytes len=4",
),
(
|| {
make_rule(
TypeKind::String { max_length: None },
Operator::Equal,
OffsetSpec::Absolute(0),
Value::String(
"This is a very long string that exceeds the cap".to_string(),
),
)
},
60, "value=long_string (cap)",
),
];
for (factory, expected, desc) in cases {
let rule = factory();
let strength = calculate_default_strength(&rule);
assert_eq!(
strength, *expected,
"calculate_default_strength mismatch for case '{desc}'"
);
}
}
#[test]
fn test_apply_modifier_add() {
assert_eq!(apply_strength_modifier(50, &StrengthModifier::Add(10)), 60);
}
#[test]
fn test_apply_modifier_subtract() {
assert_eq!(
apply_strength_modifier(50, &StrengthModifier::Subtract(10)),
40
);
}
#[test]
fn test_apply_modifier_multiply() {
assert_eq!(
apply_strength_modifier(50, &StrengthModifier::Multiply(2)),
100
);
}
#[test]
fn test_apply_modifier_divide() {
assert_eq!(
apply_strength_modifier(50, &StrengthModifier::Divide(2)),
25
);
}
#[test]
fn test_apply_modifier_set() {
assert_eq!(apply_strength_modifier(50, &StrengthModifier::Set(75)), 75);
}
#[test]
fn test_apply_modifier_add_overflow() {
assert_eq!(
apply_strength_modifier(250, &StrengthModifier::Add(100)),
MAX_STRENGTH
);
}
#[test]
fn test_apply_modifier_subtract_underflow() {
assert_eq!(
apply_strength_modifier(10, &StrengthModifier::Subtract(100)),
MIN_STRENGTH
);
}
#[test]
fn test_apply_modifier_multiply_overflow() {
assert_eq!(
apply_strength_modifier(200, &StrengthModifier::Multiply(10)),
MAX_STRENGTH
);
}
#[test]
fn test_apply_modifier_divide_by_zero() {
assert_eq!(
apply_strength_modifier(50, &StrengthModifier::Divide(0)),
50
);
}
#[test]
fn test_apply_modifier_set_negative() {
assert_eq!(
apply_strength_modifier(50, &StrengthModifier::Set(-10)),
MIN_STRENGTH
);
}
#[test]
fn test_apply_modifier_set_over_max() {
assert_eq!(
apply_strength_modifier(50, &StrengthModifier::Set(1000)),
MAX_STRENGTH
);
}
#[test]
fn test_rule_strength_without_modifier() {
let rule = make_rule(
TypeKind::Byte { signed: true },
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Uint(0),
);
assert_eq!(calculate_rule_strength(&rule), 25);
}
#[test]
fn test_rule_strength_with_add_modifier() {
let mut rule = make_rule(
TypeKind::Byte { signed: true },
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Uint(0),
);
rule.strength_modifier = Some(StrengthModifier::Add(20));
assert_eq!(calculate_rule_strength(&rule), 45);
}
#[test]
fn test_rule_strength_with_multiply_modifier() {
let mut rule = make_rule(
TypeKind::Byte { signed: true },
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Uint(0),
);
rule.strength_modifier = Some(StrengthModifier::Multiply(2));
assert_eq!(calculate_rule_strength(&rule), 50);
}
#[test]
fn test_rule_strength_with_set_modifier() {
let mut rule = make_rule(
TypeKind::Byte { signed: true },
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Uint(0),
);
rule.strength_modifier = Some(StrengthModifier::Set(100));
assert_eq!(calculate_rule_strength(&rule), 100);
}
#[test]
fn test_sort_rules_by_strength_basic() {
let mut rules = vec![
{
let mut r = make_rule(
TypeKind::Byte { signed: true },
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Uint(0),
);
r.message = "byte rule".to_string();
r
},
{
let mut r = make_rule(
TypeKind::String { max_length: None },
Operator::Equal,
OffsetSpec::Absolute(0),
Value::String("MAGIC".to_string()),
);
r.message = "string rule".to_string();
r
},
];
sort_rules_by_strength(&mut rules);
assert_eq!(rules[0].message, "string rule");
assert_eq!(rules[1].message, "byte rule");
}
#[test]
fn test_sort_rules_by_strength_with_modifier() {
let mut rules = vec![
{
let mut r = make_rule(
TypeKind::String { max_length: None },
Operator::Equal,
OffsetSpec::Absolute(0),
Value::String("TEST".to_string()),
);
r.message = "string rule".to_string();
r.strength_modifier = Some(StrengthModifier::Set(10));
r
},
{
let mut r = make_rule(
TypeKind::Byte { signed: true },
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Uint(0),
);
r.message = "byte rule".to_string();
r.strength_modifier = Some(StrengthModifier::Set(100));
r
},
];
sort_rules_by_strength(&mut rules);
assert_eq!(rules[0].message, "byte rule");
assert_eq!(rules[1].message, "string rule");
}
#[test]
fn test_sort_rules_empty() {
let mut rules: Vec<MagicRule> = vec![];
sort_rules_by_strength(&mut rules);
assert!(rules.is_empty());
}
#[test]
fn test_sort_rules_single() {
let mut rules = vec![make_rule(
TypeKind::Byte { signed: true },
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Uint(0),
)];
sort_rules_by_strength(&mut rules);
assert_eq!(rules.len(), 1);
}
#[test]
fn test_into_sorted_by_strength() {
let rules = vec![
{
let mut r = make_rule(
TypeKind::Byte { signed: true },
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Uint(0),
);
r.message = "byte rule".to_string();
r
},
{
let mut r = make_rule(
TypeKind::Long {
endian: Endianness::Big,
signed: false,
},
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Uint(0),
);
r.message = "long rule".to_string();
r
},
];
let sorted = into_sorted_by_strength(rules);
assert_eq!(sorted[0].message, "long rule");
assert_eq!(sorted[1].message, "byte rule");
}
#[test]
fn test_strength_comparison_string_vs_byte() {
let string_rule = make_rule(
TypeKind::String { max_length: None },
Operator::Equal,
OffsetSpec::Absolute(0),
Value::String("AB".to_string()),
);
let byte_rule = make_rule(
TypeKind::Byte { signed: true },
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Uint(0x7f),
);
let string_strength = calculate_rule_strength(&string_rule);
let byte_strength = calculate_rule_strength(&byte_rule);
assert!(
string_strength > byte_strength,
"String strength {string_strength} should be > byte strength {byte_strength}"
);
}
#[test]
fn test_strength_comparison_absolute_vs_relative_offset() {
let absolute_rule = make_rule(
TypeKind::Byte { signed: true },
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Uint(0x7f),
);
let relative_rule = make_rule(
TypeKind::Byte { signed: true },
Operator::Equal,
OffsetSpec::Relative(4),
Value::Uint(0x7f),
);
let absolute_strength = calculate_rule_strength(&absolute_rule);
let relative_strength = calculate_rule_strength(&relative_rule);
assert!(
absolute_strength > relative_strength,
"Absolute strength {absolute_strength} should be > relative strength {relative_strength}"
);
}
fn meta_rule(meta: crate::parser::ast::MetaType, msg: &str) -> MagicRule {
let mut rule = make_rule(
TypeKind::Meta(meta),
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Uint(0),
);
rule.message = msg.to_string();
rule
}
#[test]
fn test_meta_default_and_clear_sort_to_bottom() {
use crate::parser::ast::MetaType;
let mut rules = vec![
meta_rule(MetaType::Default, "default"),
meta_rule(MetaType::Clear, "clear"),
{
let mut r = make_rule(
TypeKind::Byte { signed: true },
Operator::Equal,
OffsetSpec::Absolute(0),
Value::Uint(0),
);
r.message = "byte".to_string();
r
},
];
sort_rules_by_strength(&mut rules);
assert_eq!(rules[0].message, "byte");
}
#[test]
fn test_meta_use_and_indirect_sort_above_default() {
use crate::parser::ast::MetaType;
let use_rule = meta_rule(MetaType::Use("sub".to_string()), "use");
let indirect_rule = meta_rule(MetaType::Indirect, "indirect");
let default_rule = meta_rule(MetaType::Default, "default");
let clear_rule = meta_rule(MetaType::Clear, "clear");
assert!(
calculate_default_strength(&use_rule) > calculate_default_strength(&default_rule),
"use should sort above default"
);
assert!(
calculate_default_strength(&indirect_rule) > calculate_default_strength(&default_rule),
"indirect should sort above default"
);
assert!(
calculate_default_strength(&use_rule) > calculate_default_strength(&clear_rule),
"use should sort above clear"
);
assert!(
calculate_default_strength(&indirect_rule) > calculate_default_strength(&clear_rule),
"indirect should sort above clear"
);
}
#[test]
fn test_meta_name_strength_is_zero() {
use crate::parser::ast::MetaType;
let name_rule = meta_rule(MetaType::Name("foo".to_string()), "name");
let default_rule = meta_rule(MetaType::Default, "default");
assert_eq!(
calculate_default_strength(&name_rule),
calculate_default_strength(&default_rule),
"Name strength should equal Default strength (both type-axis 0)"
);
}
}