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;