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}