4949//! `SigmaRuleMatcher` is thread-safe and uses `Arc` internally for efficient sharing
5050//! across threads.
5151
52- use std:: collections:: HashMap ;
52+ use std:: borrow:: Borrow ;
53+ use std:: collections:: { BTreeMap , HashMap } ;
54+ use std:: hash:: Hash ;
5355use std:: sync:: { Arc , Mutex } ;
5456
5557use once_cell:: sync:: Lazy ;
@@ -62,18 +64,6 @@ use crate::types::*;
6264static REGEX_CACHE : Lazy < Mutex < HashMap < String , std:: result:: Result < Regex , String > > > > =
6365 Lazy :: new ( || Mutex :: new ( HashMap :: new ( ) ) ) ;
6466
65- /// A compiled matcher for a Sigma detection rule.
66- ///
67- /// This struct represents a compiled version of a SigmaRule that can efficiently
68- /// match against events. It is thread-safe and can be used concurrently.
69- #[ derive( Debug , Clone ) ]
70- pub struct SigmaRuleMatcher {
71- /// The original rule this matcher was compiled from
72- pub rule : Arc < SigmaRule > ,
73- /// Compiled search identifiers for efficient matching
74- compiled_searches : HashMap < String , CompiledSearch > ,
75- }
76-
7767/// A compiled search identifier that can be evaluated against an event.
7868#[ derive( Debug , Clone ) ]
7969enum CompiledSearch {
@@ -110,6 +100,85 @@ enum CompiledPattern {
110100 Null ,
111101}
112102
103+ /// A generic event that allows matching on references
104+ pub trait Event {
105+ /// Check if the event contains a given field
106+ fn contains_key ( & self , key : & str ) -> bool ;
107+
108+ /// Retrieve the value in a given field
109+ fn get ( & self , key : & str ) -> Option < & str > ;
110+
111+ /// Iterate over all values in the event
112+ fn values ( & self ) -> impl Iterator < Item = & str > ;
113+
114+ /// Get the raw value of an event
115+ ///
116+ /// Default implementation joins all field values
117+ fn raw < ' a > ( & ' a self ) -> Option < impl AsRef < str > + ' a > {
118+ let mut buf = String :: new ( ) ;
119+ let mut iter = self . values ( ) . peekable ( ) ;
120+
121+ while let Some ( val) = iter. next ( ) {
122+ buf. push_str ( val. as_ref ( ) ) ;
123+ if iter. peek ( ) . is_some ( ) {
124+ buf. push ( ' ' ) ;
125+ }
126+ }
127+
128+ Some ( buf)
129+ }
130+ }
131+
132+ // Implement the event trait for hashmaps of string-like keys/values
133+ impl < K , V > Event for HashMap < K , V >
134+ where
135+ K : Eq + Hash + Borrow < str > ,
136+ V : AsRef < str > ,
137+ {
138+ fn contains_key ( & self , key : & str ) -> bool {
139+ HashMap :: contains_key ( self , key)
140+ }
141+
142+ fn get ( & self , key : & str ) -> Option < & str > {
143+ HashMap :: get ( self , key) . map ( |v| v. as_ref ( ) )
144+ }
145+
146+ fn values ( & self ) -> impl Iterator < Item = & str > {
147+ HashMap :: values ( self ) . map ( |v| v. as_ref ( ) )
148+ }
149+ }
150+
151+ // Implement the event trait for btree maps of string-like keys/values
152+ impl < K , V > Event for BTreeMap < K , V >
153+ where
154+ K : Eq + Ord + Borrow < str > ,
155+ V : AsRef < str > ,
156+ {
157+ fn contains_key ( & self , key : & str ) -> bool {
158+ BTreeMap :: contains_key ( self , key)
159+ }
160+
161+ fn get ( & self , key : & str ) -> Option < & str > {
162+ BTreeMap :: get ( self , key) . map ( |v| v. as_ref ( ) )
163+ }
164+
165+ fn values ( & self ) -> impl Iterator < Item = & str > {
166+ BTreeMap :: values ( self ) . map ( |v| v. as_ref ( ) )
167+ }
168+ }
169+
170+ /// A compiled matcher for a Sigma detection rule.
171+ ///
172+ /// This struct represents a compiled version of a SigmaRule that can efficiently
173+ /// match against events. It is thread-safe and can be used concurrently.
174+ #[ derive( Debug , Clone ) ]
175+ pub struct SigmaRuleMatcher {
176+ /// The original rule this matcher was compiled from
177+ pub rule : Arc < SigmaRule > ,
178+ /// Compiled search identifiers for efficient matching
179+ compiled_searches : HashMap < String , CompiledSearch > ,
180+ }
181+
113182impl SigmaRuleMatcher {
114183 /// Compile a Sigma rule into a matcher.
115184 ///
@@ -426,7 +495,7 @@ impl SigmaRuleMatcher {
426495 ///
427496 /// # Returns
428497 /// `true` if the event matches any of the rule's conditions, `false` otherwise
429- pub fn matches ( & self , event : & HashMap < String , String > ) -> bool {
498+ pub fn matches < E : Event > ( & self , event : & E ) -> bool {
430499 // Evaluate all conditions (they are implicitly OR-connected)
431500 for condition in & self . rule . detection . conditions {
432501 if self . eval_condition ( condition, event) {
@@ -437,7 +506,7 @@ impl SigmaRuleMatcher {
437506 }
438507
439508 /// Evaluate a condition expression against an event.
440- fn eval_condition ( & self , expr : & ConditionExpression , event : & HashMap < String , String > ) -> bool {
509+ fn eval_condition < E : Event > ( & self , expr : & ConditionExpression , event : & E ) -> bool {
441510 match expr {
442511 ConditionExpression :: Identifier ( name) => {
443512 self . eval_search_identifier ( name, event)
@@ -467,7 +536,7 @@ impl SigmaRuleMatcher {
467536 }
468537
469538 /// Evaluate a search identifier against an event.
470- fn eval_search_identifier ( & self , name : & str , event : & HashMap < String , String > ) -> bool {
539+ fn eval_search_identifier < E : Event > ( & self , name : & str , event : & E ) -> bool {
471540 if let Some ( search) = self . compiled_searches . get ( name) {
472541 match search {
473542 CompiledSearch :: Map ( items) => {
@@ -489,7 +558,7 @@ impl SigmaRuleMatcher {
489558 }
490559
491560 /// Evaluate a list of detection items with AND logic.
492- fn eval_detection_items_and ( & self , items : & [ CompiledDetectionItem ] , event : & HashMap < String , String > ) -> bool {
561+ fn eval_detection_items_and < E : Event > ( & self , items : & [ CompiledDetectionItem ] , event : & E ) -> bool {
493562 for item in items {
494563 if !self . eval_detection_item ( item, event) {
495564 return false ;
@@ -499,7 +568,7 @@ impl SigmaRuleMatcher {
499568 }
500569
501570 /// Evaluate a single detection item against an event.
502- fn eval_detection_item ( & self , item : & CompiledDetectionItem , event : & HashMap < String , String > ) -> bool {
571+ fn eval_detection_item < E : Event > ( & self , item : & CompiledDetectionItem , event : & E ) -> bool {
503572 // Handle exists modifier
504573 if item. modifiers . contains ( & Modifier :: Exists ) {
505574 if let Some ( field) = & item. field {
@@ -512,35 +581,42 @@ impl SigmaRuleMatcher {
512581 return false ;
513582 }
514583
515- // Get the value to match against
516- let value_to_match = if let Some ( field) = & item. field {
584+ if let Some ( field) = & item. field {
517585 // Field matching
518586 if let Some ( val) = event. get ( field) {
519- val . clone ( )
587+ self . match_patterns ( & item . patterns , & item . modifiers , val )
520588 } else {
521589 // Field doesn't exist in event
522- return false ;
590+ false
523591 }
524592 } else {
525593 // Keyword search - match against all field values or entire event
526- event. values ( ) . cloned ( ) . collect :: < Vec < _ > > ( ) . join ( " " )
527- } ;
594+ let Some ( raw) = event. raw ( ) else {
595+ return false ;
596+ } ;
528597
529- // Check if ALL modifier is present
530- let use_all_logic = item. modifiers . contains ( & Modifier :: All ) ;
598+ self . match_patterns ( & item. patterns , & item. modifiers , raw. as_ref ( ) )
599+ }
600+ }
531601
532- if use_all_logic {
602+ fn match_patterns (
603+ & self ,
604+ patterns : & [ CompiledPattern ] ,
605+ modifiers : & [ Modifier ] ,
606+ value : & str ,
607+ ) -> bool {
608+ if modifiers. contains ( & Modifier :: All ) {
533609 // ALL: all patterns must match
534- for pattern in & item . patterns {
535- if !self . match_pattern ( pattern, & value_to_match , & item . modifiers ) {
610+ for pattern in patterns {
611+ if !self . match_pattern ( pattern, value , modifiers) {
536612 return false ;
537613 }
538614 }
539615 true
540616 } else {
541617 // Default OR: any pattern can match
542- for pattern in & item . patterns {
543- if self . match_pattern ( pattern, & value_to_match , & item . modifiers ) {
618+ for pattern in patterns {
619+ if self . match_pattern ( pattern, value , modifiers) {
544620 return true ;
545621 }
546622 }
@@ -738,7 +814,7 @@ impl SigmaRuleMatcher {
738814 }
739815
740816 /// Evaluate "1 of them" - any non-underscore-prefixed search identifier matches.
741- fn eval_one_of_them ( & self , event : & HashMap < String , String > ) -> bool {
817+ fn eval_one_of_them < E : Event > ( & self , event : & E ) -> bool {
742818 for name in self . compiled_searches . keys ( ) {
743819 if !name. starts_with ( '_' ) && self . eval_search_identifier ( name, event) {
744820 return true ;
@@ -748,7 +824,7 @@ impl SigmaRuleMatcher {
748824 }
749825
750826 /// Evaluate "all of them" - all non-underscore-prefixed search identifiers match.
751- fn eval_all_of_them ( & self , event : & HashMap < String , String > ) -> bool {
827+ fn eval_all_of_them < E : Event > ( & self , event : & E ) -> bool {
752828 let mut found_any = false ;
753829 for name in self . compiled_searches . keys ( ) {
754830 if !name. starts_with ( '_' ) {
@@ -762,7 +838,7 @@ impl SigmaRuleMatcher {
762838 }
763839
764840 /// Evaluate "1 of pattern" - any matching search identifier matches.
765- fn eval_one_of_pattern ( & self , pattern : & str , event : & HashMap < String , String > ) -> bool {
841+ fn eval_one_of_pattern < E : Event > ( & self , pattern : & str , event : & E ) -> bool {
766842 for name in self . compiled_searches . keys ( ) {
767843 if self . match_identifier_pattern ( name, pattern) && self . eval_search_identifier ( name, event) {
768844 return true ;
@@ -772,7 +848,7 @@ impl SigmaRuleMatcher {
772848 }
773849
774850 /// Evaluate "all of pattern" - all matching search identifiers match.
775- fn eval_all_of_pattern ( & self , pattern : & str , event : & HashMap < String , String > ) -> bool {
851+ fn eval_all_of_pattern < E : Event > ( & self , pattern : & str , event : & E ) -> bool {
776852 let mut found_any = false ;
777853 for name in self . compiled_searches . keys ( ) {
778854 if self . match_identifier_pattern ( name, pattern) {
0 commit comments