Skip to main content

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}