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