Skip to main content

rsigma_runtime/input/
mod.rs

1//! Input format adapters for the rsigma runtime.
2//!
3//! Each adapter parses a raw log line into a typed [`EventInputDecoded`] that
4//! implements [`rsigma_eval::Event`]. The [`InputFormat`] enum selects which
5//! adapter to use, and [`parse_line`] is the main dispatch function.
6//!
7//! Always-on formats: JSON/GELF, syslog (RFC 3164/5424), plain text, auto-detect.
8//! Feature-gated formats: logfmt (`logfmt`), CEF (`cef`).
9
10use std::borrow::Cow;
11
12use rsigma_eval::{Event, EventValue, JsonEvent, KvEvent, PlainEvent};
13use serde_json::Value;
14
15mod auto;
16#[cfg(feature = "cef")]
17mod cef;
18#[cfg(feature = "evtx")]
19pub mod evtx;
20mod json;
21#[cfg(feature = "logfmt")]
22mod logfmt;
23mod plain;
24mod syslog;
25
26#[cfg(feature = "cef")]
27pub use self::cef::parse_cef;
28pub use self::json::parse_json;
29#[cfg(feature = "logfmt")]
30pub use self::logfmt::parse_logfmt;
31pub use self::syslog::{SyslogConfig, parse_syslog};
32pub use auto::auto_detect;
33pub use plain::parse_plain;
34
35/// Selects which input format adapter to use for raw log lines.
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub enum InputFormat {
38    /// Try JSON → syslog → plain (default). Carries a [`SyslogConfig`] that
39    /// applies when auto-detection selects the syslog path.
40    Auto(SyslogConfig),
41    /// NDJSON / GELF.
42    Json,
43    /// Syslog RFC 3164 / 5424.
44    Syslog(SyslogConfig),
45    /// Raw text (keyword matching only).
46    Plain,
47    /// logfmt `key=value` pairs (requires `logfmt` feature).
48    #[cfg(feature = "logfmt")]
49    Logfmt,
50    /// ArcSight Common Event Format (requires `cef` feature).
51    #[cfg(feature = "cef")]
52    Cef,
53}
54
55impl Default for InputFormat {
56    fn default() -> Self {
57        InputFormat::Auto(SyslogConfig::default())
58    }
59}
60
61/// A decoded event ready for Sigma rule evaluation.
62///
63/// Static dispatch enum — avoids `Box<dyn Event>` on the hot path while
64/// supporting all input formats through a single type.
65#[derive(Debug)]
66pub enum EventInputDecoded {
67    Json(JsonEvent<'static>),
68    Kv(KvEvent),
69    Plain(PlainEvent),
70}
71
72impl Event for EventInputDecoded {
73    fn get_field(&self, path: &str) -> Option<EventValue<'_>> {
74        match self {
75            EventInputDecoded::Json(e) => e.get_field(path),
76            EventInputDecoded::Kv(e) => e.get_field(path),
77            EventInputDecoded::Plain(e) => e.get_field(path),
78        }
79    }
80
81    fn any_string_value(&self, pred: &dyn Fn(&str) -> bool) -> bool {
82        match self {
83            EventInputDecoded::Json(e) => e.any_string_value(pred),
84            EventInputDecoded::Kv(e) => e.any_string_value(pred),
85            EventInputDecoded::Plain(e) => e.any_string_value(pred),
86        }
87    }
88
89    fn all_string_values(&self) -> Vec<Cow<'_, str>> {
90        match self {
91            EventInputDecoded::Json(e) => e.all_string_values(),
92            EventInputDecoded::Kv(e) => e.all_string_values(),
93            EventInputDecoded::Plain(e) => e.all_string_values(),
94        }
95    }
96
97    fn to_json(&self) -> Value {
98        match self {
99            EventInputDecoded::Json(e) => e.to_json(),
100            EventInputDecoded::Kv(e) => e.to_json(),
101            EventInputDecoded::Plain(e) => e.to_json(),
102        }
103    }
104}
105
106/// Parse a raw log line using the specified format.
107///
108/// Returns `None` if:
109/// - the line is empty or whitespace-only,
110/// - `InputFormat::Json` and the line is not valid JSON, or
111/// - `InputFormat::Cef` and the line is not valid CEF.
112///
113/// For `InputFormat::Auto`, invalid JSON falls through to syslog or plain
114/// text (never returns `None` for non-empty lines).
115pub fn parse_line(line: &str, format: &InputFormat) -> Option<EventInputDecoded> {
116    if line.trim().is_empty() {
117        return None;
118    }
119    Some(match format {
120        InputFormat::Auto(syslog_config) => auto_detect(line, syslog_config),
121        InputFormat::Json => parse_json(line)?,
122        InputFormat::Syslog(config) => parse_syslog(line, config),
123        InputFormat::Plain => parse_plain(line),
124        #[cfg(feature = "logfmt")]
125        InputFormat::Logfmt => parse_logfmt(line),
126        #[cfg(feature = "cef")]
127        InputFormat::Cef => parse_cef(line)?,
128    })
129}