use std::collections::BTreeMap;
use crate::{
endian::{read_uint16, read_uint32, uint16, uint32},
error::{EmapiError, Result},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TlvEntry {
pub tag: u8,
pub value: Vec<u8>,
}
impl TlvEntry {
pub fn new(tag: u8, value: impl Into<Vec<u8>>) -> Self {
Self {
tag,
value: value.into(),
}
}
pub fn try_new(tag: u16, value: impl Into<Vec<u8>>) -> Result<Self> {
if tag > 0xFF {
return Err(EmapiError::Range {
name: "tag",
min: 0,
max: 0xFF,
value: u64::from(tag),
});
}
Ok(Self::new(tag as u8, value))
}
pub fn uint8(tag: u8, value: u8) -> Result<Self> {
Ok(Self::new(tag, [value]))
}
pub fn uint16(tag: u8, value: u16) -> Result<Self> {
Ok(Self::new(tag, uint16(value)))
}
pub fn uint32(tag: u8, value: u32) -> Result<Self> {
Ok(Self::new(tag, uint32(value)))
}
pub fn string(tag: u8, value: &str) -> Result<Self> {
let mut bytes = value.as_bytes().to_vec();
bytes.push(0x00);
Ok(Self::new(tag, bytes))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TlvData {
entries: BTreeMap<u8, TlvEntry>,
}
impl TlvData {
pub fn get(&self, tag: u8) -> Option<&TlvEntry> {
self.entries.get(&tag)
}
pub fn contains_tag(&self, tag: u8) -> bool {
self.entries.contains_key(&tag)
}
pub fn uint8(&self, tag: u8) -> Result<Option<u8>> {
let Some(value) = self.value(tag) else {
return Ok(None);
};
if value.len() != 1 {
return Err(EmapiError::InvalidTlvType {
tag,
expected: "uint8",
actual_len: value.len(),
});
}
Ok(Some(value[0]))
}
pub fn uint16(&self, tag: u8) -> Result<Option<u16>> {
let Some(value) = self.value(tag) else {
return Ok(None);
};
if value.len() != 2 {
return Err(EmapiError::InvalidTlvType {
tag,
expected: "uint16",
actual_len: value.len(),
});
}
Ok(Some(read_uint16(value, 0)?))
}
pub fn uint32(&self, tag: u8) -> Result<Option<u32>> {
let Some(value) = self.value(tag) else {
return Ok(None);
};
if value.len() != 4 {
return Err(EmapiError::InvalidTlvType {
tag,
expected: "uint32",
actual_len: value.len(),
});
}
Ok(Some(read_uint32(value, 0)?))
}
pub fn string(&self, tag: u8) -> Result<Option<String>> {
let Some(value) = self.value(tag) else {
return Ok(None);
};
let end = if value.last() == Some(&0x00) {
value.len() - 1
} else {
value.len()
};
String::from_utf8(value[..end].to_vec())
.map(Some)
.map_err(|_| EmapiError::InvalidUtf8)
}
fn value(&self, tag: u8) -> Option<&[u8]> {
self.entries.get(&tag).map(|entry| entry.value.as_slice())
}
}
pub struct Tlv;
impl Tlv {
pub fn encode(entries: &[TlvEntry]) -> Result<Vec<u8>> {
let mut result = Vec::new();
for entry in entries {
if entry.value.len() > usize::from(u16::MAX) {
return Err(EmapiError::Range {
name: "length",
min: 0,
max: u64::from(u16::MAX),
value: entry.value.len() as u64,
});
}
result.push(entry.tag);
result.extend_from_slice(&uint16(entry.value.len() as u16));
result.extend_from_slice(&entry.value);
}
Ok(result)
}
pub fn decode(data: &[u8]) -> Result<TlvData> {
let mut entries = BTreeMap::new();
let mut offset = 0;
while offset < data.len() {
if offset + 3 > data.len() {
return Err(EmapiError::TruncatedTlvHeader);
}
let tag = data[offset];
let length = usize::from(read_uint16(data, offset + 1)?);
let value_start = offset + 3;
let value_end = value_start + length;
if value_end > data.len() {
return Err(EmapiError::TruncatedTlvValue);
}
entries.insert(
tag,
TlvEntry::new(tag, data[value_start..value_end].to_vec()),
);
offset = value_end;
}
Ok(TlvData { entries })
}
}