use super::elements::ElementTable;
use super::error::{ParseError, Result};
use super::value::{decode_record, DecodeContext};
use super::Cursor;
use crate::models::*;
use crate::registry::{MIN_DATA_SET_ID, OPTIONS_TEMPLATE_SET_ID, TEMPLATE_SET_ID};
use std::collections::HashMap;
const MESSAGE_HEADER_LENGTH: usize = 16;
const SET_HEADER_LENGTH: usize = 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Transport {
#[default]
Udp,
Tcp,
Sctp,
File,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DecoderConfig {
pub transport: Transport,
pub template_lifetime: Option<u32>,
pub max_templates: usize,
pub max_list_depth: usize,
pub max_enterprise_elements: usize,
}
impl Default for DecoderConfig {
fn default() -> Self {
Self {
transport: Transport::Udp,
template_lifetime: None,
max_templates: 4096,
max_list_depth: 8,
max_enterprise_elements: 4096,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Template {
pub(crate) field_specifiers: Vec<FieldSpecifier>,
pub(crate) scope_field_count: u16,
pub(crate) min_record_length: usize,
pub(crate) last_received: u32,
}
impl Template {
fn new(field_specifiers: Vec<FieldSpecifier>, scope_field_count: u16) -> Result<Self> {
let min_record_length = field_specifiers
.iter()
.map(|f| {
if f.is_variable_length() {
1
} else {
f.field_length as usize
}
})
.sum();
if min_record_length == 0 {
return Err(ParseError::InvalidData(
"Template describes zero-length Data Records".to_string(),
));
}
Ok(Self {
field_specifiers,
scope_field_count,
min_record_length,
last_received: 0,
})
}
fn is_options(&self) -> bool {
self.scope_field_count > 0
}
}
pub(crate) type TemplateCache = HashMap<(u32, u16), Template>;
#[derive(Debug, Clone, Default)]
pub struct Decoder {
config: DecoderConfig,
templates: TemplateCache,
elements: ElementTable,
last_sweep: Option<u32>,
}
impl Decoder {
pub fn new() -> Self {
Self::default()
}
pub fn with_config(config: DecoderConfig) -> Self {
Self {
config,
templates: HashMap::new(),
elements: ElementTable::default(),
last_sweep: None,
}
}
pub fn template_count(&self) -> usize {
self.templates.len()
}
pub fn has_template(&self, observation_domain_id: u32, template_id: u16) -> bool {
self.templates
.contains_key(&(observation_domain_id, template_id))
}
pub fn template_last_received(
&self,
observation_domain_id: u32,
template_id: u16,
) -> Option<u32> {
self.templates
.get(&(observation_domain_id, template_id))
.map(|t| t.last_received)
}
pub fn expire_templates(&mut self, now: u32) {
if let Some(lifetime) = self.config.template_lifetime {
self.templates
.retain(|_, t| now.saturating_sub(t.last_received) <= lifetime);
}
}
pub fn register_enterprise_element(&mut self, element: EnterpriseElement) -> Result<()> {
self.elements.register(element)
}
pub fn enterprise_element(
&self,
observation_domain_id: u32,
enterprise_number: u32,
id: u16,
) -> Option<&EnterpriseElement> {
self.elements
.get(observation_domain_id, enterprise_number, id)
}
pub fn learned_element_count(&self) -> usize {
self.elements.learned_count()
}
pub fn field_name(
&self,
observation_domain_id: u32,
specifier: &FieldSpecifier,
) -> Option<&str> {
if let Some(ie) = specifier.information_element() {
return Some(ie.name);
}
let pen = match specifier.enterprise_number {
None | Some(crate::registry::REVERSE_INFORMATION_ELEMENT_PEN) => 0,
Some(0) => return None,
Some(pen) => pen,
};
self.elements
.get(
observation_domain_id,
pen,
specifier.information_element_identifier,
)
.and_then(|e| e.name.as_deref())
}
pub fn clear(&mut self) {
self.templates.clear();
self.elements.clear_learned();
}
pub fn remove_observation_domain(&mut self, observation_domain_id: u32) {
self.templates
.retain(|(domain, _), _| *domain != observation_domain_id);
self.elements.remove_domain(observation_domain_id);
}
pub fn decode(&mut self, data: &[u8]) -> Result<Message> {
let mut cursor = Cursor::new(data);
let header = parse_message_header(&mut cursor)?;
let body = cursor.read_bytes(
header.length as usize - MESSAGE_HEADER_LENGTH,
"message body",
)?;
let now = header.export_time;
if self.config.transport == Transport::Udp && self.last_sweep != Some(now) {
self.expire_templates(now);
self.last_sweep = Some(now);
}
let mut sets = Vec::new();
let mut cursor = Cursor::new(body);
while !cursor.is_empty() {
sets.push(self.parse_set(&mut cursor, header.observation_domain_id, now)?);
}
Ok(Message { header, sets })
}
pub fn decode_all(&mut self, data: &[u8]) -> Result<Vec<Message>> {
let mut messages = Vec::new();
let mut offset = 0;
while offset < data.len() {
let message = self.decode(&data[offset..])?;
offset += message.header.length as usize;
messages.push(message);
}
Ok(messages)
}
fn parse_set(&mut self, cursor: &mut Cursor, domain: u32, now: u32) -> Result<Set> {
let header = SetHeader {
set_id: cursor.read_u16("set header")?,
length: cursor.read_u16("set header")?,
};
if (header.length as usize) < SET_HEADER_LENGTH {
return Err(ParseError::InvalidData(format!(
"Set length {} is smaller than the Set Header",
header.length
)));
}
let content = cursor.read_bytes(header.length as usize - SET_HEADER_LENGTH, "set")?;
let mut content = Cursor::new(content);
match header.set_id {
TEMPLATE_SET_ID => Ok(Set::Template {
header,
records: self.parse_template_set(&mut content, domain, now)?,
}),
OPTIONS_TEMPLATE_SET_ID => Ok(Set::OptionsTemplate {
header,
records: self.parse_options_template_set(&mut content, domain, now)?,
}),
id if id >= MIN_DATA_SET_ID => match self.templates.get(&(domain, id)) {
Some(template) => {
let ctx = DecodeContext {
templates: &self.templates,
elements: &self.elements,
observation_domain_id: domain,
max_depth: self.config.max_list_depth,
};
let mut records = Vec::new();
while content.remaining() >= template.min_record_length {
records.push(decode_record(&ctx, id, template, &mut content, 0)?);
}
for record in records.iter().filter(|r| r.scope_field_count > 0) {
self.elements
.learn(domain, record, self.config.max_enterprise_elements);
}
Ok(Set::Data { header, records })
}
None => Ok(Set::UndecodedData {
header,
payload: content.read_rest().to_vec(),
}),
},
_ => Ok(Set::Reserved {
header,
payload: content.read_rest().to_vec(),
}),
}
}
fn parse_template_set(
&mut self,
cursor: &mut Cursor,
domain: u32,
now: u32,
) -> Result<Vec<TemplateRecord>> {
let mut records = Vec::new();
while cursor.remaining() >= 4 {
let template_id = cursor.read_u16("template record header")?;
let field_count = cursor.read_u16("template record header")?;
if field_count == 0 {
self.withdraw(domain, template_id, false)?;
records.push(TemplateRecord {
template_id,
field_count,
field_specifiers: Vec::new(),
});
continue;
}
check_template_id(template_id)?;
let field_specifiers = parse_field_specifiers(cursor, field_count)?;
self.insert(
domain,
template_id,
Template::new(field_specifiers.clone(), 0)?,
now,
)?;
records.push(TemplateRecord {
template_id,
field_count,
field_specifiers,
});
}
Ok(records)
}
fn parse_options_template_set(
&mut self,
cursor: &mut Cursor,
domain: u32,
now: u32,
) -> Result<Vec<OptionsTemplateRecord>> {
let mut records = Vec::new();
while cursor.remaining() >= 4 {
let template_id = cursor.read_u16("options template record header")?;
let field_count = cursor.read_u16("options template record header")?;
if field_count == 0 {
self.withdraw(domain, template_id, true)?;
records.push(OptionsTemplateRecord {
template_id,
field_count,
scope_field_count: 0,
field_specifiers: Vec::new(),
});
continue;
}
check_template_id(template_id)?;
let scope_field_count = cursor.read_u16("options template record header")?;
if scope_field_count == 0 || scope_field_count > field_count {
return Err(ParseError::InvalidData(format!(
"Invalid Scope Field Count {} for Field Count {}",
scope_field_count, field_count
)));
}
let field_specifiers = parse_field_specifiers(cursor, field_count)?;
self.insert(
domain,
template_id,
Template::new(field_specifiers.clone(), scope_field_count)?,
now,
)?;
records.push(OptionsTemplateRecord {
template_id,
field_count,
scope_field_count,
field_specifiers,
});
}
Ok(records)
}
fn insert(
&mut self,
domain: u32,
template_id: u16,
mut template: Template,
now: u32,
) -> Result<()> {
template.last_received = now;
let key = (domain, template_id);
if !self.templates.contains_key(&key) && self.templates.len() >= self.config.max_templates {
return Err(ParseError::TemplateLimitExceeded(self.config.max_templates));
}
self.templates.insert(key, template);
Ok(())
}
fn withdraw(&mut self, domain: u32, template_id: u16, options: bool) -> Result<()> {
if self.config.transport == Transport::Udp {
return Ok(());
}
let withdraw_all_id = if options {
OPTIONS_TEMPLATE_SET_ID
} else {
TEMPLATE_SET_ID
};
if template_id == withdraw_all_id {
self.templates
.retain(|(d, _), t| *d != domain || t.is_options() != options);
Ok(())
} else if template_id >= MIN_DATA_SET_ID {
self.templates.remove(&(domain, template_id));
Ok(())
} else {
Err(ParseError::InvalidData(format!(
"Invalid Template ID {} in Template Withdrawal",
template_id
)))
}
}
}
fn parse_message_header(cursor: &mut Cursor) -> Result<MessageHeader> {
let available = cursor.remaining();
let header = MessageHeader {
version: cursor.read_u16("message header")?,
length: cursor.read_u16("message header")?,
export_time: cursor.read_u32("message header")?,
sequence_number: cursor.read_u32("message header")?,
observation_domain_id: cursor.read_u32("message header")?,
};
if header.version != crate::registry::IPFIX_VERSION {
return Err(ParseError::InvalidVersion(header.version));
}
if (header.length as usize) < MESSAGE_HEADER_LENGTH {
return Err(ParseError::InvalidData(format!(
"Message length {} is smaller than the Message Header",
header.length
)));
}
if header.length as usize > available {
return Err(ParseError::Truncated {
context: "message",
needed: header.length as usize,
available,
});
}
Ok(header)
}
fn check_template_id(template_id: u16) -> Result<()> {
if template_id < MIN_DATA_SET_ID {
return Err(ParseError::InvalidData(format!(
"Invalid Template ID {} (must be > 255)",
template_id
)));
}
Ok(())
}
fn parse_field_specifiers(cursor: &mut Cursor, field_count: u16) -> Result<Vec<FieldSpecifier>> {
let mut field_specifiers =
Vec::with_capacity((field_count as usize).min(cursor.remaining() / 4));
for _ in 0..field_count {
let (information_element_identifier, enterprise) =
cursor.read_field_id("field specifier")?;
let field_length = cursor.read_u16("field specifier")?;
let enterprise_number = if enterprise {
Some(cursor.read_u32("field specifier")?)
} else {
None
};
field_specifiers.push(FieldSpecifier {
information_element_identifier,
field_length,
enterprise_number,
});
}
Ok(field_specifiers)
}
pub fn parse_message(data: &[u8]) -> Result<Message> {
Decoder::new().decode(data)
}
pub fn parse_messages(data: &[u8]) -> Result<Vec<Message>> {
Decoder::with_config(DecoderConfig {
transport: Transport::File,
..DecoderConfig::default()
})
.decode_all(data)
}