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}