blob: 7c9157a6b49c3545140ba21e34c51cfc0309c9a2 [file] [log] [blame]
//! 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<Box<dyn Rule>>,
}
impl RuleGroup {
pub fn new() -> Self {
RuleGroup { rules: vec![] }
}
pub fn add_rule(&mut self, rule: Box<dyn Rule>) {
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<String, RuleGroup>,
}
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);
}
}
}