use crate::error::{Error, Result};
pub const TAG_UNUSED: u8 = 0x00;
pub const TAG_INVALID: u8 = 0xFF;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Header {
pub tag: u8,
pub length: usize,
pub header_len: usize,
}
impl Header {
pub fn total_len(&self) -> usize {
self.header_len + self.length
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Tlv<'a> {
pub tag: u8,
pub value: &'a [u8],
}
pub fn parse_header(data: &[u8]) -> Result<Header> {
let tag = *data.first().ok_or_else(|| malformed("empty TLV"))?;
if tag == TAG_INVALID {
return Err(malformed("tag 'FF' is not a valid simple TLV tag"));
}
let first_len = *data
.get(1)
.ok_or_else(|| malformed("truncated TLV length"))?;
if first_len != 0xFF {
return Ok(Header {
tag,
length: usize::from(first_len),
header_len: 2,
});
}
let bytes = data
.get(2..4)
.ok_or_else(|| malformed("truncated long-form TLV length"))?;
let length = usize::from(u16::from_be_bytes([bytes[0], bytes[1]]));
Ok(Header {
tag,
length,
header_len: 4,
})
}
pub fn parse(data: &[u8]) -> Result<Tlv<'_>> {
let header = parse_header(data)?;
let value = data
.get(header.header_len..header.total_len())
.ok_or_else(|| malformed("TLV value is truncated"))?;
Ok(Tlv {
tag: header.tag,
value,
})
}
pub fn iter(data: &[u8]) -> Iter<'_> {
Iter { rest: data }
}
#[derive(Debug, Clone)]
pub struct Iter<'a> {
rest: &'a [u8],
}
impl<'a> Iterator for Iter<'a> {
type Item = Result<Tlv<'a>>;
fn next(&mut self) -> Option<Self::Item> {
match self.rest.first() {
None | Some(&TAG_INVALID) => return None,
Some(_) => {}
}
let result = parse_header(self.rest).and_then(|header| {
let value = self
.rest
.get(header.header_len..header.total_len())
.ok_or_else(|| malformed("TLV value is truncated"))?;
Ok((
header.total_len(),
Tlv {
tag: header.tag,
value,
},
))
});
match result {
Ok((consumed, tlv)) => {
self.rest = &self.rest[consumed..];
Some(Ok(tlv))
}
Err(err) => {
self.rest = &[];
Some(Err(err))
}
}
}
}
pub fn find(data: &[u8], tag: u8) -> Result<Option<&[u8]>> {
for tlv in iter(data) {
let tlv = tlv?;
if tlv.tag == tag {
return Ok(Some(tlv.value));
}
}
Ok(None)
}
fn malformed(what: &str) -> Error {
Error::Malformed(what.to_owned())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_the_short_length_form() {
let header = parse_header(&[0x00, 0x03, 0xAA, 0xBB, 0xCC]).unwrap();
assert_eq!(
header,
Header {
tag: 0x00,
length: 3,
header_len: 2
}
);
assert_eq!(header.total_len(), 5);
}
#[test]
fn parses_the_long_length_form() {
let header = parse_header(&[0x01, 0xFF, 0x01, 0x00]).unwrap();
assert_eq!(
header,
Header {
tag: 0x01,
length: 256,
header_len: 4
}
);
assert_eq!(header.total_len(), 260);
}
#[test]
fn length_254_still_uses_the_short_form() {
let header = parse_header(&[0x01, 0xFE]).unwrap();
assert_eq!(
header,
Header {
tag: 0x01,
length: 254,
header_len: 2
}
);
}
#[test]
fn rejects_the_invalid_tag_and_truncation() {
assert!(parse_header(&[0xFF, 0x02]).is_err());
assert!(parse_header(&[0x01]).is_err());
assert!(parse_header(&[0x01, 0xFF, 0x00]).is_err());
assert!(parse_header(&[]).is_err());
}
#[test]
fn iterates_concatenated_records() {
let data = [0x01, 0x02, 0xAA, 0xBB, 0x02, 0x01, 0xCC, 0xFF, 0xFF];
let items: Vec<_> = iter(&data).map(|t| t.unwrap()).collect();
assert_eq!(
items,
[
Tlv {
tag: 0x01,
value: &[0xAA, 0xBB]
},
Tlv {
tag: 0x02,
value: &[0xCC]
},
]
);
}
#[test]
fn tag_00_is_an_object_not_a_terminator() {
let data = [0x00, 0x03, 0x07, 0x0A, 0x02, 0x01, 0x01, 0x05];
let items: Vec<_> = iter(&data).map(|t| t.unwrap()).collect();
assert_eq!(
items,
[
Tlv {
tag: 0x00,
value: &[0x07, 0x0A, 0x02]
},
Tlv {
tag: 0x01,
value: &[0x05]
},
]
);
}
#[test]
fn finds_by_tag() {
let data = [0x00, 0x03, 0x01, 0x04, 0x02, 0x01, 0x01, 0x09];
assert_eq!(find(&data, 0x00).unwrap(), Some(&[0x01, 0x04, 0x02][..]));
assert_eq!(find(&data, 0x01).unwrap(), Some(&[0x09][..]));
assert_eq!(find(&data, 0x02).unwrap(), None);
}
}