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 provides the public interface for magic rule evaluation,
7//! including data types for evaluation state and match results, and
8//! re-exports the core evaluation functions from submodules.
9
10use crate::{EvaluationConfig, LibmagicError};
11use serde::{Deserialize, Serialize};
12
13mod engine;
14pub mod offset;
15pub mod operators;
16pub mod strength;
17pub mod types;
18
19pub use engine::{evaluate_rules, evaluate_rules_with_config, evaluate_single_rule};
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 extracted from a successful rule match, including
205/// the matched value, position, and confidence score.
206#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
207pub struct RuleMatch {
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    /// The type used to read the matched value
217    ///
218    /// Carries the source `TypeKind` so downstream consumers (e.g., output
219    /// formatting) can determine the on-disk width of the matched value.
220    pub type_kind: crate::parser::ast::TypeKind,
221    /// Confidence score (0.0 to 1.0)
222    ///
223    /// Calculated based on match depth in the rule hierarchy.
224    /// Deeper matches indicate more specific file type identification
225    /// and thus higher confidence.
226    pub confidence: f64,
227}
228
229impl RuleMatch {
230    /// Calculate confidence score based on rule depth
231    ///
232    /// Formula: min(1.0, 0.3 + (level * 0.2))
233    /// - Level 0 (root): 0.3
234    /// - Level 1: 0.5
235    /// - Level 2: 0.7
236    /// - Level 3: 0.9
237    /// - Level 4+: 1.0 (capped)
238    ///
239    /// # Examples
240    ///
241    /// ```
242    /// use libmagic_rs::evaluator::RuleMatch;
243    ///
244    /// assert!((RuleMatch::calculate_confidence(0) - 0.3).abs() < 0.001);
245    /// assert!((RuleMatch::calculate_confidence(3) - 0.9).abs() < 0.001);
246    /// assert!((RuleMatch::calculate_confidence(10) - 1.0).abs() < 0.001);
247    /// ```
248    #[must_use]
249    pub fn calculate_confidence(level: u32) -> f64 {
250        (0.3 + (f64::from(level) * 0.2)).min(1.0)
251    }
252}
253
254#[cfg(test)]
255mod tests;