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}