1use std::collections::HashMap;
8use std::fmt;
9use std::str::FromStr;
10
11use serde::Serialize;
12
13use crate::value::{SigmaValue, Timespan};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
23#[serde(rename_all = "lowercase")]
24pub enum Status {
25 Stable,
26 Test,
27 Experimental,
28 Deprecated,
29 Unsupported,
30}
31
32impl FromStr for Status {
33 type Err = ();
34 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
35 match s {
36 "stable" => Ok(Status::Stable),
37 "test" => Ok(Status::Test),
38 "experimental" => Ok(Status::Experimental),
39 "deprecated" => Ok(Status::Deprecated),
40 "unsupported" => Ok(Status::Unsupported),
41 _ => Err(()),
42 }
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
48#[serde(rename_all = "lowercase")]
49pub enum Level {
50 Informational,
51 Low,
52 Medium,
53 High,
54 Critical,
55}
56
57impl FromStr for Level {
58 type Err = ();
59 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
60 match s {
61 "informational" => Ok(Level::Informational),
62 "low" => Ok(Level::Low),
63 "medium" => Ok(Level::Medium),
64 "high" => Ok(Level::High),
65 "critical" => Ok(Level::Critical),
66 _ => Err(()),
67 }
68 }
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
73#[serde(rename_all = "lowercase")]
74pub enum RelationType {
75 Derived,
76 Obsolete,
77 Merged,
78 Renamed,
79 Similar,
80}
81
82impl FromStr for RelationType {
83 type Err = ();
84 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
85 match s {
86 "derived" => Ok(RelationType::Derived),
87 "obsolete" => Ok(RelationType::Obsolete),
88 "merged" => Ok(RelationType::Merged),
89 "renamed" => Ok(RelationType::Renamed),
90 "similar" => Ok(RelationType::Similar),
91 _ => Err(()),
92 }
93 }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
104#[serde(rename_all = "lowercase")]
105pub enum Modifier {
106 Contains,
108 StartsWith,
109 EndsWith,
110
111 All,
113
114 Base64,
116 Base64Offset,
117 Wide,
118 Utf16be,
119 Utf16,
120 WindAsh,
121
122 Re,
124 Cidr,
125
126 Cased,
128
129 Exists,
131
132 Expand,
134
135 FieldRef,
137
138 Gt,
140 Gte,
141 Lt,
142 Lte,
143 Neq,
145
146 #[serde(rename = "i")]
148 IgnoreCase,
149 #[serde(rename = "m")]
150 Multiline,
151 #[serde(rename = "s")]
152 DotAll,
153
154 Minute,
156 Hour,
157 Day,
158 Week,
159 Month,
160 Year,
161}
162
163impl FromStr for Modifier {
167 type Err = ();
168 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
169 match s {
170 "contains" => Ok(Modifier::Contains),
171 "startswith" => Ok(Modifier::StartsWith),
172 "endswith" => Ok(Modifier::EndsWith),
173 "all" => Ok(Modifier::All),
174 "base64" => Ok(Modifier::Base64),
175 "base64offset" => Ok(Modifier::Base64Offset),
176 "wide" | "utf16le" => Ok(Modifier::Wide),
177 "utf16be" => Ok(Modifier::Utf16be),
178 "utf16" => Ok(Modifier::Utf16),
179 "windash" => Ok(Modifier::WindAsh),
180 "re" => Ok(Modifier::Re),
181 "cidr" => Ok(Modifier::Cidr),
182 "cased" => Ok(Modifier::Cased),
183 "exists" => Ok(Modifier::Exists),
184 "expand" => Ok(Modifier::Expand),
185 "fieldref" => Ok(Modifier::FieldRef),
186 "gt" => Ok(Modifier::Gt),
187 "gte" => Ok(Modifier::Gte),
188 "lt" => Ok(Modifier::Lt),
189 "lte" => Ok(Modifier::Lte),
190 "neq" => Ok(Modifier::Neq),
191 "i" | "ignorecase" => Ok(Modifier::IgnoreCase),
192 "m" | "multiline" => Ok(Modifier::Multiline),
193 "s" | "dotall" => Ok(Modifier::DotAll),
194 "minute" => Ok(Modifier::Minute),
195 "hour" => Ok(Modifier::Hour),
196 "day" => Ok(Modifier::Day),
197 "week" => Ok(Modifier::Week),
198 "month" => Ok(Modifier::Month),
199 "year" => Ok(Modifier::Year),
200 _ => Err(()),
201 }
202 }
203}
204
205#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
214pub struct FieldSpec {
215 pub name: Option<String>,
217 pub modifiers: Vec<Modifier>,
219}
220
221impl FieldSpec {
222 pub fn new(name: Option<String>, modifiers: Vec<Modifier>) -> Self {
223 FieldSpec { name, modifiers }
224 }
225
226 pub fn has_modifier(&self, m: Modifier) -> bool {
227 self.modifiers.contains(&m)
228 }
229
230 pub fn is_keyword(&self) -> bool {
231 self.name.is_none()
232 }
233}
234
235#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
246pub enum ConditionExpr {
247 And(Vec<ConditionExpr>),
249 Or(Vec<ConditionExpr>),
251 Not(Box<ConditionExpr>),
253 Identifier(String),
255 Selector {
257 quantifier: Quantifier,
258 pattern: SelectorPattern,
259 },
260}
261
262impl fmt::Display for ConditionExpr {
263 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264 match self {
265 ConditionExpr::And(args) => {
266 let parts: Vec<String> = args.iter().map(|a| format!("{a}")).collect();
267 write!(f, "({})", parts.join(" and "))
268 }
269 ConditionExpr::Or(args) => {
270 let parts: Vec<String> = args.iter().map(|a| format!("{a}")).collect();
271 write!(f, "({})", parts.join(" or "))
272 }
273 ConditionExpr::Not(arg) => write!(f, "not {arg}"),
274 ConditionExpr::Identifier(id) => write!(f, "{id}"),
275 ConditionExpr::Selector {
276 quantifier,
277 pattern,
278 } => write!(f, "{quantifier} of {pattern}"),
279 }
280 }
281}
282
283#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
285pub enum Quantifier {
286 Any,
288 All,
290 Count(u64),
292}
293
294impl fmt::Display for Quantifier {
295 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296 match self {
297 Quantifier::Any => write!(f, "1"),
298 Quantifier::All => write!(f, "all"),
299 Quantifier::Count(n) => write!(f, "{n}"),
300 }
301 }
302}
303
304#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
306pub enum SelectorPattern {
307 Them,
309 Pattern(String),
311}
312
313impl fmt::Display for SelectorPattern {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 match self {
316 SelectorPattern::Them => write!(f, "them"),
317 SelectorPattern::Pattern(p) => write!(f, "{p}"),
318 }
319 }
320}
321
322#[derive(Debug, Clone, PartialEq, Serialize)]
335pub struct DetectionItem {
336 pub field: FieldSpec,
338 pub values: Vec<SigmaValue>,
340}
341
342#[derive(Debug, Clone, PartialEq, Serialize)]
349pub enum Detection {
350 AllOf(Vec<DetectionItem>),
352 AnyOf(Vec<Detection>),
354 Keywords(Vec<SigmaValue>),
356}
357
358#[derive(Debug, Clone, PartialEq, Serialize)]
364pub struct Detections {
365 pub named: HashMap<String, Detection>,
367 pub conditions: Vec<ConditionExpr>,
369 pub condition_strings: Vec<String>,
371 pub timeframe: Option<String>,
373}
374
375#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
383pub struct LogSource {
384 pub category: Option<String>,
385 pub product: Option<String>,
386 pub service: Option<String>,
387 pub definition: Option<String>,
388 #[serde(flatten)]
390 pub custom: HashMap<String, String>,
391}
392
393#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
399pub struct Related {
400 pub id: String,
401 pub relation_type: RelationType,
402}
403
404#[derive(Debug, Clone, PartialEq, Serialize)]
412pub struct SigmaRule {
413 pub title: String,
415 pub logsource: LogSource,
416 pub detection: Detections,
417
418 pub id: Option<String>,
420 pub name: Option<String>,
421 pub related: Vec<Related>,
422 pub taxonomy: Option<String>,
423 pub status: Option<Status>,
424 pub description: Option<String>,
425 pub license: Option<String>,
426 pub author: Option<String>,
427 pub references: Vec<String>,
428 pub date: Option<String>,
429 pub modified: Option<String>,
430 pub fields: Vec<String>,
431 pub falsepositives: Vec<String>,
432 pub level: Option<Level>,
433 pub tags: Vec<String>,
434 pub scope: Vec<String>,
435
436 #[serde(skip_serializing_if = "HashMap::is_empty")]
441 pub custom_attributes: HashMap<String, String>,
442}
443
444#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
452#[serde(rename_all = "snake_case")]
453pub enum CorrelationType {
454 EventCount,
455 ValueCount,
456 Temporal,
457 TemporalOrdered,
458 ValueSum,
459 ValueAvg,
460 ValuePercentile,
461 ValueMedian,
462}
463
464impl FromStr for CorrelationType {
465 type Err = ();
466 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
467 match s {
468 "event_count" => Ok(CorrelationType::EventCount),
469 "value_count" => Ok(CorrelationType::ValueCount),
470 "temporal" => Ok(CorrelationType::Temporal),
471 "temporal_ordered" => Ok(CorrelationType::TemporalOrdered),
472 "value_sum" => Ok(CorrelationType::ValueSum),
473 "value_avg" => Ok(CorrelationType::ValueAvg),
474 "value_percentile" => Ok(CorrelationType::ValuePercentile),
475 "value_median" => Ok(CorrelationType::ValueMedian),
476 _ => Err(()),
477 }
478 }
479}
480
481#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
485pub enum ConditionOperator {
486 Lt,
487 Lte,
488 Gt,
489 Gte,
490 Eq,
491 Neq,
492}
493
494impl FromStr for ConditionOperator {
495 type Err = ();
496 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
497 match s {
498 "lt" => Ok(ConditionOperator::Lt),
499 "lte" => Ok(ConditionOperator::Lte),
500 "gt" => Ok(ConditionOperator::Gt),
501 "gte" => Ok(ConditionOperator::Gte),
502 "eq" => Ok(ConditionOperator::Eq),
503 "neq" => Ok(ConditionOperator::Neq),
504 _ => Err(()),
505 }
506 }
507}
508
509#[derive(Debug, Clone, PartialEq, Serialize)]
513pub enum CorrelationCondition {
514 Threshold {
519 predicates: Vec<(ConditionOperator, u64)>,
521 field: Option<String>,
523 },
524 Extended(ConditionExpr),
526}
527
528#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
534pub struct FieldAlias {
535 pub alias: String,
536 pub mapping: HashMap<String, String>,
538}
539
540#[derive(Debug, Clone, PartialEq, Serialize)]
544pub struct CorrelationRule {
545 pub title: String,
547 pub id: Option<String>,
548 pub name: Option<String>,
549 pub status: Option<Status>,
550 pub description: Option<String>,
551 pub author: Option<String>,
552 pub date: Option<String>,
553 pub modified: Option<String>,
554 pub references: Vec<String>,
555 pub tags: Vec<String>,
556 pub level: Option<Level>,
557
558 pub correlation_type: CorrelationType,
560 pub rules: Vec<String>,
561 pub group_by: Vec<String>,
562 pub timespan: Timespan,
563 pub condition: CorrelationCondition,
564 pub aliases: Vec<FieldAlias>,
565 pub generate: bool,
566 pub custom_attributes: HashMap<String, String>,
570}
571
572#[derive(Debug, Clone, PartialEq, Serialize)]
581pub struct FilterRule {
582 pub title: String,
583 pub id: Option<String>,
584 pub name: Option<String>,
585 pub status: Option<Status>,
586 pub description: Option<String>,
587 pub author: Option<String>,
588 pub date: Option<String>,
589 pub modified: Option<String>,
590 pub logsource: Option<LogSource>,
591
592 pub rules: Vec<String>,
594 pub detection: Detections,
596}
597
598#[derive(Debug, Clone, PartialEq, Serialize)]
607pub enum SigmaDocument {
608 Rule(Box<SigmaRule>),
609 Correlation(CorrelationRule),
610 Filter(FilterRule),
611}
612
613#[derive(Debug, Clone, Serialize)]
615pub struct SigmaCollection {
616 pub rules: Vec<SigmaRule>,
617 pub correlations: Vec<CorrelationRule>,
618 pub filters: Vec<FilterRule>,
619 #[serde(skip)]
621 pub errors: Vec<String>,
622}
623
624impl SigmaCollection {
625 pub fn new() -> Self {
626 SigmaCollection {
627 rules: Vec::new(),
628 correlations: Vec::new(),
629 filters: Vec::new(),
630 errors: Vec::new(),
631 }
632 }
633
634 pub fn len(&self) -> usize {
636 self.rules.len() + self.correlations.len() + self.filters.len()
637 }
638
639 pub fn is_empty(&self) -> bool {
640 self.len() == 0
641 }
642}
643
644impl Default for SigmaCollection {
645 fn default() -> Self {
646 Self::new()
647 }
648}