use std::{hash::Hash, time::Duration};
use super::indexed::KeyIndexed;
use crate::{Timestamp, event::FlowEvent};
pub struct FlowStateMap<T, K = crate::extract::FiveTupleKey>
where
K: Hash + Eq + Clone,
T: Default,
{
inner: KeyIndexed<K, T>,
idle_timeout: Duration,
}
impl<T, K> FlowStateMap<T, K>
where
K: Hash + Eq + Clone,
T: Default,
{
pub fn new(idle_timeout: Duration) -> Self {
Self {
inner: KeyIndexed::new_unbounded(idle_timeout),
idle_timeout,
}
}
pub fn get_or_default(&mut self, key: &K, now: Timestamp) -> &mut T {
if self.inner.peek(key, now).is_none() {
self.inner.insert(key.clone(), T::default(), now);
} else {
if let Some(value) = self.inner.remove(key) {
self.inner.insert(key.clone(), value, now);
}
}
self.inner
.get_mut(key, now)
.expect("entry just inserted is present")
}
pub fn get(&self, key: &K, now: Timestamp) -> Option<&T> {
self.inner.peek(key, now)
}
pub fn feed(&mut self, event: &FlowEvent<K>) {
match event {
FlowEvent::Ended { key, .. } => {
self.inner.remove(key);
}
FlowEvent::Started { key, ts, .. }
| FlowEvent::Packet { key, ts, .. }
| FlowEvent::Established { key, ts, .. }
| FlowEvent::Tick { key, ts, .. }
| FlowEvent::FlowAnomaly { key, ts, .. } => {
if let Some(value) = self.inner.remove(key) {
self.inner.insert(key.clone(), value, *ts);
}
}
FlowEvent::TrackerAnomaly { .. } | FlowEvent::StateChange { .. } => {
}
}
}
pub fn sweep(&mut self, now: Timestamp) {
self.inner.evict_expired(now);
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn idle_timeout(&self) -> Duration {
self.idle_timeout
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Default, Debug)]
struct PerFlow {
packets: u32,
}
#[test]
fn lazy_creates_on_get_or_default() {
let mut m: FlowStateMap<PerFlow, u32> = FlowStateMap::new(Duration::from_secs(60));
assert_eq!(m.len(), 0);
let slot = m.get_or_default(&42, Timestamp::new(0, 0));
slot.packets += 1;
assert_eq!(m.len(), 1);
assert_eq!(m.get_or_default(&42, Timestamp::new(0, 0)).packets, 1);
}
#[test]
fn feed_ended_evicts_entry() {
let mut m: FlowStateMap<PerFlow, u32> = FlowStateMap::new(Duration::from_secs(60));
m.get_or_default(&42, Timestamp::new(0, 0)).packets = 5;
assert_eq!(m.len(), 1);
let ev: FlowEvent<u32> = FlowEvent::Ended {
key: 42,
reason: crate::event::EndReason::IdleTimeout,
stats: crate::event::FlowStats::default(),
history: Default::default(),
l4: None,
};
m.feed(&ev);
assert_eq!(m.len(), 0);
}
#[test]
fn sweep_evicts_idle() {
let mut m: FlowStateMap<PerFlow, u32> = FlowStateMap::new(Duration::from_secs(60));
m.get_or_default(&42, Timestamp::new(0, 0));
m.sweep(Timestamp::new(120, 0));
assert_eq!(m.len(), 0);
}
#[test]
fn feed_packet_refreshes_last_seen() {
let mut m: FlowStateMap<PerFlow, u32> = FlowStateMap::new(Duration::from_secs(60));
m.get_or_default(&42, Timestamp::new(0, 0));
let ev: FlowEvent<u32> = FlowEvent::Packet {
key: 42,
side: crate::FlowSide::Initiator,
orientation: crate::extractor::Orientation::Forward,
len: 100,
ts: Timestamp::new(50, 0),
source_idx: None,
};
m.feed(&ev);
m.sweep(Timestamp::new(70, 0));
assert_eq!(
m.len(),
1,
"entry refreshed by Packet at t=50, sweep at t=70 within 60s TTL"
);
m.sweep(Timestamp::new(120, 0));
assert_eq!(m.len(), 0);
}
}