sigma-rust 0.2.0

a library for parsing and checking Sigma rules against log events
Documentation
use crate::field::ParserError;
use cidr::IpCidr;
use regex::Regex;
use std::cmp::Ordering;
use std::net::IpAddr;
use std::str::FromStr;

#[derive(Debug)]
pub enum FieldValue {
    String(String),
    Int(i64),
    Float(f64),
    Unsigned(u64),
    Boolean(bool),
    Null,
    Regex(Regex),
    Cidr(IpCidr),
}

impl From<i32> for FieldValue {
    fn from(i: i32) -> Self {
        Self::Int(i as i64)
    }
}

impl From<Option<i32>> for FieldValue {
    fn from(option: Option<i32>) -> Self {
        match option {
            Some(i) => Self::from(i),
            None => Self::Null,
        }
    }
}

impl From<i64> for FieldValue {
    fn from(i: i64) -> Self {
        Self::Int(i)
    }
}

impl From<u32> for FieldValue {
    fn from(u: u32) -> Self {
        Self::Unsigned(u as u64)
    }
}

impl From<u64> for FieldValue {
    fn from(u: u64) -> Self {
        Self::Unsigned(u)
    }
}

impl From<f32> for FieldValue {
    fn from(f: f32) -> Self {
        Self::Float(f as f64)
    }
}

impl From<f64> for FieldValue {
    fn from(f: f64) -> Self {
        Self::Float(f)
    }
}

impl From<bool> for FieldValue {
    fn from(b: bool) -> Self {
        Self::Boolean(b)
    }
}

impl From<String> for FieldValue {
    fn from(s: String) -> Self {
        Self::String(s)
    }
}

impl From<&str> for FieldValue {
    fn from(s: &str) -> Self {
        Self::String(s.to_string())
    }
}

#[cfg(feature = "serde_json")]
impl TryFrom<serde_json::Value> for FieldValue {
    type Error = crate::error::JSONError;

    fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
        match value {
            serde_json::Value::String(s) => Ok(FieldValue::from(s.to_string())),
            serde_json::Value::Number(n) => {
                if n.is_i64() {
                    Ok(Self::Int(n.as_i64().unwrap()))
                } else if n.is_f64() {
                    Ok(Self::Float(n.as_f64().unwrap()))
                } else {
                    Ok(Self::Unsigned(n.as_u64().unwrap()))
                }
            }
            serde_json::Value::Bool(b) => Ok(FieldValue::Boolean(b)),
            serde_json::Value::Null => Ok(FieldValue::Null),
            _ => Err(Self::Error::InvalidFieldValue(format!("{:?}", value))),
        }
    }
}

impl TryFrom<serde_yml::Value> for FieldValue {
    type Error = ParserError;

    fn try_from(value: serde_yml::Value) -> Result<Self, Self::Error> {
        match value {
            serde_yml::Value::Bool(b) => Ok(Self::Boolean(b)),
            serde_yml::Value::Number(n) => {
                if n.is_i64() {
                    Ok(Self::Int(n.as_i64().unwrap()))
                } else if n.is_f64() {
                    Ok(Self::Float(n.as_f64().unwrap()))
                } else {
                    Ok(Self::Unsigned(n.as_u64().unwrap()))
                }
            }
            serde_yml::Value::String(s) => Ok(Self::from(s.to_string())),
            serde_yml::Value::Null => Ok(Self::Null),
            _ => Err(ParserError::InvalidYAML(format!("{:?}", value))),
        }
    }
}

impl PartialEq for FieldValue {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::String(a), Self::String(b)) => a.eq(b),
            (Self::Int(a), Self::Int(b)) => a.eq(b),
            (Self::Unsigned(a), Self::Unsigned(b)) => a.eq(b),
            (Self::Float(a), Self::Float(b)) => a.eq(b),
            (Self::Boolean(a), Self::Boolean(b)) => a.eq(b),
            (Self::Regex(a), Self::Regex(b)) => a.as_str().eq(b.as_str()),
            (Self::Null, Self::Null) => true,
            _ => false,
        }
    }
}

