Skip to main content

rsigma_eval/matcher/
mod.rs

1//! Compiled matchers for zero-allocation hot-path evaluation.
2//!
3//! Each `CompiledMatcher` variant is pre-compiled at rule load time.
4//! At evaluation time, `matches()` performs the comparison against an
5//! [`EventValue`] from the event with no dynamic dispatch or allocation.
6
7mod helpers;
8mod matching;
9
10pub use helpers::{parse_expand_template, sigma_string_to_regex};
11
12use regex::Regex;
13
14use crate::event::Event;
15use ipnet::IpNet;
16
17/// A pre-compiled matcher for a single value comparison.
18///
19/// All string matchers store their values in the form needed for comparison
20/// (Unicode-lowercased for case-insensitive). The `case_insensitive` flag
21/// controls whether the input is lowercased before comparison.
22#[derive(Debug, Clone)]
23pub enum CompiledMatcher {
24    // -- String matchers --
25    /// Exact string equality.
26    Exact {
27        value: String,
28        case_insensitive: bool,
29    },
30    /// Substring containment.
31    Contains {
32        value: String,
33        case_insensitive: bool,
34    },
35    /// String starts with prefix.
36    StartsWith {
37        value: String,
38        case_insensitive: bool,
39    },
40    /// String ends with suffix.
41    EndsWith {
42        value: String,
43        case_insensitive: bool,
44    },
45    /// Compiled regex pattern (flags baked in at compile time).
46    Regex(Regex),
47
48    // -- Network --
49    /// CIDR network match for IP addresses.
50    Cidr(IpNet),
51
52    // -- Numeric --
53    /// Numeric equality.
54    NumericEq(f64),
55    /// Numeric greater-than.
56    NumericGt(f64),
57    /// Numeric greater-than-or-equal.
58    NumericGte(f64),
59    /// Numeric less-than.
60    NumericLt(f64),
61    /// Numeric less-than-or-equal.
62    NumericLte(f64),
63
64    // -- Special --
65    /// Field existence check. `true` = field must exist, `false` = must not exist.
66    Exists(bool),
67    /// Compare against another field's value.
68    FieldRef {
69        field: String,
70        case_insensitive: bool,
71    },
72    /// Match null / missing values.
73    Null,
74    /// Boolean equality.
75    BoolEq(bool),
76
77    // -- Expand --
78    /// Placeholder expansion: `%fieldname%` is resolved from the event at match time.
79    Expand {
80        template: Vec<ExpandPart>,
81        case_insensitive: bool,
82    },
83
84    // -- Timestamp --
85    /// Extract a time component from a timestamp field value and match it.
86    TimestampPart {
87        part: TimePart,
88        inner: Box<CompiledMatcher>,
89    },
90
91    // -- Negation --
92    /// Negated matcher: matches if the inner matcher does NOT match.
93    Not(Box<CompiledMatcher>),
94
95    // -- Composite --
96    /// Match if ANY child matches (OR).
97    AnyOf(Vec<CompiledMatcher>),
98    /// Match if ALL children match (AND).
99    AllOf(Vec<CompiledMatcher>),
100}
101
102/// A part of an expand template.
103#[derive(Debug, Clone)]
104pub enum ExpandPart {
105    /// Literal text.
106    Literal(String),
107    /// A placeholder field name (between `%` delimiters).
108    Placeholder(String),
109}
110
111/// Which time component to extract from a timestamp.
112#[derive(Debug, Clone, Copy)]
113pub enum TimePart {
114    Minute,
115    Hour,
116    Day,
117    Week,
118    Month,
119    Year,
120}
121
122impl CompiledMatcher {
123    /// Check if this matcher matches any string value in the event.
124    /// Used for keyword detection (field-less matching).
125    ///
126    /// Avoids allocating a `Vec` of all strings and a `String` per value by
127    /// using `matches_str` with a short-circuiting traversal.
128    #[inline]
129    pub fn matches_keyword(&self, event: &impl Event) -> bool {
130        event.any_string_value(&|s| self.matches_str(s))
131    }
132}