use std::io::{self, Write};
use serde::Serialize;
use crate::FlowEvent;
pub struct FlowEventNdjsonWriter<W: Write> {
sink: W,
options: NdjsonOptions,
}
#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
pub struct NdjsonOptions {
pub pretty: bool,
pub include_packets: bool,
pub include_started: bool,
pub include_anomalies: bool,
pub include_established: bool,
pub include_ticks: bool,
}
impl<W: Write> FlowEventNdjsonWriter<W> {
pub fn new(sink: W) -> Self {
Self::with_options(sink, NdjsonOptions::default_anomalies_on())
}
pub fn with_options(sink: W, options: NdjsonOptions) -> Self {
Self { sink, options }
}
pub fn write_lifecycle<T, K>(&mut self, ev: &T) -> io::Result<()>
where
T: crate::emit::LifecycleEvent<K>,
K: Serialize + Clone,
{
match ev.as_flow_event() {
Some(fe) => self.write_event(fe.as_ref()),
None => Ok(()),
}
}
pub fn write_event<K>(&mut self, ev: &FlowEvent<K>) -> io::Result<()>
where
K: Serialize,
{
if !self.should_emit(ev) {
return Ok(());
}
let result = if self.options.pretty {
serde_json::to_string_pretty(ev)
} else {
serde_json::to_string(ev)
};
let s = result.map_err(io::Error::other)?;
self.sink.write_all(s.as_bytes())?;
self.sink.write_all(b"\n")?;
Ok(())
}
pub fn write_owned_anomaly(&mut self, a: &crate::OwnedAnomaly) -> io::Result<()> {
let result = if self.options.pretty {
serde_json::to_string_pretty(a)
} else {
serde_json::to_string(a)
};
let s = result.map_err(io::Error::other)?;
self.sink.write_all(s.as_bytes())?;
self.sink.write_all(b"\n")?;
Ok(())
}
fn should_emit<K>(&self, ev: &FlowEvent<K>) -> bool {
match ev {
FlowEvent::Ended { .. } => true,
FlowEvent::Started { .. } => self.options.include_started,
FlowEvent::Packet { .. } => self.options.include_packets,
FlowEvent::Established { .. } => self.options.include_established,
FlowEvent::Tick { .. } => self.options.include_ticks,
FlowEvent::FlowAnomaly { .. } | FlowEvent::TrackerAnomaly { .. } => {
self.options.include_anomalies
}
FlowEvent::StateChange { .. } => false,
}
}
#[cfg(feature = "ipfix")]
pub fn write_flow_record(&mut self, rec: &crate::FlowRecord) -> io::Result<()> {
let result = if self.options.pretty {
serde_json::to_string_pretty(rec)
} else {
serde_json::to_string(rec)
};
let s = result.map_err(io::Error::other)?;
self.sink.write_all(s.as_bytes())?;
self.sink.write_all(b"\n")?;
Ok(())
}
pub fn flush(&mut self) -> io::Result<()> {
self.sink.flush()
}
pub fn finish(mut self) -> io::Result<W> {
self.flush()?;
Ok(self.sink)
}
}
impl NdjsonOptions {
fn default_anomalies_on() -> Self {
Self {
include_anomalies: true,
..Self::default()
}
}
}