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}