use std::hash::Hash;
use crate::{
extractor::{Extracted, FlowExtractor},
view::PacketView,
};
pub trait Tagger: Send + Sync + 'static {
type Tag: Hash + Eq + Clone + Send + Sync + 'static;
fn tag(&self, view: PacketView<'_>) -> Self::Tag;
}
impl<T, F> Tagger for F
where
F: Fn(PacketView<'_>) -> T + Send + Sync + 'static,
T: Hash + Eq + Clone + Send + Sync + 'static,
{
type Tag = T;
#[inline]
fn tag(&self, view: PacketView<'_>) -> T {
self(view)
}
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct TaggedKey<T, K> {
pub tag: T,
pub inner: K,
}
impl<T, K> TaggedKey<T, K> {
pub fn new(tag: T, inner: K) -> Self {
Self { tag, inner }
}
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct Tagged<E, T> {
pub extractor: E,
pub tagger: T,
}
impl<E, T> Tagged<E, T> {
#[inline]
pub fn new(extractor: E, tagger: T) -> Self {
Self { extractor, tagger }
}
}
impl<E, T> FlowExtractor for Tagged<E, T>
where
E: FlowExtractor,
T: Tagger,
{
type Key = TaggedKey<T::Tag, E::Key>;
fn extract(&self, view: PacketView<'_>) -> Option<Extracted<Self::Key>> {
let inner = self.extractor.extract(view)?;
let tag = self.tagger.tag(view);
Some(Extracted {
key: TaggedKey {
tag,
inner: inner.key,
},
orientation: inner.orientation,
l4: inner.l4,
tcp: inner.tcp,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
Timestamp,
extract::{FiveTuple, parse::test_frames::ipv4_tcp},
};
fn build_v4_frame(src_port: u16) -> Vec<u8> {
ipv4_tcp(
[0; 6],
[0; 6],
[10, 0, 0, 1],
[10, 0, 0, 2],
src_port,
80,
0,
0,
0x02,
b"",
)
}
#[test]
fn function_pointer_tagger_compiles() {
let extractor = Tagged::new(FiveTuple::bidirectional(), |_view: PacketView<'_>| 7u32);
let frame = build_v4_frame(1234);
let view = PacketView::new(&frame, Timestamp::default());
let extracted = extractor.extract(view).expect("extract");
assert_eq!(extracted.key.tag, 7);
}
#[test]
fn same_inner_key_distinct_tags_are_distinct_keys() {
let frame = build_v4_frame(1234);
let view = PacketView::new(&frame, Timestamp::default());
let nic_a = Tagged::new(FiveTuple::bidirectional(), |_: PacketView<'_>| 0u32);
let nic_b = Tagged::new(FiveTuple::bidirectional(), |_: PacketView<'_>| 1u32);
let k_a = nic_a.extract(view).expect("extract a").key;
let k_b = nic_b.extract(view).expect("extract b").key;
assert_eq!(k_a.inner, k_b.inner);
assert_ne!(k_a, k_b);
assert_eq!(k_a.tag, 0);
assert_eq!(k_b.tag, 1);
}
#[test]
fn merged_extractor_is_just_the_inner_extractor() {
let frame = build_v4_frame(1234);
let view = PacketView::new(&frame, Timestamp::default());
let merged = FiveTuple::bidirectional();
let _k = merged.extract(view).expect("extract").key;
}
#[test]
fn orientation_passes_through() {
let frame = build_v4_frame(1234);
let view = PacketView::new(&frame, Timestamp::default());
let extractor = Tagged::new(FiveTuple::bidirectional(), |_: PacketView<'_>| 0u8);
let extracted = extractor.extract(view).expect("extract");
let inner_only = FiveTuple::bidirectional().extract(view).expect("inner");
assert_eq!(extracted.orientation, inner_only.orientation);
}
#[test]
fn l4_and_tcp_metadata_pass_through() {
let frame = build_v4_frame(1234);
let view = PacketView::new(&frame, Timestamp::default());
let extractor = Tagged::new(FiveTuple::bidirectional(), |_: PacketView<'_>| 0u8);
let extracted = extractor.extract(view).expect("extract");
let inner_only = FiveTuple::bidirectional().extract(view).expect("inner");
assert_eq!(extracted.l4, inner_only.l4);
assert_eq!(extracted.tcp.is_some(), inner_only.tcp.is_some());
}
#[test]
fn closure_tagger_works() {
let nic_idx: u32 = 42;
let extractor = Tagged::new(FiveTuple::bidirectional(), move |_: PacketView<'_>| nic_idx);
let frame = build_v4_frame(1234);
let view = PacketView::new(&frame, Timestamp::default());
let extracted = extractor.extract(view).expect("extract");
assert_eq!(extracted.key.tag, 42);
}
#[test]
fn tagged_extractor_is_send_sync() {
fn assert_send_sync<T: Send + Sync + 'static>() {}
assert_send_sync::<Tagged<FiveTuple, fn(PacketView<'_>) -> u32>>();
}
#[test]
fn inner_returns_none_propagates_without_invoking_tagger() {
use std::sync::atomic::{AtomicBool, Ordering};
static TAGGED_CALLED: AtomicBool = AtomicBool::new(false);
TAGGED_CALLED.store(false, Ordering::SeqCst);
let extractor = Tagged::new(FiveTuple::bidirectional(), |_: PacketView<'_>| {
TAGGED_CALLED.store(true, Ordering::SeqCst);
0u32
});
let truncated = [0u8; 4];
let view = PacketView::new(&truncated, Timestamp::default());
assert!(extractor.extract(view).is_none());
assert!(
!TAGGED_CALLED.load(Ordering::SeqCst),
"tagger must not be called when inner returns None"
);
}
}