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