Skip to main content

libmagic_rs/evaluator/operators/
mod.rs

1// Copyright (c) 2025-2026 the libmagic-rs contributors
2// SPDX-License-Identifier: Apache-2.0
3
4//! Operator application for magic rule evaluation
5//!
6//! This module provides functions for applying comparison and bitwise operators
7//! to values during magic rule evaluation. It handles type-safe comparisons
8//! between different Value variants. Supports XOR (`^`), NOT (`~`), and
9//! any-value (`x`) matching in addition to equality, comparison, and bitwise AND.
10
11mod bitwise;
12mod comparison;
13mod equality;
14
15pub use bitwise::{
16    apply_bitwise_and, apply_bitwise_and_mask, apply_bitwise_not, apply_bitwise_not_with_width,
17    apply_bitwise_xor,
18};
19pub use comparison::{
20    apply_greater_equal, apply_greater_than, apply_less_equal, apply_less_than, compare_values,
21};
22pub use equality::{apply_equal, apply_not_equal};
23
24use crate::parser::ast::{Operator, Value};
25
26/// Apply any-value operator: always returns true (unconditional match).
27///
28/// The `x` operator in libmagic matches any value unconditionally. This is used
29/// for rules that should always match at a given offset regardless of the data.
30///
31/// # Arguments
32///
33/// * `_left` - The left-hand side value (ignored)
34/// * `_right` - The right-hand side value (ignored)
35///
36/// # Returns
37///
38/// Always returns `true`.
39///
40/// # Examples
41///
42/// ```
43/// use libmagic_rs::parser::ast::Value;
44/// use libmagic_rs::evaluator::operators::apply_any_value;
45///
46/// // Always returns true regardless of values
47/// assert!(apply_any_value(&Value::Uint(0), &Value::Uint(0)));
48/// assert!(apply_any_value(
49///     &Value::String("anything".to_string()),
50///     &Value::Uint(42),
51/// ));
52/// ```
53#[must_use]
54pub fn apply_any_value(_left: &Value, _right: &Value) -> bool {
55    true
56}
57
58/// Apply operator to two values using the specified operator type
59///
60/// This is the main operator application interface that dispatches to the appropriate
61/// operator function based on the `Operator` enum variant. This function serves as
62/// the primary entry point for operator evaluation in magic rule processing.
63///
64/// # Arguments
65///
66/// * `operator` - The operator to apply (`Equal`, `NotEqual`, `LessThan`,
67///   `GreaterThan`, `LessEqual`, `GreaterEqual`, `BitwiseAnd`, `BitwiseAndMask`,
68///   `BitwiseXor`, `BitwiseNot`, or `AnyValue`)
69/// * `left` - The left-hand side value (typically from file data)
70/// * `right` - The right-hand side value (typically from magic rule)
71///
72/// # Returns
73///
74/// `true` if the operator condition is satisfied, `false` otherwise
75///
76/// # Examples
77///
78/// ```
79/// use libmagic_rs::parser::ast::{Operator, Value};
80/// use libmagic_rs::evaluator::operators::apply_operator;
81///
82/// // Equality comparison
83/// assert!(apply_operator(
84///     &Operator::Equal,
85///     &Value::Uint(42),
86///     &Value::Uint(42)
87/// ));
88///
89/// // Inequality comparison
90/// assert!(apply_operator(
91///     &Operator::NotEqual,
92///     &Value::Uint(42),
93///     &Value::Uint(24)
94/// ));
95///
96/// // Less-than comparison
97/// assert!(apply_operator(
98///     &Operator::LessThan,
99///     &Value::Uint(5),
100///     &Value::Uint(10)
101/// ));
102///
103/// // Greater-than comparison
104/// assert!(apply_operator(
105///     &Operator::GreaterThan,
106///     &Value::Uint(10),
107///     &Value::Uint(5)
108/// ));
109///
110/// // Less-than-or-equal comparison
111/// assert!(apply_operator(
112///     &Operator::LessEqual,
113///     &Value::Uint(10),
114///     &Value::Uint(10)
115/// ));
116///
117/// // Greater-than-or-equal comparison
118/// assert!(apply_operator(
119///     &Operator::GreaterEqual,
120///     &Value::Uint(10),
121///     &Value::Uint(10)
122/// ));
123///
124/// // Bitwise AND operation
125/// assert!(apply_operator(
126///     &Operator::BitwiseAnd,
127///     &Value::Uint(0xFF),
128///     &Value::Uint(0x0F)
129/// ));
130///
131/// // Cross-type integer coercion
132/// assert!(apply_operator(
133///     &Operator::Equal,
134///     &Value::Uint(42),
135///     &Value::Int(42)
136/// ));
137///
138/// // Bitwise XOR, NOT, and any-value
139/// assert!(apply_operator(
140///     &Operator::BitwiseXor,
141///     &Value::Uint(0xFF),
142///     &Value::Uint(0x0F)
143/// ));
144/// assert!(apply_operator(
145///     &Operator::AnyValue,
146///     &Value::Uint(0),
147///     &Value::Uint(0)
148/// ));
149/// ```
150#[must_use]
151pub fn apply_operator(operator: &Operator, left: &Value, right: &Value) -> bool {
152    match operator {
153        Operator::Equal => apply_equal(left, right),
154        Operator::NotEqual => apply_not_equal(left, right),
155        Operator::LessThan => apply_less_than(left, right),
156        Operator::GreaterThan => apply_greater_than(left, right),
157        Operator::LessEqual => apply_less_equal(left, right),
158        Operator::GreaterEqual => apply_greater_equal(left, right),
159        Operator::BitwiseAnd => apply_bitwise_and(left, right),
160        Operator::BitwiseAndMask(mask) => apply_bitwise_and_mask(*mask, left, right),
161        Operator::BitwiseXor => apply_bitwise_xor(left, right),
162        Operator::BitwiseNot => apply_bitwise_not(left, right),
163        Operator::AnyValue => apply_any_value(left, right),
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn test_apply_operator_equal() {
173        // Test Equal operator dispatch
174        assert!(apply_operator(
175            &Operator::Equal,
176            &Value::Uint(42),
177            &Value::Uint(42)
178        ));
179        assert!(!apply_operator(
180            &Operator::Equal,
181            &Value::Uint(42),
182            &Value::Uint(24)
183        ));
184
185        // Test with different value types
186        assert!(apply_operator(
187            &Operator::Equal,
188            &Value::String("hello".to_string()),
189            &Value::String("hello".to_string())
190        ));
191        assert!(!apply_operator(
192            &Operator::Equal,
193            &Value::String("hello".to_string()),
194            &Value::String("world".to_string())
195        ));
196
197        // Cross-type integer coercion
198        assert!(apply_operator(
199            &Operator::Equal,
200            &Value::Uint(42),
201            &Value::Int(42)
202        ));
203    }
204
205    #[test]
206    fn test_apply_operator_not_equal() {
207        // Test NotEqual operator dispatch
208        assert!(!apply_operator(
209            &Operator::NotEqual,
210            &Value::Uint(42),
211            &Value::Uint(42)
212        ));
213        assert!(apply_operator(
214            &Operator::NotEqual,
215            &Value::Uint(42),
216            &Value::Uint(24)
217        ));
218
219        // Test with different value types
220        assert!(!apply_operator(
221            &Operator::NotEqual,
222            &Value::String("hello".to_string()),
223            &Value::String("hello".to_string())
224        ));
225        assert!(apply_operator(
226            &Operator::NotEqual,
227            &Value::String("hello".to_string()),
228            &Value::String("world".to_string())
229        ));
230
231        // Cross-type integer coercion: same value, so not-equal is false
232        assert!(!apply_operator(
233            &Operator::NotEqual,
234            &Value::Uint(42),
235            &Value::Int(42)
236        ));
237    }
238
239    #[test]
240    fn test_apply_operator_bitwise_and() {
241        // Test BitwiseAnd operator dispatch
242        assert!(apply_operator(
243            &Operator::BitwiseAnd,
244            &Value::Uint(0xFF),
245            &Value::Uint(0x0F)
246        ));
247        assert!(!apply_operator(
248            &Operator::BitwiseAnd,
249            &Value::Uint(0xF0),
250            &Value::Uint(0x0F)
251        ));
252
253        // Test with signed integers
254        assert!(apply_operator(
255            &Operator::BitwiseAnd,
256            &Value::Int(-1),
257            &Value::Int(1)
258        ));
259        assert!(!apply_operator(
260            &Operator::BitwiseAnd,
261            &Value::Int(-2),
262            &Value::Int(1)
263        ));
264
265        // Test with mixed types
266        assert!(apply_operator(
267            &Operator::BitwiseAnd,
268            &Value::Uint(0xFF),
269            &Value::Int(0x0F)
270        ));
271
272        // Non-integer types should return false
273        assert!(!apply_operator(
274            &Operator::BitwiseAnd,
275            &Value::String("test".to_string()),
276            &Value::Uint(0x01)
277        ));
278    }
279
280    #[test]
281    fn test_apply_operator_all_operators_with_same_values() {
282        let test_cases = vec![
283            // Same values - Equal should be true, NotEqual false, BitwiseAnd depends on value
284            (Value::Uint(42), Value::Uint(42)),
285            (Value::Int(-100), Value::Int(-100)),
286            (
287                Value::String("test".to_string()),
288                Value::String("test".to_string()),
289            ),
290            (Value::Bytes(vec![1, 2, 3]), Value::Bytes(vec![1, 2, 3])),
291        ];
292
293        for (left, right) in test_cases {
294            // Equal should always be true for same values
295            assert!(
296                apply_operator(&Operator::Equal, &left, &right),
297                "Equal should be true for same values: {left:?} == {right:?}"
298            );
299
300            // NotEqual should always be false for same values
301            assert!(
302                !apply_operator(&Operator::NotEqual, &left, &right),
303                "NotEqual should be false for same values: {left:?} != {right:?}"
304            );
305
306            // BitwiseAnd behavior depends on the value type and content
307            let bitwise_result = apply_operator(&Operator::BitwiseAnd, &left, &right);
308            match &left {
309                Value::Uint(n) => {
310                    // For unsigned integers, BitwiseAnd should be true if value is non-zero
311                    let expected = *n != 0;
312                    assert_eq!(
313                        bitwise_result, expected,
314                        "BitwiseAnd for Uint({n}) should be {expected}"
315                    );
316                }
317                Value::Int(n) => {
318                    // For signed integers, BitwiseAnd should be true if value is non-zero
319                    let expected = *n != 0;
320                    assert_eq!(
321                        bitwise_result, expected,
322                        "BitwiseAnd for Int({n}) should be {expected}"
323                    );
324                }
325                _ => {
326                    // For non-integers, BitwiseAnd should always be false
327                    assert!(
328                        !bitwise_result,
329                        "BitwiseAnd should be false for non-integer types: {left:?}"
330                    );
331                }
332            }
333        }
334    }
335
336    #[test]
337    fn test_apply_operator_all_operators_with_different_values() {
338        let test_cases = vec![
339            // Different values of same type
340            (Value::Uint(42), Value::Uint(24)),
341            (Value::Int(100), Value::Int(-100)),
342            (
343                Value::String("hello".to_string()),
344                Value::String("world".to_string()),
345            ),
346            (Value::Bytes(vec![1, 2, 3]), Value::Bytes(vec![4, 5, 6])),
347            // Different types (non-coercible)
348            (Value::Uint(42), Value::String("42".to_string())),
349            (Value::Int(42), Value::Bytes(vec![42])),
350        ];
351
352        for (left, right) in test_cases {
353            // Equal should always be false for truly different values
354            assert!(
355                !apply_operator(&Operator::Equal, &left, &right),
356                "Equal should be false for different values: {left:?} == {right:?}"
357            );
358
359            // NotEqual should always be true for truly different values
360            assert!(
361                apply_operator(&Operator::NotEqual, &left, &right),
362                "NotEqual should be true for different values: {left:?} != {right:?}"
363            );
364
365            // BitwiseAnd behavior depends on the value types and content
366            let bitwise_result = apply_operator(&Operator::BitwiseAnd, &left, &right);
367            match (&left, &right) {
368                (Value::Uint(a), Value::Uint(b)) => {
369                    let expected = (a & b) != 0;
370                    assert_eq!(
371                        bitwise_result, expected,
372                        "BitwiseAnd for Uint({a}) & Uint({b}) should be {expected}"
373                    );
374                }
375                (Value::Int(a), Value::Int(b)) => {
376                    #[allow(clippy::cast_sign_loss)]
377                    let expected = ((*a as u64) & (*b as u64)) != 0;
378                    assert_eq!(
379                        bitwise_result, expected,
380                        "BitwiseAnd for Int({a}) & Int({b}) should be {expected}"
381                    );
382                }
383                (Value::Uint(a), Value::Int(b)) | (Value::Int(b), Value::Uint(a)) => {
384                    #[allow(clippy::cast_sign_loss)]
385                    let expected = (a & (*b as u64)) != 0;
386                    assert_eq!(
387                        bitwise_result, expected,
388                        "BitwiseAnd for mixed Uint/Int should be {expected}"
389                    );
390                }
391                _ => {
392                    // For non-integer types, BitwiseAnd should always be false
393                    assert!(
394                        !bitwise_result,
395                        "BitwiseAnd should be false for non-integer types: {left:?} & {right:?}"
396                    );
397                }
398            }
399        }
400    }
401
402    #[test]
403    fn test_apply_operator_consistency_with_individual_functions() {
404        let test_cases = vec![
405            (Value::Uint(42), Value::Uint(42)),
406            (Value::Uint(42), Value::Uint(24)),
407            (Value::Int(-100), Value::Int(-100)),
408            (Value::Int(100), Value::Int(-100)),
409            (
410                Value::String("test".to_string()),
411                Value::String("test".to_string()),
412            ),
413            (
414                Value::String("hello".to_string()),
415                Value::String("world".to_string()),
416            ),
417            (Value::Bytes(vec![1, 2, 3]), Value::Bytes(vec![1, 2, 3])),
418            (Value::Bytes(vec![1, 2, 3]), Value::Bytes(vec![4, 5, 6])),
419            // Cross-type cases
420            (Value::Uint(42), Value::Int(42)),
421            (Value::Uint(42), Value::String("42".to_string())),
422            (Value::Int(42), Value::Bytes(vec![42])),
423        ];
424
425        for (left, right) in test_cases {
426            // Test that apply_operator gives same results as individual functions
427            assert_eq!(
428                apply_operator(&Operator::Equal, &left, &right),
429                apply_equal(&left, &right),
430                "apply_operator(Equal) should match apply_equal for {left:?}, {right:?}"
431            );
432
433            assert_eq!(
434                apply_operator(&Operator::NotEqual, &left, &right),
435                apply_not_equal(&left, &right),
436                "apply_operator(NotEqual) should match apply_not_equal for {left:?}, {right:?}"
437            );
438
439            assert_eq!(
440                apply_operator(&Operator::BitwiseAnd, &left, &right),
441                apply_bitwise_and(&left, &right),
442                "apply_operator(BitwiseAnd) should match apply_bitwise_and for {left:?}, {right:?}"
443            );
444
445            assert_eq!(
446                apply_operator(&Operator::BitwiseXor, &left, &right),
447                apply_bitwise_xor(&left, &right),
448                "apply_operator(BitwiseXor) should match apply_bitwise_xor for {left:?}, {right:?}"
449            );
450
451            assert_eq!(
452                apply_operator(&Operator::BitwiseNot, &left, &right),
453                apply_bitwise_not(&left, &right),
454                "apply_operator(BitwiseNot) should match apply_bitwise_not for {left:?}, {right:?}"
455            );
456
457            assert!(
458                apply_operator(&Operator::AnyValue, &left, &right),
459                "apply_operator(AnyValue) should always be true for {left:?}, {right:?}"
460            );
461        }
462    }
463
464    #[test]
465    fn test_apply_operator_magic_rule_scenarios() {
466        // Test scenarios that would commonly appear in magic rules
467
468        // ELF magic number check
469        let elf_magic = Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]);
470        let elf_expected = Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]);
471        assert!(apply_operator(&Operator::Equal, &elf_magic, &elf_expected));
472        assert!(!apply_operator(
473            &Operator::NotEqual,
474            &elf_magic,
475            &elf_expected
476        ));
477
478        // ZIP magic number check
479        let zip_magic = Value::Uint(0x504B_0304);
480        let zip_expected = Value::Uint(0x504B_0304);
481        assert!(apply_operator(&Operator::Equal, &zip_magic, &zip_expected));
482
483        // Bit flag checking (common in binary formats)
484        let flags = Value::Uint(0b1101_0110);
485        let flag_mask = Value::Uint(0b0000_0010); // Check if bit 1 is set
486        assert!(apply_operator(&Operator::BitwiseAnd, &flags, &flag_mask));
487
488        let no_flag_mask = Value::Uint(0b0000_0001); // Check if bit 0 is set
489        assert!(!apply_operator(
490            &Operator::BitwiseAnd,
491            &flags,
492            &no_flag_mask
493        ));
494
495        // String matching for text-based formats
496        let content = Value::String("#!/bin/bash".to_string());
497        let shebang = Value::String("#!/bin/bash".to_string());
498        assert!(apply_operator(&Operator::Equal, &content, &shebang));
499
500        let not_shebang = Value::String("#!/usr/bin/python".to_string());
501        assert!(apply_operator(&Operator::NotEqual, &content, &not_shebang));
502
503        // Version number checking
504        let version = Value::Uint(2);
505        let expected_version = Value::Uint(2);
506        let old_version = Value::Uint(1);
507        assert!(apply_operator(
508            &Operator::Equal,
509            &version,
510            &expected_version
511        ));
512        assert!(apply_operator(&Operator::NotEqual, &version, &old_version));
513    }
514
515    #[test]
516    fn test_apply_operator_edge_cases() {
517        // Test with extreme values
518        let max_uint = Value::Uint(u64::MAX);
519        let min_signed = Value::Int(i64::MIN);
520        let max_signed = Value::Int(i64::MAX);
521
522        // Self-comparison should work
523        assert!(apply_operator(&Operator::Equal, &max_uint, &max_uint));
524        assert!(apply_operator(&Operator::Equal, &min_signed, &min_signed));
525        assert!(apply_operator(&Operator::Equal, &max_signed, &max_signed));
526
527        // Cross-extreme comparisons
528        assert!(apply_operator(&Operator::NotEqual, &max_uint, &min_signed));
529        assert!(apply_operator(
530            &Operator::NotEqual,
531            &max_signed,
532            &min_signed
533        ));
534
535        // Bitwise operations with extreme values
536        assert!(apply_operator(
537            &Operator::BitwiseAnd,
538            &max_uint,
539            &Value::Uint(1)
540        ));
541        assert!(apply_operator(
542            &Operator::BitwiseAnd,
543            &min_signed,
544            &min_signed
545        ));
546
547        // Empty collections
548        let empty_bytes = Value::Bytes(vec![]);
549        let empty_string = Value::String(String::new());
550        assert!(apply_operator(&Operator::Equal, &empty_bytes, &empty_bytes));
551        assert!(apply_operator(
552            &Operator::Equal,
553            &empty_string,
554            &empty_string
555        ));
556        assert!(apply_operator(
557            &Operator::NotEqual,
558            &empty_bytes,
559            &empty_string
560        ));
561
562        // Zero values
563        let zero_uint = Value::Uint(0);
564        let zero_signed = Value::Int(0);
565        assert!(!apply_operator(
566            &Operator::BitwiseAnd,
567            &zero_uint,
568            &Value::Uint(0xFF)
569        ));
570        assert!(!apply_operator(
571            &Operator::BitwiseAnd,
572            &zero_signed,
573            &Value::Int(0xFF)
574        ));
575        assert!(!apply_operator(
576            &Operator::NotEqual,
577            &zero_uint,
578            &zero_signed
579        )); // Cross-type integer coercion: 0 == 0
580    }
581
582    #[test]
583    fn test_apply_operator_bitwise_xor() {
584        assert!(apply_operator(
585            &Operator::BitwiseXor,
586            &Value::Uint(0xFF),
587            &Value::Uint(0x0F)
588        ));
589        assert!(!apply_operator(
590            &Operator::BitwiseXor,
591            &Value::Uint(42),
592            &Value::Uint(42)
593        ));
594        assert!(!apply_operator(
595            &Operator::BitwiseXor,
596            &Value::String("x".to_string()),
597            &Value::Uint(1)
598        ));
599    }
600
601    #[test]
602    fn test_apply_operator_bitwise_not() {
603        assert!(apply_operator(
604            &Operator::BitwiseNot,
605            &Value::Uint(0),
606            &Value::Uint(u64::MAX)
607        ));
608        assert!(apply_operator(
609            &Operator::BitwiseNot,
610            &Value::Int(-1),
611            &Value::Int(0)
612        ));
613        assert!(!apply_operator(
614            &Operator::BitwiseNot,
615            &Value::Bytes(vec![0]),
616            &Value::Uint(0xFF)
617        ));
618    }
619
620    #[test]
621    fn test_apply_operator_any_value() {
622        assert!(apply_operator(
623            &Operator::AnyValue,
624            &Value::Uint(0),
625            &Value::Uint(0)
626        ));
627        assert!(apply_operator(
628            &Operator::AnyValue,
629            &Value::Int(42),
630            &Value::Int(0)
631        ));
632        assert!(apply_operator(
633            &Operator::AnyValue,
634            &Value::Bytes(vec![1, 2, 3]),
635            &Value::Bytes(vec![])
636        ));
637        assert!(apply_operator(
638            &Operator::AnyValue,
639            &Value::String("x".to_string()),
640            &Value::String("y".to_string())
641        ));
642        assert!(apply_operator(
643            &Operator::AnyValue,
644            &Value::Uint(1),
645            &Value::String(String::new())
646        ));
647        assert!(apply_operator(
648            &Operator::AnyValue,
649            &Value::Bytes(vec![]),
650            &Value::Bytes(vec![])
651        ));
652    }
653
654    #[test]
655    fn test_apply_operator_all_combinations() {
656        let operators = [
657            Operator::Equal,
658            Operator::NotEqual,
659            Operator::LessThan,
660            Operator::GreaterThan,
661            Operator::LessEqual,
662            Operator::GreaterEqual,
663            Operator::BitwiseAnd,
664            Operator::BitwiseAndMask(0xFF),
665            Operator::BitwiseXor,
666            Operator::BitwiseNot,
667            Operator::AnyValue,
668        ];
669        let values = [
670            Value::Uint(42),
671            Value::Int(-42),
672            Value::Bytes(vec![42]),
673            Value::String("42".to_string()),
674        ];
675
676        // Test all operator-value combinations to ensure no panics
677        for operator in &operators {
678            for left in &values {
679                for right in &values {
680                    // This should not panic for any combination
681                    let result = apply_operator(operator, left, right);
682
683                    // Verify the result is consistent with individual function calls
684                    let expected = match operator {
685                        Operator::Equal => apply_equal(left, right),
686                        Operator::NotEqual => apply_not_equal(left, right),
687                        Operator::LessThan => apply_less_than(left, right),
688                        Operator::GreaterThan => apply_greater_than(left, right),
689                        Operator::LessEqual => apply_less_equal(left, right),
690                        Operator::GreaterEqual => apply_greater_equal(left, right),
691                        Operator::BitwiseAnd => apply_bitwise_and(left, right),
692                        Operator::BitwiseAndMask(mask) => {
693                            apply_bitwise_and_mask(*mask, left, right)
694                        }
695                        Operator::BitwiseXor => apply_bitwise_xor(left, right),
696                        Operator::BitwiseNot => apply_bitwise_not(left, right),
697                        Operator::AnyValue => apply_any_value(left, right),
698                    };
699
700                    assert_eq!(
701                        result, expected,
702                        "apply_operator({operator:?}, {left:?}, {right:?}) should match individual function"
703                    );
704                }
705            }
706        }
707    }
708}