use std::collections::HashMap;
use std::hash::Hash;
use std::marker::PhantomData;
use arrayvec::ArrayVec;
use crate::PacketView;
use crate::Timestamp;
use crate::datagram_driver::FlowDatagramDriver;
use crate::detect::signatures::{SignatureFn, SignatureMatch};
use crate::extract::parse::{self, ParsedL4};
use crate::extractor::{Extracted, FlowExtractor, Orientation};
use crate::session::{DatagramParser, SessionParser};
use crate::session_driver::FlowSessionDriver;
use crate::tracker::FlowTrackerConfig;
use super::Event;
use super::erased::DriverSlot;
pub const PROBE_BUFFER_CAP: usize = 64;
pub const DEFAULT_PROBE_PACKETS: u8 = 4;
pub(super) enum FlowDetection {
Probing {
seen: u8,
init_buf: ArrayVec<u8, PROBE_BUFFER_CAP>,
resp_buf: ArrayVec<u8, PROBE_BUFFER_CAP>,
},
Pinned,
GaveUp,
}
impl Default for FlowDetection {
fn default() -> Self {
Self::Probing {
seen: 0,
init_buf: ArrayVec::new(),
resp_buf: ArrayVec::new(),
}
}
}
pub(super) struct HeuristicSessionSlot<E, P, M, F>
where
E: FlowExtractor,
P: SessionParser,
{
pub(super) extractor: E,
pub(super) driver: FlowSessionDriver<E, P, ()>,
pub(super) signature: SignatureFn,
pub(super) max_probe_packets: u8,
pub(super) lift: F,
pub(super) parser_kind: &'static str,
pub(super) states: HashMap<E::Key, FlowDetection>,
pub(super) _marker: PhantomData<M>,
}
impl<E, P, M, F> HeuristicSessionSlot<E, P, M, F>
where
E: FlowExtractor + Clone,
P: SessionParser + Clone,
{
pub(super) fn new(
extractor: E,
parser: P,
config: FlowTrackerConfig,
signature: SignatureFn,
max_probe_packets: u8,
monotonic_timestamps: bool,
lift: F,
) -> Self {
let parser_kind = parser.parser_kind();
Self {
driver: FlowSessionDriver::with_config(extractor.clone(), parser, config)
.with_monotonic_timestamps(monotonic_timestamps),
extractor,
signature,
max_probe_packets,
lift,
parser_kind,
states: HashMap::new(),
_marker: PhantomData,
}
}
}
impl<E, P, M, F> DriverSlot<E::Key, M> for HeuristicSessionSlot<E, P, M, F>
where
E: FlowExtractor + Send,
E::Key: Hash + Eq + Clone + Send + 'static,
P: SessionParser + Send + 'static,
P::Message: Send + 'static,
F: Fn(P::Message) -> M + Send + 'static,
M: Send + 'static,
{
fn track(&mut self, view: PacketView<'_>, ts: Timestamp) -> Vec<Event<E::Key, M>> {
let Some(extracted) = self.extractor.extract(view) else {
return Vec::new();
};
let Extracted {
key, orientation, ..
} = extracted;
let Some(payload) = tcp_payload(view.frame) else {
return Vec::new();
};
let state = self.states.entry(key.clone()).or_default();
let should_dispatch = match state {
FlowDetection::Pinned => true,
FlowDetection::GaveUp => false,
FlowDetection::Probing {
seen,
init_buf,
resp_buf,
} => {
match orientation {
Orientation::Forward => extend_probe(init_buf, payload),
Orientation::Reverse => extend_probe(resp_buf, payload),
}
let init_match = (self.signature)(init_buf);
let resp_match = (self.signature)(resp_buf);
if matches!(init_match, SignatureMatch::Match)
|| matches!(resp_match, SignatureMatch::Match)
{
*state = FlowDetection::Pinned;
true
} else {
*seen = seen.saturating_add(1);
if *seen >= self.max_probe_packets {
*state = FlowDetection::GaveUp;
}
false
}
}
};
if !should_dispatch {
return Vec::new();
}
let parser_kind = self.parser_kind;
self.driver
.track(view)
.into_iter()
.filter_map(|e| super::erased::lift_event_pub(e, &self.lift, ts, parser_kind))
.collect()
}
fn sweep(&mut self, now: Timestamp) -> Vec<Event<E::Key, M>> {
let parser_kind = self.parser_kind;
self.driver
.sweep(now)
.into_iter()
.filter_map(|e| super::erased::lift_event_pub(e, &self.lift, now, parser_kind))
.collect()
}
fn finish(&mut self) -> Vec<Event<E::Key, M>> {
let parser_kind = self.parser_kind;
self.driver
.finish()
.into_iter()
.filter_map(|e| {
super::erased::lift_event_pub(e, &self.lift, Timestamp::MAX, parser_kind)
})
.collect()
}
}
pub(super) struct HeuristicDatagramSlot<E, D, M, F>
where
E: FlowExtractor,
D: DatagramParser,
{
pub(super) extractor: E,
pub(super) driver: FlowDatagramDriver<E, D, ()>,
pub(super) signature: SignatureFn,
pub(super) max_probe_packets: u8,
pub(super) lift: F,
pub(super) parser_kind: &'static str,
pub(super) states: HashMap<E::Key, FlowDetection>,
pub(super) _marker: PhantomData<M>,
}
impl<E, D, M, F> HeuristicDatagramSlot<E, D, M, F>
where
E: FlowExtractor + Clone,
D: DatagramParser + Clone,
{
pub(super) fn new(
extractor: E,
parser: D,
config: FlowTrackerConfig,
signature: SignatureFn,
max_probe_packets: u8,
monotonic_timestamps: bool,
lift: F,
) -> Self {
let parser_kind = parser.parser_kind();
Self {
driver: FlowDatagramDriver::with_config(extractor.clone(), parser, config)
.with_monotonic_timestamps(monotonic_timestamps),
extractor,
signature,
max_probe_packets,
lift,
parser_kind,
states: HashMap::new(),
_marker: PhantomData,
}
}
}
impl<E, D, M, F> DriverSlot<E::Key, M> for HeuristicDatagramSlot<E, D, M, F>
where
E: FlowExtractor + Send,
E::Key: Hash + Eq + Clone + Send + 'static,
D: DatagramParser + Send + 'static,
D::Message: Send + 'static,
F: Fn(D::Message) -> M + Send + 'static,
M: Send + 'static,
{
fn track(&mut self, view: PacketView<'_>, ts: Timestamp) -> Vec<Event<E::Key, M>> {
let Some(Extracted { key, .. }) = self.extractor.extract(view) else {
return Vec::new();
};
let Some(payload) = udp_payload(view.frame) else {
return Vec::new();
};
let state = self.states.entry(key.clone()).or_default();
let should_dispatch = match state {
FlowDetection::Pinned => true,
FlowDetection::GaveUp => false,
FlowDetection::Probing { seen, .. } => {
if matches!((self.signature)(&payload), SignatureMatch::Match) {
*state = FlowDetection::Pinned;
true
} else {
*seen = seen.saturating_add(1);
if *seen >= self.max_probe_packets {
*state = FlowDetection::GaveUp;
}
false
}
}
};
if !should_dispatch {
return Vec::new();
}
let parser_kind = self.parser_kind;
self.driver
.track(view)
.into_iter()
.filter_map(|e| super::erased::lift_event_pub(e, &self.lift, ts, parser_kind))
.collect()
}
fn sweep(&mut self, now: Timestamp) -> Vec<Event<E::Key, M>> {
let parser_kind = self.parser_kind;
self.driver
.sweep(now)
.into_iter()
.filter_map(|e| super::erased::lift_event_pub(e, &self.lift, now, parser_kind))
.collect()
}
fn finish(&mut self) -> Vec<Event<E::Key, M>> {
let parser_kind = self.parser_kind;
self.driver
.finish()
.into_iter()
.filter_map(|e| {
super::erased::lift_event_pub(e, &self.lift, Timestamp::MAX, parser_kind)
})
.collect()
}
}
fn extend_probe(buf: &mut ArrayVec<u8, PROBE_BUFFER_CAP>, payload: &[u8]) {
let room = PROBE_BUFFER_CAP - buf.len();
let to_take = room.min(payload.len());
let _ = buf.try_extend_from_slice(&payload[..to_take]);
}
fn tcp_payload(frame: &[u8]) -> Option<&[u8]> {
let parsed = parse::parse_eth(frame)?;
let ParsedL4::Tcp(t) = parsed.l4? else {
return None;
};
let end = t.payload_offset.checked_add(t.payload_len)?;
if end > frame.len() {
return None;
}
Some(&frame[t.payload_offset..end])
}
fn udp_payload(frame: &[u8]) -> Option<Vec<u8>> {
let parsed = parse::parse_eth(frame)?;
let ParsedL4::Udp(u) = parsed.l4? else {
return None;
};
let end = u.payload_offset.checked_add(u.payload_len)?;
if end > frame.len() {
return None;
}
Some(frame[u.payload_offset..end].to_vec())
}