Skip to main content

libmagic_rs/evaluator/
mod.rs

1// Copyright (c) 2025-2026 the libmagic-rs contributors
2// SPDX-License-Identifier: Apache-2.0
3
4//! Rule evaluation engine
5//!
6//! This module contains the core evaluation logic for executing magic rules
7//! against file buffers to identify file types.
8
9use crate::parser::ast::MagicRule;
10use crate::{EvaluationConfig, LibmagicError};
11use serde::{Deserialize, Serialize};
12
13#[cfg(test)]
14use crate::parser::ast::{Endianness, OffsetSpec, Operator, TypeKind, Value};
15
16pub mod offset;
17pub mod operators;
18pub mod strength;
19pub mod types;
20
21/// Context for maintaining evaluation state during rule processing
22///
23/// The `EvaluationContext` tracks the current state of rule evaluation,
24/// including the current offset position, recursion depth for nested rules,
25/// and configuration settings that control evaluation behavior.
26///
27/// # Examples
28///
29/// ```rust
30/// use libmagic_rs::evaluator::EvaluationContext;
31/// use libmagic_rs::EvaluationConfig;
32///
33/// let config = EvaluationConfig::default();
34/// let context = EvaluationContext::new(config);
35///
36/// assert_eq!(context.current_offset(), 0);
37/// assert_eq!(context.recursion_depth(), 0);
38/// ```
39#[derive(Debug, Clone)]
40pub struct EvaluationContext {
41    /// Current offset position in the file buffer
42    current_offset: usize,
43    /// Current recursion depth for nested rule evaluation
44    recursion_depth: u32,
45    /// Configuration settings for evaluation behavior
46    config: EvaluationConfig,
47}
48
49impl EvaluationContext {
50    /// Create a new evaluation context with the given configuration
51    ///
52    /// # Arguments
53    ///
54    /// * `config` - Configuration settings for evaluation behavior
55    ///
56    /// # Examples
57    ///
58    /// ```rust
59    /// use libmagic_rs::evaluator::EvaluationContext;
60    /// use libmagic_rs::EvaluationConfig;
61    ///
62    /// let config = EvaluationConfig::default();
63    /// let context = EvaluationContext::new(config);
64    /// ```
65    #[must_use]
66    pub const fn new(config: EvaluationConfig) -> Self {
67        Self {
68            current_offset: 0,
69            recursion_depth: 0,
70            config,
71        }
72    }
73
74    /// Get the current offset position
75    ///
76    /// # Returns
77    ///
78    /// The current offset position in the file buffer
79    #[must_use]
80    pub const fn current_offset(&self) -> usize {
81        self.current_offset
82    }
83
84    /// Set the current offset position
85    ///
86    /// # Arguments
87    ///
88    /// * `offset` - The new offset position
89    pub fn set_current_offset(&mut self, offset: usize) {
90        self.current_offset = offset;
91    }
92
93    /// Get the current recursion depth
94    ///
95    /// # Returns
96    ///
97    /// The current recursion depth for nested rule evaluation
98    #[must_use]
99    pub const fn recursion_depth(&self) -> u32 {
100        self.recursion_depth
101    }
102
103    /// Increment the recursion depth
104    ///
105    /// # Returns
106    ///
107    /// `Ok(())` if the recursion depth is within limits, or `Err(LibmagicError)`
108    /// if the maximum recursion depth would be exceeded
109    ///
110    /// # Errors
111    ///
112    /// Returns `LibmagicError::EvaluationError` if incrementing would exceed
113    /// the maximum recursion depth configured in the evaluation config.
114    pub fn increment_recursion_depth(&mut self) -> Result<(), LibmagicError> {
115        if self.recursion_depth >= self.config.max_recursion_depth {
116            return Err(LibmagicError::EvaluationError(
117                crate::error::EvaluationError::recursion_limit_exceeded(self.recursion_depth),
118            ));
119        }
120        self.recursion_depth += 1;
121        Ok(())
122    }
123
124    /// Decrement the recursion depth
125    ///
126    /// # Errors
127    ///
128    /// Returns an error if the recursion depth is already 0, as this indicates
129    /// a programming error in the evaluation logic (mismatched increment/decrement calls).
130    pub fn decrement_recursion_depth(&mut self) -> Result<(), LibmagicError> {
131        if self.recursion_depth == 0 {
132            return Err(LibmagicError::EvaluationError(
133                crate::error::EvaluationError::internal_error(
134                    "Attempted to decrement recursion depth below 0",
135                ),
136            ));
137        }
138        self.recursion_depth -= 1;
139        Ok(())
140    }
141
142    /// Get a reference to the evaluation configuration
143    ///
144    /// # Returns
145    ///
146    /// A reference to the `EvaluationConfig` used by this context
147    #[must_use]
148    pub const fn config(&self) -> &EvaluationConfig {
149        &self.config
150    }
151
152    /// Check if evaluation should stop at the first match
153    ///
154    /// # Returns
155    ///
156    /// `true` if evaluation should stop at the first match, `false` otherwise
157    #[must_use]
158    pub const fn should_stop_at_first_match(&self) -> bool {
159        self.config.stop_at_first_match
160    }
161
162    /// Get the maximum string length allowed
163    ///
164    /// # Returns
165    ///
166    /// The maximum string length that should be read during evaluation
167    #[must_use]
168    pub const fn max_string_length(&self) -> usize {
169        self.config.max_string_length
170    }
171
172    /// Check if MIME type mapping is enabled
173    ///
174    /// # Returns
175    ///
176    /// `true` if MIME type mapping should be performed, `false` otherwise
177    #[must_use]
178    pub const fn enable_mime_types(&self) -> bool {
179        self.config.enable_mime_types
180    }
181
182    /// Get the evaluation timeout in milliseconds
183    ///
184    /// # Returns
185    ///
186    /// The timeout duration in milliseconds, or `None` if no timeout is set
187    #[must_use]
188    pub const fn timeout_ms(&self) -> Option<u64> {
189        self.config.timeout_ms
190    }
191
192    /// Reset the context to initial state while preserving configuration
193    ///
194    /// This resets the current offset and recursion depth to 0, but keeps
195    /// the same configuration settings.
196    pub fn reset(&mut self) {
197        self.current_offset = 0;
198        self.recursion_depth = 0;
199    }
200}
201
202/// Result of evaluating a magic rule
203///
204/// Contains information about a successful rule match, including the rule
205/// that matched and its associated message.
206#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
207pub struct MatchResult {
208    /// The message associated with the matching rule
209    pub message: String,
210    /// The offset where the match occurred
211    pub offset: usize,
212    /// The rule level (depth in hierarchy)
213    pub level: u32,
214    /// The matched value
215    pub value: crate::parser::ast::Value,
216    /// Confidence score (0.0 to 1.0)
217    ///
218    /// Calculated based on match depth in the rule hierarchy.
219    /// Deeper matches indicate more specific file type identification
220    /// and thus higher confidence.
221    pub confidence: f64,
222}
223
224impl MatchResult {
225    /// Calculate confidence score based on rule depth
226    ///
227    /// Formula: min(1.0, 0.3 + (level * 0.2))
228    /// - Level 0 (root): 0.3
229    /// - Level 1: 0.5
230    /// - Level 2: 0.7
231    /// - Level 3: 0.9
232    /// - Level 4+: 1.0 (capped)
233    ///
234    /// # Examples
235    ///
236    /// ```
237    /// use libmagic_rs::evaluator::MatchResult;
238    ///
239    /// assert!((MatchResult::calculate_confidence(0) - 0.3).abs() < 0.001);
240    /// assert!((MatchResult::calculate_confidence(3) - 0.9).abs() < 0.001);
241    /// assert!((MatchResult::calculate_confidence(10) - 1.0).abs() < 0.001);
242    /// ```
243    #[must_use]
244    pub fn calculate_confidence(level: u32) -> f64 {
245        (0.3 + (f64::from(level) * 0.2)).min(1.0)
246    }
247}
248
249/// Evaluate a single magic rule against a file buffer
250///
251/// This function performs the core rule evaluation by:
252/// 1. Resolving the rule's offset specification to an absolute position
253/// 2. Reading and interpreting bytes at that position according to the rule's type
254/// 3. Applying the rule's operator to compare the read value with the expected value
255///
256/// # Arguments
257///
258/// * `rule` - The magic rule to evaluate
259/// * `buffer` - The file buffer to evaluate against
260///
261/// # Returns
262///
263/// Returns `Ok(Some((offset, value)))` if the rule matches (with the resolved offset and
264/// read value), `Ok(None)` if it doesn't match, or `Err(LibmagicError)` if evaluation
265/// fails due to buffer access issues or other errors.
266///
267/// # Examples
268///
269/// ```rust
270/// use libmagic_rs::evaluator::evaluate_single_rule;
271/// use libmagic_rs::parser::ast::{MagicRule, OffsetSpec, TypeKind, Operator, Value};
272///
273/// // Create a rule to check for ELF magic bytes at offset 0
274/// let rule = MagicRule {
275///     offset: OffsetSpec::Absolute(0),
276///     typ: TypeKind::Byte,
277///     op: Operator::Equal,
278///     value: Value::Uint(0x7f),
279///     message: "ELF magic".to_string(),
280///     children: vec![],
281///     level: 0,
282///     strength_modifier: None,
283/// };
284///
285/// let elf_buffer = &[0x7f, 0x45, 0x4c, 0x46]; // ELF magic bytes
286/// let result = evaluate_single_rule(&rule, elf_buffer).unwrap();
287/// assert!(result.is_some()); // Should match
288///
289/// let non_elf_buffer = &[0x50, 0x4b, 0x03, 0x04]; // ZIP magic bytes
290/// let result = evaluate_single_rule(&rule, non_elf_buffer).unwrap();
291/// assert!(result.is_none()); // Should not match
292/// ```
293///
294/// # Errors
295///
296/// * `LibmagicError::EvaluationError` - If offset resolution fails, buffer access is out of bounds,
297///   or type interpretation fails
298pub fn evaluate_single_rule(
299    rule: &MagicRule,
300    buffer: &[u8],
301) -> Result<Option<(usize, crate::parser::ast::Value)>, LibmagicError> {
302    // Step 1: Resolve the offset specification to an absolute position
303    let absolute_offset = offset::resolve_offset(&rule.offset, buffer)?;
304
305    // Step 2: Read and interpret bytes at the resolved offset according to the rule's type
306    let read_value = types::read_typed_value(buffer, absolute_offset, &rule.typ)
307        .map_err(|e| LibmagicError::EvaluationError(e.into()))?;
308
309    // Step 3: Apply the operator to compare the read value with the expected value
310    Ok(
311        operators::apply_operator(&rule.op, &read_value, &rule.value)
312            .then_some((absolute_offset, read_value)),
313    )
314}
315
316/// Evaluate a list of magic rules against a file buffer with hierarchical processing
317///
318/// This function implements the core hierarchical rule evaluation algorithm with graceful
319/// error handling:
320/// 1. Evaluates each top-level rule in sequence
321/// 2. If a parent rule matches, evaluates its child rules for refinement
322/// 3. Collects all matches or stops at first match based on configuration
323/// 4. Maintains evaluation context for recursion limits and state
324/// 5. Implements graceful degradation by skipping problematic rules and continuing evaluation
325///
326/// The hierarchical evaluation follows these principles:
327/// - Parent rules must match before children are evaluated
328/// - Child rules provide refinement and additional detail
329/// - Evaluation can stop at first match or continue for all matches
330/// - Recursion depth is limited to prevent infinite loops
331/// - Problematic rules are skipped to allow evaluation to continue
332///
333/// # Arguments
334///
335/// * `rules` - The list of magic rules to evaluate
336/// * `buffer` - The file buffer to evaluate against
337/// * `context` - Mutable evaluation context for state management
338///
339/// # Returns
340///
341/// Returns `Ok(Vec<MatchResult>)` containing all matches found. Errors in individual rules
342/// are logged and skipped to allow evaluation to continue. Only returns `Err(LibmagicError)`
343/// for critical failures like timeout or recursion limit exceeded.
344///
345/// # Examples
346///
347/// ```rust
348/// use libmagic_rs::evaluator::{evaluate_rules, EvaluationContext, MatchResult};
349/// use libmagic_rs::parser::ast::{MagicRule, OffsetSpec, TypeKind, Operator, Value};
350/// use libmagic_rs::EvaluationConfig;
351///
352/// // Create a hierarchical rule set for ELF files
353/// let parent_rule = MagicRule {
354///     offset: OffsetSpec::Absolute(0),
355///     typ: TypeKind::Byte,
356///     op: Operator::Equal,
357///     value: Value::Uint(0x7f),
358///     message: "ELF".to_string(),
359///     children: vec![
360///         MagicRule {
361///             offset: OffsetSpec::Absolute(4),
362///             typ: TypeKind::Byte,
363///             op: Operator::Equal,
364///             value: Value::Uint(2),
365///             message: "64-bit".to_string(),
366///             children: vec![],
367///             level: 1,
368///             strength_modifier: None,
369///         }
370///     ],
371///     level: 0,
372///     strength_modifier: None,
373/// };
374///
375/// let rules = vec![parent_rule];
376/// let buffer = &[0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01]; // ELF64 header
377/// let config = EvaluationConfig::default();
378/// let mut context = EvaluationContext::new(config);
379///
380/// let matches = evaluate_rules(&rules, buffer, &mut context).unwrap();
381/// assert_eq!(matches.len(), 2); // Parent and child should both match
382/// ```
383///
384/// # Errors
385///
386/// * `LibmagicError::Timeout` - If evaluation exceeds configured timeout
387/// * `LibmagicError::EvaluationError` - Only for critical failures like recursion limit exceeded
388///
389/// Individual rule evaluation errors are handled gracefully and do not stop the overall evaluation.
390pub fn evaluate_rules(
391    rules: &[MagicRule],
392    buffer: &[u8],
393    context: &mut EvaluationContext,
394) -> Result<Vec<MatchResult>, LibmagicError> {
395    let mut matches = Vec::with_capacity(8);
396    let start_time = std::time::Instant::now();
397    let mut rule_count = 0u32;
398
399    for rule in rules {
400        // Check timeout periodically (every 16 rules) to reduce syscall overhead
401        rule_count = rule_count.wrapping_add(1);
402        if rule_count.trailing_zeros() >= 4 {
403            if let Some(timeout_ms) = context.timeout_ms() {
404                if start_time.elapsed().as_millis() > u128::from(timeout_ms) {
405                    return Err(LibmagicError::Timeout { timeout_ms });
406                }
407            }
408        }
409
410        // Evaluate the current rule with graceful error handling
411        let match_data = match evaluate_single_rule(rule, buffer) {
412            Ok(data) => data,
413            Err(_e) => {
414                // Skip rules with evaluation errors (graceful degradation)
415                continue;
416            }
417        };
418
419        if let Some((absolute_offset, read_value)) = match_data {
420            let match_result = MatchResult {
421                message: rule.message.clone(),
422                offset: absolute_offset,
423                level: rule.level,
424                value: read_value,
425                confidence: MatchResult::calculate_confidence(rule.level),
426            };
427            matches.push(match_result);
428
429            // If this rule has children, evaluate them recursively
430            if !rule.children.is_empty() {
431                // Check recursion depth limit - this is a critical error that should stop evaluation
432                context.increment_recursion_depth()?;
433
434                // Recursively evaluate child rules with graceful error handling
435                match evaluate_rules(&rule.children, buffer, context) {
436                    Ok(child_matches) => {
437                        matches.extend(child_matches);
438                    }
439                    Err(LibmagicError::Timeout { .. }) => {
440                        // Timeout is critical, propagate it up
441                        context.decrement_recursion_depth()?;
442                        return Err(LibmagicError::Timeout {
443                            timeout_ms: context.timeout_ms().unwrap_or(0),
444                        });
445                    }
446                    Err(LibmagicError::EvaluationError(
447                        crate::error::EvaluationError::RecursionLimitExceeded { .. },
448                    )) => {
449                        // Recursion limit is critical, propagate it up
450                        context.decrement_recursion_depth()?;
451                        return Err(LibmagicError::EvaluationError(
452                            crate::error::EvaluationError::RecursionLimitExceeded {
453                                depth: context.recursion_depth(),
454                            },
455                        ));
456                    }
457                    Err(_e) => {
458                        // Non-critical child evaluation errors are skipped (graceful degradation)
459                    }
460                }
461
462                // Restore recursion depth
463                context.decrement_recursion_depth()?;
464            }
465
466            // Stop at first match if configured to do so
467            if context.should_stop_at_first_match() {
468                break;
469            }
470        }
471    }
472
473    Ok(matches)
474}
475
476/// Evaluate magic rules with a fresh context
477///
478/// This is a convenience function that creates a new evaluation context
479/// and evaluates the rules. Useful for simple evaluation scenarios.
480///
481/// # Arguments
482///
483/// * `rules` - The list of magic rules to evaluate
484/// * `buffer` - The file buffer to evaluate against
485/// * `config` - Configuration for evaluation behavior
486///
487/// # Returns
488///
489/// Returns `Ok(Vec<MatchResult>)` containing all matches found, or `Err(LibmagicError)`
490/// if evaluation fails.
491///
492/// # Examples
493///
494/// ```rust
495/// use libmagic_rs::evaluator::{evaluate_rules_with_config, MatchResult};
496/// use libmagic_rs::parser::ast::{MagicRule, OffsetSpec, TypeKind, Operator, Value};
497/// use libmagic_rs::EvaluationConfig;
498///
499/// let rule = MagicRule {
500///     offset: OffsetSpec::Absolute(0),
501///     typ: TypeKind::Byte,
502///     op: Operator::Equal,
503///     value: Value::Uint(0x7f),
504///     message: "ELF magic".to_string(),
505///     children: vec![],
506///     level: 0,
507///     strength_modifier: None,
508/// };
509///
510/// let rules = vec![rule];
511/// let buffer = &[0x7f, 0x45, 0x4c, 0x46];
512/// let config = EvaluationConfig::default();
513///
514/// let matches = evaluate_rules_with_config(&rules, buffer, &config).unwrap();
515/// assert_eq!(matches.len(), 1);
516/// assert_eq!(matches[0].message, "ELF magic");
517/// ```
518///
519/// # Errors
520///
521/// * `LibmagicError::EvaluationError` - If rule evaluation fails
522/// * `LibmagicError::Timeout` - If evaluation exceeds configured timeout
523pub fn evaluate_rules_with_config(
524    rules: &[MagicRule],
525    buffer: &[u8],
526    config: &EvaluationConfig,
527) -> Result<Vec<MatchResult>, LibmagicError> {
528    let mut context = EvaluationContext::new(config.clone());
529    evaluate_rules(rules, buffer, &mut context)
530}
531
532#[cfg(test)]
533mod tests {
534    use super::*;
535    use crate::parser::ast::{Endianness, OffsetSpec, Operator, TypeKind, Value};
536
537    #[test]
538    fn test_evaluate_single_rule_byte_equal_match() {
539        let rule = MagicRule {
540            offset: OffsetSpec::Absolute(0),
541            typ: TypeKind::Byte,
542            op: Operator::Equal,
543            value: Value::Uint(0x7f),
544            message: "ELF magic".to_string(),
545            children: vec![],
546            level: 0,
547            strength_modifier: None,
548        };
549
550        let buffer = &[0x7f, 0x45, 0x4c, 0x46]; // ELF magic bytes
551        let result = evaluate_single_rule(&rule, buffer).unwrap();
552        assert!(result.is_some());
553    }
554
555    #[test]
556    fn test_evaluate_single_rule_byte_equal_no_match() {
557        let rule = MagicRule {
558            offset: OffsetSpec::Absolute(0),
559            typ: TypeKind::Byte,
560            op: Operator::Equal,
561            value: Value::Uint(0x7f),
562            message: "ELF magic".to_string(),
563            children: vec![],
564            level: 0,
565            strength_modifier: None,
566        };
567
568        let buffer = &[0x50, 0x4b, 0x03, 0x04]; // ZIP magic bytes
569        let result = evaluate_single_rule(&rule, buffer).unwrap();
570        assert!(result.is_none());
571    }
572
573    #[test]
574    fn test_evaluate_single_rule_byte_not_equal_match() {
575        let rule = MagicRule {
576            offset: OffsetSpec::Absolute(0),
577            typ: TypeKind::Byte,
578            op: Operator::NotEqual,
579            value: Value::Uint(0x00),
580            message: "Non-zero byte".to_string(),
581            children: vec![],
582            level: 0,
583            strength_modifier: None,
584        };
585
586        let buffer = &[0x7f, 0x45, 0x4c, 0x46];
587        let result = evaluate_single_rule(&rule, buffer).unwrap();
588        assert!(result.is_some()); // 0x7f != 0x00
589    }
590
591    #[test]
592    fn test_evaluate_single_rule_byte_not_equal_no_match() {
593        let rule = MagicRule {
594            offset: OffsetSpec::Absolute(0),
595            typ: TypeKind::Byte,
596            op: Operator::NotEqual,
597            value: Value::Uint(0x7f),
598            message: "Not ELF magic".to_string(),
599            children: vec![],
600            level: 0,
601            strength_modifier: None,
602        };
603
604        let buffer = &[0x7f, 0x45, 0x4c, 0x46];
605        let result = evaluate_single_rule(&rule, buffer).unwrap();
606        assert!(result.is_none()); // 0x7f == 0x7f, so NotEqual is false
607    }
608
609    #[test]
610    fn test_evaluate_single_rule_byte_bitwise_and_match() {
611        let rule = MagicRule {
612            offset: OffsetSpec::Absolute(0),
613            typ: TypeKind::Byte,
614            op: Operator::BitwiseAnd,
615            value: Value::Uint(0x80), // Check if high bit is set
616            message: "High bit set".to_string(),
617            children: vec![],
618            level: 0,
619            strength_modifier: None,
620        };
621
622        let buffer = &[0xff, 0x45, 0x4c, 0x46]; // 0xff has high bit set
623        let result = evaluate_single_rule(&rule, buffer).unwrap();
624        assert!(result.is_some()); // 0xff & 0x80 = 0x80 (non-zero)
625    }
626
627    #[test]
628    fn test_evaluate_single_rule_byte_bitwise_and_no_match() {
629        let rule = MagicRule {
630            offset: OffsetSpec::Absolute(0),
631            typ: TypeKind::Byte,
632            op: Operator::BitwiseAnd,
633            value: Value::Uint(0x80), // Check if high bit is set
634            message: "High bit set".to_string(),
635            children: vec![],
636            level: 0,
637            strength_modifier: None,
638        };
639
640        let buffer = &[0x7f, 0x45, 0x4c, 0x46]; // 0x7f has high bit clear
641        let result = evaluate_single_rule(&rule, buffer).unwrap();
642        assert!(result.is_none()); // 0x7f & 0x80 = 0x00 (zero)
643    }
644
645    #[test]
646    fn test_evaluate_single_rule_short_little_endian() {
647        let rule = MagicRule {
648            offset: OffsetSpec::Absolute(0),
649            typ: TypeKind::Short {
650                endian: Endianness::Little,
651                signed: false,
652            },
653            op: Operator::Equal,
654            value: Value::Uint(0x1234),
655            message: "Little-endian short".to_string(),
656            children: vec![],
657            level: 0,
658            strength_modifier: None,
659        };
660
661        let buffer = &[0x34, 0x12, 0x56, 0x78]; // 0x1234 in little-endian
662        let result = evaluate_single_rule(&rule, buffer).unwrap();
663        assert!(result.is_some());
664    }
665
666    #[test]
667    fn test_evaluate_single_rule_short_big_endian() {
668        let rule = MagicRule {
669            offset: OffsetSpec::Absolute(0),
670            typ: TypeKind::Short {
671                endian: Endianness::Big,
672                signed: false,
673            },
674            op: Operator::Equal,
675            value: Value::Uint(0x1234),
676            message: "Big-endian short".to_string(),
677            children: vec![],
678            level: 0,
679            strength_modifier: None,
680        };
681
682        let buffer = &[0x12, 0x34, 0x56, 0x78]; // 0x1234 in big-endian
683        let result = evaluate_single_rule(&rule, buffer).unwrap();
684        assert!(result.is_some());
685    }
686
687    #[test]
688    fn test_evaluate_single_rule_short_signed_positive() {
689        let rule = MagicRule {
690            offset: OffsetSpec::Absolute(0),
691            typ: TypeKind::Short {
692                endian: Endianness::Little,
693                signed: true,
694            },
695            op: Operator::Equal,
696            value: Value::Int(32767), // 0x7fff
697            message: "Positive signed short".to_string(),
698            children: vec![],
699            level: 0,
700            strength_modifier: None,
701        };
702
703        let buffer = &[0xff, 0x7f, 0x00, 0x00]; // 0x7fff in little-endian
704        let result = evaluate_single_rule(&rule, buffer).unwrap();
705        assert!(result.is_some());
706    }
707
708    #[test]
709    fn test_evaluate_single_rule_short_signed_negative() {
710        let rule = MagicRule {
711            offset: OffsetSpec::Absolute(0),
712            typ: TypeKind::Short {
713                endian: Endianness::Little,
714                signed: true,
715            },
716            op: Operator::Equal,
717            value: Value::Int(-1), // 0xffff as signed
718            message: "Negative signed short".to_string(),
719            children: vec![],
720            level: 0,
721            strength_modifier: None,
722        };
723
724        let buffer = &[0xff, 0xff, 0x00, 0x00]; // 0xffff in little-endian
725        let result = evaluate_single_rule(&rule, buffer).unwrap();
726        assert!(result.is_some());
727    }
728
729    #[test]
730    fn test_evaluate_single_rule_long_little_endian() {
731        let rule = MagicRule {
732            offset: OffsetSpec::Absolute(0),
733            typ: TypeKind::Long {
734                endian: Endianness::Little,
735                signed: false,
736            },
737            op: Operator::Equal,
738            value: Value::Uint(0x1234_5678),
739            message: "Little-endian long".to_string(),
740            children: vec![],
741            level: 0,
742            strength_modifier: None,
743        };
744
745        let buffer = &[0x78, 0x56, 0x34, 0x12, 0x00]; // 0x12345678 in little-endian
746        let result = evaluate_single_rule(&rule, buffer).unwrap();
747        assert!(result.is_some());
748    }
749
750    #[test]
751    fn test_evaluate_single_rule_long_big_endian() {
752        let rule = MagicRule {
753            offset: OffsetSpec::Absolute(0),
754            typ: TypeKind::Long {
755                endian: Endianness::Big,
756                signed: false,
757            },
758            op: Operator::Equal,
759            value: Value::Uint(0x1234_5678),
760            message: "Big-endian long".to_string(),
761            children: vec![],
762            level: 0,
763            strength_modifier: None,
764        };
765
766        let buffer = &[0x12, 0x34, 0x56, 0x78, 0x00]; // 0x12345678 in big-endian
767        let result = evaluate_single_rule(&rule, buffer).unwrap();
768        assert!(result.is_some());
769    }
770
771    #[test]
772    fn test_evaluate_single_rule_long_signed_positive() {
773        let rule = MagicRule {
774            offset: OffsetSpec::Absolute(0),
775            typ: TypeKind::Long {
776                endian: Endianness::Little,
777                signed: true,
778            },
779            op: Operator::Equal,
780            value: Value::Int(2_147_483_647), // 0x7fffffff
781            message: "Positive signed long".to_string(),
782            children: vec![],
783            level: 0,
784            strength_modifier: None,
785        };
786
787        let buffer = &[0xff, 0xff, 0xff, 0x7f, 0x00]; // 0x7fffffff in little-endian
788        let result = evaluate_single_rule(&rule, buffer).unwrap();
789        assert!(result.is_some());
790    }
791
792    #[test]
793    fn test_evaluate_single_rule_long_signed_negative() {
794        let rule = MagicRule {
795            offset: OffsetSpec::Absolute(0),
796            typ: TypeKind::Long {
797                endian: Endianness::Little,
798                signed: true,
799            },
800            op: Operator::Equal,
801            value: Value::Int(-1), // 0xffffffff as signed
802            message: "Negative signed long".to_string(),
803            children: vec![],
804            level: 0,
805            strength_modifier: None,
806        };
807
808        let buffer = &[0xff, 0xff, 0xff, 0xff, 0x00]; // 0xffffffff in little-endian
809        let result = evaluate_single_rule(&rule, buffer).unwrap();
810        assert!(result.is_some());
811    }
812
813    #[test]
814    fn test_evaluate_single_rule_different_offsets() {
815        let rule = MagicRule {
816            offset: OffsetSpec::Absolute(2), // Read from offset 2
817            typ: TypeKind::Byte,
818            op: Operator::Equal,
819            value: Value::Uint(0x4c),
820            message: "ELF class byte".to_string(),
821            children: vec![],
822            level: 0,
823            strength_modifier: None,
824        };
825
826        let buffer = &[0x7f, 0x45, 0x4c, 0x46]; // ELF magic bytes
827        let result = evaluate_single_rule(&rule, buffer).unwrap();
828        assert!(result.is_some()); // buffer[2] == 0x4c
829    }
830
831    #[test]
832    fn test_evaluate_single_rule_negative_offset() {
833        let rule = MagicRule {
834            offset: OffsetSpec::Absolute(-1), // Last byte
835            typ: TypeKind::Byte,
836            op: Operator::Equal,
837            value: Value::Uint(0x46),
838            message: "Last byte".to_string(),
839            children: vec![],
840            level: 0,
841            strength_modifier: None,
842        };
843
844        let buffer = &[0x7f, 0x45, 0x4c, 0x46]; // ELF magic bytes
845        let result = evaluate_single_rule(&rule, buffer).unwrap();
846        assert!(result.is_some()); // Last byte is 0x46
847    }
848
849    #[test]
850    fn test_evaluate_single_rule_from_end_offset() {
851        let rule = MagicRule {
852            offset: OffsetSpec::FromEnd(-2), // Second to last byte
853            typ: TypeKind::Byte,
854            op: Operator::Equal,
855            value: Value::Uint(0x4c),
856            message: "Second to last byte".to_string(),
857            children: vec![],
858            level: 0,
859            strength_modifier: None,
860        };
861
862        let buffer = &[0x7f, 0x45, 0x4c, 0x46]; // ELF magic bytes
863        let result = evaluate_single_rule(&rule, buffer).unwrap();
864        assert!(result.is_some()); // buffer[2] == 0x4c (second to last)
865    }
866
867    #[test]
868    fn test_evaluate_single_rule_offset_out_of_bounds() {
869        let rule = MagicRule {
870            offset: OffsetSpec::Absolute(10), // Beyond buffer
871            typ: TypeKind::Byte,
872            op: Operator::Equal,
873            value: Value::Uint(0x00),
874            message: "Out of bounds".to_string(),
875            children: vec![],
876            level: 0,
877            strength_modifier: None,
878        };
879
880        let buffer = &[0x7f, 0x45, 0x4c, 0x46]; // Only 4 bytes
881        let result = evaluate_single_rule(&rule, buffer);
882        assert!(result.is_err());
883
884        match result.unwrap_err() {
885            LibmagicError::EvaluationError(msg) => {
886                let error_string = format!("{msg}");
887                assert!(error_string.contains("Buffer overrun"));
888            }
889            _ => panic!("Expected EvaluationError"),
890        }
891    }
892
893    #[test]
894    fn test_evaluate_single_rule_short_insufficient_bytes() {
895        let rule = MagicRule {
896            offset: OffsetSpec::Absolute(3), // Only 1 byte left
897            typ: TypeKind::Short {
898                endian: Endianness::Little,
899                signed: false,
900            },
901            op: Operator::Equal,
902            value: Value::Uint(0x1234),
903            message: "Insufficient bytes".to_string(),
904            children: vec![],
905            level: 0,
906            strength_modifier: None,
907        };
908
909        let buffer = &[0x7f, 0x45, 0x4c, 0x46]; // 4 bytes total
910        let result = evaluate_single_rule(&rule, buffer);
911        assert!(result.is_err());
912
913        match result.unwrap_err() {
914            LibmagicError::EvaluationError(msg) => {
915                let error_string = format!("{msg}");
916                assert!(error_string.contains("Buffer overrun"));
917            }
918            _ => panic!("Expected EvaluationError"),
919        }
920    }
921
922    #[test]
923    fn test_evaluate_single_rule_long_insufficient_bytes() {
924        let rule = MagicRule {
925            offset: OffsetSpec::Absolute(2), // Only 2 bytes left
926            typ: TypeKind::Long {
927                endian: Endianness::Little,
928                signed: false,
929            },
930            op: Operator::Equal,
931            value: Value::Uint(0x1234_5678),
932            message: "Insufficient bytes".to_string(),
933            children: vec![],
934            level: 0,
935            strength_modifier: None,
936        };
937
938        let buffer = &[0x7f, 0x45, 0x4c, 0x46]; // 4 bytes total
939        let result = evaluate_single_rule(&rule, buffer);
940        assert!(result.is_err());
941
942        match result.unwrap_err() {
943            LibmagicError::EvaluationError(msg) => {
944                let error_string = format!("{msg}");
945                assert!(error_string.contains("Buffer overrun"));
946            }
947            _ => panic!("Expected EvaluationError"),
948        }
949    }
950
951    #[test]
952    fn test_evaluate_single_rule_empty_buffer() {
953        let rule = MagicRule {
954            offset: OffsetSpec::Absolute(0),
955            typ: TypeKind::Byte,
956            op: Operator::Equal,
957            value: Value::Uint(0x00),
958            message: "Empty buffer".to_string(),
959            children: vec![],
960            level: 0,
961            strength_modifier: None,
962        };
963
964        let buffer = &[]; // Empty buffer
965        let result = evaluate_single_rule(&rule, buffer);
966        assert!(result.is_err());
967
968        match result.unwrap_err() {
969            LibmagicError::EvaluationError(msg) => {
970                let error_string = format!("{msg}");
971                assert!(error_string.contains("Buffer overrun"));
972            }
973            _ => panic!("Expected EvaluationError"),
974        }
975    }
976
977    #[test]
978    fn test_evaluate_single_rule_string_type_supported() {
979        let rule = MagicRule {
980            offset: OffsetSpec::Absolute(0),
981            typ: TypeKind::String { max_length: None },
982            op: Operator::Equal,
983            value: Value::String("test".to_string()),
984            message: "String type".to_string(),
985            children: vec![],
986            level: 0,
987            strength_modifier: None,
988        };
989
990        // Test matching string
991        let buffer = b"test\x00 data";
992        let result = evaluate_single_rule(&rule, buffer);
993        assert!(result.is_ok());
994        let matches = result.unwrap();
995        assert!(matches.is_some()); // Should match
996
997        // Test non-matching string
998        let rule_no_match = MagicRule {
999            offset: OffsetSpec::Absolute(0),
1000            typ: TypeKind::String { max_length: None },
1001            op: Operator::Equal,
1002            value: Value::String("hello".to_string()),
1003            message: "String type".to_string(),
1004            children: vec![],
1005            level: 0,
1006            strength_modifier: None,
1007        };
1008
1009        let result = evaluate_single_rule(&rule_no_match, buffer);
1010        assert!(result.is_ok());
1011        let matches = result.unwrap();
1012        assert!(matches.is_none()); // Should not match
1013    }
1014}
1015
1016#[test]
1017fn test_evaluate_single_rule_cross_type_comparison() {
1018    // Test that cross-type integer comparisons use coercion (Uint(42) == Int(42))
1019    let rule = MagicRule {
1020        offset: OffsetSpec::Absolute(0),
1021        typ: TypeKind::Byte,
1022        op: Operator::Equal,
1023        value: Value::Int(42), // Int value vs Uint from byte read
1024        message: "Cross-type comparison".to_string(),
1025        children: vec![],
1026        level: 0,
1027        strength_modifier: None,
1028    };
1029
1030    let buffer = &[42]; // Byte value 42
1031    let result = evaluate_single_rule(&rule, buffer).unwrap();
1032    assert!(result.is_some()); // Should match via cross-type integer coercion
1033}
1034
1035#[test]
1036fn test_evaluate_single_rule_bitwise_and_with_shorts() {
1037    let rule = MagicRule {
1038        offset: OffsetSpec::Absolute(0),
1039        typ: TypeKind::Short {
1040            endian: Endianness::Little,
1041            signed: false,
1042        },
1043        op: Operator::BitwiseAnd,
1044        value: Value::Uint(0xff00), // Check high byte
1045        message: "High byte check".to_string(),
1046        children: vec![],
1047        level: 0,
1048        strength_modifier: None,
1049    };
1050
1051    let buffer = &[0x34, 0x12]; // 0x1234 in little-endian
1052    let result = evaluate_single_rule(&rule, buffer).unwrap();
1053    assert!(result.is_some()); // 0x1234 & 0xff00 = 0x1200 (non-zero)
1054}
1055
1056#[test]
1057fn test_evaluate_single_rule_bitwise_and_with_longs() {
1058    let rule = MagicRule {
1059        offset: OffsetSpec::Absolute(0),
1060        typ: TypeKind::Long {
1061            endian: Endianness::Big,
1062            signed: false,
1063        },
1064        op: Operator::BitwiseAnd,
1065        value: Value::Uint(0xffff_0000), // Check high word
1066        message: "High word check".to_string(),
1067        children: vec![],
1068        level: 0,
1069        strength_modifier: None,
1070    };
1071
1072    let buffer = &[0x12, 0x34, 0x56, 0x78]; // 0x12345678 in big-endian
1073    let result = evaluate_single_rule(&rule, buffer).unwrap();
1074    assert!(result.is_some()); // 0x12345678 & 0xffff0000 = 0x12340000 (non-zero)
1075}
1076
1077#[test]
1078fn test_evaluate_single_rule_comprehensive_elf_check() {
1079    // Test a comprehensive ELF magic check
1080    let rule = MagicRule {
1081        offset: OffsetSpec::Absolute(0),
1082        typ: TypeKind::Long {
1083            endian: Endianness::Little,
1084            signed: false,
1085        },
1086        op: Operator::Equal,
1087        value: Value::Uint(0x464c_457f), // ELF magic as 32-bit little-endian
1088        message: "ELF executable".to_string(),
1089        children: vec![],
1090        level: 0,
1091        strength_modifier: None,
1092    };
1093
1094    let elf_buffer = &[0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01]; // ELF64 header start
1095    let result = evaluate_single_rule(&rule, elf_buffer).unwrap();
1096    assert!(result.is_some());
1097
1098    let non_elf_buffer = &[0x50, 0x4b, 0x03, 0x04, 0x14, 0x00]; // ZIP header
1099    let result = evaluate_single_rule(&rule, non_elf_buffer).unwrap();
1100    assert!(result.is_none());
1101}
1102
1103#[test]
1104fn test_evaluate_single_rule_native_endianness() {
1105    let rule = MagicRule {
1106        offset: OffsetSpec::Absolute(0),
1107        typ: TypeKind::Short {
1108            endian: Endianness::Native,
1109            signed: false,
1110        },
1111        op: Operator::NotEqual,
1112        value: Value::Uint(0),
1113        message: "Non-zero native short".to_string(),
1114        children: vec![],
1115        level: 0,
1116        strength_modifier: None,
1117    };
1118
1119    let buffer = &[0x01, 0x02]; // Non-zero bytes
1120    let result = evaluate_single_rule(&rule, buffer).unwrap();
1121    assert!(result.is_some()); // Should be non-zero regardless of endianness
1122}
1123
1124#[test]
1125fn test_evaluate_single_rule_all_operators() {
1126    let buffer = &[0x42, 0x00, 0xff, 0x80];
1127
1128    // Test Equal operator
1129    let equal_rule = MagicRule {
1130        offset: OffsetSpec::Absolute(0),
1131        typ: TypeKind::Byte,
1132        op: Operator::Equal,
1133        value: Value::Uint(0x42),
1134        message: "Equal test".to_string(),
1135        children: vec![],
1136        level: 0,
1137        strength_modifier: None,
1138    };
1139    assert!(evaluate_single_rule(&equal_rule, buffer).unwrap().is_some());
1140
1141    // Test NotEqual operator
1142    let not_equal_rule = MagicRule {
1143        offset: OffsetSpec::Absolute(1),
1144        typ: TypeKind::Byte,
1145        op: Operator::NotEqual,
1146        value: Value::Uint(0x42),
1147        message: "NotEqual test".to_string(),
1148        children: vec![],
1149        level: 0,
1150        strength_modifier: None,
1151    };
1152    assert!(
1153        evaluate_single_rule(&not_equal_rule, buffer)
1154            .unwrap()
1155            .is_some()
1156    ); // 0x00 != 0x42
1157
1158    // Test BitwiseAnd operator
1159    let bitwise_and_rule = MagicRule {
1160        offset: OffsetSpec::Absolute(3),
1161        typ: TypeKind::Byte,
1162        op: Operator::BitwiseAnd,
1163        value: Value::Uint(0x80),
1164        message: "BitwiseAnd test".to_string(),
1165        children: vec![],
1166        level: 0,
1167        strength_modifier: None,
1168    };
1169    assert!(
1170        evaluate_single_rule(&bitwise_and_rule, buffer)
1171            .unwrap()
1172            .is_some()
1173    ); // 0x80 & 0x80 = 0x80
1174}
1175
1176#[test]
1177fn test_evaluate_single_rule_edge_case_values() {
1178    // Test with maximum values
1179    let max_uint_rule = MagicRule {
1180        offset: OffsetSpec::Absolute(0),
1181        typ: TypeKind::Long {
1182            endian: Endianness::Little,
1183            signed: false,
1184        },
1185        op: Operator::Equal,
1186        value: Value::Uint(0xffff_ffff),
1187        message: "Max uint32".to_string(),
1188        children: vec![],
1189        level: 0,
1190        strength_modifier: None,
1191    };
1192
1193    let max_buffer = &[0xff, 0xff, 0xff, 0xff];
1194    let result = evaluate_single_rule(&max_uint_rule, max_buffer).unwrap();
1195    assert!(result.is_some());
1196
1197    // Test with minimum signed value
1198    let min_int_rule = MagicRule {
1199        offset: OffsetSpec::Absolute(0),
1200        typ: TypeKind::Long {
1201            endian: Endianness::Little,
1202            signed: true,
1203        },
1204        op: Operator::Equal,
1205        value: Value::Int(-2_147_483_648), // i32::MIN
1206        message: "Min int32".to_string(),
1207        children: vec![],
1208        level: 0,
1209        strength_modifier: None,
1210    };
1211
1212    let min_buffer = &[0x00, 0x00, 0x00, 0x80]; // 0x80000000 in little-endian
1213    let result = evaluate_single_rule(&min_int_rule, min_buffer).unwrap();
1214    assert!(result.is_some());
1215}
1216
1217#[test]
1218fn test_evaluate_single_rule_various_buffer_sizes() {
1219    // Test with single byte buffer
1220    let single_byte_rule = MagicRule {
1221        offset: OffsetSpec::Absolute(0),
1222        typ: TypeKind::Byte,
1223        op: Operator::Equal,
1224        value: Value::Uint(0xaa),
1225        message: "Single byte".to_string(),
1226        children: vec![],
1227        level: 0,
1228        strength_modifier: None,
1229    };
1230
1231    let single_buffer = &[0xaa];
1232    let result = evaluate_single_rule(&single_byte_rule, single_buffer).unwrap();
1233    assert!(result.is_some());
1234
1235    // Test with large buffer
1236    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
1237    let large_buffer: Vec<u8> = (0..1024).map(|i| (i % 256) as u8).collect();
1238    let large_rule = MagicRule {
1239        offset: OffsetSpec::Absolute(1000),
1240        typ: TypeKind::Byte,
1241        op: Operator::Equal,
1242        value: Value::Uint((1000 % 256) as u64),
1243        message: "Large buffer".to_string(),
1244        children: vec![],
1245        level: 0,
1246        strength_modifier: None,
1247    };
1248
1249    let result = evaluate_single_rule(&large_rule, &large_buffer).unwrap();
1250    assert!(result.is_some());
1251}
1252
1253// Tests for EvaluationContext
1254#[test]
1255fn test_evaluation_context_new() {
1256    let config = EvaluationConfig::default();
1257    let context = EvaluationContext::new(config.clone());
1258
1259    assert_eq!(context.current_offset(), 0);
1260    assert_eq!(context.recursion_depth(), 0);
1261    assert_eq!(
1262        context.config().max_recursion_depth,
1263        config.max_recursion_depth
1264    );
1265    assert_eq!(context.config().max_string_length, config.max_string_length);
1266    assert_eq!(
1267        context.config().stop_at_first_match,
1268        config.stop_at_first_match
1269    );
1270}
1271
1272#[test]
1273fn test_evaluation_context_offset_management() {
1274    let config = EvaluationConfig::default();
1275    let mut context = EvaluationContext::new(config);
1276
1277    // Test initial offset
1278    assert_eq!(context.current_offset(), 0);
1279
1280    // Test setting offset
1281    context.set_current_offset(42);
1282    assert_eq!(context.current_offset(), 42);
1283
1284    // Test setting different offset
1285    context.set_current_offset(1024);
1286    assert_eq!(context.current_offset(), 1024);
1287
1288    // Test setting offset to 0
1289    context.set_current_offset(0);
1290    assert_eq!(context.current_offset(), 0);
1291}
1292
1293#[test]
1294fn test_evaluation_context_recursion_depth_management() {
1295    let config = EvaluationConfig::default();
1296    let mut context = EvaluationContext::new(config);
1297
1298    // Test initial recursion depth
1299    assert_eq!(context.recursion_depth(), 0);
1300
1301    // Test incrementing recursion depth
1302    context.increment_recursion_depth().unwrap();
1303    assert_eq!(context.recursion_depth(), 1);
1304
1305    context.increment_recursion_depth().unwrap();
1306    assert_eq!(context.recursion_depth(), 2);
1307
1308    // Test decrementing recursion depth
1309    context.decrement_recursion_depth().unwrap();
1310    assert_eq!(context.recursion_depth(), 1);
1311
1312    context.decrement_recursion_depth().unwrap();
1313    assert_eq!(context.recursion_depth(), 0);
1314}
1315
1316#[test]
1317fn test_evaluation_context_recursion_depth_limit() {
1318    let config = EvaluationConfig {
1319        max_recursion_depth: 2,
1320        ..Default::default()
1321    };
1322    let mut context = EvaluationContext::new(config);
1323
1324    // Should be able to increment up to the limit
1325    assert!(context.increment_recursion_depth().is_ok());
1326    assert_eq!(context.recursion_depth(), 1);
1327
1328    assert!(context.increment_recursion_depth().is_ok());
1329    assert_eq!(context.recursion_depth(), 2);
1330
1331    // Should fail when exceeding the limit
1332    let result = context.increment_recursion_depth();
1333    assert!(result.is_err());
1334    assert_eq!(context.recursion_depth(), 2); // Should not have changed
1335
1336    match result.unwrap_err() {
1337        LibmagicError::EvaluationError(msg) => {
1338            let error_string = format!("{msg}");
1339            assert!(error_string.contains("Recursion limit exceeded"));
1340        }
1341        _ => panic!("Expected EvaluationError"),
1342    }
1343}
1344
1345#[test]
1346fn test_evaluation_context_recursion_depth_underflow() {
1347    let config = EvaluationConfig::default();
1348    let mut context = EvaluationContext::new(config);
1349
1350    // Should return an error when trying to decrement below 0
1351    let result = context.decrement_recursion_depth();
1352    assert!(result.is_err());
1353
1354    let err = result.unwrap_err();
1355    let err_msg = err.to_string();
1356    assert!(
1357        err_msg.contains("decrement recursion depth below 0"),
1358        "Expected error about decrementing below 0, got: {err_msg}"
1359    );
1360}
1361
1362#[test]
1363fn test_evaluation_context_config_access() {
1364    let config = EvaluationConfig {
1365        max_recursion_depth: 10,
1366        max_string_length: 4096,
1367        stop_at_first_match: false,
1368        enable_mime_types: true,
1369        timeout_ms: Some(2000),
1370    };
1371
1372    let context = EvaluationContext::new(config);
1373
1374    // Test config access
1375    assert_eq!(context.config().max_recursion_depth, 10);
1376    assert_eq!(context.config().max_string_length, 4096);
1377    assert!(!context.config().stop_at_first_match);
1378
1379    // Test convenience methods
1380    assert!(!context.should_stop_at_first_match());
1381    assert_eq!(context.max_string_length(), 4096);
1382}
1383
1384#[test]
1385fn test_evaluation_context_reset() {
1386    let config = EvaluationConfig::default();
1387    let mut context = EvaluationContext::new(config.clone());
1388
1389    // Modify the context state
1390    context.set_current_offset(100);
1391    context.increment_recursion_depth().unwrap();
1392    context.increment_recursion_depth().unwrap();
1393
1394    assert_eq!(context.current_offset(), 100);
1395    assert_eq!(context.recursion_depth(), 2);
1396
1397    // Reset should restore initial state but keep config
1398    context.reset();
1399
1400    assert_eq!(context.current_offset(), 0);
1401    assert_eq!(context.recursion_depth(), 0);
1402    assert_eq!(
1403        context.config().max_recursion_depth,
1404        config.max_recursion_depth
1405    );
1406}
1407
1408#[test]
1409fn test_evaluation_context_clone() {
1410    let config = EvaluationConfig {
1411        max_recursion_depth: 5,
1412        max_string_length: 2048,
1413        ..Default::default()
1414    };
1415
1416    let mut context = EvaluationContext::new(config);
1417    context.set_current_offset(50);
1418    context.increment_recursion_depth().unwrap();
1419
1420    // Clone the context
1421    let cloned_context = context.clone();
1422
1423    // Both should have the same state
1424    assert_eq!(context.current_offset(), cloned_context.current_offset());
1425    assert_eq!(context.recursion_depth(), cloned_context.recursion_depth());
1426    assert_eq!(
1427        context.config().max_recursion_depth,
1428        cloned_context.config().max_recursion_depth
1429    );
1430    assert_eq!(
1431        context.config().max_string_length,
1432        cloned_context.config().max_string_length
1433    );
1434
1435    // Modifying one should not affect the other
1436    context.set_current_offset(75);
1437    assert_eq!(context.current_offset(), 75);
1438    assert_eq!(cloned_context.current_offset(), 50);
1439}
1440
1441#[test]
1442fn test_evaluation_context_with_custom_config() {
1443    let config = EvaluationConfig {
1444        max_recursion_depth: 15,
1445        max_string_length: 16384,
1446        stop_at_first_match: false,
1447        enable_mime_types: true,
1448        timeout_ms: Some(5000),
1449    };
1450
1451    let context = EvaluationContext::new(config);
1452
1453    assert_eq!(context.config().max_recursion_depth, 15);
1454    assert_eq!(context.max_string_length(), 16384);
1455    assert!(!context.should_stop_at_first_match());
1456
1457    // Test that we can increment up to the custom limit
1458    let mut mutable_context = context;
1459    for i in 1..=15 {
1460        assert!(mutable_context.increment_recursion_depth().is_ok());
1461        assert_eq!(mutable_context.recursion_depth(), i);
1462    }
1463
1464    // Should fail on the 16th increment
1465    let result = mutable_context.increment_recursion_depth();
1466    assert!(result.is_err());
1467}
1468
1469#[test]
1470fn test_evaluation_context_mime_types_access() {
1471    let config_with_mime = EvaluationConfig {
1472        enable_mime_types: true,
1473        ..Default::default()
1474    };
1475    let context_with_mime = EvaluationContext::new(config_with_mime);
1476    assert!(context_with_mime.enable_mime_types());
1477
1478    let config_without_mime = EvaluationConfig {
1479        enable_mime_types: false,
1480        ..Default::default()
1481    };
1482    let context_without_mime = EvaluationContext::new(config_without_mime);
1483    assert!(!context_without_mime.enable_mime_types());
1484}
1485
1486#[test]
1487fn test_evaluation_context_timeout_access() {
1488    let config_with_timeout = EvaluationConfig {
1489        timeout_ms: Some(5000),
1490        ..Default::default()
1491    };
1492    let context_with_timeout = EvaluationContext::new(config_with_timeout);
1493    assert_eq!(context_with_timeout.timeout_ms(), Some(5000));
1494
1495    let config_without_timeout = EvaluationConfig {
1496        timeout_ms: None,
1497        ..Default::default()
1498    };
1499    let context_without_timeout = EvaluationContext::new(config_without_timeout);
1500    assert_eq!(context_without_timeout.timeout_ms(), None);
1501}
1502
1503#[test]
1504fn test_evaluation_context_comprehensive_config() {
1505    let config = EvaluationConfig {
1506        max_recursion_depth: 30,
1507        max_string_length: 16384,
1508        stop_at_first_match: false,
1509        enable_mime_types: true,
1510        timeout_ms: Some(10000),
1511    };
1512    let context = EvaluationContext::new(config);
1513
1514    assert_eq!(context.config().max_recursion_depth, 30);
1515    assert_eq!(context.config().max_string_length, 16384);
1516    assert!(!context.should_stop_at_first_match());
1517    assert!(context.enable_mime_types());
1518    assert_eq!(context.timeout_ms(), Some(10000));
1519    assert_eq!(context.max_string_length(), 16384);
1520}
1521
1522#[test]
1523fn test_evaluation_context_performance_config() {
1524    let config = EvaluationConfig {
1525        max_recursion_depth: 5,
1526        max_string_length: 512,
1527        stop_at_first_match: true,
1528        enable_mime_types: false,
1529        timeout_ms: Some(1000),
1530    };
1531    let context = EvaluationContext::new(config);
1532
1533    assert_eq!(context.config().max_recursion_depth, 5);
1534    assert_eq!(context.max_string_length(), 512);
1535    assert!(context.should_stop_at_first_match());
1536    assert!(!context.enable_mime_types());
1537    assert_eq!(context.timeout_ms(), Some(1000));
1538}
1539
1540#[test]
1541fn test_match_result_creation() {
1542    let match_result = MatchResult {
1543        message: "ELF executable".to_string(),
1544        offset: 0,
1545        level: 0,
1546        value: Value::Uint(0x7f),
1547        confidence: MatchResult::calculate_confidence(0),
1548    };
1549
1550    assert_eq!(match_result.message, "ELF executable");
1551    assert_eq!(match_result.offset, 0);
1552    assert_eq!(match_result.level, 0);
1553    assert_eq!(match_result.value, Value::Uint(0x7f));
1554    assert!((match_result.confidence - 0.3).abs() < 0.001);
1555}
1556
1557#[test]
1558fn test_match_result_clone() {
1559    let original = MatchResult {
1560        message: "Test message".to_string(),
1561        offset: 42,
1562        level: 1,
1563        value: Value::String("test".to_string()),
1564        confidence: MatchResult::calculate_confidence(1),
1565    };
1566
1567    let cloned = original.clone();
1568    assert_eq!(original, cloned);
1569}
1570
1571#[test]
1572fn test_match_result_debug() {
1573    let match_result = MatchResult {
1574        message: "Debug test".to_string(),
1575        offset: 10,
1576        level: 2,
1577        value: Value::Bytes(vec![0x01, 0x02]),
1578        confidence: MatchResult::calculate_confidence(2),
1579    };
1580
1581    let debug_str = format!("{match_result:?}");
1582    assert!(debug_str.contains("MatchResult"));
1583    assert!(debug_str.contains("Debug test"));
1584    assert!(debug_str.contains("10"));
1585    assert!(debug_str.contains('2'));
1586}
1587
1588#[test]
1589fn test_confidence_calculation_depth_0() {
1590    let confidence = MatchResult::calculate_confidence(0);
1591    assert!((confidence - 0.3).abs() < 0.001);
1592}
1593
1594#[test]
1595fn test_confidence_calculation_depth_1() {
1596    let confidence = MatchResult::calculate_confidence(1);
1597    assert!((confidence - 0.5).abs() < 0.001);
1598}
1599
1600#[test]
1601fn test_confidence_calculation_depth_2() {
1602    let confidence = MatchResult::calculate_confidence(2);
1603    assert!((confidence - 0.7).abs() < 0.001);
1604}
1605
1606#[test]
1607fn test_confidence_calculation_depth_3() {
1608    let confidence = MatchResult::calculate_confidence(3);
1609    assert!((confidence - 0.9).abs() < 0.001);
1610}
1611
1612#[test]
1613fn test_confidence_calculation_capped_at_1() {
1614    // Level 4+ should cap at 1.0
1615    let confidence_4 = MatchResult::calculate_confidence(4);
1616    assert!((confidence_4 - 1.0).abs() < 0.001);
1617
1618    let confidence_10 = MatchResult::calculate_confidence(10);
1619    assert!((confidence_10 - 1.0).abs() < 0.001);
1620
1621    let confidence_100 = MatchResult::calculate_confidence(100);
1622    assert!((confidence_100 - 1.0).abs() < 0.001);
1623}
1624
1625#[test]
1626fn test_evaluate_rules_empty_list() {
1627    let rules = vec![];
1628    let buffer = &[0x7f, 0x45, 0x4c, 0x46];
1629    let config = EvaluationConfig::default();
1630    let mut context = EvaluationContext::new(config);
1631
1632    let matches = evaluate_rules(&rules, buffer, &mut context).unwrap();
1633    assert!(matches.is_empty());
1634}
1635
1636#[test]
1637fn test_evaluate_rules_single_matching_rule() {
1638    let rule = MagicRule {
1639        offset: OffsetSpec::Absolute(0),
1640        typ: TypeKind::Byte,
1641        op: Operator::Equal,
1642        value: Value::Uint(0x7f),
1643        message: "ELF magic".to_string(),
1644        children: vec![],
1645        level: 0,
1646        strength_modifier: None,
1647    };
1648
1649    let rules = vec![rule];
1650    let buffer = &[0x7f, 0x45, 0x4c, 0x46];
1651    let config = EvaluationConfig::default();
1652    let mut context = EvaluationContext::new(config);
1653
1654    let matches = evaluate_rules(&rules, buffer, &mut context).unwrap();
1655    assert_eq!(matches.len(), 1);
1656    assert_eq!(matches[0].message, "ELF magic");
1657    assert_eq!(matches[0].offset, 0);
1658    assert_eq!(matches[0].level, 0);
1659    assert_eq!(matches[0].value, Value::Uint(0x7f));
1660}
1661
1662#[test]
1663fn test_evaluate_rules_single_non_matching_rule() {
1664    let rule = MagicRule {
1665        offset: OffsetSpec::Absolute(0),
1666        typ: TypeKind::Byte,
1667        op: Operator::Equal,
1668        value: Value::Uint(0x50), // ZIP magic, not ELF
1669        message: "ZIP magic".to_string(),
1670        children: vec![],
1671        level: 0,
1672        strength_modifier: None,
1673    };
1674
1675    let rules = vec![rule];
1676    let buffer = &[0x7f, 0x45, 0x4c, 0x46]; // ELF buffer
1677    let config = EvaluationConfig::default();
1678    let mut context = EvaluationContext::new(config);
1679
1680    let matches = evaluate_rules(&rules, buffer, &mut context).unwrap();
1681    assert!(matches.is_empty());
1682}
1683
1684#[test]
1685fn test_evaluate_rules_multiple_rules_stop_at_first() {
1686    let rule1 = MagicRule {
1687        offset: OffsetSpec::Absolute(0),
1688        typ: TypeKind::Byte,
1689        op: Operator::Equal,
1690        value: Value::Uint(0x7f),
1691        message: "First match".to_string(),
1692        children: vec![],
1693        level: 0,
1694        strength_modifier: None,
1695    };
1696
1697    let rule2 = MagicRule {
1698        offset: OffsetSpec::Absolute(1),
1699        typ: TypeKind::Byte,
1700        op: Operator::Equal,
1701        value: Value::Uint(0x45),
1702        message: "Second match".to_string(),
1703        children: vec![],
1704        level: 0,
1705        strength_modifier: None,
1706    };
1707
1708    let rule_list = vec![rule1, rule2];
1709    let buffer = &[0x7f, 0x45, 0x4c, 0x46];
1710    let config = EvaluationConfig {
1711        stop_at_first_match: true,
1712        ..Default::default()
1713    };
1714    let mut context = EvaluationContext::new(config);
1715
1716    let matches = evaluate_rules(&rule_list, buffer, &mut context).unwrap();
1717    assert_eq!(matches.len(), 1);
1718    assert_eq!(matches[0].message, "First match");
1719}
1720
1721#[test]
1722fn test_evaluate_rules_multiple_rules_find_all() {
1723    let rule1 = MagicRule {
1724        offset: OffsetSpec::Absolute(0),
1725        typ: TypeKind::Byte,
1726        op: Operator::Equal,
1727        value: Value::Uint(0x7f),
1728        message: "First match".to_string(),
1729        children: vec![],
1730        level: 0,
1731        strength_modifier: None,
1732    };
1733
1734    let rule2 = MagicRule {
1735        offset: OffsetSpec::Absolute(1),
1736        typ: TypeKind::Byte,
1737        op: Operator::Equal,
1738        value: Value::Uint(0x45),
1739        message: "Second match".to_string(),
1740        children: vec![],
1741        level: 0,
1742        strength_modifier: None,
1743    };
1744
1745    let rule_set = vec![rule1, rule2];
1746    let buffer = &[0x7f, 0x45, 0x4c, 0x46];
1747    let config = EvaluationConfig {
1748        stop_at_first_match: false,
1749        ..Default::default()
1750    };
1751    let mut context = EvaluationContext::new(config);
1752
1753    let matches = evaluate_rules(&rule_set, buffer, &mut context).unwrap();
1754    assert_eq!(matches.len(), 2);
1755    assert_eq!(matches[0].message, "First match");
1756    assert_eq!(matches[1].message, "Second match");
1757}
1758
1759#[test]
1760fn test_evaluate_rules_hierarchical_parent_child() {
1761    let child_rule = MagicRule {
1762        offset: OffsetSpec::Absolute(4),
1763        typ: TypeKind::Byte,
1764        op: Operator::Equal,
1765        value: Value::Uint(0x02), // ELF class 64-bit
1766        message: "64-bit".to_string(),
1767        children: vec![],
1768        level: 1,
1769        strength_modifier: None,
1770    };
1771
1772    let parent_rule = MagicRule {
1773        offset: OffsetSpec::Absolute(0),
1774        typ: TypeKind::Byte,
1775        op: Operator::Equal,
1776        value: Value::Uint(0x7f),
1777        message: "ELF".to_string(),
1778        children: vec![child_rule],
1779        level: 0,
1780        strength_modifier: None,
1781    };
1782
1783    let rules = vec![parent_rule];
1784    let buffer = &[0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01]; // ELF64 header
1785    let config = EvaluationConfig::default();
1786    let mut context = EvaluationContext::new(config);
1787
1788    let matches = evaluate_rules(&rules, buffer, &mut context).unwrap();
1789    assert_eq!(matches.len(), 2);
1790    assert_eq!(matches[0].message, "ELF");
1791    assert_eq!(matches[0].level, 0);
1792    assert_eq!(matches[1].message, "64-bit");
1793    assert_eq!(matches[1].level, 1);
1794}
1795
1796#[test]
1797fn test_evaluate_rules_hierarchical_parent_no_match() {
1798    let child_rule = MagicRule {
1799        offset: OffsetSpec::Absolute(4),
1800        typ: TypeKind::Byte,
1801        op: Operator::Equal,
1802        value: Value::Uint(0x02),
1803        message: "64-bit".to_string(),
1804        children: vec![],
1805        level: 1,
1806        strength_modifier: None,
1807    };
1808
1809    let parent_rule = MagicRule {
1810        offset: OffsetSpec::Absolute(0),
1811        typ: TypeKind::Byte,
1812        op: Operator::Equal,
1813        value: Value::Uint(0x50), // ZIP magic, not ELF
1814        message: "ZIP".to_string(),
1815        children: vec![child_rule],
1816        level: 0,
1817        strength_modifier: None,
1818    };
1819
1820    let rules = vec![parent_rule];
1821    let buffer = &[0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01]; // ELF buffer
1822    let config = EvaluationConfig::default();
1823    let mut context = EvaluationContext::new(config);
1824
1825    let matches = evaluate_rules(&rules, buffer, &mut context).unwrap();
1826    assert!(matches.is_empty()); // Parent doesn't match, so children shouldn't be evaluated
1827}
1828
1829#[test]
1830fn test_evaluate_rules_hierarchical_parent_match_child_no_match() {
1831    let child_rule = MagicRule {
1832        offset: OffsetSpec::Absolute(4),
1833        typ: TypeKind::Byte,
1834        op: Operator::Equal,
1835        value: Value::Uint(0x01), // ELF class 32-bit, but buffer has 64-bit
1836        message: "32-bit".to_string(),
1837        children: vec![],
1838        level: 1,
1839        strength_modifier: None,
1840    };
1841
1842    let parent_rule = MagicRule {
1843        offset: OffsetSpec::Absolute(0),
1844        typ: TypeKind::Byte,
1845        op: Operator::Equal,
1846        value: Value::Uint(0x7f),
1847        message: "ELF".to_string(),
1848        children: vec![child_rule],
1849        level: 0,
1850        strength_modifier: None,
1851    };
1852
1853    let rules = vec![parent_rule];
1854    let buffer = &[0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01]; // ELF64 header
1855    let config = EvaluationConfig::default();
1856    let mut context = EvaluationContext::new(config);
1857
1858    let matches = evaluate_rules(&rules, buffer, &mut context).unwrap();
1859    assert_eq!(matches.len(), 1); // Only parent matches
1860    assert_eq!(matches[0].message, "ELF");
1861    assert_eq!(matches[0].level, 0);
1862}
1863
1864#[test]
1865fn test_evaluate_rules_deep_hierarchy() {
1866    let grandchild_rule = MagicRule {
1867        offset: OffsetSpec::Absolute(5),
1868        typ: TypeKind::Byte,
1869        op: Operator::Equal,
1870        value: Value::Uint(0x01), // Little endian
1871        message: "little-endian".to_string(),
1872        children: vec![],
1873        level: 2,
1874        strength_modifier: None,
1875    };
1876
1877    let child_rule = MagicRule {
1878        offset: OffsetSpec::Absolute(4),
1879        typ: TypeKind::Byte,
1880        op: Operator::Equal,
1881        value: Value::Uint(0x02), // 64-bit
1882        message: "64-bit".to_string(),
1883        children: vec![grandchild_rule],
1884        level: 1,
1885        strength_modifier: None,
1886    };
1887
1888    let parent_rule = MagicRule {
1889        offset: OffsetSpec::Absolute(0),
1890        typ: TypeKind::Byte,
1891        op: Operator::Equal,
1892        value: Value::Uint(0x7f),
1893        message: "ELF".to_string(),
1894        children: vec![child_rule],
1895        level: 0,
1896        strength_modifier: None,
1897    };
1898
1899    let rules = vec![parent_rule];
1900    let buffer = &[0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01]; // ELF64 little-endian header
1901    let config = EvaluationConfig::default();
1902    let mut context = EvaluationContext::new(config);
1903
1904    let matches = evaluate_rules(&rules, buffer, &mut context).unwrap();
1905    assert_eq!(matches.len(), 3);
1906    assert_eq!(matches[0].message, "ELF");
1907    assert_eq!(matches[0].level, 0);
1908    assert_eq!(matches[1].message, "64-bit");
1909    assert_eq!(matches[1].level, 1);
1910    assert_eq!(matches[2].message, "little-endian");
1911    assert_eq!(matches[2].level, 2);
1912}
1913
1914#[test]
1915fn test_evaluate_rules_multiple_children() {
1916    let child1 = MagicRule {
1917        offset: OffsetSpec::Absolute(4),
1918        typ: TypeKind::Byte,
1919        op: Operator::Equal,
1920        value: Value::Uint(0x02),
1921        message: "64-bit".to_string(),
1922        children: vec![],
1923        level: 1,
1924        strength_modifier: None,
1925    };
1926
1927    let child2 = MagicRule {
1928        offset: OffsetSpec::Absolute(5),
1929        typ: TypeKind::Byte,
1930        op: Operator::Equal,
1931        value: Value::Uint(0x01),
1932        message: "little-endian".to_string(),
1933        children: vec![],
1934        level: 1,
1935        strength_modifier: None,
1936    };
1937
1938    let parent_rule = MagicRule {
1939        offset: OffsetSpec::Absolute(0),
1940        typ: TypeKind::Byte,
1941        op: Operator::Equal,
1942        value: Value::Uint(0x7f),
1943        message: "ELF".to_string(),
1944        children: vec![child1, child2],
1945        level: 0,
1946        strength_modifier: None,
1947    };
1948
1949    let rules = vec![parent_rule];
1950    let buffer = &[0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01];
1951    let config = EvaluationConfig {
1952        stop_at_first_match: false, // Find all matches
1953        ..Default::default()
1954    };
1955    let mut context = EvaluationContext::new(config);
1956
1957    let matches = evaluate_rules(&rules, buffer, &mut context).unwrap();
1958    assert_eq!(matches.len(), 3);
1959    assert_eq!(matches[0].message, "ELF");
1960    assert_eq!(matches[1].message, "64-bit");
1961    assert_eq!(matches[2].message, "little-endian");
1962}
1963
1964#[test]
1965fn test_evaluate_rules_recursion_depth_limit() {
1966    // Create a deeply nested rule structure that exceeds the limit
1967    let mut current_rule = MagicRule {
1968        offset: OffsetSpec::Absolute(10),
1969        typ: TypeKind::Byte,
1970        op: Operator::Equal,
1971        value: Value::Uint(0x00),
1972        message: "Deep level".to_string(),
1973        children: vec![],
1974        level: 10,
1975        strength_modifier: None,
1976    };
1977
1978    // Build a chain of nested rules
1979    for i in (0u32..10u32).rev() {
1980        current_rule = MagicRule {
1981            offset: OffsetSpec::Absolute(i64::from(i)),
1982            typ: TypeKind::Byte,
1983            op: Operator::Equal,
1984            value: Value::Uint(u64::from(i)),
1985            message: format!("Level {i}"),
1986            children: vec![current_rule],
1987            level: i,
1988            strength_modifier: None,
1989        };
1990    }
1991
1992    let rules = vec![current_rule];
1993    let buffer = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; // Matches all levels
1994    let config = EvaluationConfig {
1995        max_recursion_depth: 5, // Limit to 5 levels
1996        ..Default::default()
1997    };
1998    let mut context = EvaluationContext::new(config);
1999
2000    let result = evaluate_rules(&rules, buffer, &mut context);
2001    assert!(result.is_err());
2002
2003    match result.unwrap_err() {
2004        LibmagicError::EvaluationError(msg) => {
2005            let error_string = format!("{msg}");
2006            assert!(error_string.contains("Recursion limit exceeded"));
2007        }
2008        _ => panic!("Expected EvaluationError for recursion limit"),
2009    }
2010}
2011
2012#[test]
2013fn test_evaluate_rules_with_config_convenience() {
2014    let rule = MagicRule {
2015        offset: OffsetSpec::Absolute(0),
2016        typ: TypeKind::Byte,
2017        op: Operator::Equal,
2018        value: Value::Uint(0x7f),
2019        message: "ELF magic".to_string(),
2020        children: vec![],
2021        level: 0,
2022        strength_modifier: None,
2023    };
2024
2025    let rules = vec![rule];
2026    let buffer = &[0x7f, 0x45, 0x4c, 0x46];
2027    let config = EvaluationConfig::default();
2028
2029    let matches = evaluate_rules_with_config(&rules, buffer, &config).unwrap();
2030    assert_eq!(matches.len(), 1);
2031    assert_eq!(matches[0].message, "ELF magic");
2032}
2033
2034#[test]
2035fn test_evaluate_rules_timeout() {
2036    let rule = MagicRule {
2037        offset: OffsetSpec::Absolute(0),
2038        typ: TypeKind::Byte,
2039        op: Operator::Equal,
2040        value: Value::Uint(0x7f),
2041        message: "ELF magic".to_string(),
2042        children: vec![],
2043        level: 0,
2044        strength_modifier: None,
2045    };
2046
2047    let rules = vec![rule];
2048    let buffer = &[0x7f, 0x45, 0x4c, 0x46];
2049    let config = EvaluationConfig {
2050        timeout_ms: Some(0), // Immediate timeout
2051        ..Default::default()
2052    };
2053    let mut context = EvaluationContext::new(config);
2054
2055    // Note: This test might be flaky due to timing, but it demonstrates the timeout mechanism
2056    let result = evaluate_rules(&rules, buffer, &mut context);
2057    // The result could be either success (if evaluation is very fast) or timeout
2058    // We just verify that timeout errors are handled correctly when they occur
2059    if let Err(LibmagicError::Timeout { timeout_ms }) = result {
2060        assert_eq!(timeout_ms, 0);
2061    }
2062}
2063
2064#[test]
2065fn test_evaluate_rules_empty_buffer() {
2066    let rule = MagicRule {
2067        offset: OffsetSpec::Absolute(0),
2068        typ: TypeKind::Byte,
2069        op: Operator::Equal,
2070        value: Value::Uint(0x7f),
2071        message: "Should not match".to_string(),
2072        children: vec![],
2073        level: 0,
2074        strength_modifier: None,
2075    };
2076
2077    let rules = vec![rule];
2078    let buffer = &[]; // Empty buffer
2079    let config = EvaluationConfig::default();
2080    let mut context = EvaluationContext::new(config);
2081
2082    // With graceful error handling, this should succeed but return no matches
2083    let result = evaluate_rules(&rules, buffer, &mut context);
2084    assert!(result.is_ok());
2085
2086    let matches = result.unwrap();
2087    assert_eq!(matches.len(), 0); // No matches due to buffer overrun being handled gracefully
2088}
2089
2090#[test]
2091fn test_evaluate_rules_mixed_matching_non_matching() {
2092    let rule1 = MagicRule {
2093        offset: OffsetSpec::Absolute(0),
2094        typ: TypeKind::Byte,
2095        op: Operator::Equal,
2096        value: Value::Uint(0x7f),
2097        message: "Matches".to_string(),
2098        children: vec![],
2099        level: 0,
2100        strength_modifier: None,
2101    };
2102
2103    let rule2 = MagicRule {
2104        offset: OffsetSpec::Absolute(1),
2105        typ: TypeKind::Byte,
2106        op: Operator::Equal,
2107        value: Value::Uint(0x99), // Doesn't match
2108        message: "Doesn't match".to_string(),
2109        children: vec![],
2110        level: 0,
2111        strength_modifier: None,
2112    };
2113
2114    let rule3 = MagicRule {
2115        offset: OffsetSpec::Absolute(2),
2116        typ: TypeKind::Byte,
2117        op: Operator::Equal,
2118        value: Value::Uint(0x4c),
2119        message: "Also matches".to_string(),
2120        children: vec![],
2121        level: 0,
2122        strength_modifier: None,
2123    };
2124
2125    let rule_collection = vec![rule1, rule2, rule3];
2126    let buffer = &[0x7f, 0x45, 0x4c, 0x46];
2127    let config = EvaluationConfig {
2128        stop_at_first_match: false,
2129        ..Default::default()
2130    };
2131    let mut context = EvaluationContext::new(config);
2132
2133    let matches = evaluate_rules(&rule_collection, buffer, &mut context).unwrap();
2134    assert_eq!(matches.len(), 2);
2135    assert_eq!(matches[0].message, "Matches");
2136    assert_eq!(matches[1].message, "Also matches");
2137}
2138
2139#[test]
2140fn test_evaluate_rules_context_state_preservation() {
2141    let rule = MagicRule {
2142        offset: OffsetSpec::Absolute(0),
2143        typ: TypeKind::Byte,
2144        op: Operator::Equal,
2145        value: Value::Uint(0x7f),
2146        message: "ELF magic".to_string(),
2147        children: vec![],
2148        level: 0,
2149        strength_modifier: None,
2150    };
2151
2152    let rules = vec![rule];
2153    let buffer = &[0x7f, 0x45, 0x4c, 0x46];
2154    let config = EvaluationConfig::default();
2155    let mut context = EvaluationContext::new(config);
2156
2157    // Set some initial state
2158    context.set_current_offset(100);
2159    let initial_offset = context.current_offset();
2160    let initial_depth = context.recursion_depth();
2161
2162    let matches = evaluate_rules(&rules, buffer, &mut context).unwrap();
2163    assert_eq!(matches.len(), 1);
2164
2165    // Context state should be preserved
2166    assert_eq!(context.current_offset(), initial_offset);
2167    assert_eq!(context.recursion_depth(), initial_depth);
2168}
2169
2170#[test]
2171fn test_evaluation_context_state_management_sequence() {
2172    let config = EvaluationConfig::default();
2173    let mut context = EvaluationContext::new(config);
2174
2175    // Simulate a sequence of evaluation operations
2176    assert_eq!(context.current_offset(), 0);
2177    assert_eq!(context.recursion_depth(), 0);
2178
2179    // Start evaluation at offset 10
2180    context.set_current_offset(10);
2181    assert_eq!(context.current_offset(), 10);
2182
2183    // Enter nested rule evaluation
2184    context.increment_recursion_depth().unwrap();
2185    assert_eq!(context.recursion_depth(), 1);
2186
2187    // Move to different offset during nested evaluation
2188    context.set_current_offset(25);
2189    assert_eq!(context.current_offset(), 25);
2190
2191    // Enter deeper nesting
2192    context.increment_recursion_depth().unwrap();
2193    assert_eq!(context.recursion_depth(), 2);
2194
2195    // Exit nested evaluation
2196    context.decrement_recursion_depth().unwrap();
2197    assert_eq!(context.recursion_depth(), 1);
2198
2199    // Continue evaluation at different offset
2200    context.set_current_offset(50);
2201    assert_eq!(context.current_offset(), 50);
2202
2203    // Exit all nesting
2204    context.decrement_recursion_depth().unwrap();
2205    assert_eq!(context.recursion_depth(), 0);
2206
2207    // Final state check
2208    assert_eq!(context.current_offset(), 50);
2209    assert_eq!(context.recursion_depth(), 0);
2210}
2211#[test]
2212fn test_error_recovery_skip_problematic_rules() {
2213    // Test that evaluation continues when individual rules fail
2214    let rules = vec![
2215        // Valid rule that should match
2216        MagicRule {
2217            offset: OffsetSpec::Absolute(0),
2218            typ: TypeKind::Byte,
2219            op: Operator::Equal,
2220            value: Value::Uint(0x7f),
2221            message: "Valid rule".to_string(),
2222            children: vec![],
2223            level: 0,
2224            strength_modifier: None,
2225        },
2226        // Invalid rule with out-of-bounds offset
2227        MagicRule {
2228            offset: OffsetSpec::Absolute(100), // Beyond buffer
2229            typ: TypeKind::Byte,
2230            op: Operator::Equal,
2231            value: Value::Uint(0x00),
2232            message: "Invalid rule".to_string(),
2233            children: vec![],
2234            level: 0,
2235            strength_modifier: None,
2236        },
2237        // Another valid rule that should match
2238        MagicRule {
2239            offset: OffsetSpec::Absolute(1),
2240            typ: TypeKind::Byte,
2241            op: Operator::Equal,
2242            value: Value::Uint(0x45),
2243            message: "Another valid rule".to_string(),
2244            children: vec![],
2245            level: 0,
2246            strength_modifier: None,
2247        },
2248    ];
2249
2250    let buffer = &[0x7f, 0x45, 0x4c, 0x46]; // ELF magic bytes
2251    let config = EvaluationConfig {
2252        max_recursion_depth: 20,
2253        max_string_length: 8192,
2254        stop_at_first_match: false, // Don't stop at first match
2255        enable_mime_types: false,
2256        timeout_ms: None,
2257    };
2258    let mut context = EvaluationContext::new(config);
2259
2260    // Evaluation should succeed despite the problematic rule
2261    let matches = evaluate_rules(&rules, buffer, &mut context).unwrap();
2262
2263    // Should have 2 matches (skipping the problematic one)
2264    assert_eq!(matches.len(), 2);
2265    assert_eq!(matches[0].message, "Valid rule");
2266    assert_eq!(matches[1].message, "Another valid rule");
2267}
2268
2269#[test]
2270fn test_error_recovery_child_rule_failures() {
2271    // Test that parent evaluation continues when child rules fail
2272    let rules = vec![MagicRule {
2273        offset: OffsetSpec::Absolute(0),
2274        typ: TypeKind::Byte,
2275        op: Operator::Equal,
2276        value: Value::Uint(0x7f),
2277        message: "Parent rule".to_string(),
2278        children: vec![
2279            // Valid child rule
2280            MagicRule {
2281                offset: OffsetSpec::Absolute(1),
2282                typ: TypeKind::Byte,
2283                op: Operator::Equal,
2284                value: Value::Uint(0x45),
2285                message: "Valid child".to_string(),
2286                children: vec![],
2287                level: 1,
2288                strength_modifier: None,
2289            },
2290            // Invalid child rule
2291            MagicRule {
2292                offset: OffsetSpec::Absolute(100), // Beyond buffer
2293                typ: TypeKind::Byte,
2294                op: Operator::Equal,
2295                value: Value::Uint(0x00),
2296                message: "Invalid child".to_string(),
2297                children: vec![],
2298                level: 1,
2299                strength_modifier: None,
2300            },
2301        ],
2302        level: 0,
2303        strength_modifier: None,
2304    }];
2305
2306    let buffer = &[0x7f, 0x45, 0x4c, 0x46]; // ELF magic bytes
2307    let config = EvaluationConfig::default();
2308    let mut context = EvaluationContext::new(config);
2309
2310    // Evaluation should succeed with parent and valid child
2311    let matches = evaluate_rules(&rules, buffer, &mut context).unwrap();
2312
2313    // Should have parent match and valid child match
2314    assert_eq!(matches.len(), 2);
2315    assert_eq!(matches[0].message, "Parent rule");
2316    assert_eq!(matches[1].message, "Valid child");
2317}
2318
2319#[test]
2320fn test_error_recovery_mixed_rule_types() {
2321    // Test error recovery with different types of rule failures
2322    let rules = vec![
2323        // Valid byte rule
2324        MagicRule {
2325            offset: OffsetSpec::Absolute(0),
2326            typ: TypeKind::Byte,
2327            op: Operator::Equal,
2328            value: Value::Uint(0x7f),
2329            message: "Valid byte".to_string(),
2330            children: vec![],
2331            level: 0,
2332            strength_modifier: None,
2333        },
2334        // Invalid short rule (insufficient bytes)
2335        MagicRule {
2336            offset: OffsetSpec::Absolute(3), // Only 1 byte left for short
2337            typ: TypeKind::Short {
2338                endian: Endianness::Little,
2339                signed: false,
2340            },
2341            op: Operator::Equal,
2342            value: Value::Uint(0x1234),
2343            message: "Invalid short".to_string(),
2344            children: vec![],
2345            level: 0,
2346            strength_modifier: None,
2347        },
2348        // Valid string rule
2349        MagicRule {
2350            offset: OffsetSpec::Absolute(1),
2351            typ: TypeKind::String {
2352                max_length: Some(3),
2353            },
2354            op: Operator::Equal,
2355            value: Value::String("ELF".to_string()),
2356            message: "Valid string".to_string(),
2357            children: vec![],
2358            level: 0,
2359            strength_modifier: None,
2360        },
2361    ];
2362
2363    let buffer = &[0x7f, b'E', b'L', b'F']; // ELF magic bytes
2364    let config = EvaluationConfig {
2365        max_recursion_depth: 20,
2366        max_string_length: 8192,
2367        stop_at_first_match: false, // Don't stop at first match
2368        enable_mime_types: false,
2369        timeout_ms: None,
2370    };
2371    let mut context = EvaluationContext::new(config);
2372
2373    // Evaluation should succeed with valid rules
2374    let matches = evaluate_rules(&rules, buffer, &mut context).unwrap();
2375
2376    // Should have 2 matches (byte and string, skipping invalid short)
2377    assert_eq!(matches.len(), 2);
2378    assert_eq!(matches[0].message, "Valid byte");
2379    assert_eq!(matches[1].message, "Valid string");
2380}
2381
2382#[test]
2383fn test_error_recovery_all_rules_fail() {
2384    // Test behavior when all rules fail
2385    let rules = vec![
2386        // Out of bounds offset
2387        MagicRule {
2388            offset: OffsetSpec::Absolute(100),
2389            typ: TypeKind::Byte,
2390            op: Operator::Equal,
2391            value: Value::Uint(0x00),
2392            message: "Out of bounds".to_string(),
2393            children: vec![],
2394            level: 0,
2395            strength_modifier: None,
2396        },
2397        // Insufficient bytes for type
2398        MagicRule {
2399            offset: OffsetSpec::Absolute(2),
2400            typ: TypeKind::Long {
2401                endian: Endianness::Little,
2402                signed: false,
2403            },
2404            op: Operator::Equal,
2405            value: Value::Uint(0x1234_5678),
2406            message: "Insufficient bytes".to_string(),
2407            children: vec![],
2408            level: 0,
2409            strength_modifier: None,
2410        },
2411    ];
2412
2413    let buffer = &[0x7f, 0x45]; // Short buffer
2414    let config = EvaluationConfig::default();
2415    let mut context = EvaluationContext::new(config);
2416
2417    // Evaluation should succeed but return no matches
2418    let matches = evaluate_rules(&rules, buffer, &mut context).unwrap();
2419    assert_eq!(matches.len(), 0);
2420}
2421
2422#[test]
2423fn test_error_recovery_timeout_propagation() {
2424    // Test that timeout errors are properly propagated (not gracefully handled)
2425    let rules = vec![MagicRule {
2426        offset: OffsetSpec::Absolute(0),
2427        typ: TypeKind::Byte,
2428        op: Operator::Equal,
2429        value: Value::Uint(0x7f),
2430        message: "Test rule".to_string(),
2431        children: vec![],
2432        level: 0,
2433        strength_modifier: None,
2434    }];
2435
2436    let buffer = &[0x7f, 0x45, 0x4c, 0x46];
2437    let config = EvaluationConfig {
2438        max_recursion_depth: 10,
2439        max_string_length: 1024,
2440        stop_at_first_match: false,
2441        enable_mime_types: false,
2442        timeout_ms: Some(0), // Immediate timeout
2443    };
2444    let mut context = EvaluationContext::new(config);
2445
2446    // The timeout test is inherently flaky due to timing, so we'll just test
2447    // that the timeout configuration is properly set and the function doesn't panic
2448    let result = evaluate_rules(&rules, buffer, &mut context);
2449
2450    // The result should either be success (if evaluation was fast) or timeout error
2451    match result {
2452        Ok(_) | Err(LibmagicError::Timeout { .. }) => {
2453            // Evaluation was fast enough or timeout occurred, both are acceptable
2454        }
2455        Err(e) => {
2456            panic!("Unexpected error type: {e:?}");
2457        }
2458    }
2459}
2460
2461#[test]
2462fn test_error_recovery_recursion_limit_propagation() {
2463    // Test that recursion limit errors are properly propagated
2464    let rules = vec![MagicRule {
2465        offset: OffsetSpec::Absolute(0),
2466        typ: TypeKind::Byte,
2467        op: Operator::Equal,
2468        value: Value::Uint(0x7f),
2469        message: "Parent".to_string(),
2470        children: vec![MagicRule {
2471            offset: OffsetSpec::Absolute(1),
2472            typ: TypeKind::Byte,
2473            op: Operator::Equal,
2474            value: Value::Uint(0x45),
2475            message: "Child".to_string(),
2476            children: vec![],
2477            level: 1,
2478            strength_modifier: None,
2479        }],
2480        level: 0,
2481        strength_modifier: None,
2482    }];
2483
2484    let buffer = &[0x7f, 0x45, 0x4c, 0x46];
2485    let config = EvaluationConfig {
2486        max_recursion_depth: 0, // No recursion allowed
2487        max_string_length: 1024,
2488        stop_at_first_match: false,
2489        enable_mime_types: false,
2490        timeout_ms: None,
2491    };
2492    let mut context = EvaluationContext::new(config);
2493
2494    // Should return recursion limit error when trying to evaluate children
2495    let result = evaluate_rules(&rules, buffer, &mut context);
2496    assert!(result.is_err());
2497
2498    match result.unwrap_err() {
2499        LibmagicError::EvaluationError(crate::error::EvaluationError::RecursionLimitExceeded {
2500            ..
2501        }) => {
2502            // Expected recursion limit error
2503        }
2504        _ => panic!("Expected recursion limit error"),
2505    }
2506}
2507
2508#[test]
2509fn test_error_recovery_preserves_context_state() {
2510    // Test that context state is preserved despite rule failures
2511    let rules = vec![
2512        // Valid rule
2513        MagicRule {
2514            offset: OffsetSpec::Absolute(0),
2515            typ: TypeKind::Byte,
2516            op: Operator::Equal,
2517            value: Value::Uint(0x7f),
2518            message: "Valid rule".to_string(),
2519            children: vec![],
2520            level: 0,
2521            strength_modifier: None,
2522        },
2523        // Invalid rule
2524        MagicRule {
2525            offset: OffsetSpec::Absolute(100),
2526            typ: TypeKind::Byte,
2527            op: Operator::Equal,
2528            value: Value::Uint(0x00),
2529            message: "Invalid rule".to_string(),
2530            children: vec![],
2531            level: 0,
2532            strength_modifier: None,
2533        },
2534    ];
2535
2536    let buffer = &[0x7f, 0x45, 0x4c, 0x46];
2537    let config = EvaluationConfig::default();
2538    let mut context = EvaluationContext::new(config);
2539
2540    // Set initial context state
2541    context.set_current_offset(42);
2542    let initial_offset = context.current_offset();
2543    let initial_depth = context.recursion_depth();
2544
2545    // Evaluation should succeed
2546    let matches = evaluate_rules(&rules, buffer, &mut context).unwrap();
2547    assert_eq!(matches.len(), 1);
2548
2549    // Context state should be preserved
2550    assert_eq!(context.current_offset(), initial_offset);
2551    assert_eq!(context.recursion_depth(), initial_depth);
2552}
2553#[test]
2554fn test_debug_error_recovery() {
2555    // Simple test to debug error recovery
2556    let rule = MagicRule {
2557        offset: OffsetSpec::Absolute(100), // Beyond buffer
2558        typ: TypeKind::Byte,
2559        op: Operator::Equal,
2560        value: Value::Uint(0x00),
2561        message: "Out of bounds rule".to_string(),
2562        children: vec![],
2563        level: 0,
2564        strength_modifier: None,
2565    };
2566
2567    let buffer = &[0x7f, 0x45]; // Short buffer
2568
2569    // Test single rule evaluation - should fail
2570    let single_result = evaluate_single_rule(&rule, buffer);
2571    println!("Single rule result: {single_result:?}");
2572    assert!(single_result.is_err());
2573
2574    // Test rules evaluation - should succeed with no matches
2575    let rules = vec![rule];
2576    let config = EvaluationConfig::default();
2577    let mut context = EvaluationContext::new(config);
2578
2579    let matches = evaluate_rules(&rules, buffer, &mut context).unwrap();
2580    println!("Rules evaluation matches: {}", matches.len());
2581    assert_eq!(matches.len(), 0);
2582}
2583#[test]
2584fn test_debug_mixed_rules() {
2585    let rules = vec![
2586        // Valid rule that should match
2587        MagicRule {
2588            offset: OffsetSpec::Absolute(0),
2589            typ: TypeKind::Byte,
2590            op: Operator::Equal,
2591            value: Value::Uint(0x7f),
2592            message: "Valid rule".to_string(),
2593            children: vec![],
2594            level: 0,
2595            strength_modifier: None,
2596        },
2597        // Invalid rule with out-of-bounds offset
2598        MagicRule {
2599            offset: OffsetSpec::Absolute(100), // Beyond buffer
2600            typ: TypeKind::Byte,
2601            op: Operator::Equal,
2602            value: Value::Uint(0x00),
2603            message: "Invalid rule".to_string(),
2604            children: vec![],
2605            level: 0,
2606            strength_modifier: None,
2607        },
2608        // Another valid rule that should match
2609        MagicRule {
2610            offset: OffsetSpec::Absolute(1),
2611            typ: TypeKind::Byte,
2612            op: Operator::Equal,
2613            value: Value::Uint(0x45),
2614            message: "Another valid rule".to_string(),
2615            children: vec![],
2616            level: 0,
2617            strength_modifier: None,
2618        },
2619    ];
2620
2621    let buffer = &[0x7f, 0x45, 0x4c, 0x46]; // ELF magic bytes
2622
2623    // Test each rule individually
2624    for (i, rule) in rules.iter().enumerate() {
2625        let result = evaluate_single_rule(rule, buffer);
2626        println!("Rule {}: '{}' -> {:?}", i, rule.message, result);
2627    }
2628
2629    // Test rules evaluation
2630    let config = EvaluationConfig::default();
2631    let mut context = EvaluationContext::new(config);
2632
2633    let matches = evaluate_rules(&rules, buffer, &mut context).unwrap();
2634    println!("Total matches: {}", matches.len());
2635    for (i, m) in matches.iter().enumerate() {
2636        println!("Match {}: '{}'", i, m.message);
2637    }
2638}