use super::row::{NPrintRow, bits_per_row, encode_from_layers};
use crate::PacketView;
pub const DEFAULT_MAX_PACKETS: usize = 100;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct NPrintConfig {
pub include_ethernet: bool,
pub include_ipv4: bool,
pub include_tcp: bool,
pub include_udp: bool,
pub max_packets: usize,
}
impl Default for NPrintConfig {
fn default() -> Self {
Self {
include_ethernet: true,
include_ipv4: true,
include_tcp: true,
include_udp: true,
max_packets: DEFAULT_MAX_PACKETS,
}
}
}
impl NPrintConfig {
pub fn row_width(&self) -> usize {
bits_per_row(
self.include_ethernet,
self.include_ipv4,
self.include_tcp,
self.include_udp,
)
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct NPrintMatrix {
config: NPrintConfig,
rows: Vec<NPrintRow>,
}
impl NPrintMatrix {
pub fn new(config: NPrintConfig) -> Self {
Self {
config,
rows: Vec::new(),
}
}
pub fn with_default_config() -> Self {
Self::new(NPrintConfig::default())
}
pub fn config(&self) -> &NPrintConfig {
&self.config
}
pub fn rows(&self) -> &[NPrintRow] {
&self.rows
}
pub fn len(&self) -> usize {
self.rows.len()
}
pub fn is_empty(&self) -> bool {
self.rows.is_empty()
}
pub fn is_full(&self) -> bool {
self.rows.len() >= self.config.max_packets
}
pub fn push_absent(&mut self) -> bool {
if self.is_full() {
return false;
}
self.rows.push(NPrintRow::absent(self.config.row_width()));
true
}
pub fn push_view(&mut self, view: &PacketView<'_>) -> bool {
if self.is_full() {
return false;
}
let Ok(layers) = view.layers() else {
return false;
};
let row = encode_from_layers(
&layers,
self.config.include_ethernet,
self.config.include_ipv4,
self.config.include_tcp,
self.config.include_udp,
);
self.rows.push(row);
true
}
pub fn clear(&mut self) {
self.rows.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Timestamp;
fn make_frame_tcp(src_port: u16, dst_port: u16) -> Vec<u8> {
let mut f = Vec::new();
f.extend_from_slice(&[0xAA; 6]);
f.extend_from_slice(&[0xBB; 6]);
f.extend_from_slice(&[0x08, 0x00]);
let ipv4 = [
0x45, 0x00, 0x00, 0x28, 0x00, 0x01, 0x40, 0x00, 0x40, 0x06, 0x00, 0x00, 10, 0, 0, 1, 10, 0, 0, 2, ];
f.extend_from_slice(&ipv4);
let mut tcp = vec![];
tcp.extend_from_slice(&src_port.to_be_bytes());
tcp.extend_from_slice(&dst_port.to_be_bytes());
tcp.extend_from_slice(&[0, 0, 0, 0]); tcp.extend_from_slice(&[0, 0, 0, 0]); tcp.extend_from_slice(&[0x50, 0x02, 0x71, 0x10]); tcp.extend_from_slice(&[0, 0, 0, 0]); f.extend_from_slice(&tcp);
f
}
#[test]
fn default_config_includes_all_layers() {
let c = NPrintConfig::default();
assert_eq!(c.row_width(), 112 + 160 + 160 + 64);
assert_eq!(c.max_packets, 100);
}
#[test]
fn push_view_appends_row_for_tcp_packet() {
let frame = make_frame_tcp(443, 12345);
let view = PacketView::new(&frame, Timestamp::default());
let mut m = NPrintMatrix::with_default_config();
assert!(m.push_view(&view));
assert_eq!(m.len(), 1);
let row = &m.rows()[0];
assert_eq!(row.bits.len(), 112 + 160 + 160 + 64);
use crate::nprint::NPrintBit::{One, Zero};
assert_eq!(
&row.bits[0..8],
&[One, Zero, One, Zero, One, Zero, One, Zero]
);
let udp_off = 112 + 160 + 160;
assert!(
row.bits[udp_off..]
.iter()
.all(|b| *b == crate::nprint::NPrintBit::Absent)
);
}
#[test]
fn max_packets_caps_growth() {
let cfg = NPrintConfig {
max_packets: 3,
..NPrintConfig::default()
};
let mut m = NPrintMatrix::new(cfg);
let frame = make_frame_tcp(80, 1234);
let view = PacketView::new(&frame, Timestamp::default());
for _ in 0..5 {
m.push_view(&view);
}
assert_eq!(m.len(), 3);
assert!(m.is_full());
assert!(!m.push_view(&view));
}
#[test]
fn ipv4_only_config_has_shorter_row() {
let cfg = NPrintConfig {
include_ethernet: false,
include_ipv4: true,
include_tcp: false,
include_udp: false,
max_packets: 10,
};
let m = NPrintMatrix::new(cfg);
assert_eq!(m.config().row_width(), 160);
}
#[test]
fn push_absent_pads_with_absent_bits() {
let mut m = NPrintMatrix::with_default_config();
assert!(m.push_absent());
assert_eq!(m.len(), 1);
assert!(
m.rows()[0]
.bits
.iter()
.all(|b| *b == crate::nprint::NPrintBit::Absent)
);
}
#[test]
fn clear_resets_rows_only() {
let mut m = NPrintMatrix::with_default_config();
m.push_absent();
m.push_absent();
m.clear();
assert!(m.is_empty());
assert_eq!(m.config().max_packets, 100);
}
#[test]
fn push_view_fails_on_layer_parse_error() {
let view = PacketView::new(&[0u8, 0, 0, 0], Timestamp::default());
let mut m = NPrintMatrix::with_default_config();
assert!(!m.push_view(&view));
assert_eq!(m.len(), 0);
}
}