ipfix_parser/models/message.rs
1//! IPFIX message structures
2//!
3//! This module contains the data structures representing IPFIX Messages, Sets,
4//! Template Records and Data Records as defined in
5//! [RFC 7011 Section 3](https://www.rfc-editor.org/rfc/rfc7011#section-3).
6
7use super::value::FieldValue;
8use crate::registry::{self, InformationElement};
9
10/// IPFIX Message Header - RFC 7011 Section 3.1
11///
12/// ```text
13/// 0 1 2 3
14/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
15/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16/// | Version Number | Length |
17/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
18/// | Export Time |
19/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20/// | Sequence Number |
21/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
22/// | Observation Domain ID |
23/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
24/// ```
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27pub struct MessageHeader {
28 /// Version of IPFIX to which this Message conforms (0x000a)
29 pub version: u16,
30 /// Total length of the IPFIX Message, in octets, including the Message
31 /// Header and Set(s)
32 pub length: u16,
33 /// Time at which the IPFIX Message Header leaves the Exporter, in seconds
34 /// since the UNIX epoch
35 pub export_time: u32,
36 /// Sequence counter (modulo 2^32) of all IPFIX Data Records sent in the
37 /// current stream from the current Observation Domain, prior to this
38 /// Message (RFC 7011 Errata 4396)
39 pub sequence_number: u32,
40 /// Identifier of the Observation Domain, locally unique to the Exporting
41 /// Process
42 pub observation_domain_id: u32,
43}
44
45/// Set Header - RFC 7011 Section 3.3.2
46///
47/// ```text
48/// 0 1 2 3
49/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
50/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
51/// | Set ID | Length |
52/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
53/// ```
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
56pub struct SetHeader {
57 /// Identifies the Set: 2 = Template Set, 3 = Options Template Set,
58 /// 256 and above = Data Set (the Set ID is the Template ID)
59 pub set_id: u16,
60 /// Total length of the Set, in octets, including the Set Header, all
61 /// records, and the optional padding
62 pub length: u16,
63}
64
65/// Field Specifier - RFC 7011 Section 3.2
66///
67/// ```text
68/// 0 1 2 3
69/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
70/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
71/// |E| Information Element ident. | Field Length |
72/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
73/// | Enterprise Number |
74/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
75/// ```
76///
77/// The Enterprise bit (`E`) is not stored separately: it is set if and only if
78/// `enterprise_number` is `Some`.
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
80#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
81pub struct FieldSpecifier {
82 /// Information Element identifier (15 bits, Enterprise bit removed)
83 pub information_element_identifier: u16,
84 /// Length of the encoded Information Element, in octets.
85 /// [`VARIABLE_LENGTH`](Self::VARIABLE_LENGTH) (65535) means variable-length.
86 pub field_length: u16,
87 /// IANA Private Enterprise Number, present if the Enterprise bit is set
88 pub enterprise_number: Option<u32>,
89}
90
91impl FieldSpecifier {
92 /// Field Length value reserved for variable-length Information Elements
93 /// (RFC 7011 Section 7)
94 pub const VARIABLE_LENGTH: u16 = 65535;
95
96 /// Whether the Information Element is variable-length
97 pub fn is_variable_length(&self) -> bool {
98 self.field_length == Self::VARIABLE_LENGTH
99 }
100
101 /// Whether the Field Specifier refers to a reverse Information Element
102 /// (RFC 5103, Private Enterprise Number 29305)
103 pub fn is_reverse(&self) -> bool {
104 self.enterprise_number == Some(registry::REVERSE_INFORMATION_ELEMENT_PEN)
105 }
106
107 /// The IANA Information Element this Field Specifier refers to.
108 ///
109 /// For reverse Information Elements (RFC 5103), the forward Information
110 /// Element is returned. RFC 5103 Section 6.1 lets a Collecting Process
111 /// *discard* the reverse counterpart of a non-reversible element; this
112 /// parser keeps and decodes it instead. Returns `None` for
113 /// enterprise-specific or unknown Information Elements.
114 pub fn information_element(&self) -> Option<&'static InformationElement> {
115 match self.enterprise_number {
116 None | Some(registry::REVERSE_INFORMATION_ELEMENT_PEN) => {
117 registry::lookup(self.information_element_identifier)
118 }
119 Some(_) => None,
120 }
121 }
122}
123
124/// Template Record - RFC 7011 Section 3.4.1
125///
126/// A Template Record Header followed by `field_count` Field Specifiers.
127///
128/// ```text
129/// 0 1 2 3
130/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
131/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
132/// | Template ID (> 255) | Field Count |
133/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
134/// ```
135///
136/// A record with a Field Count of 0 is a Template Withdrawal
137/// (RFC 7011 Section 8.1); Template ID 2 withdraws all Templates of the
138/// Observation Domain.
139#[derive(Debug, Clone, PartialEq, Eq, Hash)]
140#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
141pub struct TemplateRecord {
142 /// Template ID, in the range 256 to 65535 (or 2 for an All Templates
143 /// Withdrawal)
144 pub template_id: u16,
145 /// Number of fields in this Template Record
146 pub field_count: u16,
147 /// Field Specifiers
148 pub field_specifiers: Vec<FieldSpecifier>,
149}
150
151impl TemplateRecord {
152 /// Whether this record is a Template Withdrawal (RFC 7011 Section 8.1)
153 pub fn is_withdrawal(&self) -> bool {
154 self.field_count == 0
155 }
156}
157
158/// Options Template Record - RFC 7011 Section 3.4.2.2
159///
160/// An Options Template Record Header followed by `field_count` Field
161/// Specifiers, the first `scope_field_count` of which are Scope Fields.
162///
163/// ```text
164/// 0 1 2 3
165/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
166/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
167/// | Template ID (> 255) | Field Count |
168/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
169/// | Scope Field Count |
170/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
171/// ```
172///
173/// A record with a Field Count of 0 is an Options Template Withdrawal
174/// (RFC 7011 Section 8.1); it carries no Scope Field Count (set to 0 here).
175/// Template ID 3 withdraws all Options Templates of the Observation Domain.
176#[derive(Debug, Clone, PartialEq, Eq, Hash)]
177#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
178pub struct OptionsTemplateRecord {
179 /// Template ID, in the range 256 to 65535 (or 3 for an All Options
180 /// Templates Withdrawal)
181 pub template_id: u16,
182 /// Number of all fields in this Options Template Record, including the
183 /// Scope Fields
184 pub field_count: u16,
185 /// Number of Scope Fields (MUST NOT be zero, except in a withdrawal)
186 pub scope_field_count: u16,
187 /// Field Specifiers (Scope Fields first)
188 pub field_specifiers: Vec<FieldSpecifier>,
189}
190
191impl OptionsTemplateRecord {
192 /// Whether this record is an Options Template Withdrawal
193 /// (RFC 7011 Section 8.1)
194 pub fn is_withdrawal(&self) -> bool {
195 self.field_count == 0
196 }
197}
198
199/// A decoded field of a Data Record: the Field Specifier from the Template and
200/// the decoded value
201#[derive(Debug, Clone, PartialEq)]
202#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
203pub struct Field {
204 /// Field Specifier from the Template
205 pub specifier: FieldSpecifier,
206 /// Decoded value
207 pub value: FieldValue,
208}
209
210impl Field {
211 /// The IANA Information Element of this field (forward element for
212 /// reverse fields), if known
213 pub fn information_element(&self) -> Option<&'static InformationElement> {
214 self.specifier.information_element()
215 }
216
217 /// The IANA name of this field's Information Element, if known.
218 ///
219 /// Reverse Information Elements (RFC 5103) return the forward name; use
220 /// [`FieldSpecifier::is_reverse`] to distinguish them.
221 pub fn name(&self) -> Option<&'static str> {
222 self.information_element().map(|ie| ie.name)
223 }
224}
225
226/// Data Record - RFC 7011 Section 3.4.3
227///
228/// The Field Values of a Data Record are in the same order as the Field
229/// Specifiers of the Template (or Options Template) it was decoded with.
230#[derive(Debug, Clone, PartialEq)]
231#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
232pub struct DataRecord {
233 /// Template ID used to decode this record
234 pub template_id: u16,
235 /// Number of leading Scope Fields (0 unless decoded with an Options
236 /// Template)
237 pub scope_field_count: u16,
238 /// Decoded fields
239 pub fields: Vec<Field>,
240}
241
242impl DataRecord {
243 /// Find the first IANA (non-reverse) field by Information Element name
244 pub fn get(&self, name: &str) -> Option<&FieldValue> {
245 self.fields
246 .iter()
247 .find(|f| f.specifier.enterprise_number.is_none() && f.name() == Some(name))
248 .map(|f| &f.value)
249 }
250
251 /// Find the first field by Information Element identifier and Private
252 /// Enterprise Number (`None` for IANA elements)
253 pub fn get_by_id(&self, id: u16, enterprise_number: Option<u32>) -> Option<&FieldValue> {
254 self.fields
255 .iter()
256 .find(|f| {
257 f.specifier.information_element_identifier == id
258 && f.specifier.enterprise_number == enterprise_number
259 })
260 .map(|f| &f.value)
261 }
262
263 /// The Scope Fields of a record decoded with an Options Template
264 pub fn scope_fields(&self) -> &[Field] {
265 let n = (self.scope_field_count as usize).min(self.fields.len());
266 &self.fields[..n]
267 }
268
269 /// The non-scope fields
270 pub fn non_scope_fields(&self) -> &[Field] {
271 let n = (self.scope_field_count as usize).min(self.fields.len());
272 &self.fields[n..]
273 }
274}
275
276/// IPFIX Set - RFC 7011 Section 3.3
277#[derive(Debug, Clone, PartialEq)]
278#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
279pub enum Set {
280 /// Template Set (Set ID 2)
281 Template {
282 header: SetHeader,
283 records: Vec<TemplateRecord>,
284 },
285 /// Options Template Set (Set ID 3)
286 OptionsTemplate {
287 header: SetHeader,
288 records: Vec<OptionsTemplateRecord>,
289 },
290 /// Data Set decoded with a known Template (Set ID = Template ID >= 256)
291 Data {
292 header: SetHeader,
293 records: Vec<DataRecord>,
294 },
295 /// Data Set whose Template is unknown to the decoder. RFC 7011 Section 8
296 /// lets the Collecting Process buffer or drop it; the raw Set content
297 /// (without the Set Header) is kept so the caller can decide.
298 UndecodedData { header: SetHeader, payload: Vec<u8> },
299 /// Set with a reserved Set ID (0, 1 or 4-255), kept as raw content
300 Reserved { header: SetHeader, payload: Vec<u8> },
301}
302
303impl Set {
304 /// The Set Header
305 pub fn header(&self) -> &SetHeader {
306 match self {
307 Set::Template { header, .. }
308 | Set::OptionsTemplate { header, .. }
309 | Set::Data { header, .. }
310 | Set::UndecodedData { header, .. }
311 | Set::Reserved { header, .. } => header,
312 }
313 }
314}
315
316/// IPFIX Message - RFC 7011 Section 3
317#[derive(Debug, Clone, PartialEq)]
318#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
319pub struct Message {
320 /// Message Header
321 pub header: MessageHeader,
322 /// Sets, in the order they appear in the Message
323 pub sets: Vec<Set>,
324}
325
326impl Message {
327 /// Iterate over all decoded Data Records of the Message
328 pub fn data_records(&self) -> impl Iterator<Item = &DataRecord> {
329 self.sets.iter().flat_map(|set| match set {
330 Set::Data { records, .. } => records.as_slice(),
331 _ => &[],
332 })
333 }
334}