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