use super::error::{ParseError, Result};
use crate::models::*;
use crate::registry::type_records::*;
use crate::registry::{self, DataType, DataTypeSemantics, REVERSE_INFORMATION_ELEMENT_PEN};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq)]
enum Learned {
Element(EnterpriseElement),
Conflicting,
}
#[derive(Debug, Clone, Default)]
pub(crate) struct ElementTable {
registered: HashMap<(u32, u16), EnterpriseElement>,
learned: HashMap<(u32, u32, u16), Learned>,
}
impl ElementTable {
pub(crate) fn register(&mut self, mut element: EnterpriseElement) -> Result<()> {
if element.enterprise_number == REVERSE_INFORMATION_ELEMENT_PEN {
return Err(ParseError::InvalidData(
"Reverse Information Elements (PEN 29305) cannot be registered".to_string(),
));
}
if element.enterprise_number == 0 && registry::lookup(element.id).is_some() {
return Err(ParseError::InvalidData(format!(
"Information Element {} is already defined by IANA",
element.id
)));
}
if element.id > 0x7FFF {
return Err(ParseError::InvalidData(format!(
"Information Element identifier {} exceeds 15 bits",
element.id
)));
}
element.source = ElementSource::Registered;
self.registered
.insert((element.enterprise_number, element.id), element);
Ok(())
}
pub(crate) fn get(&self, domain: u32, pen: u32, id: u16) -> Option<&EnterpriseElement> {
self.registered
.get(&(pen, id))
.or_else(|| match self.learned.get(&(domain, pen, id)) {
Some(Learned::Element(element)) => Some(element),
_ => None,
})
}
pub(crate) fn data_type(&self, domain: u32, specifier: &FieldSpecifier) -> Option<DataType> {
let id = specifier.information_element_identifier;
match specifier.enterprise_number {
None | Some(REVERSE_INFORMATION_ELEMENT_PEN) => registry::lookup(id)
.map(|ie| ie.data_type)
.or_else(|| self.get(domain, 0, id).map(|e| e.data_type)),
Some(0) => None,
Some(pen) => self.get(domain, pen, id).map(|e| e.data_type),
}
}
pub(crate) fn learned_count(&self) -> usize {
self.learned
.values()
.filter(|l| matches!(l, Learned::Element(_)))
.count()
}
pub(crate) fn clear_learned(&mut self) {
self.learned.clear();
}
pub(crate) fn remove_domain(&mut self, domain: u32) {
self.learned.retain(|(d, _, _), _| *d != domain);
}
pub(crate) fn learn(&mut self, domain: u32, record: &DataRecord, max_learned: usize) {
let Some(element) = parse_type_record(record) else {
return;
};
let (pen, id) = (element.enterprise_number, element.id);
if pen == REVERSE_INFORMATION_ELEMENT_PEN
|| (pen == 0 && registry::lookup(id).is_some())
|| self.registered.contains_key(&(pen, id))
{
return;
}
let key = (domain, pen, id);
match self.learned.get_mut(&key) {
Some(learned @ Learned::Element(_)) => {
if *learned != Learned::Element(element) {
*learned = Learned::Conflicting;
}
}
Some(Learned::Conflicting) => {}
None => {
if self.learned.len() < max_learned {
self.learned.insert(key, Learned::Element(element));
}
}
}
}
}
fn iana_field(fields: &[Field], id: u16) -> Option<&FieldValue> {
fields
.iter()
.find(|f| {
f.specifier.enterprise_number.is_none()
&& f.specifier.information_element_identifier == id
})
.map(|f| &f.value)
}
fn unsigned(fields: &[Field], id: u16) -> Option<u64> {
match iana_field(fields, id) {
Some(FieldValue::Unsigned(v)) => Some(*v),
_ => None,
}
}
fn text(fields: &[Field], id: u16) -> Option<String> {
match iana_field(fields, id) {
Some(FieldValue::String(s)) if !s.contains('\0') => Some(s.clone()),
_ => None,
}
}
fn parse_type_record(record: &DataRecord) -> Option<EnterpriseElement> {
let scope = record.scope_fields();
let id = unsigned(scope, INFORMATION_ELEMENT_ID)?;
let pen = unsigned(scope, PRIVATE_ENTERPRISE_NUMBER)?;
let id = u16::try_from(id).ok().filter(|id| *id <= 0x7FFF)?;
let pen = u32::try_from(pen).ok()?;
let fields = &record.fields;
let data_type =
DataType::from_value(u8::try_from(unsigned(fields, INFORMATION_ELEMENT_DATA_TYPE)?).ok()?)?;
let semantics = match unsigned(fields, INFORMATION_ELEMENT_SEMANTICS) {
Some(value) => Some(DataTypeSemantics::from_value(u8::try_from(value).ok()?)?),
None => None,
};
let element = EnterpriseElement {
enterprise_number: pen,
id,
name: text(fields, INFORMATION_ELEMENT_NAME),
data_type,
semantics,
units: unsigned(fields, INFORMATION_ELEMENT_UNITS).and_then(|u| u16::try_from(u).ok()),
range_begin: unsigned(fields, INFORMATION_ELEMENT_RANGE_BEGIN),
range_end: unsigned(fields, INFORMATION_ELEMENT_RANGE_END),
description: text(fields, INFORMATION_ELEMENT_DESCRIPTION),
source: ElementSource::TypeRecord,
};
element.has_valid_semantics().then_some(element)
}