Skip to main content

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}