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