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}