Skip to main content

ipfix_parser/parsers/
decoder.rs

1//! Message, Set and Template parsing, and the stateful Template cache
2//!
3//! # Template management (RFC 7011 Section 8)
4//!
5//! Templates are scoped to a Transport Session and an Observation Domain. A
6//! [`Decoder`] represents one Transport Session: create one per Exporter (TCP
7//! connection, SCTP association, or UDP source) and keep it alive while the
8//! session lasts. Within a [`Decoder`], Templates are keyed by
9//! (Observation Domain ID, Template ID).
10//!
11//! Templates and Template Withdrawals take effect in the order in which they
12//! appear in a Message (RFC 7011 Section 8.1), so a Data Set can be decoded
13//! with a Template defined earlier in the same Message.
14//!
15//! # Transport (RFC 7011 Section 8.4)
16//!
17//! Template management depends on the transport, set with
18//! [`DecoderConfig::transport`] ([`Transport::Udp`] by default):
19//!
20//! - Over UDP, Template Withdrawals MUST be ignored by the Collecting Process.
21//!   They are still returned in the parsed [`Set`], but do not change the
22//!   cache. This also prevents a spoofed UDP packet from wiping the cache.
23//! - Over UDP, Templates not refreshed within
24//!   [`DecoderConfig::template_lifetime`] are discarded. Time is the Export
25//!   Time of the Message Headers, so decoding stays deterministic and does not
26//!   depend on the system clock: a Template is expired when the Export Time of
27//!   the current Message is more than the lifetime after the Export Time of
28//!   the Message that last carried it. The RFC suggests a lifetime of at least
29//!   3 times the Exporter's Template retransmission interval.
30//! - Over TCP and SCTP, Templates live until withdrawn or until the Transport
31//!   Session ends ([`Decoder::clear`]).
32//! - IPFIX Files (RFC 5655) are a serialized Message stream read as one
33//!   Transport Session. A File Reader must support the union of the Template
34//!   management features of all transports (RFC 5655 Section 7.1): Templates
35//!   can be redefined, Template Withdrawals are applied, and Templates never
36//!   expire.
37//!
38//! # Known Specification Errata
39//!
40//! - **RFC 7011 Errata 7413 (Section 3.4.1):** a Template whose Data Records
41//!   would be zero octets long is rejected. Otherwise every octet of a Data Set
42//!   would hold infinitely many zero-length records.
43
44use super::elements::ElementTable;
45use super::error::{ParseError, Result};
46use super::value::{decode_record, DecodeContext};
47use super::Cursor;
48use crate::models::*;
49use crate::registry::{MIN_DATA_SET_ID, OPTIONS_TEMPLATE_SET_ID, TEMPLATE_SET_ID};
50use std::collections::HashMap;
51
52/// Length of the IPFIX Message Header, in octets
53const MESSAGE_HEADER_LENGTH: usize = 16;
54
55/// Length of the Set Header, in octets
56const SET_HEADER_LENGTH: usize = 4;
57
58/// Transport protocol of the Transport Session (RFC 7011 Section 10)
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
60pub enum Transport {
61    /// UDP: Template Withdrawals are ignored, and Templates can expire (see
62    /// [`DecoderConfig::template_lifetime`])
63    #[default]
64    Udp,
65    /// TCP: Template Withdrawals are applied, Templates never expire
66    Tcp,
67    /// SCTP: Template Withdrawals are applied, Templates never expire
68    Sctp,
69    /// IPFIX File (RFC 5655): Template Withdrawals are applied, Templates never
70    /// expire. One file is one Transport Session.
71    File,
72}
73
74/// Decoder configuration
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub struct DecoderConfig {
77    /// Transport protocol of the Transport Session, which drives Template
78    /// management (RFC 7011 Section 8.4). Defaults to UDP, the transport of
79    /// most deployments.
80    pub transport: Transport,
81    /// Template lifetime in seconds, for UDP only (RFC 7011 Section 8.4):
82    /// Templates not refreshed for longer than this are discarded. `None`
83    /// (the default) keeps Templates until they are redefined.
84    pub template_lifetime: Option<u32>,
85    /// Maximum number of Templates and Options Templates kept in the cache,
86    /// across all Observation Domains. Defining a new Template beyond this
87    /// limit returns [`ParseError::TemplateLimitExceeded`]; redefining an
88    /// existing Template is always allowed.
89    pub max_templates: usize,
90    /// Maximum nesting depth of structured data (RFC 6313 lists). Deeper lists
91    /// are kept as [`FieldValue::Raw`].
92    pub max_list_depth: usize,
93    /// Maximum number of enterprise-specific Information Element definitions
94    /// learned from type records (RFC 5610), across all Observation Domains.
95    /// Further type records are ignored.
96    pub max_enterprise_elements: usize,
97}
98
99impl Default for DecoderConfig {
100    fn default() -> Self {
101        Self {
102            transport: Transport::Udp,
103            template_lifetime: None,
104            max_templates: 4096,
105            max_list_depth: 8,
106            max_enterprise_elements: 4096,
107        }
108    }
109}
110
111/// A Template or Options Template stored in the cache
112#[derive(Debug, Clone, PartialEq, Eq)]
113pub(crate) struct Template {
114    pub(crate) field_specifiers: Vec<FieldSpecifier>,
115    /// 0 for (non-options) Templates
116    pub(crate) scope_field_count: u16,
117    /// Smallest possible encoded Data Record length (variable-length fields
118    /// count as one octet)
119    pub(crate) min_record_length: usize,
120    /// Export Time of the Message that last carried this Template
121    pub(crate) last_received: u32,
122}
123
124impl Template {
125    fn new(field_specifiers: Vec<FieldSpecifier>, scope_field_count: u16) -> Result<Self> {
126        let min_record_length = field_specifiers
127            .iter()
128            .map(|f| {
129                if f.is_variable_length() {
130                    1
131                } else {
132                    f.field_length as usize
133                }
134            })
135            .sum();
136        if min_record_length == 0 {
137            return Err(ParseError::InvalidData(
138                "Template describes zero-length Data Records".to_string(),
139            ));
140        }
141        Ok(Self {
142            field_specifiers,
143            scope_field_count,
144            min_record_length,
145            last_received: 0,
146        })
147    }
148
149    fn is_options(&self) -> bool {
150        self.scope_field_count > 0
151    }
152}
153
154pub(crate) type TemplateCache = HashMap<(u32, u16), Template>;
155
156/// Stateful IPFIX decoder holding the Template cache of one Transport Session
157#[derive(Debug, Clone, Default)]
158pub struct Decoder {
159    config: DecoderConfig,
160    templates: TemplateCache,
161    /// Enterprise-specific Information Element definitions (RFC 5610)
162    elements: ElementTable,
163    /// Export Time of the last expiry sweep (sweeps run at most once per
164    /// second of Export Time)
165    last_sweep: Option<u32>,
166}
167
168impl Decoder {
169    /// Create a decoder with the default configuration
170    pub fn new() -> Self {
171        Self::default()
172    }
173
174    /// Create a decoder with a custom configuration
175    pub fn with_config(config: DecoderConfig) -> Self {
176        Self {
177            config,
178            templates: HashMap::new(),
179            elements: ElementTable::default(),
180            last_sweep: None,
181        }
182    }
183
184    /// Number of Templates and Options Templates in the cache
185    pub fn template_count(&self) -> usize {
186        self.templates.len()
187    }
188
189    /// Whether a Template (or Options Template) is known for this Observation
190    /// Domain and Template ID
191    pub fn has_template(&self, observation_domain_id: u32, template_id: u16) -> bool {
192        self.templates
193            .contains_key(&(observation_domain_id, template_id))
194    }
195
196    /// Export Time of the Message that last carried this Template (or Options
197    /// Template), i.e. the "Last Received" of RFC 7011 Section 8.4
198    pub fn template_last_received(
199        &self,
200        observation_domain_id: u32,
201        template_id: u16,
202    ) -> Option<u32> {
203        self.templates
204            .get(&(observation_domain_id, template_id))
205            .map(|t| t.last_received)
206    }
207
208    /// Discard the Templates not refreshed within
209    /// [`DecoderConfig::template_lifetime`] at time `now` (in seconds since the
210    /// UNIX epoch, like the Export Time). This runs automatically for each
211    /// decoded Message over UDP; call it to expire Templates with your own
212    /// clock, e.g. when an Exporter stopped sending. Does nothing without a
213    /// lifetime.
214    pub fn expire_templates(&mut self, now: u32) {
215        if let Some(lifetime) = self.config.template_lifetime {
216            self.templates
217                .retain(|_, t| now.saturating_sub(t.last_received) <= lifetime);
218        }
219    }
220
221    /// Register the definition of an enterprise-specific Information Element
222    /// (e.g. from vendor documentation), so that its fields are decoded with
223    /// its data type instead of being kept as [`FieldValue::Raw`].
224    ///
225    /// Registered definitions apply to all Observation Domains, survive
226    /// [`clear`](Self::clear), and always take precedence over type records
227    /// sent by the Exporter (RFC 5610 Section 3.9). Registering the same
228    /// element again replaces the definition. Fails for reverse elements
229    /// (PEN 29305) and for IANA elements already in the registry.
230    pub fn register_enterprise_element(&mut self, element: EnterpriseElement) -> Result<()> {
231        self.elements.register(element)
232    }
233
234    /// The definition of an enterprise-specific Information Element: the
235    /// registered one, or else the one learned from type records (RFC 5610)
236    /// in this Observation Domain. With `enterprise_number` 0, the definition
237    /// of an IANA element newer than the registry.
238    pub fn enterprise_element(
239        &self,
240        observation_domain_id: u32,
241        enterprise_number: u32,
242        id: u16,
243    ) -> Option<&EnterpriseElement> {
244        self.elements
245            .get(observation_domain_id, enterprise_number, id)
246    }
247
248    /// Number of enterprise-specific Information Element definitions learned
249    /// from type records (RFC 5610), excluding conflicting ones
250    pub fn learned_element_count(&self) -> usize {
251        self.elements.learned_count()
252    }
253
254    /// The name of a field's Information Element: the IANA name (forward name
255    /// for reverse elements), or the name of its enterprise-specific
256    /// definition
257    pub fn field_name(
258        &self,
259        observation_domain_id: u32,
260        specifier: &FieldSpecifier,
261    ) -> Option<&str> {
262        if let Some(ie) = specifier.information_element() {
263            return Some(ie.name);
264        }
265        let pen = match specifier.enterprise_number {
266            None | Some(crate::registry::REVERSE_INFORMATION_ELEMENT_PEN) => 0,
267            // PEN 0 with the Enterprise bit set is not an IANA element
268            Some(0) => return None,
269            Some(pen) => pen,
270        };
271        self.elements
272            .get(
273                observation_domain_id,
274                pen,
275                specifier.information_element_identifier,
276            )
277            .and_then(|e| e.name.as_deref())
278    }
279
280    /// Remove all Templates and learned type records (e.g. at the end of the
281    /// Transport Session). Registered definitions are kept.
282    pub fn clear(&mut self) {
283        self.templates.clear();
284        self.elements.clear_learned();
285    }
286
287    /// Remove all Templates and learned type records of an Observation Domain
288    pub fn remove_observation_domain(&mut self, observation_domain_id: u32) {
289        self.templates
290            .retain(|(domain, _), _| *domain != observation_domain_id);
291        self.elements.remove_domain(observation_domain_id);
292    }
293
294    /// Decode one IPFIX Message, updating the Template cache.
295    ///
296    /// Octets after the Message Length are ignored; use
297    /// [`decode_all`](Self::decode_all) for a buffer holding several Messages.
298    pub fn decode(&mut self, data: &[u8]) -> Result<Message> {
299        let mut cursor = Cursor::new(data);
300        let header = parse_message_header(&mut cursor)?;
301        let body = cursor.read_bytes(
302            header.length as usize - MESSAGE_HEADER_LENGTH,
303            "message body",
304        )?;
305
306        let now = header.export_time;
307        if self.config.transport == Transport::Udp && self.last_sweep != Some(now) {
308            self.expire_templates(now);
309            self.last_sweep = Some(now);
310        }
311
312        let mut sets = Vec::new();
313        let mut cursor = Cursor::new(body);
314        while !cursor.is_empty() {
315            sets.push(self.parse_set(&mut cursor, header.observation_domain_id, now)?);
316        }
317
318        Ok(Message { header, sets })
319    }
320
321    /// Decode a buffer of consecutive IPFIX Messages (e.g. a TCP stream or an
322    /// IPFIX File), updating the Template cache.
323    pub fn decode_all(&mut self, data: &[u8]) -> Result<Vec<Message>> {
324        let mut messages = Vec::new();
325        let mut offset = 0;
326        while offset < data.len() {
327            let message = self.decode(&data[offset..])?;
328            offset += message.header.length as usize;
329            messages.push(message);
330        }
331        Ok(messages)
332    }
333
334    fn parse_set(&mut self, cursor: &mut Cursor, domain: u32, now: u32) -> Result<Set> {
335        let header = SetHeader {
336            set_id: cursor.read_u16("set header")?,
337            length: cursor.read_u16("set header")?,
338        };
339        if (header.length as usize) < SET_HEADER_LENGTH {
340            return Err(ParseError::InvalidData(format!(
341                "Set length {} is smaller than the Set Header",
342                header.length
343            )));
344        }
345        let content = cursor.read_bytes(header.length as usize - SET_HEADER_LENGTH, "set")?;
346        let mut content = Cursor::new(content);
347
348        match header.set_id {
349            TEMPLATE_SET_ID => Ok(Set::Template {
350                header,
351                records: self.parse_template_set(&mut content, domain, now)?,
352            }),
353            OPTIONS_TEMPLATE_SET_ID => Ok(Set::OptionsTemplate {
354                header,
355                records: self.parse_options_template_set(&mut content, domain, now)?,
356            }),
357            id if id >= MIN_DATA_SET_ID => match self.templates.get(&(domain, id)) {
358                Some(template) => {
359                    let ctx = DecodeContext {
360                        templates: &self.templates,
361                        elements: &self.elements,
362                        observation_domain_id: domain,
363                        max_depth: self.config.max_list_depth,
364                    };
365                    let mut records = Vec::new();
366                    // Records are read until the remaining octets are fewer than the
367                    // smallest possible record: those are padding (RFC 7011 Section 3.3.1)
368                    while content.remaining() >= template.min_record_length {
369                        records.push(decode_record(&ctx, id, template, &mut content, 0)?);
370                    }
371                    // Information Element type records (RFC 5610) apply to the
372                    // following Data Sets
373                    for record in records.iter().filter(|r| r.scope_field_count > 0) {
374                        self.elements
375                            .learn(domain, record, self.config.max_enterprise_elements);
376                    }
377                    Ok(Set::Data { header, records })
378                }
379                None => Ok(Set::UndecodedData {
380                    header,
381                    payload: content.read_rest().to_vec(),
382                }),
383            },
384            _ => Ok(Set::Reserved {
385                header,
386                payload: content.read_rest().to_vec(),
387            }),
388        }
389    }
390
391    /// Parse a Template Set (RFC 7011 Section 3.4.1) and apply it to the cache
392    fn parse_template_set(
393        &mut self,
394        cursor: &mut Cursor,
395        domain: u32,
396        now: u32,
397    ) -> Result<Vec<TemplateRecord>> {
398        let mut records = Vec::new();
399        // A Template Record is at least 4 octets: fewer remaining octets are padding
400        while cursor.remaining() >= 4 {
401            let template_id = cursor.read_u16("template record header")?;
402            let field_count = cursor.read_u16("template record header")?;
403
404            if field_count == 0 {
405                self.withdraw(domain, template_id, false)?;
406                records.push(TemplateRecord {
407                    template_id,
408                    field_count,
409                    field_specifiers: Vec::new(),
410                });
411                continue;
412            }
413
414            check_template_id(template_id)?;
415            let field_specifiers = parse_field_specifiers(cursor, field_count)?;
416            self.insert(
417                domain,
418                template_id,
419                Template::new(field_specifiers.clone(), 0)?,
420                now,
421            )?;
422            records.push(TemplateRecord {
423                template_id,
424                field_count,
425                field_specifiers,
426            });
427        }
428        Ok(records)
429    }
430
431    /// Parse an Options Template Set (RFC 7011 Section 3.4.2.2) and apply it to
432    /// the cache
433    fn parse_options_template_set(
434        &mut self,
435        cursor: &mut Cursor,
436        domain: u32,
437        now: u32,
438    ) -> Result<Vec<OptionsTemplateRecord>> {
439        let mut records = Vec::new();
440        // An Options Template Withdrawal is 4 octets: fewer remaining octets are padding
441        while cursor.remaining() >= 4 {
442            let template_id = cursor.read_u16("options template record header")?;
443            let field_count = cursor.read_u16("options template record header")?;
444
445            if field_count == 0 {
446                self.withdraw(domain, template_id, true)?;
447                records.push(OptionsTemplateRecord {
448                    template_id,
449                    field_count,
450                    scope_field_count: 0,
451                    field_specifiers: Vec::new(),
452                });
453                continue;
454            }
455
456            check_template_id(template_id)?;
457            let scope_field_count = cursor.read_u16("options template record header")?;
458            if scope_field_count == 0 || scope_field_count > field_count {
459                return Err(ParseError::InvalidData(format!(
460                    "Invalid Scope Field Count {} for Field Count {}",
461                    scope_field_count, field_count
462                )));
463            }
464            let field_specifiers = parse_field_specifiers(cursor, field_count)?;
465            self.insert(
466                domain,
467                template_id,
468                Template::new(field_specifiers.clone(), scope_field_count)?,
469                now,
470            )?;
471            records.push(OptionsTemplateRecord {
472                template_id,
473                field_count,
474                scope_field_count,
475                field_specifiers,
476            });
477        }
478        Ok(records)
479    }
480
481    fn insert(
482        &mut self,
483        domain: u32,
484        template_id: u16,
485        mut template: Template,
486        now: u32,
487    ) -> Result<()> {
488        template.last_received = now;
489        let key = (domain, template_id);
490        if !self.templates.contains_key(&key) && self.templates.len() >= self.config.max_templates {
491            return Err(ParseError::TemplateLimitExceeded(self.config.max_templates));
492        }
493        self.templates.insert(key, template);
494        Ok(())
495    }
496
497    /// Apply a Template Withdrawal (RFC 7011 Section 8.1). Over UDP,
498    /// withdrawals MUST be ignored (RFC 7011 Section 8.4).
499    fn withdraw(&mut self, domain: u32, template_id: u16, options: bool) -> Result<()> {
500        if self.config.transport == Transport::Udp {
501            return Ok(());
502        }
503        let withdraw_all_id = if options {
504            OPTIONS_TEMPLATE_SET_ID
505        } else {
506            TEMPLATE_SET_ID
507        };
508        if template_id == withdraw_all_id {
509            // All (Options) Templates Withdrawal
510            self.templates
511                .retain(|(d, _), t| *d != domain || t.is_options() != options);
512            Ok(())
513        } else if template_id >= MIN_DATA_SET_ID {
514            self.templates.remove(&(domain, template_id));
515            Ok(())
516        } else {
517            Err(ParseError::InvalidData(format!(
518                "Invalid Template ID {} in Template Withdrawal",
519                template_id
520            )))
521        }
522    }
523}
524
525/// Parse an IPFIX Message Header (RFC 7011 Section 3.1)
526fn parse_message_header(cursor: &mut Cursor) -> Result<MessageHeader> {
527    let available = cursor.remaining();
528    let header = MessageHeader {
529        version: cursor.read_u16("message header")?,
530        length: cursor.read_u16("message header")?,
531        export_time: cursor.read_u32("message header")?,
532        sequence_number: cursor.read_u32("message header")?,
533        observation_domain_id: cursor.read_u32("message header")?,
534    };
535    if header.version != crate::registry::IPFIX_VERSION {
536        return Err(ParseError::InvalidVersion(header.version));
537    }
538    if (header.length as usize) < MESSAGE_HEADER_LENGTH {
539        return Err(ParseError::InvalidData(format!(
540            "Message length {} is smaller than the Message Header",
541            header.length
542        )));
543    }
544    if header.length as usize > available {
545        return Err(ParseError::Truncated {
546            context: "message",
547            needed: header.length as usize,
548            available,
549        });
550    }
551    Ok(header)
552}
553
554fn check_template_id(template_id: u16) -> Result<()> {
555    if template_id < MIN_DATA_SET_ID {
556        return Err(ParseError::InvalidData(format!(
557            "Invalid Template ID {} (must be > 255)",
558            template_id
559        )));
560    }
561    Ok(())
562}
563
564/// Parse `field_count` Field Specifiers (RFC 7011 Section 3.2)
565fn parse_field_specifiers(cursor: &mut Cursor, field_count: u16) -> Result<Vec<FieldSpecifier>> {
566    // Each Field Specifier is at least 4 octets: bound the allocation by the input
567    let mut field_specifiers =
568        Vec::with_capacity((field_count as usize).min(cursor.remaining() / 4));
569    for _ in 0..field_count {
570        let (information_element_identifier, enterprise) =
571            cursor.read_field_id("field specifier")?;
572        let field_length = cursor.read_u16("field specifier")?;
573        let enterprise_number = if enterprise {
574            Some(cursor.read_u32("field specifier")?)
575        } else {
576            None
577        };
578        field_specifiers.push(FieldSpecifier {
579            information_element_identifier,
580            field_length,
581            enterprise_number,
582        });
583    }
584    Ok(field_specifiers)
585}
586
587/// Parse a single self-contained IPFIX Message.
588///
589/// Only Templates defined in the Message itself are used to decode its Data
590/// Sets; Data Sets with unknown Templates are returned as
591/// [`Set::UndecodedData`]. Use a [`Decoder`] to keep Templates across Messages.
592///
593/// This uses the default [`DecoderConfig`] ([`Transport::Udp`], the transport
594/// of a single datagram): Template Withdrawals are ignored.
595pub fn parse_message(data: &[u8]) -> Result<Message> {
596    Decoder::new().decode(data)
597}
598
599/// Parse a buffer of consecutive IPFIX Messages, sharing Templates across
600/// them (as if they belonged to the same Transport Session).
601///
602/// A buffer of consecutive Messages is read as an IPFIX File (RFC 5655), with
603/// [`Transport::File`] semantics: Template Withdrawals are applied and
604/// Templates never expire. Compressed files must be decompressed by the
605/// caller.
606pub fn parse_messages(data: &[u8]) -> Result<Vec<Message>> {
607    Decoder::with_config(DecoderConfig {
608        transport: Transport::File,
609        ..DecoderConfig::default()
610    })
611    .decode_all(data)
612}