//! Handles stream processing of commands and events. use chrono::NaiveDateTime; use std::collections::BTreeMap; use std::io::Write; use crate::parser::Packet; /// Signals are pre-defined indicators that are seen in a packet stream. pub struct Signal { /// Where in the packet stream we see this signal. pub index: usize, /// Timestamp where this signal is seen. pub ts: NaiveDateTime, /// Tag identifying the signal. Signals must be pre-defined so we're going /// to enforce a static lifetime here. pub tag: &'static str, } /// Trait that describes a single rule processor. A rule should be used to represent a certain type /// of analysis (for example: ACL Connections rule may keep track of all ACL connections and report /// on failed connections). pub trait Rule { /// Process a single packet. fn process(&mut self, packet: &Packet); /// Generate a report for this rule based on the input stream so far. Usually, this should /// report on the instances of this rule that were discovered or any error conditions that are /// relevant to this rule. fn report(&self, writer: &mut dyn Write); /// Report on any signals seen by this rule on the input stream so far. Signals are /// structured indicators that specify a specific type of condition that are pre-defined and /// used to bucket interesting behavior. Not all reportable events are signals but all signals /// are reportable events. fn report_signals(&self) -> &[Signal]; } /// Grouping of rules. This is used to make it easier to enable/disable certain rules for /// processing a file. pub struct RuleGroup { rules: Vec>, } impl RuleGroup { pub fn new() -> Self { RuleGroup { rules: vec![] } } pub fn add_rule(&mut self, rule: Box) { self.rules.push(rule); } pub fn process(&mut self, packet: &Packet) { for rule in &mut self.rules { rule.process(packet); } } pub fn report(&self, writer: &mut dyn Write) { for rule in &self.rules { rule.report(writer); } } pub fn report_signals(&self, writer: &mut dyn Write) { for rule in &self.rules { for signal in rule.report_signals() { let _ = writeln!(writer, "({}, {}, {})", signal.index, signal.ts, signal.tag); } } } } /// Main entry point to process input data and run rules on them. pub struct RuleEngine { groups: BTreeMap, } impl RuleEngine { pub fn new() -> Self { RuleEngine { groups: BTreeMap::new() } } pub fn add_rule_group(&mut self, name: String, group: RuleGroup) { self.groups.insert(name, group); } /// Consume a packet and run it through the various rules processors. pub fn process(&mut self, packet: Packet) { for group in self.groups.values_mut() { group.process(&packet); } } pub fn report(&self, writer: &mut dyn Write) { for group in self.groups.values() { group.report(writer); } } pub fn report_signals(&self, writer: &mut dyn Write) { for group in self.groups.values() { group.report_signals(writer); } } }