Skip to main content

rsigma_convert/
condition.rs

1use std::collections::HashMap;
2
3use rsigma_parser::*;
4
5use crate::backend::Backend;
6use crate::error::{ConvertError, Result};
7use crate::state::ConversionState;
8
9/// Recursively walk a `ConditionExpr` tree and convert each node into a query fragment.
10pub fn convert_condition_expr(
11    backend: &dyn Backend,
12    expr: &ConditionExpr,
13    detections: &HashMap<String, Detection>,
14    state: &mut ConversionState,
15) -> Result<String> {
16    match expr {
17        ConditionExpr::Identifier(name) => {
18            let det = detections.get(name).ok_or_else(|| {
19                ConvertError::RuleConversion(format!("detection '{name}' not found"))
20            })?;
21            backend.convert_detection(det, state)
22        }
23
24        ConditionExpr::And(exprs) => {
25            let parts: Vec<String> = exprs
26                .iter()
27                .map(|e| convert_condition_expr(backend, e, detections, state))
28                .collect::<Result<Vec<_>>>()?;
29            backend.convert_condition_and(&parts)
30        }
31
32        ConditionExpr::Or(exprs) => {
33            let parts: Vec<String> = exprs
34                .iter()
35                .map(|e| convert_condition_expr(backend, e, detections, state))
36                .collect::<Result<Vec<_>>>()?;
37            backend.convert_condition_or(&parts)
38        }
39
40        ConditionExpr::Not(inner) => {
41            let part = convert_condition_expr(backend, inner, detections, state)?;
42            backend.convert_condition_not(&part)
43        }
44
45        ConditionExpr::Selector {
46            quantifier,
47            pattern,
48        } => {
49            let names: Vec<&String> = match pattern {
50                SelectorPattern::Them => {
51                    detections.keys().filter(|n| !n.starts_with('_')).collect()
52                }
53                SelectorPattern::Pattern(pat) => detections
54                    .keys()
55                    .filter(|n| pattern_matches(pat, n))
56                    .collect(),
57            };
58
59            if names.is_empty() {
60                return Err(ConvertError::RuleConversion(
61                    "selector matched no detections".into(),
62                ));
63            }
64
65            let parts: Vec<String> = names
66                .iter()
67                .map(|name| {
68                    let det = detections.get(*name).unwrap();
69                    backend.convert_detection(det, state)
70                })
71                .collect::<Result<Vec<_>>>()?;
72
73            match quantifier {
74                Quantifier::Any | Quantifier::Count(1) => backend.convert_condition_or(&parts),
75                Quantifier::All => backend.convert_condition_and(&parts),
76                Quantifier::Count(n) => Err(ConvertError::RuleConversion(format!(
77                    "'{n} of' quantifier not supported in conversion"
78                ))),
79            }
80        }
81    }
82}
83
84/// Simple wildcard match on detection names (supports `*` glob at end, start, or middle).
85fn pattern_matches(pattern: &str, name: &str) -> bool {
86    if pattern == "*" {
87        return true;
88    }
89    if let Some(prefix) = pattern.strip_suffix('*') {
90        return name.starts_with(prefix);
91    }
92    if let Some(suffix) = pattern.strip_prefix('*') {
93        return name.ends_with(suffix);
94    }
95    if let Some((prefix, suffix)) = pattern.split_once('*') {
96        return name.starts_with(prefix) && name.ends_with(suffix);
97    }
98    pattern == name
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn test_pattern_matches_star_suffix() {
107        assert!(pattern_matches("selection_*", "selection_main"));
108        assert!(pattern_matches("selection_*", "selection_"));
109        assert!(!pattern_matches("selection_*", "filter_main"));
110    }
111
112    #[test]
113    fn test_pattern_matches_star_prefix() {
114        assert!(pattern_matches("*_main", "selection_main"));
115        assert!(!pattern_matches("*_main", "selection_alt"));
116    }
117
118    #[test]
119    fn test_pattern_matches_star_middle() {
120        assert!(pattern_matches("sel*main", "selection_main"));
121        assert!(!pattern_matches("sel*main", "filter_main"));
122    }
123
124    #[test]
125    fn test_pattern_matches_exact() {
126        assert!(pattern_matches("selection", "selection"));
127        assert!(!pattern_matches("selection", "filter"));
128    }
129
130    #[test]
131    fn test_pattern_matches_star_only() {
132        assert!(pattern_matches("*", "anything"));
133    }
134}