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::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, Eq, 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 context: "datagram header".to_string(),
83 }),
84 };
85 }
86
87 match datagram::parse_datagram(remaining, self.max_samples) {
88 Ok((rest, dg)) => {
89 datagrams.push(dg);
90 remaining = rest;
91 }
92 Err(e) => {
93 return ParseResult {
94 datagrams,
95 error: Some(e),
96 };
97 }
98 }
99 }
100
101 ParseResult {
102 datagrams,
103 error: None,
104 }
105 }
106}
107
108/// Builder for configuring an [`SflowParser`].
109#[derive(Debug, Clone)]
110pub struct SflowParserBuilder {
111 max_samples: Option<u32>,
112}
113
114impl SflowParserBuilder {
115 /// Set the maximum number of samples allowed per datagram.
116 /// Datagrams exceeding this limit will return a
117 /// [`SflowError::TooManySamples`] error before parsing any samples.
118 pub fn with_max_samples(mut self, max: u32) -> Self {
119 self.max_samples = Some(max);
120 self
121 }
122
123 /// Build the configured [`SflowParser`].
124 pub fn build(self) -> SflowParser {
125 SflowParser {
126 max_samples: self.max_samples,
127 }
128 }
129}