Skip to main content

flowscope/session/
mod.rs

1//! Pluggable L7 message parsers.
2//!
3//! Two trait families:
4//!
5//! - [`SessionParser`] — for **stream-based** protocols (HTTP/1, TLS,
6//!   DNS-over-TCP). One parser per session; receives bytes via
7//!   `feed_initiator` / `feed_responder`; returns a `Vec` of typed
8//!   messages every call. Pair with `netring::SessionStream` to get
9//!   an async stream of L7 events.
10//!
11//! - [`DatagramParser`] — for **packet-based** protocols (DNS-over-UDP,
12//!   syslog, NTP, SNMP). Receives one L4 payload at a time. Pair with
13//!   `netring::DatagramStream`.
14//!
15//! Both trait shapes return owned `Vec<Message>` rather than borrowed
16//! iterators or `SmallVec` to keep the public API stable across
17//! versions of `smallvec` etc. The per-call allocation is amortized
18//! across many bytes worth of work.
19//!
20//! # SessionParser vs `Reassembler`
21//!
22//! [`crate::Reassembler`] is the lower-level hook: one instance per
23//! `(flow, side)`, receives raw TCP segments and yields the ordered
24//! byte stream as [`crate::StreamChunks`] (data interleaved with gap
25//! marks). `SessionParser` is the higher-level
26//! abstraction: one instance per flow, two `feed_*` methods,
27//! returns typed messages directly. Pick whichever fits your
28//! integration:
29//!
30//! | Concern                       | `Reassembler`           | `SessionParser`             |
31//! |-------------------------------|-------------------------|------------------------------|
32//! | Granularity                   | per (flow, side)        | per flow                     |
33//! | Output                        | `StreamChunks` (bytes + gaps) | typed messages         |
34//! | Cross-direction state         | painful                 | natural                      |
35//! | UDP support                   | no                      | use [`DatagramParser`]       |
36//!
37//! # Running parsers
38//!
39//! - [`SessionDriver`] / [`DatagramDriver`] — one parser type over a
40//!   whole capture, yielding an ordered stream of [`SessionEvent`]s
41//!   (lifecycle, messages, parser closes, anomalies). What
42//!   `netring`'s `SessionStream` / `DatagramStream` wrap.
43//! - [`crate::driver::Driver`] — several parsers (by port, by
44//!   signature, or on every flow) sharing one flow table, with typed
45//!   per-parser drain handles.
46//!
47//! Both are built on the same engine, so they behave identically:
48//! one flow table, one reassembler per flow side (shared by every
49//! parser), and these per-parser rules:
50//!
51//! - A parser that reports [`SessionParser::is_poisoned`] or
52//!   [`SessionParser::is_done`] after a call is **closed** (a parser
53//!   close event with [`EndReason::ParseError`] /
54//!   [`EndReason::ParserDone`], plus an
55//!   [`AnomalyKind::SessionParseError`] for poison when anomalies are
56//!   on). It is never fed again for that flow — and never re-created:
57//!   the flow itself stays tracked until its transport end. A parser
58//!   close never ends the flow; `Ended` / `Closed` always carries a
59//!   transport reason (`Fin`, `Rst`, `IdleTimeout`, `Evicted`,
60//!   `ForceClosed`).
61//! - The one reporting difference: a parser still open when its flow
62//!   ends. `SessionDriver` / `DatagramDriver` say so with `Closed`
63//!   alone; the typed `Driver` also emits one `Event::ParserClosed`
64//!   per slot carrying the transport reason
65//!   ([`EndReason::is_transport`]) right before `Ended`.
66//! - Missing bytes are reported through [`SessionParser::on_gap`].
67//!   The default answer, [`GapResponse::StopSide`], stops feeding
68//!   **that side** only (a parser side-stopped event with
69//!   [`EndReason::StreamGap`]); the other side keeps being parsed.
70//!   [`GapResponse::Continue`] resynchronises, [`GapResponse::Stop`]
71//!   closes the whole parser.
72//! - A reassembly stop on one side (per-side cap under
73//!   [`crate::OverflowPolicy::DropFlow`], memcap) stops that side
74//!   with [`EndReason::BufferOverflow`].
75//! - When both sides are stopped the parser closes once (detail
76//!   `"both sides stopped"`).
77//! - At flow end each side still being read gets
78//!   [`SessionParser::fin_initiator`] / [`SessionParser::fin_responder`]
79//!   (graceful end, or that side sent a FIN) or `rst_*`; a stopped
80//!   side gets neither.
81//! - Datagram parsers only see the transports they declare
82//!   ([`DatagramParser::transports`], default [`Transports::UDP`]):
83//!   a UDP parser never gets ICMP messages and vice versa.
84//!
85//! # Example
86//!
87//! ```
88//! use flowscope::{FlowSide, SessionParser, Timestamp};
89//!
90//! #[derive(Default, Clone)]
91//! struct LineParser {
92//!     init_buf: Vec<u8>,
93//!     resp_buf: Vec<u8>,
94//! }
95//!
96//! impl SessionParser for LineParser {
97//!     type Message = (FlowSide, String);
98//!
99//!     fn feed_initiator(&mut self, bytes: &[u8], _ts: Timestamp, out: &mut Vec<Self::Message>) {
100//!         feed(&mut self.init_buf, bytes, FlowSide::Initiator, out);
101//!     }
102//!     fn feed_responder(&mut self, bytes: &[u8], _ts: Timestamp, out: &mut Vec<Self::Message>) {
103//!         feed(&mut self.resp_buf, bytes, FlowSide::Responder, out);
104//!     }
105//! }
106//!
107//! fn feed(buf: &mut Vec<u8>, bytes: &[u8], side: FlowSide, out: &mut Vec<(FlowSide, String)>) {
108//!     buf.extend_from_slice(bytes);
109//!     while let Some(nl) = buf.iter().position(|&b| b == b'\n') {
110//!         let line = String::from_utf8_lossy(&buf[..nl]).into_owned();
111//!         out.push((side, line));
112//!         buf.drain(..=nl);
113//!     }
114//! }
115//! ```
116
117use crate::{
118    event::{AnomalyKind, EndReason, FlowSide, FlowStats},
119    extractor::{L4Proto, Orientation},
120    parser_kind::ParserKind,
121    timestamp::Timestamp,
122};
123
124#[cfg(all(feature = "extractors", feature = "reassembler"))]
125pub(crate) mod core;
126#[cfg(all(feature = "extractors", feature = "reassembler"))]
127mod driver;
128#[cfg(all(feature = "extractors", feature = "reassembler"))]
129pub(crate) mod engine;
130
131#[cfg(all(feature = "extractors", feature = "reassembler"))]
132pub use driver::{DatagramDriver, SessionDriver};
133
134/// Default per-side buffer cap for [`BufferedFrameDrain`] /
135/// [`AccumulatingSessionParser`]. 64 KiB matches the TCP
136/// receive-buffer scale.
137pub const DEFAULT_FRAME_DRAIN_MAX_BUFFER: usize = 64 * 1024;
138
139/// Reasons [`BufferedFrameDrain`] / [`AccumulatingSessionParser`]
140/// poison themselves.
141#[derive(Debug, Clone, PartialEq, Eq)]
142#[non_exhaustive]
143pub enum FrameDrainError {
144    /// Buffer reached its `max_buffer` ceiling before the parser
145    /// drained a complete message. Indicates protocol desync —
146    /// the parser can't make progress.
147    BufferFull,
148    /// The `parse_one` closure returned `Some((msg, 0))` — a
149    /// zero-byte advance. Reserved for closure-author bugs; the
150    /// drain stops to avoid an infinite loop and the parser is
151    /// poisoned.
152    ZeroByteAdvance,
153}
154
155impl std::fmt::Display for FrameDrainError {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        match self {
158            FrameDrainError::BufferFull => f.write_str("parser buffer exceeded max_buffer cap"),
159            FrameDrainError::ZeroByteAdvance => {
160                f.write_str("parse_one returned Some((_, 0)) — zero-byte advance")
161            }
162        }
163    }
164}
165
166impl std::error::Error for FrameDrainError {}
167
168/// Buffered drain helper for custom parsers.
169///
170/// Encapsulates the "accumulate bytes, repeatedly call a parser
171/// closure, drain consumed prefix, retain partial" pattern that
172/// every custom [`SessionParser`] writes. Catches the off-by-one
173/// bugs around drain offsets.
174///
175/// Typical use inside a [`SessionParser`] impl:
176///
177/// ```rust
178/// # use flowscope::session::BufferedFrameDrain;
179/// # use flowscope::{FlowSide, SessionParser, Timestamp};
180/// # #[derive(Debug, Clone)] struct Msg;
181/// # fn parse_one(b: &[u8]) -> Option<(Msg, usize)> { None }
182/// #[derive(Default, Clone)]
183/// struct MyParser {
184///     init: BufferedFrameDrain<Msg>,
185///     resp: BufferedFrameDrain<Msg>,
186/// }
187///
188/// impl SessionParser for MyParser {
189///     type Message = Msg;
190///     fn feed_initiator(&mut self, b: &[u8], _: Timestamp, out: &mut Vec<Msg>) {
191///         let _ = self.init.extend(b);
192///         self.init.drain_with(parse_one);
193///         out.append(&mut self.init.take_messages());
194///     }
195///     fn feed_responder(&mut self, b: &[u8], _: Timestamp, out: &mut Vec<Msg>) {
196///         let _ = self.resp.extend(b);
197///         self.resp.drain_with(parse_one);
198///         out.append(&mut self.resp.take_messages());
199///     }
200/// }
201/// ```
202///
203/// New in 0.10.0 (plan 106).
204#[derive(Debug)]
205pub struct BufferedFrameDrain<M> {
206    buf: Vec<u8>,
207    out: Vec<M>,
208    max_buffer: usize,
209    poisoned: Option<FrameDrainError>,
210}
211
212impl<M> Default for BufferedFrameDrain<M> {
213    fn default() -> Self {
214        Self::new()
215    }
216}
217
218impl<M> Clone for BufferedFrameDrain<M> {
219    /// Clones the configuration (`max_buffer`) but resets the
220    /// buffer / messages / poison state — typical use is to
221    /// rebuild the helper per session.
222    fn clone(&self) -> Self {
223        Self {
224            buf: Vec::new(),
225            out: Vec::new(),
226            max_buffer: self.max_buffer,
227            poisoned: None,
228        }
229    }
230}
231
232impl<M> BufferedFrameDrain<M> {
233    /// Construct with the [default buffer
234    /// cap](DEFAULT_FRAME_DRAIN_MAX_BUFFER).
235    pub fn new() -> Self {
236        Self::with_max_buffer(DEFAULT_FRAME_DRAIN_MAX_BUFFER)
237    }
238
239    /// Construct with a custom per-side buffer cap.
240    pub fn with_max_buffer(max_buffer: usize) -> Self {
241        Self {
242            buf: Vec::new(),
243            out: Vec::new(),
244            max_buffer,
245            poisoned: None,
246        }
247    }
248
249    /// Append `bytes` to the buffer.
250    ///
251    /// Returns `Err(FrameDrainError::BufferFull)` if the new
252    /// length would exceed `max_buffer`. The helper sets its
253    /// internal poison flag (queryable via [`Self::is_poisoned`]).
254    /// `bytes` are still appended up to the cap before poisoning,
255    /// so consumers that surface poison to the driver get the
256    /// last bit of data they can use.
257    pub fn extend(&mut self, bytes: &[u8]) -> Result<(), FrameDrainError> {
258        if self.poisoned.is_some() {
259            return Err(self.poisoned.clone().unwrap());
260        }
261        let new_len = self.buf.len().saturating_add(bytes.len());
262        if new_len > self.max_buffer {
263            let room = self.max_buffer.saturating_sub(self.buf.len());
264            self.buf.extend_from_slice(&bytes[..room.min(bytes.len())]);
265            self.poisoned = Some(FrameDrainError::BufferFull);
266            return Err(FrameDrainError::BufferFull);
267        }
268        self.buf.extend_from_slice(bytes);
269        Ok(())
270    }
271
272    /// Repeatedly call `parse_one` and drain the consumed prefix.
273    /// Pushes each parsed message into the internal `out` queue
274    /// (drain via [`Self::take_messages`]).
275    ///
276    /// `parse_one(buf) -> Option<(M, usize)>` semantics:
277    /// - `Some((msg, n))` — a complete message; advance `n` bytes.
278    /// - `None` — need more bytes.
279    /// - `Some((_, 0))` is treated as poison (zero-byte advance
280    ///   would loop forever).
281    pub fn drain_with<F>(&mut self, mut parse_one: F)
282    where
283        F: FnMut(&[u8]) -> Option<(M, usize)>,
284    {
285        while self.poisoned.is_none() {
286            match parse_one(&self.buf) {
287                Some((msg, 0)) => {
288                    self.out.push(msg);
289                    self.poisoned = Some(FrameDrainError::ZeroByteAdvance);
290                    return;
291                }
292                Some((msg, n)) => {
293                    self.out.push(msg);
294                    if n >= self.buf.len() {
295                        self.buf.clear();
296                    } else {
297                        self.buf.drain(..n);
298                    }
299                }
300                None => return,
301            }
302        }
303    }
304
305    /// Take the accumulated messages, clearing the queue.
306    pub fn take_messages(&mut self) -> Vec<M> {
307        std::mem::take(&mut self.out)
308    }
309
310    /// Bytes currently held in the buffer.
311    pub fn buffered_len(&self) -> usize {
312        self.buf.len()
313    }
314
315    /// `true` if the helper has poisoned itself (`BufferFull` or
316    /// `ZeroByteAdvance`). Once set, [`Self::extend`] is a no-op.
317    pub fn is_poisoned(&self) -> bool {
318        self.poisoned.is_some()
319    }
320
321    /// Poison reason, if any.
322    pub fn poison_reason(&self) -> Option<&FrameDrainError> {
323        self.poisoned.as_ref()
324    }
325}
326
327/// Convenience [`SessionParser`] impl over a `parse_one` closure
328/// of shape `Fn(&[u8]) -> Option<(M, usize)>`.
329///
330/// Wraps two [`BufferedFrameDrain`]s (one per side) and exposes
331/// the canonical "init + resp + drain-loop" pattern through one
332/// constructor call. Reduces ~25 LoC of boilerplate per custom
333/// parser to:
334///
335/// ```rust
336/// # use flowscope::session::AccumulatingSessionParser;
337/// # use flowscope::ParserKind;
338/// # #[derive(Debug, Clone)] struct Msg;
339/// # fn parse_one(b: &[u8]) -> Option<(Msg, usize)> { None }
340/// let parser = AccumulatingSessionParser::new(ParserKind::Other("my-protocol"), parse_one);
341/// ```
342///
343/// The closure must be `Clone + Send + 'static` so the parser
344/// can be cloned per session (via the [`SessionParserFactory`]
345/// blanket impl). Capturing closures usually satisfy `Clone` if
346/// the captured values do.
347///
348/// New in 0.10.0 (plan 106).
349pub struct AccumulatingSessionParser<F, M>
350where
351    F: Fn(&[u8]) -> Option<(M, usize)> + Clone + Send + 'static,
352    M: Send + std::fmt::Debug + 'static,
353{
354    parser_kind: ParserKind,
355    parse_one: F,
356    max_buffer: usize,
357    init: BufferedFrameDrain<M>,
358    resp: BufferedFrameDrain<M>,
359}
360
361impl<F, M> std::fmt::Debug for AccumulatingSessionParser<F, M>
362where
363    F: Fn(&[u8]) -> Option<(M, usize)> + Clone + Send + 'static,
364    M: Send + std::fmt::Debug + 'static,
365{
366    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
367        f.debug_struct("AccumulatingSessionParser")
368            .field("parser_kind", &self.parser_kind)
369            .field("init_buffered", &self.init.buffered_len())
370            .field("resp_buffered", &self.resp.buffered_len())
371            .field(
372                "poisoned",
373                &(self.init.is_poisoned() || self.resp.is_poisoned()),
374            )
375            .finish()
376    }
377}
378
379impl<F, M> Clone for AccumulatingSessionParser<F, M>
380where
381    F: Fn(&[u8]) -> Option<(M, usize)> + Clone + Send + 'static,
382    M: Send + std::fmt::Debug + 'static,
383{
384    /// Clones the configuration + parse closure; resets per-session
385    /// buffer state. Use for per-session reuse via the
386    /// [`SessionParserFactory`] blanket impl.
387    fn clone(&self) -> Self {
388        Self {
389            parser_kind: self.parser_kind,
390            parse_one: self.parse_one.clone(),
391            max_buffer: self.max_buffer,
392            init: BufferedFrameDrain::with_max_buffer(self.max_buffer),
393            resp: BufferedFrameDrain::with_max_buffer(self.max_buffer),
394        }
395    }
396}
397
398impl<F, M> AccumulatingSessionParser<F, M>
399where
400    F: Fn(&[u8]) -> Option<(M, usize)> + Clone + Send + 'static,
401    M: Send + std::fmt::Debug + 'static,
402{
403    /// Construct with the [default per-side buffer
404    /// cap](DEFAULT_FRAME_DRAIN_MAX_BUFFER).
405    pub fn new(parser_kind: ParserKind, parse_one: F) -> Self {
406        Self::with_max_buffer(parser_kind, parse_one, DEFAULT_FRAME_DRAIN_MAX_BUFFER)
407    }
408
409    /// Construct with a custom per-side buffer cap.
410    pub fn with_max_buffer(parser_kind: ParserKind, parse_one: F, max_buffer: usize) -> Self {
411        Self {
412            parser_kind,
413            parse_one,
414            max_buffer,
415            init: BufferedFrameDrain::with_max_buffer(max_buffer),
416            resp: BufferedFrameDrain::with_max_buffer(max_buffer),
417        }
418    }
419}
420
421impl<F, M> SessionParser for AccumulatingSessionParser<F, M>
422where
423    F: Fn(&[u8]) -> Option<(M, usize)> + Clone + Send + 'static,
424    M: Send + std::fmt::Debug + 'static,
425{
426    type Message = M;
427
428    fn feed_initiator(&mut self, bytes: &[u8], _ts: Timestamp, out: &mut Vec<M>) {
429        if self.init.extend(bytes).is_err() {
430            out.append(&mut self.init.take_messages());
431            return;
432        }
433        let parse_one = self.parse_one.clone();
434        self.init.drain_with(parse_one);
435        out.append(&mut self.init.take_messages());
436    }
437
438    fn feed_responder(&mut self, bytes: &[u8], _ts: Timestamp, out: &mut Vec<M>) {
439        if self.resp.extend(bytes).is_err() {
440            out.append(&mut self.resp.take_messages());
441            return;
442        }
443        let parse_one = self.parse_one.clone();
444        self.resp.drain_with(parse_one);
445        out.append(&mut self.resp.take_messages());
446    }
447
448    fn parser_kind(&self) -> ParserKind {
449        self.parser_kind
450    }
451
452    fn is_poisoned(&self) -> bool {
453        self.init.is_poisoned() || self.resp.is_poisoned()
454    }
455
456    fn poison_reason(&self) -> Option<&str> {
457        // First poisoned side wins.
458        let err = self.init.poison_reason().or(self.resp.poison_reason())?;
459        Some(match err {
460            FrameDrainError::BufferFull => "buffer cap exceeded",
461            FrameDrainError::ZeroByteAdvance => "parse_one returned zero-byte advance",
462        })
463    }
464}
465
466/// Convenience [`DatagramParser`] impl over a `parse_one` closure
467/// of shape `Fn(&[u8]) -> Option<M>`.
468///
469/// One UDP packet, one optional message. The closure receives the
470/// raw payload; return `Some(message)` to emit one event,
471/// `None` to drop the packet silently.
472///
473/// New in 0.10.0 (plan 106).
474pub struct PerDatagramParser<F, M>
475where
476    F: Fn(&[u8]) -> Option<M> + Clone + Send + 'static,
477    M: Send + std::fmt::Debug + 'static,
478{
479    parser_kind: ParserKind,
480    parse_one: F,
481}
482
483impl<F, M> std::fmt::Debug for PerDatagramParser<F, M>
484where
485    F: Fn(&[u8]) -> Option<M> + Clone + Send + 'static,
486    M: Send + std::fmt::Debug + 'static,
487{
488    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
489        f.debug_struct("PerDatagramParser")
490            .field("parser_kind", &self.parser_kind)
491            .finish()
492    }
493}
494
495impl<F, M> Clone for PerDatagramParser<F, M>
496where
497    F: Fn(&[u8]) -> Option<M> + Clone + Send + 'static,
498    M: Send + std::fmt::Debug + 'static,
499{
500    fn clone(&self) -> Self {
501        Self {
502            parser_kind: self.parser_kind,
503            parse_one: self.parse_one.clone(),
504        }
505    }
506}
507
508impl<F, M> PerDatagramParser<F, M>
509where
510    F: Fn(&[u8]) -> Option<M> + Clone + Send + 'static,
511    M: Send + std::fmt::Debug + 'static,
512{
513    pub fn new(parser_kind: ParserKind, parse_one: F) -> Self {
514        Self {
515            parser_kind,
516            parse_one,
517        }
518    }
519}
520
521impl<F, M> DatagramParser for PerDatagramParser<F, M>
522where
523    F: Fn(&[u8]) -> Option<M> + Clone + Send + 'static,
524    M: Send + std::fmt::Debug + 'static,
525{
526    type Message = M;
527
528    fn parse(&mut self, payload: &[u8], _side: FlowSide, _ts: Timestamp, out: &mut Vec<M>) {
529        if let Some(msg) = (self.parse_one)(payload) {
530            out.push(msg);
531        }
532    }
533
534    fn parser_kind(&self) -> ParserKind {
535        self.parser_kind
536    }
537}
538
539/// Parses a stream-oriented L7 protocol session. One instance per
540/// flow; both directions feed through the same parser, allowing
541/// state to interleave.
542///
543/// Implementors are owned by the per-flow slot; sync (no `await`).
544/// Backpressure flows from the consuming `Stream` back to the
545/// kernel ring once the per-flow message buffer fills up — see
546/// the `netring::SessionStream` adapter.
547///
548/// # Per-flow rich state
549///
550/// For consumers that maintain per-flow user state updated by BOTH
551/// the reassembler and the parser — TCP rich stats, application-
552/// level counters, middleware state machines — keep that state on
553/// [`crate::FlowEntry::user`] (typed via the `S` parameter on
554/// the internal session engine) and update it from your event
555/// loop after `track()`. The pattern is documented in
556/// `docs/recipes.md` → "Per-flow user state via the consumer
557/// loop". Avoid piping `&mut S` through `feed_*` — it would
558/// ripple a generic parameter through every shipped parser.
559pub trait SessionParser: Send + 'static {
560    /// L7 message produced by this parser.
561    ///
562    /// - `Send + 'static` so messages can cross task boundaries.
563    /// - `Debug` is required for the per-message `tracing::trace!`
564    ///   event under the `tracing` feature (filter via
565    ///   `EnvFilter::new("flowscope.message=warn")` to suppress).
566    ///   Almost every Rust type derives `Debug` anyway, and the
567    ///   bound is trivial to add for those that don't.
568    type Message: Send + std::fmt::Debug + 'static;
569
570    /// Feed the next chunk of bytes from the **initiator** side.
571    /// `ts` is the observed time of the packet carrying these bytes.
572    /// Push any complete messages parsed during this call into
573    /// `out`.
574    ///
575    /// **0.11 break (plan 119):** signature changed from
576    /// `-> Vec<Self::Message>` to taking `out: &mut Vec<…>`.
577    /// Same idiom as `httparse::Request::parse` etc.
578    fn feed_initiator(&mut self, bytes: &[u8], ts: Timestamp, out: &mut Vec<Self::Message>);
579
580    /// Feed the next chunk of bytes from the **responder** side.
581    fn feed_responder(&mut self, bytes: &[u8], ts: Timestamp, out: &mut Vec<Self::Message>);
582
583    /// The initiator side ended cleanly: called once at flow end when
584    /// the flow ended gracefully ([`EndReason::is_graceful`]: FIN,
585    /// idle timeout, force close) or the initiator sent a FIN — after
586    /// its final bytes were fed. Not called for a side the parser
587    /// stopped reading ([`GapResponse::StopSide`], reassembly stop).
588    /// Default: no-op.
589    fn fin_initiator(&mut self, _out: &mut Vec<Self::Message>) {}
590
591    /// The responder side ended cleanly; see
592    /// [`fin_initiator`](Self::fin_initiator).
593    fn fin_responder(&mut self, _out: &mut Vec<Self::Message>) {}
594
595    /// The initiator side ended abruptly: called once at flow end
596    /// instead of [`fin_initiator`](Self::fin_initiator) when the flow
597    /// was not ended gracefully (RST, eviction) and this side sent no
598    /// FIN. Not called for a stopped side. Default: no-op.
599    fn rst_initiator(&mut self) {}
600
601    /// The responder side ended abruptly; see
602    /// [`rst_initiator`](Self::rst_initiator).
603    fn rst_responder(&mut self) {}
604
605    /// Periodic time hook. The driver calls this on every `sweep` /
606    /// `finish` with the sweep's `now`, for every still-live parser.
607    /// Lets stateful parsers emit time-driven messages (timeouts,
608    /// unanswered requests). Emitted messages are attributed to
609    /// [`FlowSide::Initiator`]. Default: no-op.
610    fn on_tick(&mut self, _now: Timestamp, _out: &mut Vec<Self::Message>) {}
611
612    /// Bytes are missing from `side`'s stream: `missing` bytes the
613    /// reassembler never saw (capture loss, asymmetric routing, an
614    /// out-of-order hole that expired, or bytes dropped by the
615    /// sliding-window buffer cap). The next `feed_*` call for that
616    /// side resumes **after** the hole.
617    ///
618    /// Return [`GapResponse::Continue`] if the parser can cope (it
619    /// resynchronises on message boundaries, or only counts bytes).
620    /// The default, [`GapResponse::StopSide`], stops feeding **this
621    /// side** (a framed parser fed a spliced stream would otherwise
622    /// mis-parse silently, as in Suricata, where app-layer parsers
623    /// that don't declare gap support stop on a gap) while the other
624    /// side keeps being parsed and still gets its `fin_*` / `rst_*`.
625    /// [`GapResponse::Stop`] closes the whole parser
626    /// ([`EndReason::StreamGap`]) — for protocols whose state spans
627    /// both directions (HTTP/2's HPACK).
628    ///
629    /// A stopped side gets no further `feed_*`, `on_gap` or
630    /// `fin_*` / `rst_*` call: flush anything worth keeping here.
631    /// Messages pushed into `out` are emitted before the stop.
632    fn on_gap(
633        &mut self,
634        _side: FlowSide,
635        _missing: u64,
636        _ts: Timestamp,
637        _out: &mut Vec<Self::Message>,
638    ) -> GapResponse {
639        GapResponse::StopSide
640    }
641
642    /// True after the parser has hit an unrecoverable error and
643    /// can no longer make progress. The engines check this after
644    /// every `feed_*` / `on_gap` / `on_tick` call and close the parser
645    /// on `true` — it is not fed again for that flow, and the flow
646    /// itself stays tracked. Default: `false` (parser never poisons).
647    ///
648    /// Parsers that want to drop a malformed message and keep
649    /// going should NOT use this — just don't push the message
650    /// into the returned `Vec`. Reserve poison for cases where
651    /// internal state is corrupted past recovery (desynced framing,
652    /// invalid magic bytes that won't appear later, etc.).
653    ///
654    /// The reassembly-side counterpart is
655    /// [`crate::Reassembler::stop_reason`].
656    fn is_poisoned(&self) -> bool {
657        false
658    }
659
660    /// Optional human-readable description of why the parser
661    /// poisoned. Consulted only when [`is_poisoned`](Self::is_poisoned)
662    /// returns `true`. Default: `None`.
663    ///
664    /// The driver truncates to ~256 bytes when forwarding the
665    /// resulting `FlowAnomaly` ([`AnomalyKind::SessionParseError`]).
666    fn poison_reason(&self) -> Option<&str> {
667        None
668    }
669
670    /// Symmetric "I'm done — close this flow cleanly" signal.
671    /// Default: `false` (parser never self-terminates).
672    ///
673    /// Returning `true` tells the engine this parser has no more
674    /// useful work to extract. The engine closes the parser with
675    /// [`crate::EndReason::ParserDone`] right after emitting the
676    /// messages of the same `feed_*` / `on_tick` call and stops
677    /// feeding it; the flow itself stays tracked until its transport
678    /// end.
679    ///
680    /// Reserve for protocols where **nothing** further on the
681    /// connection can matter: a session-end sentinel after which the
682    /// peers only close (e.g. an SMTP `QUIT` / `221` exchange), or a
683    /// protocol switch to something this parser cannot read. Not for
684    /// "one exchange complete" — DNS-over-TCP and HTTP/1.1 reuse the
685    /// connection, and a parser done after the first pair would lose
686    /// every later one. Do **not** use this to give up on bad input —
687    /// that's [`is_poisoned`](Self::is_poisoned), which routes through
688    /// [`crate::EndReason::ParseError`].
689    ///
690    /// Should be idempotent: once `is_done()` returns `true`, it
691    /// should keep returning `true` for the lifetime of the parser.
692    /// [`is_poisoned`](Self::is_poisoned) takes precedence — a
693    /// parser that's both `is_done` and `is_poisoned` surfaces as
694    /// `ParseError`, not `ParserDone`.
695    fn is_done(&self) -> bool {
696        false
697    }
698
699    /// Typed identity of this parser, surfaced on
700    /// [`crate::driver::Event::ParserClosed::parser_kind`] and
701    /// [`crate::driver::SlotHandle::parser_kind`]. Lifted from
702    /// `&'static str` to the [`ParserKind`] enum in 0.20 (#109);
703    /// originally added in 0.5.0.
704    ///
705    /// Built-in parsers return their dedicated variant
706    /// ([`ParserKind::Http1`], [`ParserKind::DnsUdp`], …).
707    /// Downstream parsers either add a [`ParserKind::Other`] with a
708    /// stable, label-safe slug (`"crate-name/protocol"`) or leave
709    /// the default.
710    ///
711    /// - Default: [`ParserKind::Unspecified`] (no kind set).
712    /// - `as_str()` yields the metric-label slug (allocation-free).
713    fn parser_kind(&self) -> ParserKind {
714        ParserKind::Unspecified
715    }
716}
717
718/// Builds a fresh [`SessionParser`] per session. Modeled on
719/// [`crate::ReassemblerFactory`].
720///
721/// Most parsers can skip implementing this manually: any parser
722/// that's `SessionParser + Default + Clone` automatically becomes
723/// a factory via the blanket impl below.
724pub trait SessionParserFactory<K>: Send + 'static {
725    type Parser: SessionParser;
726    fn new_parser(&mut self, key: &K) -> Self::Parser;
727}
728
729impl<K, P> SessionParserFactory<K> for P
730where
731    P: SessionParser + Default + Clone,
732{
733    type Parser = P;
734    fn new_parser(&mut self, _key: &K) -> P {
735        self.clone()
736    }
737}
738
739/// Parses a packet-oriented L7 protocol. One instance per flow;
740/// receives one L4 payload at a time along with which side sent it.
741pub trait DatagramParser: Send + 'static {
742    /// L7 message produced by this parser. Same `Debug` bound as
743    /// [`SessionParser::Message`].
744    type Message: Send + std::fmt::Debug + 'static;
745
746    /// Parse one L4 payload. `side` is the direction relative to
747    /// the flow's initiator; `ts` is the observed time of the
748    /// datagram. Push any complete messages decoded into `out`.
749    ///
750    /// **0.11 break (plan 119):** signature changed from
751    /// `-> Vec<Self::Message>` to taking `out: &mut Vec<…>`.
752    fn parse(
753        &mut self,
754        payload: &[u8],
755        side: FlowSide,
756        ts: Timestamp,
757        out: &mut Vec<Self::Message>,
758    );
759
760    /// Periodic time hook — see [`SessionParser::on_tick`]. The
761    /// driver calls this on every `sweep` / `finish`. Default: no-op.
762    fn on_tick(&mut self, _now: Timestamp, _out: &mut Vec<Self::Message>) {}
763
764    /// True after the parser has hit an unrecoverable error. See
765    /// [`SessionParser::is_poisoned`] for the contract.
766    fn is_poisoned(&self) -> bool {
767        false
768    }
769
770    /// Optional reason for poison. See
771    /// [`SessionParser::poison_reason`].
772    fn poison_reason(&self) -> Option<&str> {
773        None
774    }
775
776    /// Symmetric "I'm done — close this flow cleanly" signal,
777    /// mirroring [`SessionParser::is_done`]. Default: `false`.
778    fn is_done(&self) -> bool {
779        false
780    }
781
782    /// See [`SessionParser::parser_kind`]. Default
783    /// [`ParserKind::Unspecified`].
784    fn parser_kind(&self) -> ParserKind {
785        ParserKind::Unspecified
786    }
787
788    /// Which transports this parser reads. The engines hand it only
789    /// datagrams of these transports: a UDP parser never sees an
790    /// ICMP message and an ICMP parser never sees a UDP payload.
791    /// Default [`Transports::UDP`]. New in 0.25.0.
792    fn transports(&self) -> Transports {
793        Transports::UDP
794    }
795}
796
797bitflags::bitflags! {
798    /// Transports a [`DatagramParser`] reads (see
799    /// [`DatagramParser::transports`]). New in 0.25.0.
800    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
801    pub struct Transports: u8 {
802        /// UDP payloads.
803        const UDP = 1;
804        /// Whole ICMPv4 messages.
805        const ICMP = 1 << 1;
806        /// Whole ICMPv6 messages.
807        const ICMPV6 = 1 << 2;
808        /// SCTP packets (the whole SCTP packet: common header and chunks).
809        const SCTP = 1 << 3;
810        /// Any other non-TCP IP protocol (the whole L4 payload).
811        const OTHER = 1 << 4;
812    }
813}
814
815impl Transports {
816    /// ICMPv4 and ICMPv6.
817    pub const ICMP_ANY: Transports = Transports::ICMP.union(Transports::ICMPV6);
818
819    /// Whether a datagram of this L4 protocol is admitted. Unknown
820    /// L4 (`None`) counts as [`Transports::OTHER`].
821    pub fn admits(self, l4: Option<crate::L4Proto>) -> bool {
822        use crate::L4Proto;
823        let bit = match l4 {
824            Some(L4Proto::Udp) => Transports::UDP,
825            Some(L4Proto::Icmp) => Transports::ICMP,
826            Some(L4Proto::IcmpV6) => Transports::ICMPV6,
827            Some(L4Proto::Sctp) => Transports::SCTP,
828            Some(L4Proto::Tcp) => return false,
829            _ => Transports::OTHER,
830        };
831        self.contains(bit)
832    }
833}
834
835/// Builds a fresh [`DatagramParser`] per session.
836pub trait DatagramParserFactory<K>: Send + 'static {
837    type Parser: DatagramParser;
838    fn new_parser(&mut self, key: &K) -> Self::Parser;
839
840    /// Transports the parsers read (see
841    /// [`DatagramParser::transports`]). Default [`Transports::UDP`];
842    /// factories that clone a template delegate to it.
843    fn transports(&self) -> Transports {
844        Transports::UDP
845    }
846}
847
848impl<K, P> DatagramParserFactory<K> for P
849where
850    P: DatagramParser + Default + Clone,
851{
852    type Parser = P;
853    fn new_parser(&mut self, _key: &K) -> P {
854        self.clone()
855    }
856    fn transports(&self) -> Transports {
857        DatagramParser::transports(self)
858    }
859}
860
861/// Answer to [`SessionParser::on_gap`].
862#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
863#[non_exhaustive]
864pub enum GapResponse {
865    /// Keep feeding this parser; bytes after the gap follow.
866    Continue,
867    /// **Default.** Stop feeding the side with the gap; the other
868    /// side keeps being parsed. Reported as a parser side stop
869    /// ([`SessionEvent::ParserSideStopped`] /
870    /// [`crate::driver::Event::ParserSideStopped`]). When both sides
871    /// are stopped the parser is closed. New in 0.25.0.
872    #[default]
873    StopSide,
874    /// Close the parser for this flow ([`EndReason::StreamGap`]).
875    Stop,
876}
877
878/// Output of [`SessionDriver`] / [`DatagramDriver`]: flow lifecycle,
879/// parser messages, parser closes and anomalies, in the order they
880/// happened. `K` is the flow key, `M` the parser's message type.
881///
882/// For one flow the order is: `Started`, then any number of
883/// `Application` / `FlowAnomaly` / `Tick` / `ParserSideStopped`,
884/// possibly one `ParserClosed` (the parser gave up or finished early
885/// — the flow goes on), then `Closed`. Messages a parser flushes at
886/// flow end (`fin_*`) come before `Closed`.
887#[derive(Debug, Clone)]
888#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
889#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "snake_case"))]
890#[cfg_attr(
891    feature = "serde",
892    serde(bound(
893        serialize = "K: serde::Serialize, M: serde::Serialize",
894        deserialize = "K: serde::de::DeserializeOwned, M: serde::de::DeserializeOwned"
895    ))
896)]
897#[non_exhaustive]
898pub enum SessionEvent<K, M> {
899    /// First packet of a new flow.
900    Started {
901        key: K,
902        /// Logical role of the side that sent the first packet
903        /// (arrival-order relative; see `orientation` for the
904        /// race-stable axis).
905        side: FlowSide,
906        /// Canonical, address-sorted direction of the first packet.
907        orientation: Orientation,
908        ts: Timestamp,
909        l4: Option<L4Proto>,
910    },
911    /// The parser emitted a message.
912    Application {
913        key: K,
914        /// Side whose bytes produced the message (`on_tick` output is
915        /// attributed to the initiator).
916        side: FlowSide,
917        /// Canonical direction matching `side`.
918        orientation: Orientation,
919        message: M,
920        /// Timestamp of the packet (or sweep) that produced it.
921        ts: Timestamp,
922        /// [`SessionParser::parser_kind`] of the producing parser.
923        parser_kind: ParserKind,
924    },
925    /// The parser was closed before its flow ended — poisoned
926    /// ([`EndReason::ParseError`]), done ([`EndReason::ParserDone`]),
927    /// a gap answered with [`GapResponse::Stop`]
928    /// ([`EndReason::StreamGap`]), or both sides stopped (detail
929    /// `"both sides stopped"`, reason of the last side stop). The flow
930    /// stays tracked and ends later with `Closed`; the parser is not
931    /// fed again for it. (A parser still open when its flow ends gets
932    /// no `ParserClosed`: `Closed` says so — so `reason.is_parser()`
933    /// always holds here. The typed [`crate::driver::Driver`] differs:
934    /// it emits an `Event::ParserClosed` with the transport reason per
935    /// slot right before `Ended`; [`EndReason::is_transport`] /
936    /// [`EndReason::is_parser`] tell the two apart.) Not emitted while
937    /// events are paused (0.26, #204).
938    ParserClosed {
939        key: K,
940        parser_kind: ParserKind,
941        reason: EndReason,
942        /// Human-readable detail: the parser's `poison_reason()`
943        /// (truncated to 256 bytes), the gap size, or the stop reason.
944        detail: Option<String>,
945        ts: Timestamp,
946    },
947    /// The parser stopped reading one side of the flow — a gap it
948    /// cannot bridge ([`EndReason::StreamGap`],
949    /// [`GapResponse::StopSide`]) or a reassembly limit on that side
950    /// ([`EndReason::BufferOverflow`]). The other side keeps being
951    /// parsed; when both sides are stopped a `ParserClosed` follows.
952    /// New in 0.25.0.
953    ParserSideStopped {
954        key: K,
955        parser_kind: ParserKind,
956        side: FlowSide,
957        reason: EndReason,
958        /// The gap size or the reassembly stop reason.
959        detail: Option<String>,
960        ts: Timestamp,
961    },
962    /// The flow ended (transport reason: FIN / RST / idle / eviction /
963    /// force-close). `stats` includes the reassembly diagnostics.
964    Closed {
965        key: K,
966        reason: EndReason,
967        stats: FlowStats,
968        l4: Option<L4Proto>,
969        ts: Timestamp,
970    },
971    /// Per-flow anomaly (reassembly, parser poison). Only when the
972    /// driver was built with anomalies enabled.
973    FlowAnomaly {
974        key: K,
975        kind: AnomalyKind,
976        ts: Timestamp,
977    },
978    /// Tracker-global anomaly (eviction pressure, memcap). Only when
979    /// anomalies are enabled.
980    TrackerAnomaly { kind: AnomalyKind, ts: Timestamp },
981    /// Periodic stats snapshot, when
982    /// [`crate::FlowTrackerConfig::flow_tick_interval`] is set.
983    Tick {
984        key: K,
985        stats: FlowStats,
986        ts: Timestamp,
987    },
988}
989
990impl<K, M> SessionEvent<K, M> {
991    /// The session form of a flow-tracker event, as the session
992    /// engines emit it: `Started` and anomalies / ticks map 1:1,
993    /// `Ended` becomes `Closed` (stamped with the flow's last packet).
994    /// Per-packet `Packet` / `Established` / `StateChange` have no
995    /// session form (`None`). Lets a caller moving from a flow stream to
996    /// a session stream keep the flow events it already queued.
997    pub fn from_flow_event(ev: crate::event::FlowEvent<K>) -> Option<Self> {
998        use crate::event::FlowEvent;
999        Some(match ev {
1000            FlowEvent::Started {
1001                key,
1002                side,
1003                orientation,
1004                ts,
1005                l4,
1006            } => SessionEvent::Started {
1007                key,
1008                side,
1009                orientation,
1010                ts,
1011                l4,
1012            },
1013            FlowEvent::Ended {
1014                key,
1015                reason,
1016                stats,
1017                l4,
1018                ..
1019            } => {
1020                let ts = stats.last_seen;
1021                SessionEvent::Closed {
1022                    key,
1023                    reason,
1024                    stats,
1025                    l4,
1026                    ts,
1027                }
1028            }
1029            FlowEvent::FlowAnomaly { key, kind, ts } => SessionEvent::FlowAnomaly { key, kind, ts },
1030            FlowEvent::TrackerAnomaly { kind, ts } => SessionEvent::TrackerAnomaly { kind, ts },
1031            FlowEvent::Tick { key, stats, ts } => SessionEvent::Tick { key, stats, ts },
1032            FlowEvent::Packet { .. }
1033            | FlowEvent::Established { .. }
1034            | FlowEvent::StateChange { .. } => return None,
1035        })
1036    }
1037
1038    /// The flow key, when the variant has one.
1039    pub fn key(&self) -> Option<&K> {
1040        match self {
1041            SessionEvent::Started { key, .. }
1042            | SessionEvent::Application { key, .. }
1043            | SessionEvent::ParserClosed { key, .. }
1044            | SessionEvent::ParserSideStopped { key, .. }
1045            | SessionEvent::Closed { key, .. }
1046            | SessionEvent::FlowAnomaly { key, .. }
1047            | SessionEvent::Tick { key, .. } => Some(key),
1048            SessionEvent::TrackerAnomaly { .. } => None,
1049        }
1050    }
1051
1052    /// The event's timestamp.
1053    pub fn timestamp(&self) -> Timestamp {
1054        match self {
1055            SessionEvent::Started { ts, .. }
1056            | SessionEvent::Application { ts, .. }
1057            | SessionEvent::ParserClosed { ts, .. }
1058            | SessionEvent::ParserSideStopped { ts, .. }
1059            | SessionEvent::Closed { ts, .. }
1060            | SessionEvent::FlowAnomaly { ts, .. }
1061            | SessionEvent::TrackerAnomaly { ts, .. }
1062            | SessionEvent::Tick { ts, .. } => *ts,
1063        }
1064    }
1065
1066    /// The anomaly kind, for the two anomaly variants.
1067    pub fn anomaly_kind(&self) -> Option<&AnomalyKind> {
1068        match self {
1069            SessionEvent::FlowAnomaly { kind, .. } | SessionEvent::TrackerAnomaly { kind, .. } => {
1070                Some(kind)
1071            }
1072            _ => None,
1073        }
1074    }
1075}
1076
1077/// A [`SessionParserFactory`] / [`DatagramParserFactory`] that clones
1078/// a template parser for every flow. The blanket factory impls need
1079/// `Default + Clone`; this one only needs `Clone`, for parsers built
1080/// from configuration.
1081#[derive(Debug, Clone)]
1082pub struct TemplateFactory<P>(pub P);
1083
1084impl<K, P> SessionParserFactory<K> for TemplateFactory<P>
1085where
1086    P: SessionParser + Clone,
1087{
1088    type Parser = P;
1089    fn new_parser(&mut self, _key: &K) -> P {
1090        self.0.clone()
1091    }
1092}
1093
1094impl<K, P> DatagramParserFactory<K> for TemplateFactory<P>
1095where
1096    P: DatagramParser + Clone,
1097{
1098    type Parser = P;
1099    fn new_parser(&mut self, _key: &K) -> P {
1100        self.0.clone()
1101    }
1102    fn transports(&self) -> Transports {
1103        self.0.transports()
1104    }
1105}
1106
1107#[cfg(any(feature = "ftp", feature = "smtp"))]
1108/// Gap recovery for line-oriented protocols: the bytes right after a
1109/// gap start mid-line, so they are discarded up to and including the
1110/// next `\n`. Feed each new chunk through this while `pending` is
1111/// set; returns the part of `bytes` to parse.
1112pub(crate) fn skip_partial_line<'a>(pending: &mut bool, bytes: &'a [u8]) -> &'a [u8] {
1113    if !*pending {
1114        return bytes;
1115    }
1116    match bytes.iter().position(|&b| b == b'\n') {
1117        Some(p) => {
1118            *pending = false;
1119            &bytes[p + 1..]
1120        }
1121        None => &[],
1122    }
1123}
1124
1125#[cfg(any(feature = "smb", feature = "modbus"))]
1126/// Gap recovery for framed binary protocols: when `pending`, drop
1127/// buffered bytes up to the first offset `find` accepts as a frame
1128/// start (keeping at most `keep` trailing bytes while none is found).
1129pub(crate) fn resync_frames(
1130    pending: &mut bool,
1131    buf: &mut bytes::BytesMut,
1132    keep: usize,
1133    find: impl Fn(&[u8]) -> Option<usize>,
1134) {
1135    if !*pending {
1136        return;
1137    }
1138    match find(buf) {
1139        Some(at) => {
1140            let _ = buf.split_to(at);
1141            *pending = false;
1142        }
1143        None => {
1144            let drop = buf.len().saturating_sub(keep);
1145            let _ = buf.split_to(drop);
1146        }
1147    }
1148}
1149
1150#[cfg(test)]
1151mod tests {
1152    use super::*;
1153
1154    #[test]
1155    fn flow_events_map_to_their_session_form() {
1156        use crate::event::FlowEvent;
1157        let stats = FlowStats {
1158            last_seen: Timestamp::new(7, 0),
1159            ..Default::default()
1160        };
1161        let closed: Option<SessionEvent<u8, ()>> =
1162            SessionEvent::from_flow_event(FlowEvent::Ended {
1163                key: 1u8,
1164                reason: EndReason::Fin,
1165                stats,
1166                history: Default::default(),
1167                l4: None,
1168            });
1169        assert!(matches!(
1170            closed,
1171            Some(SessionEvent::Closed { key: 1, reason: EndReason::Fin, ts, .. }) if ts == Timestamp::new(7, 0)
1172        ));
1173        let tick: Option<SessionEvent<u8, ()>> = SessionEvent::from_flow_event(FlowEvent::Tick {
1174            key: 2u8,
1175            stats: FlowStats::default(),
1176            ts: Timestamp::new(8, 0),
1177        });
1178        assert!(matches!(tick, Some(SessionEvent::Tick { key: 2, .. })));
1179    }
1180
1181    #[derive(Default, Clone)]
1182    struct CountParser {
1183        init_bytes: usize,
1184        resp_bytes: usize,
1185    }
1186
1187    impl SessionParser for CountParser {
1188        type Message = (FlowSide, usize);
1189        fn feed_initiator(&mut self, b: &[u8], _ts: Timestamp, out: &mut Vec<Self::Message>) {
1190            self.init_bytes += b.len();
1191            out.push((FlowSide::Initiator, self.init_bytes));
1192        }
1193        fn feed_responder(&mut self, b: &[u8], _ts: Timestamp, out: &mut Vec<Self::Message>) {
1194            self.resp_bytes += b.len();
1195            out.push((FlowSide::Responder, self.resp_bytes));
1196        }
1197    }
1198
1199    #[test]
1200    fn auto_impl_session_parser_factory() {
1201        // CountParser is Default + Clone + SessionParser → automatic factory.
1202        let mut f: CountParser = CountParser::default();
1203        let mut p: CountParser = SessionParserFactory::<u32>::new_parser(&mut f, &7);
1204        let mut m = Vec::new();
1205        p.feed_initiator(b"abc", Timestamp::default(), &mut m);
1206        assert_eq!(m, vec![(FlowSide::Initiator, 3)]);
1207    }
1208
1209    #[derive(Default, Clone)]
1210    struct EchoDgram;
1211    impl DatagramParser for EchoDgram {
1212        type Message = (FlowSide, Vec<u8>);
1213        fn parse(
1214            &mut self,
1215            payload: &[u8],
1216            side: FlowSide,
1217            _ts: Timestamp,
1218            out: &mut Vec<Self::Message>,
1219        ) {
1220            out.push((side, payload.to_vec()));
1221        }
1222    }
1223
1224    #[test]
1225    fn auto_impl_datagram_parser_factory() {
1226        let mut f = EchoDgram;
1227        let mut p: EchoDgram = DatagramParserFactory::<()>::new_parser(&mut f, &());
1228        let mut m = Vec::new();
1229        p.parse(b"hello", FlowSide::Responder, Timestamp::default(), &mut m);
1230        assert_eq!(m, vec![(FlowSide::Responder, b"hello".to_vec())]);
1231    }
1232}