#![cfg(all(
feature = "test-helpers",
feature = "extractors",
feature = "session",
feature = "reassembler",
))]
use std::time::Duration;
use flowscope::driver::{DeferredDriverBuilder, Driver, Event};
use flowscope::extract::FiveTuple;
use flowscope::extract::parse::test_frames::{ipv4_tcp, ipv4_udp};
use flowscope::{DatagramParser, FlowSide, PacketView, SessionParser, Timestamp};
#[derive(Default, Clone)]
struct CountParser;
impl SessionParser for CountParser {
type Message = usize;
fn feed_initiator(&mut self, b: &[u8], _ts: Timestamp, out: &mut Vec<Self::Message>) {
if !b.is_empty() {
out.push(b.len());
}
}
fn feed_responder(&mut self, b: &[u8], _ts: Timestamp, out: &mut Vec<Self::Message>) {
if !b.is_empty() {
out.push(b.len());
}
}
fn parser_kind(&self) -> &'static str {
"count"
}
}
#[derive(Default, Clone)]
struct UdpEcho;
impl DatagramParser for UdpEcho {
type Message = Vec<u8>;
fn parse(&mut self, b: &[u8], _: FlowSide, _: Timestamp, out: &mut Vec<Self::Message>) {
if !b.is_empty() {
out.push(b.to_vec());
}
}
fn parser_kind(&self) -> &'static str {
"udp-echo"
}
}
fn tcp_packet(seq: u32, payload: &[u8]) -> Vec<u8> {
ipv4_tcp(
[1; 6],
[2; 6],
[10, 0, 0, 1],
[10, 0, 0, 2],
33000,
80,
seq,
0,
0x18,
payload,
)
}
#[test]
fn deferred_builder_handle_drains_after_build_with() {
let mut builder = Driver::<FiveTuple>::deferred();
let mut slot = builder.session_on_ports(CountParser, [80]);
let mut driver = builder.build_with(FiveTuple::bidirectional());
let mut events = Vec::new();
let mut seq: u32 = 1000;
for i in 0..5u32 {
let frame = tcp_packet(seq, b"hello");
driver.track_into(PacketView::new(&frame, Timestamp::new(i, 0)), &mut events);
seq = seq.wrapping_add(5);
}
let mut msgs = Vec::new();
let n = slot.drain(&mut msgs);
assert_eq!(n, 5, "all 5 packets produced messages");
assert!(msgs.iter().all(|m| m.message == 5));
}
#[test]
fn deferred_emit_anomalies_propagates_through_build_with() {
let mut b1 = Driver::<FiveTuple>::deferred();
b1.emit_anomalies(true);
let driver_with: Driver<FiveTuple> = b1.build_with(FiveTuple::bidirectional());
let b2 = Driver::<FiveTuple>::deferred();
let driver_without: Driver<FiveTuple> = b2.build_with(FiveTuple::bidirectional());
let _ = driver_with;
let _ = driver_without;
}
#[test]
fn deferred_multi_slot_independent_drains() {
let mut builder = Driver::<FiveTuple>::deferred();
let mut tcp_slot = builder.session_on_ports(CountParser, [80]);
let mut udp_slot = builder.datagram_on_ports(UdpEcho, [53]);
let mut driver = builder.build_with(FiveTuple::bidirectional());
let tcp_frame = tcp_packet(1000, b"hi");
let udp_frame = ipv4_udp([10, 0, 0, 1], [10, 0, 0, 2], 33000, 53, b"ye");
let mut events = Vec::new();
driver.track_into(
PacketView::new(&tcp_frame, Timestamp::new(0, 0)),
&mut events,
);
driver.track_into(
PacketView::new(&udp_frame, Timestamp::new(1, 0)),
&mut events,
);
let mut tcp_msgs = Vec::new();
let mut udp_msgs = Vec::new();
tcp_slot.drain(&mut tcp_msgs);
udp_slot.drain(&mut udp_msgs);
assert_eq!(tcp_msgs.len(), 1);
assert_eq!(udp_msgs.len(), 1);
assert_eq!(udp_msgs[0].message, b"ye");
}
#[test]
fn deferred_session_broadcast_observes_every_flow() {
let mut builder = Driver::<FiveTuple>::deferred();
let mut slot = builder.session_broadcast(CountParser);
let mut driver = builder.build_with(FiveTuple::bidirectional());
let mut events = Vec::new();
let f1 = tcp_packet(1000, b"alpha");
let f2 = ipv4_tcp(
[9; 6],
[8; 6],
[10, 0, 0, 5],
[10, 0, 0, 6],
55000,
9000,
2000,
0,
0x18,
b"beta",
);
driver.track_into(PacketView::new(&f1, Timestamp::new(0, 0)), &mut events);
driver.track_into(PacketView::new(&f2, Timestamp::new(1, 0)), &mut events);
let mut msgs = Vec::new();
slot.drain(&mut msgs);
assert_eq!(msgs.len(), 2, "broadcast sees both flows");
}
#[test]
fn deferred_session_heuristic_pins_on_signature_match() {
use flowscope::detect::signatures::SignatureMatch;
fn sig(probe: &[u8]) -> SignatureMatch {
if probe.starts_with(b"PIN") {
SignatureMatch::Match
} else if probe.len() >= 3 {
SignatureMatch::NoMatch
} else {
SignatureMatch::NeedMoreData
}
}
let mut builder = Driver::<FiveTuple>::deferred();
let mut slot = builder.session_heuristic(CountParser, sig);
let mut driver = builder.build_with(FiveTuple::bidirectional());
let mut events = Vec::new();
let mut seq: u32 = 1000;
for payload in &[b"PINGdata".as_slice(), b"more".as_slice()] {
let frame = tcp_packet(seq, payload);
driver.track_into(PacketView::new(&frame, Timestamp::new(0, 0)), &mut events);
seq = seq.wrapping_add(payload.len() as u32);
}
let mut msgs = Vec::new();
slot.drain(&mut msgs);
assert!(!msgs.is_empty(), "heuristic should emit after pinning");
}
#[test]
fn deferred_datagram_broadcast_observes_every_flow() {
let mut builder = Driver::<FiveTuple>::deferred();
let mut slot = builder.datagram_broadcast(UdpEcho);
let mut driver = builder.build_with(FiveTuple::bidirectional());
let mut events = Vec::new();
let f1 = ipv4_udp([10, 0, 0, 1], [10, 0, 0, 2], 33000, 5300, b"x");
let f2 = ipv4_udp([10, 0, 0, 3], [10, 0, 0, 4], 22000, 8888, b"y");
driver.track_into(PacketView::new(&f1, Timestamp::new(0, 0)), &mut events);
driver.track_into(PacketView::new(&f2, Timestamp::new(1, 0)), &mut events);
let mut msgs = Vec::new();
slot.drain(&mut msgs);
assert_eq!(msgs.len(), 2);
}
#[test]
fn deferred_builder_propagates_monotonic_timestamps_knob() {
let mut builder = Driver::<FiveTuple>::deferred();
builder.monotonic_timestamps(true);
let _slot = builder.session_on_ports(CountParser, [80]);
let mut driver = builder.build_with(FiveTuple::bidirectional());
let mut events = Vec::new();
let frame = tcp_packet(1000, b"x");
driver.track_into(PacketView::new(&frame, Timestamp::new(100, 0)), &mut events);
driver.track_into(PacketView::new(&frame, Timestamp::new(50, 0)), &mut events);
}
#[test]
fn deferred_builder_propagates_idle_timeout_fn() {
use std::time::Duration;
let mut builder = Driver::<FiveTuple>::deferred();
builder.idle_timeout_fn(|_key, _l4| Some(Duration::from_secs(10)));
let _slot = builder.session_on_ports(CountParser, [80]);
let _driver = builder.build_with(FiveTuple::bidirectional());
}
#[test]
fn deferred_builder_propagates_dedup() {
use flowscope::Dedup;
let mut builder = Driver::<FiveTuple>::deferred();
builder.dedup(Dedup::new(Duration::from_millis(100), 128));
let _slot = builder.session_on_ports(CountParser, [80]);
let _driver = builder.build_with(FiveTuple::bidirectional());
}
#[test]
fn deferred_builder_matches_eager_for_same_inputs() {
let mut eager_builder = Driver::builder(FiveTuple::bidirectional());
let mut eager_slot = eager_builder.session_on_ports(CountParser, [80]);
let mut eager_driver = eager_builder.build();
let mut deferred_builder: DeferredDriverBuilder<FiveTuple> = Driver::<FiveTuple>::deferred();
let mut deferred_slot = deferred_builder.session_on_ports(CountParser, [80]);
let mut deferred_driver = deferred_builder.build_with(FiveTuple::bidirectional());
let mut eager_events: Vec<Event<_>> = Vec::new();
let mut deferred_events: Vec<Event<_>> = Vec::new();
let mut seq: u32 = 1000;
for i in 0..10u32 {
let payload = format!("ev-{i:02}");
let frame = tcp_packet(seq, payload.as_bytes());
eager_driver.track_into(
PacketView::new(&frame, Timestamp::new(i, 0)),
&mut eager_events,
);
deferred_driver.track_into(
PacketView::new(&frame, Timestamp::new(i, 0)),
&mut deferred_events,
);
seq = seq.wrapping_add(payload.len() as u32);
}
assert_eq!(
eager_events.len(),
deferred_events.len(),
"event count parity"
);
let mut em = Vec::new();
let mut dm = Vec::new();
eager_slot.drain(&mut em);
deferred_slot.drain(&mut dm);
assert_eq!(em.len(), dm.len(), "message count parity");
for (a, b) in em.iter().zip(dm.iter()) {
assert_eq!(a.message, b.message);
assert_eq!(a.side, b.side);
}
}