impl PartialOrd for FieldValue {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        match (self, other) {
            (Self::String(a), Self::String(b)) => a.partial_cmp(b),
            (Self::Int(a), Self::Int(b)) => a.partial_cmp(b),
            (Self::Unsigned(a), Self::Unsigned(b)) => a.partial_cmp(b),
            (Self::Float(a), Self::Float(b)) => a.partial_cmp(b),
            (Self::Boolean(a), Self::Boolean(b)) => a.partial_cmp(b),
            (Self::Null, Self::Null) => Some(Ordering::Equal),
            _ => None,
        }
    }
}

impl FieldValue {
    pub(crate) fn value_to_string(&self) -> String {
        match self {
            Self::String(s) => s.to_string(),
            Self::Int(i) => i.to_string(),
            Self::Float(f) => f.to_string(),
            Self::Unsigned(u) => u.to_string(),
            Self::Boolean(b) => b.to_string(),
            Self::Regex(r) => r.to_string(),
            Self::Cidr(c) => c.to_string(),
            Self::Null => "null".to_string(),
        }
    }

    pub(crate) fn contains(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::String(a), Self::String(b)) => a.contains(b),
            _ => false,
        }
    }

    pub(crate) fn starts_with(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::String(a), Self::String(b)) => a.starts_with(b),
            _ => false,
        }
    }
    pub(crate) fn ends_with(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::String(a), Self::String(b)) => a.ends_with(b),
            _ => false,
        }
    }

    pub(crate) fn is_regex_match(&self, target: &str) -> bool {
        match self {
            Self::Regex(r) => r.is_match(target),
            _ => false,
        }
    }

    pub(crate) fn cidr_contains(&self, other: &Self) -> bool {
        let ip_addr = match IpAddr::from_str(other.value_to_string().as_str()) {
            Ok(ip) => ip,
            Err(_) => return false,
        };

        match self {
            Self::Cidr(cidr) => cidr.contains(&ip_addr),
            _ => false,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[allow(clippy::neg_cmp_op_on_partial_ord)]
    #[test]
    fn test_field_value_type() {
        assert_eq!(FieldValue::from("1"), FieldValue::from("1"));
        assert_eq!(FieldValue::from("2"), FieldValue::from("2"));
        assert_ne!(FieldValue::from("1"), FieldValue::from("3"));
        assert_ne!(FieldValue::from("2"), FieldValue::Int(2_i64));
        assert_ne!(FieldValue::Int(3), FieldValue::Float(3.0));

        assert!(FieldValue::Int(10) < FieldValue::Int(20));
        assert!(!(FieldValue::Int(20) < FieldValue::from("30")));
        assert!(!(FieldValue::Int(20) < FieldValue::Float(30.0)));
        assert!(!(FieldValue::Int(34) < FieldValue::Float(30.0)));
        assert!(FieldValue::Boolean(false) < FieldValue::Boolean(true));
        assert!(FieldValue::Int(10) >= FieldValue::Int(10));
        assert!(FieldValue::Int(10) > FieldValue::Int(4));
        assert!(FieldValue::Int(10) >= FieldValue::Int(4));
        assert!(
            FieldValue::from(18446744073709551615_u64) > FieldValue::from(18446744073709551614_u64)
        );
        assert_eq!(
            FieldValue::from(18446744073709551615_u64),
            FieldValue::from(18446744073709551615_u64)
        );

        let yaml = r#"
        EventID: 18446744073709551615
"#;
        let v: serde_yml::Value = serde_yml::from_str(yaml).unwrap();
        let field_value = FieldValue::try_from(v["EventID"].clone()).unwrap();
        assert_eq!(field_value, FieldValue::Unsigned(18446744073709551615));
    }
}