santh-sigma 0.1.1

Sigma rule compiler for security research
Documentation
use secir::{Error, Result};
use super::ConditionExpr;

pub(crate) struct ConditionParser {
    pub(crate) tokens: Vec<String>,
    pub(crate) index: usize,
    pub(crate) depth: usize,
}

const MAX_CONDITION_DEPTH: usize = 64;

pub(crate) fn tokenize_condition(input: &str) -> Vec<String> {
    let mut tokens = Vec::new();
    let mut current = String::new();

    let flush = |current: &mut String, tokens: &mut Vec<String>| {
        let trimmed = current.trim().to_string();
        if !trimmed.is_empty() {
            tokens.push(trimmed);
        }
        current.clear();
    };

    let chars: Vec<char> = input.chars().collect();
    let mut i = 0;
    while i < chars.len() {
        let ch = chars[i];
        match ch {
            '(' | ')' | '|' => {
                flush(&mut current, &mut tokens);
                tokens.push(ch.to_string());
            }
            '>' | '<' | '=' => {
                flush(&mut current, &mut tokens);
                if i + 1 < chars.len() && chars[i + 1] == '=' {
                    tokens.push(format!("{}{}", ch, chars[i + 1]));
                    i += 1;
                } else {
                    tokens.push(ch.to_string());
                }
            }
            c if c.is_whitespace() => {
                flush(&mut current, &mut tokens);
            }
            _ => current.push(ch),
        }
        i += 1;
    }

    flush(&mut current, &mut tokens);
    tokens
}

impl ConditionParser {
    pub(crate) fn parse_expr(&mut self) -> Result<ConditionExpr> {
        if self.depth >= MAX_CONDITION_DEPTH {
            return Err(Error::TemplateValidation {
                id: "sigma-condition".to_string(),
                reason: format!(
                    "condition nesting exceeds limit of {MAX_CONDITION_DEPTH}. \
                     Fix: simplify the condition expression."
                ),
            });
        }
        self.depth += 1;
        let result = self.parse_expr_inner();
        self.depth -= 1;
        result
    }

    fn parse_expr_inner(&mut self) -> Result<ConditionExpr> {
        let mut expr = self.parse_and()?;
        while self.peek_keyword("|") {
            self.index += 1;
            if self.peek_keyword("near") {
                self.index += 1;
                let right = self.parse_and()?;
                expr = ConditionExpr::Near(Box::new(expr), Box::new(right));
            } else if self.peek_keyword("count") {
                return Err(Error::TemplateValidation {
                    id: "sigma-condition".to_string(),
                    reason: "Aggregation functions like count() are not supported yet. Fix: remove aggregation from the rule.".to_string(),
                });
            } else {
                break;
            }
        }
        Ok(expr)
    }

    fn parse_and(&mut self) -> Result<ConditionExpr> {
        let mut expr = self.parse_or()?;
        while self.peek_keyword("and") {
            self.index += 1;
            let right = self.parse_or()?;
            expr = ConditionExpr::And(Box::new(expr), Box::new(right));
        }
        Ok(expr)
    }

    fn parse_or(&mut self) -> Result<ConditionExpr> {
        let mut expr = self.parse_not()?;
        while self.peek_keyword("or") {
            self.index += 1;
            let right = self.parse_not()?;
            expr = ConditionExpr::Or(Box::new(expr), Box::new(right));
        }
        Ok(expr)
    }

    fn parse_not(&mut self) -> Result<ConditionExpr> {
        if self.peek_keyword("not") {
            self.index += 1;
            let inner = self.parse_primary()?;
            return Ok(ConditionExpr::Not(Box::new(inner)));
        }
        self.parse_primary()
    }

    fn parse_primary(&mut self) -> Result<ConditionExpr> {
        if let Some(of_expr) = self.parse_of_expression()? {
            return Ok(of_expr);
        }

        let token = self
            .tokens
            .get(self.index)
            .ok_or_else(|| Error::TemplateValidation {
                id: "sigma-condition".to_string(),
                reason: "unexpected end of condition".to_string(),
            })?;

        if token == "(" {
            self.index += 1;
            let expr = self.parse_expr()?;
            let closing = self
                .tokens
                .get(self.index)
                .ok_or_else(|| Error::TemplateValidation {
                    id: "sigma-condition".to_string(),
                    reason: "missing ')' in condition".to_string(),
                })?;
            if closing != ")" {
                return Err(Error::TemplateValidation {
                    id: "sigma-condition".to_string(),
                    reason: "missing ')' in condition".to_string(),
                });
            }
            self.index += 1;
            return Ok(expr);
        }

        if matches!(
            token.to_ascii_lowercase().as_str(),
            "and" | "or" | "not" | ")"
        ) {
            return Err(Error::TemplateValidation {
                id: "sigma-condition".to_string(),
                reason: format!("unexpected token '{}'", token),
            });
        }

        self.index += 1;
        Ok(ConditionExpr::Selection(token.clone()))
    }

    fn parse_of_expression(&mut self) -> Result<Option<ConditionExpr>> {
        if self.index >= self.tokens.len() {
            return Ok(None);
        }

        let quantifier = self.tokens[self.index].to_ascii_lowercase();
        let count = if quantifier == "all" {
            None
        } else if let Ok(value) = quantifier.parse::<usize>() {
            Some(value)
        } else {
            return Ok(None);
        };

        if !self
            .tokens
            .get(self.index + 1)
            .is_some_and(|token| token.eq_ignore_ascii_case("of"))
        {
            return Ok(None);
        }

        let Some(pattern) = self.tokens.get(self.index + 2) else {
            return Err(Error::TemplateValidation {
                id: "sigma-condition".to_string(),
                reason: "unexpected end of condition".to_string(),
            });
        };

        self.index += 3;
        Ok(Some(ConditionExpr::Of {
            count,
            pattern: pattern.clone(),
        }))
    }

    fn peek_keyword(&self, keyword: &str) -> bool {
        self.tokens
            .get(self.index)
            .map(|token| token.eq_ignore_ascii_case(keyword))
            .unwrap_or(false)
    }
}