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