libmagic_rs/evaluator/strength.rs
1// Copyright (c) 2025-2026 the libmagic-rs contributors
2// SPDX-License-Identifier: Apache-2.0
3
4//! Strength calculation for magic rules
5//!
6//! This module implements the strength calculation algorithm based on libmagic's
7//! `apprentice_magic_strength` function. Strength is used to order rules during
8//! evaluation, giving priority to more specific rules.
9//!
10//! # Algorithm Overview
11//!
12//! The default strength of a rule is calculated based on several factors:
13//! - **Type specificity**: String types have higher strength than numeric types
14//! - **Operator specificity**: Equality operators are more specific than bitwise
15//! - **Offset type**: Absolute offsets are more reliable than indirect/relative
16//! - **Value length**: Longer strings are more specific matches
17//!
18//! The calculated strength can be modified using `!:strength` directives in magic
19//! files, which apply arithmetic operations to the default strength.
20
21use crate::parser::ast::{MagicRule, OffsetSpec, Operator, StrengthModifier, TypeKind, Value};
22
23/// Maximum strength value (clamped to prevent overflow)
24pub const MAX_STRENGTH: i32 = 255;
25
26/// Minimum strength value (clamped to prevent negative strength)
27pub const MIN_STRENGTH: i32 = 0;
28
29/// Calculate the default strength of a magic rule based on its specificity.
30///
31/// This function implements an algorithm inspired by libmagic's `apprentice_magic_strength`
32/// function. The strength is calculated based on:
33///
34/// - **Type contribution**: How specific the type matching is
35/// - **Operator contribution**: How specific the comparison is
36/// - **Offset contribution**: How reliable the offset is
37/// - **Value length contribution**: For strings, longer matches are more specific
38///
39/// # Arguments
40///
41/// * `rule` - The magic rule to calculate strength for
42///
43/// # Returns
44///
45/// The calculated default strength as an `i32`, clamped to `[MIN_STRENGTH, MAX_STRENGTH]`
46///
47/// # Examples
48///
49/// ```
50/// use libmagic_rs::parser::ast::{MagicRule, OffsetSpec, TypeKind, Operator, Value};
51/// use libmagic_rs::evaluator::strength::calculate_default_strength;
52///
53/// let rule = MagicRule {
54/// offset: OffsetSpec::Absolute(0),
55/// typ: TypeKind::String { max_length: None },
56/// op: Operator::Equal,
57/// value: Value::String("ELF".to_string()),
58/// message: "ELF file".to_string(),
59/// children: vec![],
60/// level: 0,
61/// strength_modifier: None,
62/// };
63///
64/// let strength = calculate_default_strength(&rule);
65/// assert!(strength > 0);
66/// ```
67#[must_use]
68pub fn calculate_default_strength(rule: &MagicRule) -> i32 {
69 let mut strength: i32 = 0;
70
71 // Type contribution: more specific types get higher strength
72 strength += match &rule.typ {
73 // Strings are most specific (they match exact byte sequences)
74 TypeKind::String { max_length } => {
75 // Base string strength
76 let base = 20;
77 // Add bonus for limited-length strings (more constrained match)
78 if max_length.is_some() { base + 5 } else { base }
79 }
80 // 64-bit integers are most specific among numerics
81 TypeKind::Quad { .. } => 16,
82 // 32-bit integers are fairly specific
83 TypeKind::Long { .. } => 15,
84 // 16-bit integers are moderately specific
85 TypeKind::Short { .. } => 10,
86 // Single bytes are least specific
87 TypeKind::Byte { .. } => 5,
88 };
89
90 // Operator contribution: equality is most specific
91 strength += match &rule.op {
92 // Exact equality is most specific
93 Operator::Equal => 10,
94 // Inequality is somewhat specific
95 Operator::NotEqual => 5,
96 // Comparison operators are moderately specific
97 Operator::LessThan
98 | Operator::GreaterThan
99 | Operator::LessEqual
100 | Operator::GreaterEqual => 6,
101 // Bitwise AND with mask is moderately specific
102 Operator::BitwiseAndMask(_) => 7,
103 // Plain bitwise AND is least specific
104 Operator::BitwiseAnd => 3,
105 };
106
107 // Offset contribution: absolute offsets are most reliable
108 strength += match &rule.offset {
109 // Absolute offsets are most reliable
110 OffsetSpec::Absolute(_) => 10,
111 // From-end offsets are also reliable (just from the other end)
112 OffsetSpec::FromEnd(_) => 8,
113 // Indirect offsets depend on reading a pointer first
114 OffsetSpec::Indirect { .. } => 5,
115 // Relative offsets depend on previous match position
116 OffsetSpec::Relative(_) => 3,
117 };
118
119 // Value length contribution: longer values are more specific
120 // Only applicable to string and bytes values
121 let value_length_bonus = match &rule.value {
122 Value::String(s) => {
123 // Each character adds to specificity, capped at 20
124 i32::try_from(s.len()).unwrap_or(20).min(20)
125 }
126 Value::Bytes(b) => {
127 // Each byte adds to specificity, capped at 20
128 i32::try_from(b.len()).unwrap_or(20).min(20)
129 }
130 // Numeric values don't get length bonus
131 Value::Uint(_) | Value::Int(_) => 0,
132 };
133 strength += value_length_bonus;
134
135 // Clamp to valid range
136 strength.clamp(MIN_STRENGTH, MAX_STRENGTH)
137}
138
139/// Apply a strength modifier to a base strength value.
140///
141/// This function applies the arithmetic operation specified by the `StrengthModifier`
142/// to the given base strength. The result is clamped to `[MIN_STRENGTH, MAX_STRENGTH]`.
143///
144/// # Arguments
145///
146/// * `base_strength` - The default calculated strength
147/// * `modifier` - The modifier to apply
148///
149/// # Returns
150///
151/// The modified strength, clamped to valid range
152///
153/// # Examples
154///
155/// ```
156/// use libmagic_rs::parser::ast::StrengthModifier;
157/// use libmagic_rs::evaluator::strength::apply_strength_modifier;
158///
159/// // Add 10 to strength
160/// assert_eq!(apply_strength_modifier(50, &StrengthModifier::Add(10)), 60);
161///
162/// // Subtract 5 from strength
163/// assert_eq!(apply_strength_modifier(50, &StrengthModifier::Subtract(5)), 45);
164///
165/// // Multiply by 2
166/// assert_eq!(apply_strength_modifier(50, &StrengthModifier::Multiply(2)), 100);
167///
168/// // Divide by 2
169/// assert_eq!(apply_strength_modifier(50, &StrengthModifier::Divide(2)), 25);
170///
171/// // Set to absolute value
172/// assert_eq!(apply_strength_modifier(50, &StrengthModifier::Set(75)), 75);
173/// ```
174#[must_use]
175pub fn apply_strength_modifier(base_strength: i32, modifier: &StrengthModifier) -> i32 {
176 let result = match modifier {
177 StrengthModifier::Add(n) => base_strength.saturating_add(*n),
178 StrengthModifier::Subtract(n) => base_strength.saturating_sub(*n),
179 StrengthModifier::Multiply(n) => base_strength.saturating_mul(*n),
180 StrengthModifier::Divide(n) => {
181 if *n == 0 {
182 // Division by zero: log warning and return base strength unchanged
183 eprintln!("Warning: strength modifier !:strength /0 ignored (division by zero)");
184 base_strength
185 } else {
186 base_strength / n
187 }
188 }
189 StrengthModifier::Set(n) => *n,
190 };
191
192 // Clamp to valid range
193 result.clamp(MIN_STRENGTH, MAX_STRENGTH)
194}
195
196/// Calculate the final strength of a magic rule, including any modifiers.
197///
198/// This function first calculates the default strength based on the rule's
199/// specificity, then applies any strength modifier if present.
200///
201/// # Arguments
202///
203/// * `rule` - The magic rule to calculate strength for
204///
205/// # Returns
206///
207/// The final calculated strength, clamped to `[MIN_STRENGTH, MAX_STRENGTH]`
208///
209/// # Examples
210///
211/// ```
212/// use libmagic_rs::parser::ast::{MagicRule, OffsetSpec, TypeKind, Operator, Value, StrengthModifier};
213/// use libmagic_rs::evaluator::strength::calculate_rule_strength;
214///
215/// let rule = MagicRule {
216/// offset: OffsetSpec::Absolute(0),
217/// typ: TypeKind::Byte { signed: true },
218/// op: Operator::Equal,
219/// value: Value::Uint(0x7f),
220/// message: "ELF magic".to_string(),
221/// children: vec![],
222/// level: 0,
223/// strength_modifier: Some(StrengthModifier::Add(20)),
224/// };
225///
226/// let strength = calculate_rule_strength(&rule);
227/// // Base: 5 (byte) + 10 (equal) + 10 (absolute) + 0 (numeric) = 25
228/// // With modifier: 25 + 20 = 45
229/// assert_eq!(strength, 45);
230/// ```
231#[must_use]
232pub fn calculate_rule_strength(rule: &MagicRule) -> i32 {
233 let base_strength = calculate_default_strength(rule);
234
235 if let Some(ref modifier) = rule.strength_modifier {
236 apply_strength_modifier(base_strength, modifier)
237 } else {
238 base_strength
239 }
240}
241
242/// Sort magic rules by their calculated strength in descending order.
243///
244/// Higher strength rules are evaluated first, as they represent more specific
245/// matches. This function sorts the rules in-place.
246///
247/// # Arguments
248///
249/// * `rules` - The slice of magic rules to sort
250///
251/// # Examples
252///
253/// ```
254/// use libmagic_rs::parser::ast::{MagicRule, OffsetSpec, TypeKind, Operator, Value};
255/// use libmagic_rs::evaluator::strength::sort_rules_by_strength;
256///
257/// let mut rules = vec![
258/// MagicRule {
259/// offset: OffsetSpec::Absolute(0),
260/// typ: TypeKind::Byte { signed: true },
261/// op: Operator::Equal,
262/// value: Value::Uint(0x7f),
263/// message: "byte rule".to_string(),
264/// children: vec![],
265/// level: 0,
266/// strength_modifier: None,
267/// },
268/// MagicRule {
269/// offset: OffsetSpec::Absolute(0),
270/// typ: TypeKind::String { max_length: None },
271/// op: Operator::Equal,
272/// value: Value::String("MAGIC".to_string()),
273/// message: "string rule".to_string(),
274/// children: vec![],
275/// level: 0,
276/// strength_modifier: None,
277/// },
278/// ];
279///
280/// sort_rules_by_strength(&mut rules);
281///
282/// // String rule should come first (higher strength)
283/// assert_eq!(rules[0].message, "string rule");
284/// assert_eq!(rules[1].message, "byte rule");
285/// ```
286pub fn sort_rules_by_strength(rules: &mut [MagicRule]) {
287 rules.sort_by(|a, b| {
288 let strength_a = calculate_rule_strength(a);
289 let strength_b = calculate_rule_strength(b);
290 // Sort in descending order (higher strength first)
291 strength_b.cmp(&strength_a)
292 });
293}
294
295/// Sort magic rules by strength and return the sorted vec (consuming the input).
296///
297/// This is a convenience function that takes ownership of the rules vector,
298/// sorts it by strength, and returns the sorted vector.
299///
300/// # Arguments
301///
302/// * `rules` - The vector of magic rules to sort
303///
304/// # Returns
305///
306/// The sorted vector with higher strength rules first
307///
308/// # Examples
309///
310/// ```
311/// use libmagic_rs::parser::ast::{MagicRule, OffsetSpec, TypeKind, Operator, Value};
312/// use libmagic_rs::evaluator::strength::into_sorted_by_strength;
313///
314/// let rules = vec![
315/// MagicRule {
316/// offset: OffsetSpec::Absolute(0),
317/// typ: TypeKind::Byte { signed: true },
318/// op: Operator::Equal,
319/// value: Value::Uint(0),
320/// message: "byte rule".to_string(),
321/// children: vec![],
322/// level: 0,
323/// strength_modifier: None,
324/// },
325/// MagicRule {
326/// offset: OffsetSpec::Absolute(0),
327/// typ: TypeKind::String { max_length: None },
328/// op: Operator::Equal,
329/// value: Value::String("MAGIC".to_string()),
330/// message: "string rule".to_string(),
331/// children: vec![],
332/// level: 0,
333/// strength_modifier: None,
334/// },
335/// ];
336///
337/// let sorted = into_sorted_by_strength(rules);
338/// assert_eq!(sorted[0].message, "string rule");
339/// ```
340#[must_use]
341pub fn into_sorted_by_strength(mut rules: Vec<MagicRule>) -> Vec<MagicRule> {
342 sort_rules_by_strength(&mut rules);
343 rules
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349 use crate::parser::ast::Endianness;
350
351 // Helper to create a basic test rule
352 fn make_rule(typ: TypeKind, op: Operator, offset: OffsetSpec, value: Value) -> MagicRule {
353 MagicRule {
354 offset,
355 typ,
356 op,
357 value,
358 message: "test".to_string(),
359 children: vec![],
360 level: 0,
361 strength_modifier: None,
362 }
363 }
364
365 // ============================================================
366 // Tests for calculate_default_strength
367 // ============================================================
368
369 #[test]
370 fn test_strength_type_byte() {
371 let rule = make_rule(
372 TypeKind::Byte { signed: true },
373 Operator::Equal,
374 OffsetSpec::Absolute(0),
375 Value::Uint(0),
376 );
377 let strength = calculate_default_strength(&rule);
378 // Byte: 5, Equal: 10, Absolute: 10, Numeric: 0 = 25
379 assert_eq!(strength, 25);
380 }
381
382 #[test]
383 fn test_strength_type_short() {
384 let rule = make_rule(
385 TypeKind::Short {
386 endian: Endianness::Little,
387 signed: false,
388 },
389 Operator::Equal,
390 OffsetSpec::Absolute(0),
391 Value::Uint(0),
392 );
393 let strength = calculate_default_strength(&rule);
394 // Short: 10, Equal: 10, Absolute: 10, Numeric: 0 = 30
395 assert_eq!(strength, 30);
396 }
397
398 #[test]
399 fn test_strength_type_long() {
400 let rule = make_rule(
401 TypeKind::Long {
402 endian: Endianness::Big,
403 signed: false,
404 },
405 Operator::Equal,
406 OffsetSpec::Absolute(0),
407 Value::Uint(0),
408 );
409 let strength = calculate_default_strength(&rule);
410 // Long: 15, Equal: 10, Absolute: 10, Numeric: 0 = 35
411 assert_eq!(strength, 35);
412 }
413
414 #[test]
415 fn test_strength_type_quad() {
416 let rule = make_rule(
417 TypeKind::Quad {
418 endian: Endianness::Little,
419 signed: false,
420 },
421 Operator::Equal,
422 OffsetSpec::Absolute(0),
423 Value::Uint(0),
424 );
425 let strength = calculate_default_strength(&rule);
426 // Quad: 16, Equal: 10, Absolute: 10, Numeric: 0 = 36
427 assert_eq!(strength, 36);
428 }
429
430 #[test]
431 fn test_strength_type_string() {
432 let rule = make_rule(
433 TypeKind::String { max_length: None },
434 Operator::Equal,
435 OffsetSpec::Absolute(0),
436 Value::String("ELF".to_string()),
437 );
438 let strength = calculate_default_strength(&rule);
439 // String: 20, Equal: 10, Absolute: 10, String length 3: 3 = 43
440 assert_eq!(strength, 43);
441 }
442
443 #[test]
444 fn test_strength_type_string_with_max_length() {
445 let rule = make_rule(
446 TypeKind::String {
447 max_length: Some(10),
448 },
449 Operator::Equal,
450 OffsetSpec::Absolute(0),
451 Value::String("TEST".to_string()),
452 );
453 let strength = calculate_default_strength(&rule);
454 // String with max_length: 25, Equal: 10, Absolute: 10, String length 4: 4 = 49
455 assert_eq!(strength, 49);
456 }
457
458 #[test]
459 fn test_strength_operator_not_equal() {
460 let rule = make_rule(
461 TypeKind::Byte { signed: true },
462 Operator::NotEqual,
463 OffsetSpec::Absolute(0),
464 Value::Uint(0),
465 );
466 let strength = calculate_default_strength(&rule);
467 // Byte: 5, NotEqual: 5, Absolute: 10, Numeric: 0 = 20
468 assert_eq!(strength, 20);
469 }
470
471 #[test]
472 fn test_strength_operator_bitwise_and() {
473 let rule = make_rule(
474 TypeKind::Byte { signed: true },
475 Operator::BitwiseAnd,
476 OffsetSpec::Absolute(0),
477 Value::Uint(0),
478 );
479 let strength = calculate_default_strength(&rule);
480 // Byte: 5, BitwiseAnd: 3, Absolute: 10, Numeric: 0 = 18
481 assert_eq!(strength, 18);
482 }
483
484 #[test]
485 fn test_strength_operator_bitwise_and_mask() {
486 let rule = make_rule(
487 TypeKind::Byte { signed: true },
488 Operator::BitwiseAndMask(0xFF),
489 OffsetSpec::Absolute(0),
490 Value::Uint(0),
491 );
492 let strength = calculate_default_strength(&rule);
493 // Byte: 5, BitwiseAndMask: 7, Absolute: 10, Numeric: 0 = 22
494 assert_eq!(strength, 22);
495 }
496
497 #[test]
498 fn test_strength_comparison_operators() {
499 let operators = [
500 Operator::LessThan,
501 Operator::GreaterThan,
502 Operator::LessEqual,
503 Operator::GreaterEqual,
504 ];
505 for op in operators {
506 let rule = make_rule(
507 TypeKind::Byte { signed: true },
508 op.clone(),
509 OffsetSpec::Absolute(0),
510 Value::Uint(0),
511 );
512 let strength = calculate_default_strength(&rule);
513 // Byte: 5, Comparison: 6, Absolute: 10, Numeric: 0 = 21
514 assert_eq!(strength, 21, "Failed for operator: {op:?}");
515 }
516 }
517
518 #[test]
519 fn test_strength_offset_indirect() {
520 let rule = make_rule(
521 TypeKind::Byte { signed: true },
522 Operator::Equal,
523 OffsetSpec::Indirect {
524 base_offset: 0,
525 pointer_type: TypeKind::Long {
526 endian: Endianness::Little,
527 signed: false,
528 },
529 adjustment: 0,
530 endian: Endianness::Little,
531 },
532 Value::Uint(0),
533 );
534 let strength = calculate_default_strength(&rule);
535 // Byte: 5, Equal: 10, Indirect: 5, Numeric: 0 = 20
536 assert_eq!(strength, 20);
537 }
538
539 #[test]
540 fn test_strength_offset_relative() {
541 let rule = make_rule(
542 TypeKind::Byte { signed: true },
543 Operator::Equal,
544 OffsetSpec::Relative(4),
545 Value::Uint(0),
546 );
547 let strength = calculate_default_strength(&rule);
548 // Byte: 5, Equal: 10, Relative: 3, Numeric: 0 = 18
549 assert_eq!(strength, 18);
550 }
551
552 #[test]
553 fn test_strength_offset_from_end() {
554 let rule = make_rule(
555 TypeKind::Byte { signed: true },
556 Operator::Equal,
557 OffsetSpec::FromEnd(-4),
558 Value::Uint(0),
559 );
560 let strength = calculate_default_strength(&rule);
561 // Byte: 5, Equal: 10, FromEnd: 8, Numeric: 0 = 23
562 assert_eq!(strength, 23);
563 }
564
565 #[test]
566 fn test_strength_value_bytes() {
567 let rule = make_rule(
568 TypeKind::Byte { signed: true },
569 Operator::Equal,
570 OffsetSpec::Absolute(0),
571 Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]),
572 );
573 let strength = calculate_default_strength(&rule);
574 // Byte: 5, Equal: 10, Absolute: 10, Bytes length 4: 4 = 29
575 assert_eq!(strength, 29);
576 }
577
578 #[test]
579 fn test_strength_value_long_string() {
580 let rule = make_rule(
581 TypeKind::String { max_length: None },
582 Operator::Equal,
583 OffsetSpec::Absolute(0),
584 Value::String("This is a very long string that exceeds the cap".to_string()),
585 );
586 let strength = calculate_default_strength(&rule);
587 // String: 20, Equal: 10, Absolute: 10, String length capped at 20: 20 = 60
588 assert_eq!(strength, 60);
589 }
590
591 // ============================================================
592 // Tests for apply_strength_modifier
593 // ============================================================
594
595 #[test]
596 fn test_apply_modifier_add() {
597 assert_eq!(apply_strength_modifier(50, &StrengthModifier::Add(10)), 60);
598 }
599
600 #[test]
601 fn test_apply_modifier_subtract() {
602 assert_eq!(
603 apply_strength_modifier(50, &StrengthModifier::Subtract(10)),
604 40
605 );
606 }
607
608 #[test]
609 fn test_apply_modifier_multiply() {
610 assert_eq!(
611 apply_strength_modifier(50, &StrengthModifier::Multiply(2)),
612 100
613 );
614 }
615
616 #[test]
617 fn test_apply_modifier_divide() {
618 assert_eq!(
619 apply_strength_modifier(50, &StrengthModifier::Divide(2)),
620 25
621 );
622 }
623
624 #[test]
625 fn test_apply_modifier_set() {
626 assert_eq!(apply_strength_modifier(50, &StrengthModifier::Set(75)), 75);
627 }
628
629 #[test]
630 fn test_apply_modifier_add_overflow() {
631 // Should clamp to MAX_STRENGTH
632 assert_eq!(
633 apply_strength_modifier(250, &StrengthModifier::Add(100)),
634 MAX_STRENGTH
635 );
636 }
637
638 #[test]
639 fn test_apply_modifier_subtract_underflow() {
640 // Should clamp to MIN_STRENGTH
641 assert_eq!(
642 apply_strength_modifier(10, &StrengthModifier::Subtract(100)),
643 MIN_STRENGTH
644 );
645 }
646
647 #[test]
648 fn test_apply_modifier_multiply_overflow() {
649 // Should clamp to MAX_STRENGTH
650 assert_eq!(
651 apply_strength_modifier(200, &StrengthModifier::Multiply(10)),
652 MAX_STRENGTH
653 );
654 }
655
656 #[test]
657 fn test_apply_modifier_divide_by_zero() {
658 // Should return base strength unchanged
659 assert_eq!(
660 apply_strength_modifier(50, &StrengthModifier::Divide(0)),
661 50
662 );
663 }
664
665 #[test]
666 fn test_apply_modifier_set_negative() {
667 // Should clamp to MIN_STRENGTH
668 assert_eq!(
669 apply_strength_modifier(50, &StrengthModifier::Set(-10)),
670 MIN_STRENGTH
671 );
672 }
673
674 #[test]
675 fn test_apply_modifier_set_over_max() {
676 // Should clamp to MAX_STRENGTH
677 assert_eq!(
678 apply_strength_modifier(50, &StrengthModifier::Set(1000)),
679 MAX_STRENGTH
680 );
681 }
682
683 // ============================================================
684 // Tests for calculate_rule_strength
685 // ============================================================
686
687 #[test]
688 fn test_rule_strength_without_modifier() {
689 let rule = make_rule(
690 TypeKind::Byte { signed: true },
691 Operator::Equal,
692 OffsetSpec::Absolute(0),
693 Value::Uint(0),
694 );
695 // Byte: 5, Equal: 10, Absolute: 10, Numeric: 0 = 25
696 assert_eq!(calculate_rule_strength(&rule), 25);
697 }
698
699 #[test]
700 fn test_rule_strength_with_add_modifier() {
701 let mut rule = make_rule(
702 TypeKind::Byte { signed: true },
703 Operator::Equal,
704 OffsetSpec::Absolute(0),
705 Value::Uint(0),
706 );
707 rule.strength_modifier = Some(StrengthModifier::Add(20));
708 // Base: 25, Add 20 = 45
709 assert_eq!(calculate_rule_strength(&rule), 45);
710 }
711
712 #[test]
713 fn test_rule_strength_with_multiply_modifier() {
714 let mut rule = make_rule(
715 TypeKind::Byte { signed: true },
716 Operator::Equal,
717 OffsetSpec::Absolute(0),
718 Value::Uint(0),
719 );
720 rule.strength_modifier = Some(StrengthModifier::Multiply(2));
721 // Base: 25, Multiply by 2 = 50
722 assert_eq!(calculate_rule_strength(&rule), 50);
723 }
724
725 #[test]
726 fn test_rule_strength_with_set_modifier() {
727 let mut rule = make_rule(
728 TypeKind::Byte { signed: true },
729 Operator::Equal,
730 OffsetSpec::Absolute(0),
731 Value::Uint(0),
732 );
733 rule.strength_modifier = Some(StrengthModifier::Set(100));
734 // Set overrides base strength
735 assert_eq!(calculate_rule_strength(&rule), 100);
736 }
737
738 // ============================================================
739 // Tests for sort_rules_by_strength
740 // ============================================================
741
742 #[test]
743 fn test_sort_rules_by_strength_basic() {
744 let mut rules = vec![
745 {
746 let mut r = make_rule(
747 TypeKind::Byte { signed: true },
748 Operator::Equal,
749 OffsetSpec::Absolute(0),
750 Value::Uint(0),
751 );
752 r.message = "byte rule".to_string();
753 r
754 },
755 {
756 let mut r = make_rule(
757 TypeKind::String { max_length: None },
758 Operator::Equal,
759 OffsetSpec::Absolute(0),
760 Value::String("MAGIC".to_string()),
761 );
762 r.message = "string rule".to_string();
763 r
764 },
765 ];
766
767 sort_rules_by_strength(&mut rules);
768
769 // String rule should come first (higher strength)
770 assert_eq!(rules[0].message, "string rule");
771 assert_eq!(rules[1].message, "byte rule");
772 }
773
774 #[test]
775 fn test_sort_rules_by_strength_with_modifier() {
776 let mut rules = vec![
777 {
778 let mut r = make_rule(
779 TypeKind::String { max_length: None },
780 Operator::Equal,
781 OffsetSpec::Absolute(0),
782 Value::String("TEST".to_string()),
783 );
784 r.message = "string rule".to_string();
785 // Lower the strength with a modifier
786 r.strength_modifier = Some(StrengthModifier::Set(10));
787 r
788 },
789 {
790 let mut r = make_rule(
791 TypeKind::Byte { signed: true },
792 Operator::Equal,
793 OffsetSpec::Absolute(0),
794 Value::Uint(0),
795 );
796 r.message = "byte rule".to_string();
797 // Boost the strength with a modifier
798 r.strength_modifier = Some(StrengthModifier::Set(100));
799 r
800 },
801 ];
802
803 sort_rules_by_strength(&mut rules);
804
805 // Byte rule should now come first due to strength modifier
806 assert_eq!(rules[0].message, "byte rule");
807 assert_eq!(rules[1].message, "string rule");
808 }
809
810 #[test]
811 fn test_sort_rules_empty() {
812 let mut rules: Vec<MagicRule> = vec![];
813 sort_rules_by_strength(&mut rules);
814 assert!(rules.is_empty());
815 }
816
817 #[test]
818 fn test_sort_rules_single() {
819 let mut rules = vec![make_rule(
820 TypeKind::Byte { signed: true },
821 Operator::Equal,
822 OffsetSpec::Absolute(0),
823 Value::Uint(0),
824 )];
825 sort_rules_by_strength(&mut rules);
826 assert_eq!(rules.len(), 1);
827 }
828
829 #[test]
830 fn test_into_sorted_by_strength() {
831 let rules = vec![
832 {
833 let mut r = make_rule(
834 TypeKind::Byte { signed: true },
835 Operator::Equal,
836 OffsetSpec::Absolute(0),
837 Value::Uint(0),
838 );
839 r.message = "byte rule".to_string();
840 r
841 },
842 {
843 let mut r = make_rule(
844 TypeKind::Long {
845 endian: Endianness::Big,
846 signed: false,
847 },
848 Operator::Equal,
849 OffsetSpec::Absolute(0),
850 Value::Uint(0),
851 );
852 r.message = "long rule".to_string();
853 r
854 },
855 ];
856
857 let sorted = into_sorted_by_strength(rules);
858
859 // Long rule should come first (higher strength)
860 assert_eq!(sorted[0].message, "long rule");
861 assert_eq!(sorted[1].message, "byte rule");
862 }
863
864 // ============================================================
865 // Edge case and integration tests
866 // ============================================================
867
868 #[test]
869 fn test_strength_comparison_string_vs_byte() {
870 let string_rule = make_rule(
871 TypeKind::String { max_length: None },
872 Operator::Equal,
873 OffsetSpec::Absolute(0),
874 Value::String("AB".to_string()),
875 );
876 let byte_rule = make_rule(
877 TypeKind::Byte { signed: true },
878 Operator::Equal,
879 OffsetSpec::Absolute(0),
880 Value::Uint(0x7f),
881 );
882
883 let string_strength = calculate_rule_strength(&string_rule);
884 let byte_strength = calculate_rule_strength(&byte_rule);
885
886 // String should have higher strength even with short value
887 assert!(
888 string_strength > byte_strength,
889 "String strength {string_strength} should be > byte strength {byte_strength}"
890 );
891 }
892
893 #[test]
894 fn test_strength_comparison_absolute_vs_relative_offset() {
895 let absolute_rule = make_rule(
896 TypeKind::Byte { signed: true },
897 Operator::Equal,
898 OffsetSpec::Absolute(0),
899 Value::Uint(0x7f),
900 );
901 let relative_rule = make_rule(
902 TypeKind::Byte { signed: true },
903 Operator::Equal,
904 OffsetSpec::Relative(4),
905 Value::Uint(0x7f),
906 );
907
908 let absolute_strength = calculate_rule_strength(&absolute_rule);
909 let relative_strength = calculate_rule_strength(&relative_rule);
910
911 // Absolute should have higher strength
912 assert!(
913 absolute_strength > relative_strength,
914 "Absolute strength {absolute_strength} should be > relative strength {relative_strength}"
915 );
916 }
917}