Skip to main content

flowparser_sflow/
lib.rs

1#![doc = include_str!("../README.md")]
2#![forbid(unsafe_code)]
3
4pub mod counter_records;
5pub mod datagram;
6pub mod error;
7pub mod flow_records;
8pub mod samples;
9
10#[cfg(test)]
11mod tests;
12
13pub use counter_records::CounterRecord;
14pub use datagram::{AddressType, SflowDatagram};
15pub use error::{ParseContext, ParseErrorKind, SflowError};
16pub use flow_records::FlowRecord;
17pub use samples::SflowSample;
18
19use serde::{Deserialize, Serialize};
20
21/// Result of parsing one or more sFlow datagrams from a byte buffer.
22///
23/// Contains all successfully parsed datagrams and an optional error
24/// if parsing failed partway through. This allows partial results
25/// when a buffer contains multiple datagrams and one is malformed.
26#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
27pub struct ParseResult {
28    /// Successfully parsed sFlow datagrams.
29    pub datagrams: Vec<SflowDatagram>,
30    /// Error encountered during parsing, if any. When present,
31    /// `datagrams` may still contain successfully parsed entries
32    /// from before the error occurred.
33    pub error: Option<SflowError>,
34}
35
36/// Stateless sFlow v5 datagram parser.
37///
38/// Unlike NetFlow V9/IPFIX parsers, `SflowParser` requires no mutable state
39/// between calls since sFlow v5 is fully self-describing. Each call to
40/// [`parse_bytes`](SflowParser::parse_bytes) is independent.
41///
42/// # Examples
43///
44/// ```
45/// use flowparser_sflow::SflowParser;
46///
47/// let parser = SflowParser::default();
48/// let result = parser.parse_bytes(&[/* sflow datagram bytes */]);
49/// for datagram in &result.datagrams {
50///     println!("seq={} samples={}", datagram.sequence_number, datagram.samples.len());
51/// }
52/// ```
53#[derive(Debug, Clone, Default)]
54pub struct SflowParser {
55    max_samples: Option<u32>,
56}
57
58impl SflowParser {
59    /// Create a builder for configuring the parser.
60    pub fn builder() -> SflowParserBuilder {
61        SflowParserBuilder { max_samples: None }
62    }
63
64    /// Parse one or more sFlow v5 datagrams from a byte slice.
65    ///
66    /// Returns a [`ParseResult`] containing all successfully parsed datagrams
67    /// and an optional error. Parsing is stateless — each call is independent.
68    pub fn parse_bytes(&self, packet: &[u8]) -> ParseResult {
69        let mut datagrams = Vec::new();
70        let mut remaining = packet;
71
72        loop {
73            if remaining.is_empty() {
74                break;
75            }
76
77            if remaining.len() < 4 {
78                return ParseResult {
79                    datagrams,
80                    error: Some(SflowError::Incomplete {
81                        available: remaining.len(),
82                        expected: None,
83                        context: ParseContext::DatagramHeader,
84                    }),
85                };
86            }
87
88            match datagram::parse_datagram(remaining, self.max_samples) {
89                Ok((rest, dg)) => {
90                    datagrams.push(dg);
91                    remaining = rest;
92                }
93                Err(e) => {
94                    return ParseResult {
95                        datagrams,
96                        error: Some(e),
97                    };
98                }
99            }
100        }
101
102        ParseResult {
103            datagrams,
104            error: None,
105        }
106    }
107}
108
109/// Builder for configuring an [`SflowParser`].
110#[derive(Debug, Clone)]
111pub struct SflowParserBuilder {
112    max_samples: Option<u32>,
113}
114
115impl SflowParserBuilder {
116    /// Set the maximum number of samples allowed per datagram.
117    /// Datagrams exceeding this limit will return a
118    /// [`SflowError::TooManySamples`] error before parsing any samples.
119    pub fn with_max_samples(mut self, max: u32) -> Self {
120        self.max_samples = Some(max);
121        self
122    }
123
124    /// Build the configured [`SflowParser`].
125    pub fn build(self) -> SflowParser {
126        SflowParser {
127            max_samples: self.max_samples,
128        }
129    }
130}