sigma_rust/
event.rs

1use crate::basevalue::BaseValue;
2use crate::field::{FieldValue, MatchModifier, Modifier};
3use crate::wildcard::{match_tokenized, tokenize};
4use std::collections::HashMap;
5use std::hash::Hash;
6use std::net::IpAddr;
7use std::str::FromStr;
8
9#[cfg(feature = "serde_json")]
10#[derive(Debug, serde::Deserialize)]
11struct EventProxy {
12    #[serde(flatten)]
13    value: serde_json::Value,
14}
15
16#[derive(Debug, PartialEq)]
17pub enum EventValue {
18    Value(BaseValue),
19    Sequence(Vec<EventValue>),
20    Map(HashMap<String, EventValue>),
21}
22
23#[cfg(feature = "serde_json")]
24impl TryFrom<serde_json::Value> for EventValue {
25    type Error = crate::error::JSONError;
26
27    fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
28        match value {
29            serde_json::Value::Null
30            | serde_json::Value::Bool(_)
31            | serde_json::Value::Number(_)
32            | serde_json::Value::String(_) => Ok(Self::Value(BaseValue::try_from(value)?)),
33            serde_json::Value::Array(a) => {
34                let mut result = Vec::with_capacity(a.len());
35                for item in a {
36                    result.push(Self::try_from(item)?);
37                }
38                Ok(Self::Sequence(result))
39            }
40            serde_json::Value::Object(data) => {
41                let mut result = HashMap::with_capacity(data.len());
42                for (key, value) in data {
43                    result.insert(key, Self::try_from(value)?);
44                }
45                Ok(Self::Map(result))
46            }
47        }
48    }
49}
50
51impl EventValue {
52    pub(crate) fn contains_keyword(&self, s: &str) -> bool {
53        match self {
54            Self::Value(v) => {
55                // Case-insensitive matching for keywords
56                //https://github.com/SigmaHQ/sigma-specification/blob/main/specification/sigma-rules-specification.md#lists
57                let tokens = tokenize(s, true);
58                match_tokenized(&tokens, v.value_to_string().as_str(), true)
59            }
60            Self::Sequence(seq) => seq.iter().any(|v| v.contains_keyword(s)),
61            Self::Map(m) => m.values().any(|v| v.contains_keyword(s)),
62        }
63    }
64
65    pub(crate) fn matches(&self, field_value: &FieldValue, modifier: &Modifier) -> bool {
66        match (&self, field_value) {
67            (Self::Value(target), FieldValue::Base(value)) => match modifier.match_modifier {
68                // Entered in fieldref case
69                Some(MatchModifier::Contains) => match (target, value) {
70                    (BaseValue::String(target), BaseValue::String(value)) => {
71                        if modifier.cased {
72                            target.contains(value)
73                        } else {
74                            target.to_lowercase().contains(&value.to_lowercase())
75                        }
76                    }
77                    _ => false,
78                },
79                Some(MatchModifier::StartsWith) => match (target, value) {
80                    (BaseValue::String(target), BaseValue::String(value)) => {
81                        if modifier.cased {
82                            target.starts_with(value)
83                        } else {
84                            target.to_lowercase().starts_with(&value.to_lowercase())
85                        }
86                    }
87                    _ => false,
88                },
89                Some(MatchModifier::EndsWith) => match (target, value) {
90                    (BaseValue::String(target), BaseValue::String(value)) => {
91                        if modifier.cased {
92                            target.ends_with(value)
93                        } else {
94                            target.to_lowercase().ends_with(&value.to_lowercase())
95                        }
96                    }
97                    _ => false,
98                },
99
100                Some(MatchModifier::Gt) => target > value,
101                Some(MatchModifier::Gte) => target >= value,
102                Some(MatchModifier::Lt) => target < value,
103                Some(MatchModifier::Lte) => target <= value,
104
105                // Regex and CIDR would already be compiled into FieldValue::Regex and FieldValue::Cidr
106                Some(MatchModifier::Re) | Some(MatchModifier::Cidr) => false,
107
108                // implicit equals
109                None => value == target,
110            },
111            (Self::Value(v), FieldValue::WildcardPattern(w)) => {
112                if let BaseValue::String(s) = v {
113                    match_tokenized(w, s, !modifier.cased)
114                } else {
115                    match_tokenized(w, v.value_to_string().as_str(), !modifier.cased)
116                }
117            }
118
119            (Self::Value(v), FieldValue::Regex(r)) => r.is_match(&v.value_to_string()),
120            (Self::Value(v), FieldValue::Cidr(c)) => {
121                if let BaseValue::String(s) = v {
122                    match IpAddr::from_str(s) {
123                        Ok(ip) => c.contains(&ip),
124                        Err(_) => false,
125                    }
126                } else {
127                    false
128                }
129            }
130
131            // We currently do not support matching against lists and hashmaps, see
132            // https://github.com/jopohl/sigma-rust/issues/9
133            (Self::Sequence(_), _) => false,
134            (Self::Map(_), _) => false,
135        }
136    }
137}
138
139impl<T> From<T> for EventValue
140where
141    T: Into<BaseValue>,
142{
143    fn from(value: T) -> Self {
144        Self::Value(value.into())
145    }
146}
147
148/// The `Event` struct represents a log event.
149///
150/// It is a collection of key-value pairs
151/// where the key is a string and the value is a string, number, or boolean
152/// The value may also be `None` to represent a null value.
153#[derive(Debug, Default)]
154#[cfg_attr(feature = "serde_json", derive(serde::Deserialize))]
155#[cfg_attr(feature = "serde_json", serde(try_from = "EventProxy"))]
156pub struct Event {
157    inner: HashMap<String, EventValue>,
158}
159
160#[cfg(feature = "serde_json")]
161impl TryFrom<EventProxy> for Event {
162    type Error = crate::error::JSONError;
163
164    fn try_from(other: EventProxy) -> Result<Self, Self::Error> {
165        Self::try_from(other.value)
166    }
167}
168
169impl<T, S, const N: usize> From<[(S, T); N]> for Event
170where
171    S: Into<String> + Hash + Eq,
172    T: Into<EventValue>,
173{
174    fn from(values: [(S, T); N]) -> Self {
175        let mut data = HashMap::with_capacity(N);
176        for (k, v) in values {
177            data.insert(k.into(), v.into());
178        }
179        Self { inner: data }
180    }
181}
182
183impl Event {
184    /// Create a new empty event
185    pub fn new() -> Self {
186        Self::default()
187    }
188
189    /// Insert a key-value pair into the event.
190    /// If the key already exists, the value will be replaced.
191    ///
192    /// # Example
193    /// ```rust
194    /// use sigma_rust::Event;
195    /// let mut event = Event::new();
196    /// event.insert("name", "John Doe");
197    /// event.insert("age", 43);
198    /// event.insert("is_admin", true);
199    /// event.insert("null_value", None);
200    /// ```
201    pub fn insert<T, S>(&mut self, key: S, value: T)
202    where
203        S: Into<String> + Hash + Eq,
204        T: Into<EventValue>,
205    {
206        self.inner.insert(key.into(), value.into());
207    }
208
209    /// Iterate over the key-value pairs in the event
210    pub fn iter(&self) -> impl Iterator<Item = (&String, &EventValue)> {
211        self.inner.iter()
212    }
213
214    /// Get the value for a key in the event
215    pub fn get(&self, key: &str) -> Option<&EventValue> {
216        if let Some(ev) = self.inner.get(key) {
217            return Some(ev);
218        }
219
220        let mut nested_key = key;
221        let mut current = &self.inner;
222        while let Some((head, tail)) = nested_key.split_once('.') {
223            if let Some(EventValue::Map(map)) = current.get(head) {
224                if let Some(value) = map.get(tail) {
225                    return Some(value);
226                }
227                current = map;
228                nested_key = tail;
229            } else {
230                return None;
231            }
232        }
233        None
234    }
235
236    pub fn values(&self) -> impl Iterator<Item = &EventValue> {
237        self.inner.values()
238    }
239}
240
241#[cfg(feature = "serde_json")]
242impl TryFrom<serde_json::Value> for Event {
243    type Error = crate::error::JSONError;
244
245    fn try_from(data: serde_json::Value) -> Result<Self, Self::Error> {
246        let mut result = Self::default();
247        match data {
248            serde_json::Value::Object(data) => {
249                for (key, value) in data {
250                    result.insert(key, EventValue::try_from(value)?);
251                }
252            }
253            _ => return Err(Self::Error::InvalidEvent()),
254        }
255        Ok(result)
256    }
257}
258
259#[cfg(feature = "serde_json")]
260#[cfg(test)]
261mod tests {
262    use super::*;
263    use crate::wildcard::tokenize;
264    use serde_json::json;
265
266    #[test]
267    fn test_matches() {
268        let mut modifier = Modifier::default();
269
270        assert!(EventValue::from("zsh").matches(&FieldValue::from("zsh"), &modifier));
271        assert!(!EventValue::from("zsh").matches(&FieldValue::from("bash"), &modifier));
272
273        modifier.match_modifier = Some(MatchModifier::StartsWith);
274
275        assert!(EventValue::from("zsh").matches(&FieldValue::from("z"), &modifier));
276        assert!(!EventValue::from("zsh").matches(&FieldValue::from("sd"), &modifier));
277
278        modifier.match_modifier = Some(MatchModifier::EndsWith);
279        assert!(EventValue::from("zsh").matches(&FieldValue::from("sh"), &modifier));
280        assert!(!EventValue::from("zsh").matches(&FieldValue::from("sd"), &modifier));
281
282        modifier.match_modifier = Some(MatchModifier::Contains);
283        assert!(EventValue::from("zsh").matches(&FieldValue::from("s"), &modifier));
284        assert!(!EventValue::from("zsh").matches(&FieldValue::from("d"), &modifier));
285    }
286
287    #[test]
288    fn test_load_from_json() {
289        let event: Event = json!({
290            "name": "John Doe",
291            "age": 43,
292            "address": {
293                "city": "New York",
294                "state": "NY"
295            }
296        })
297        .try_into()
298        .unwrap();
299
300        assert_eq!(event.inner["name"], EventValue::from("John Doe"));
301        assert_eq!(event.inner["age"], EventValue::from(43));
302        assert_eq!(
303            event.inner["address"],
304            EventValue::Map({
305                let mut map = HashMap::new();
306                map.insert("city".to_string(), EventValue::from("New York"));
307                map.insert("state".to_string(), EventValue::from("NY"));
308                map
309            })
310        );
311    }
312
313    #[test]
314    fn test_wildcard_matches() {
315        let modifier = Modifier::default();
316        let wildcard = FieldValue::WildcardPattern(tokenize("4?", false));
317
318        assert!(EventValue::from("42").matches(&wildcard, &modifier));
319        assert!(EventValue::from(43).matches(&wildcard, &modifier));
320        assert!(EventValue::from(43u32).matches(&wildcard, &modifier));
321        assert!(!EventValue::from(53).matches(&wildcard, &modifier));
322        assert!(!EventValue::from(433).matches(&wildcard, &modifier));
323        assert!(!EventValue::from(None).matches(&wildcard, &modifier));
324
325        let wildcard = FieldValue::WildcardPattern(tokenize("f*", false));
326        assert!(EventValue::from(false).matches(&wildcard, &modifier));
327        assert!(!EventValue::from(true).matches(&wildcard, &modifier));
328        assert!(!EventValue::from(None).matches(&wildcard, &modifier));
329    }
330
331    #[test]
332    fn test_iter() {
333        let event = Event::from([("name", 2)]);
334        let mut event_iter = event.iter();
335        assert_eq!(
336            event_iter.next(),
337            Some((&"name".to_string(), &EventValue::from(2)))
338        );
339        assert_eq!(event_iter.next(), None);
340    }
341}