Skip to main content

flowparser_sflow/samples/
mod.rs

1pub mod counter_sample;
2pub mod discarded_packet;
3pub mod flow_sample;
4
5use nom::number::complete::be_u32;
6use serde::{Deserialize, Serialize};
7
8use crate::error::{ParseContext, ParseErrorKind, SflowError};
9pub use counter_sample::{CounterSample, ExpandedCounterSample};
10pub use discarded_packet::DiscardedPacket;
11pub use flow_sample::{ExpandedFlowSample, FlowSample};
12
13/// An sFlow sample carried within a datagram.
14///
15/// Each datagram can contain a mix of flow samples (packet-level data)
16/// and counter samples (interface statistics). Expanded variants use
17/// separate fields for source ID type and index instead of a packed u32.
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
19pub enum SflowSample {
20    /// Standard flow sample (enterprise=0, format=1).
21    Flow(FlowSample),
22    /// Standard counter sample (enterprise=0, format=2).
23    Counter(CounterSample),
24    /// Expanded flow sample with unpacked source ID (enterprise=0, format=3).
25    ExpandedFlow(ExpandedFlowSample),
26    /// Expanded counter sample with unpacked source ID (enterprise=0, format=4).
27    ExpandedCounter(ExpandedCounterSample),
28    /// Discarded packet notification (enterprise=0, format=5).
29    DiscardedPacket(DiscardedPacket),
30    /// Unrecognized sample type, preserved as raw bytes.
31    Unknown {
32        /// Enterprise code from the sample header.
33        enterprise: u32,
34        /// Format code from the sample header.
35        format: u32,
36        /// Raw sample data.
37        data: Vec<u8>,
38    },
39}
40
41pub(crate) fn parse_samples(
42    mut input: &[u8],
43    num_samples: u32,
44) -> Result<(&[u8], Vec<SflowSample>), SflowError> {
45    // Cap capacity to prevent DoS: each sample needs at least 8 bytes (format + length)
46    let cap = (num_samples as usize).min(input.len() / 8);
47    let mut samples = Vec::with_capacity(cap);
48
49    for _ in 0..num_samples {
50        let (rest, data_format) =
51            be_u32(input).map_err(|_: nom::Err<nom::error::Error<&[u8]>>| {
52                SflowError::Incomplete {
53                    available: input.len(),
54                    expected: None,
55                    context: ParseContext::SampleDataFormat,
56                }
57            })?;
58
59        let enterprise = data_format >> 12;
60        let format = data_format & 0xFFF;
61
62        let (rest, sample_length) =
63            be_u32(rest).map_err(|_: nom::Err<nom::error::Error<&[u8]>>| {
64                SflowError::Incomplete {
65                    available: rest.len(),
66                    expected: None,
67                    context: ParseContext::SampleLength,
68                }
69            })?;
70
71        let sample_length = sample_length as usize;
72        if rest.len() < sample_length {
73            return Err(SflowError::Incomplete {
74                available: rest.len(),
75                expected: Some(sample_length),
76                context: ParseContext::SampleData,
77            });
78        }
79
80        let sample_data = &rest[..sample_length];
81        let after_sample = &rest[sample_length..];
82
83        let sample = if enterprise == 0 {
84            match format {
85                1 => {
86                    let (_, fs) = flow_sample::parse_flow_sample(sample_data).map_err(|e| {
87                        SflowError::ParseError {
88                            offset: 0,
89                            context: ParseContext::FlowSample,
90                            kind: nom_err_to_kind(&e),
91                        }
92                    })?;
93                    SflowSample::Flow(fs)
94                }
95                2 => {
96                    let (_, cs) =
97                        counter_sample::parse_counter_sample(sample_data).map_err(|e| {
98                            SflowError::ParseError {
99                                offset: 0,
100                                context: ParseContext::CounterSample,
101                                kind: nom_err_to_kind(&e),
102                            }
103                        })?;
104                    SflowSample::Counter(cs)
105                }
106                3 => {
107                    let (_, efs) = flow_sample::parse_expanded_flow_sample(sample_data)
108                        .map_err(|e| SflowError::ParseError {
109                            offset: 0,
110                            context: ParseContext::ExpandedFlowSample,
111                            kind: nom_err_to_kind(&e),
112                        })?;
113                    SflowSample::ExpandedFlow(efs)
114                }
115                4 => {
116                    let (_, ecs) = counter_sample::parse_expanded_counter_sample(sample_data)
117                        .map_err(|e| SflowError::ParseError {
118                        offset: 0,
119                        context: ParseContext::ExpandedCounterSample,
120                        kind: nom_err_to_kind(&e),
121                    })?;
122                    SflowSample::ExpandedCounter(ecs)
123                }
124                5 => {
125                    let (_, dp) = discarded_packet::parse_discarded_packet(sample_data)
126                        .map_err(|e| SflowError::ParseError {
127                            offset: 0,
128                            context: ParseContext::DiscardedPacket,
129                            kind: nom_err_to_kind(&e),
130                        })?;
131                    SflowSample::DiscardedPacket(dp)
132                }
133                _ => SflowSample::Unknown {
134                    enterprise,
135                    format,
136                    data: sample_data.to_vec(),
137                },
138            }
139        } else {
140            SflowSample::Unknown {
141                enterprise,
142                format,
143                data: sample_data.to_vec(),
144            }
145        };
146
147        samples.push(sample);
148        input = after_sample;
149    }
150
151    Ok((input, samples))
152}
153
154fn nom_err_to_kind(e: &nom::Err<nom::error::Error<&[u8]>>) -> ParseErrorKind {
155    match e {
156        nom::Err::Error(e) | nom::Err::Failure(e) => ParseErrorKind::NomError(e.code),
157        nom::Err::Incomplete(_) => ParseErrorKind::NomError(nom::error::ErrorKind::Complete),
158    }
159}