use crossbeam_channel::TryRecvError;
use crossbeam_channel::{Receiver, Sender};
use std::borrow::Cow;
use usiem::components::common::{
CommandDefinition, SiemComponentCapabilities, SiemComponentStateStorage, SiemFunctionCall,
SiemFunctionResponse, SiemFunctionType, SiemMessage, UserRole,
};
use usiem::components::common::{LogParser, LogParsingError};
use usiem::components::SiemComponent;
use usiem::events::SiemLog;
pub struct BasicParserComponent {
kernel_sender: Sender<SiemMessage>,
local_chnl_rcv: Receiver<SiemMessage>,
local_chnl_snd: Sender<SiemMessage>,
log_receiver: Receiver<SiemLog>,
log_sender: Sender<SiemLog>,
conn: Option<Box<dyn SiemComponentStateStorage>>,
parsers: Vec<Box<dyn LogParser>>,
cache: Vec<SiemLog>,
kernel_errors: usize,
send_errors: usize,
}
impl BasicParserComponent {
pub fn new() -> BasicParserComponent {
let (kernel_sender, _receiver) = crossbeam_channel::bounded(1000);
let (local_chnl_snd, local_chnl_rcv) = crossbeam_channel::unbounded();
let (log_sender, log_receiver) = crossbeam_channel::unbounded();
return BasicParserComponent {
kernel_sender,
local_chnl_rcv,
local_chnl_snd,
log_receiver,
log_sender,
parsers: Vec::new(),
conn: None,
cache: Vec::with_capacity(128),
kernel_errors: 0,
send_errors: 0,
};
}
pub fn add_parser(&mut self, parser: Box<dyn LogParser>) {
self.parsers.push(parser);
}
fn list_parsers(&self) -> SiemMessage {
let mut content = String::with_capacity(4096);
content.push_str("[");
if self.parsers.len() > 0{
content.push_str("\"");
}
let content2 = self.parsers.iter().map(|x| x.name()).collect::<Vec<&str>>().join("\",\"");
content.push_str(&content2);
if self.parsers.len() > 0{
content.push_str("\"");
}
content.push_str("]");
SiemMessage::Response(SiemFunctionResponse::OTHER(
Cow::Borrowed("LIST_PARSERS"),
Ok(Cow::Owned(content)),
))
}
fn parse_log(&mut self, log: SiemLog) {
let mut selected_parser = None;
for parser in &self.parsers {
if parser.device_match(&log) {
selected_parser = Some(parser);
break;
}
}
let res = match selected_parser {
Some(parser) => match parser.parse_log(log) {
Ok(lg) => self.log_sender.send(lg),
Err(e) => match e {
LogParsingError::NoValidParser(lg) => self.log_sender.send(lg),
LogParsingError::ParserError(lg) => {
let r = self
.kernel_sender
.send(SiemMessage::Notification(Cow::Borrowed(
"Error parsing log...",
)));
match r {
Err(_e) => {
self.kernel_errors += 1;
}
_ => {}
}
self.log_sender.send(lg)
}
},
},
None => self.log_sender.send(log),
};
match res {
Err(e) => {
let log = e.into_inner();
self.cache.push(log);
self.send_errors += 1;
}
_ => {}
}
}
}
impl SiemComponent for BasicParserComponent {
fn name(&self) -> Cow<'static, str> {
Cow::Borrowed("BasicParser")
}
fn local_channel(&self) -> Sender<SiemMessage> {
self.local_chnl_snd.clone()
}
fn set_log_channel(&mut self, log_sender: Sender<SiemLog>, receiver: Receiver<SiemLog>) {
self.log_receiver = receiver;
self.log_sender = log_sender;
}
fn set_kernel_sender(&mut self, sender: Sender<SiemMessage>) {
self.kernel_sender = sender;
}
fn run(&mut self) {
loop {
let rcv_action = (&self.local_chnl_rcv).try_recv();
match rcv_action {
Ok(msg) => match msg {
SiemMessage::Command(cmd) => match cmd {
SiemFunctionCall::STOP_COMPONENT(_n) => return,
SiemFunctionCall::OTHER(name, _params) => {
if name == "LIST_PARSERS" {
let _r = self.kernel_sender.send(self.list_parsers());
}
}
_ => {}
},
SiemMessage::Log(msg) => {
self.parse_log(msg);
}
_ => {}
},
Err(e) => match e {
TryRecvError::Empty => {
std::thread::sleep(std::time::Duration::from_millis(10));
}
TryRecvError::Disconnected => return,
},
}
let rcv_log = (&self.log_receiver).try_recv();
match rcv_log {
Ok(log) => {
self.parse_log(log);
}
Err(e) => match e {
TryRecvError::Empty => {
continue;
}
TryRecvError::Disconnected => return,
},
}
}
}
fn set_storage(&mut self, conn: Box<dyn SiemComponentStateStorage>) {
self.conn = Some(conn);
}
fn capabilities(&self) -> SiemComponentCapabilities {
let datasets = Vec::new();
let mut commands = Vec::new();
let stop_component = CommandDefinition::new(SiemFunctionType::STOP_COMPONENT,Cow::Borrowed("Stop BasicParser") ,Cow::Borrowed("This allows stopping all Basic Parser components.\nUse only when really needed, like when there is a bug in the parsing process.") , UserRole::Administrator);
commands.push(stop_component);
let start_component = CommandDefinition::new(
SiemFunctionType::START_COMPONENT,
Cow::Borrowed("Start Basic Parser"),
Cow::Borrowed("This allows processing logs."),
UserRole::Administrator,
);
commands.push(start_component);
let list_parsers = CommandDefinition::new(
SiemFunctionType::OTHER(
Cow::Borrowed("LIST_PARSERS"),
std::collections::BTreeMap::new(),
),
Cow::Borrowed("List log parsers"),
Cow::Borrowed("List all parsers in this component."),
UserRole::Administrator,
);
commands.push(list_parsers);
SiemComponentCapabilities::new(
Cow::Borrowed("BasicParser"),
Cow::Borrowed("Parse logs using multiple diferent parsers"),
Cow::Borrowed(""),
datasets,
commands,
)
}
}
#[cfg(test)]
mod parser_test {
use super::BasicParserComponent;
use std::borrow::Cow;
use usiem::components::common::{LogParser, LogParsingError, SiemFunctionCall, SiemMessage, SiemFunctionResponse};
use usiem::components::SiemComponent;
use usiem::events::field::{SiemField, SiemIp};
use usiem::events::SiemLog;
struct DummyParserTextDUMMY {}
impl LogParser for DummyParserTextDUMMY {
fn parse_log(&self, mut log: SiemLog) -> Result<SiemLog, LogParsingError> {
log.add_field("parser", SiemField::from_str("DummyParserTextDUMMY"));
Ok(log)
}
fn device_match(&self, log: &SiemLog) -> bool {
log.message().contains("DUMMY")
}
fn name(&self) -> &str {
"This is a dummy that parsers if contains DUMMY in text"
}
}
struct DummyParserALL {}
impl LogParser for DummyParserALL {
fn parse_log(&self, mut log: SiemLog) -> Result<SiemLog, LogParsingError> {
log.add_field("parser", SiemField::from_str("DummyParserALL"));
Ok(log)
}
fn device_match(&self, _log: &SiemLog) -> bool {
true
}
fn name(&self) -> &str {
"This is a dummy parser that always parses logs"
}
}
#[test]
fn test_parser() {
let (log_to_component, log_receiver) = crossbeam_channel::unbounded();
let (log_sender, next_log_receiver) = crossbeam_channel::unbounded();
let parser1 = DummyParserTextDUMMY {};
let parser2 = DummyParserALL {};
let mut parser = BasicParserComponent::new();
let component_channel = parser.local_channel();
parser.add_parser(Box::from(parser1));
parser.add_parser(Box::from(parser2));
parser.set_log_channel(log_sender, log_receiver);
let log1 = SiemLog::new(
String::from("This is a DUMMY log for DummyParserTextDUMMY"),
0,
SiemIp::V4(0),
);
let log2 = SiemLog::new(
String::from("This is a text log for DummyParserALL"),
0,
SiemIp::V4(1),
);
let _r = log_to_component.send(log1);
let _r = log_to_component.send(log2);
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(200));
let _r = component_channel.send(SiemMessage::Command(
SiemFunctionCall::STOP_COMPONENT(Cow::Borrowed("BasicParserComponent")),
));
});
parser.run();
let log1 = next_log_receiver.recv();
match log1 {
Ok(log) => {
assert_eq!(
log.field("parser"),
Some(&SiemField::from_str("DummyParserTextDUMMY"))
);
}
_ => {
panic!("Must be received")
}
}
let log2 = next_log_receiver.recv();
match log2 {
Ok(log) => {
assert_eq!(
log.field("parser"),
Some(&SiemField::from_str("DummyParserALL"))
);
}
_ => {
panic!("Must be received")
}
}
}
#[test]
fn test_list_parsers() {
let (kernel_sender, kernel_receiver) = crossbeam_channel::unbounded();
let parser1 = DummyParserTextDUMMY {};
let parser2 = DummyParserALL {};
let mut parser = BasicParserComponent::new();
let component_channel = parser.local_channel();
parser.add_parser(Box::from(parser1));
parser.add_parser(Box::from(parser2));
parser.set_kernel_sender(kernel_sender);
std::thread::spawn(move || {
let _r = component_channel.send(SiemMessage::Command(
SiemFunctionCall::OTHER(Cow::Borrowed("LIST_PARSERS"),serde_json::json!("{}")),
));
std::thread::sleep(std::time::Duration::from_millis(200));
let _r = component_channel.send(SiemMessage::Command(
SiemFunctionCall::STOP_COMPONENT(Cow::Borrowed("BasicParserComponent")),
));
});
parser.run();
let response = kernel_receiver.recv();
match response {
Ok(response) => {
match response {
SiemMessage::Response(response) => {
match response {
SiemFunctionResponse::OTHER(name, params) => {
if name != "LIST_PARSERS" {
panic!("Must be LIST_PARSERS response")
}
match params{
Ok(response) => {
assert_eq!(response.contains("This is a dummy that parsers if contains DUMMY in text"), true);
assert_eq!(response.contains("This is a dummy parser that always parses logs"), true);
},
_ => {panic!("Must not be error")}
}
},
_ => {panic!("Must be OTHER")}
}
},
_ => {panic!("Must be response")}
}
}
_ => {
panic!("Must be received")
}
}
}
}