#![allow(clippy::doc_markdown, clippy::cast_possible_truncation)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BerError(pub &'static str);
impl BerError {
#[must_use]
pub fn is_incomplete(&self) -> bool {
matches!(
self.0,
"empty" | "missing length" | "truncated length" | "truncated content"
)
}
#[must_use]
pub fn into_inner_malformed(self) -> BerError {
if self.is_incomplete() {
BerError("malformed inner TLV")
} else {
self
}
}
}
impl core::fmt::Display for BerError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "BER error: {}", self.0)
}
}
impl std::error::Error for BerError {}
pub fn parse_tlv(buf: &[u8]) -> Result<(u8, &[u8], &[u8]), BerError> {
let tag = *buf.first().ok_or(BerError("empty"))?;
let len_byte = *buf.get(1).ok_or(BerError("missing length"))?;
let (len, header) = if len_byte & 0x80 == 0 {
(len_byte as usize, 2)
} else {
let n = (len_byte & 0x7f) as usize;
if n == 0 {
return Err(BerError("indefinite length not allowed"));
}
if n > 4 {
return Err(BerError("length too large"));
}
let mut len = 0usize;
for i in 0..n {
let b = *buf.get(2 + i).ok_or(BerError("truncated length"))?;
len = (len << 8) | b as usize;
}
(len, 2 + n)
};
let end = header.checked_add(len).ok_or(BerError("length overflow"))?;
if end > buf.len() {
return Err(BerError("truncated content"));
}
Ok((tag, &buf[header..end], &buf[end..]))
}
pub fn parse_integer(content: &[u8]) -> Result<i64, BerError> {
if content.is_empty() || content.len() > 8 {
return Err(BerError("bad integer width"));
}
let mut value: i64 = if content[0] & 0x80 != 0 { -1 } else { 0 };
for &b in content {
value = (value << 8) | i64::from(b);
}
Ok(value)
}
pub fn parse_bool(content: &[u8]) -> Result<bool, BerError> {
match content {
[b] => Ok(*b != 0),
_ => Err(BerError("bad boolean")),
}
}
pub struct Children<'a> {
rest: &'a [u8],
}
impl<'a> Children<'a> {
#[must_use]
pub fn new(content: &'a [u8]) -> Self {
Self { rest: content }
}
}
impl<'a> Iterator for Children<'a> {
type Item = Result<(u8, &'a [u8]), BerError>;
fn next(&mut self) -> Option<Self::Item> {
if self.rest.is_empty() {
return None;
}
match parse_tlv(self.rest) {
Ok((tag, content, rest)) => {
self.rest = rest;
Some(Ok((tag, content)))
}
Err(e) => {
self.rest = &[];
Some(Err(e))
}
}
}
}
fn encode_len(len: usize) -> Vec<u8> {
if len < 0x80 {
vec![len as u8]
} else {
let mut bytes = Vec::new();
let mut n = len;
while n > 0 {
bytes.insert(0, (n & 0xff) as u8);
n >>= 8;
}
let mut out = vec![0x80 | (bytes.len() as u8)];
out.extend_from_slice(&bytes);
out
}
}
#[must_use]
pub fn tlv(tag: u8, content: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(content.len() + 4);
out.push(tag);
out.extend_from_slice(&encode_len(content.len()));
out.extend_from_slice(content);
out
}
#[must_use]
pub fn integer_content(value: i64) -> Vec<u8> {
if value == 0 {
return vec![0];
}
let be = value.to_be_bytes();
let mut start = 0;
while start < 7 {
let b = be[start];
let next = be[start + 1];
let redundant = (b == 0x00 && next & 0x80 == 0) || (b == 0xff && next & 0x80 != 0);
if redundant {
start += 1;
} else {
break;
}
}
be[start..].to_vec()
}
#[must_use]
pub fn encode_integer(value: i64) -> Vec<u8> {
tlv(0x02, &integer_content(value))
}
#[must_use]
pub fn encode_enumerated(value: i64) -> Vec<u8> {
tlv(0x0a, &integer_content(value))
}
#[must_use]
pub fn encode_octet_string(s: &[u8]) -> Vec<u8> {
tlv(0x04, s)
}
#[must_use]
pub fn encode_sequence(children: &[Vec<u8>]) -> Vec<u8> {
let content: Vec<u8> = children.concat();
tlv(0x30, &content)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trips_integer() {
for v in [0i64, 1, 127, 128, 255, 256, -1, -128, 65_535, 2_147_483_647] {
let enc = encode_integer(v);
let (tag, content, rest) = parse_tlv(&enc).unwrap();
assert_eq!(tag, 0x02);
assert!(rest.is_empty());
assert_eq!(parse_integer(content).unwrap(), v);
}
}
#[test]
fn short_and_long_lengths() {
let s = encode_octet_string(b"hi");
assert_eq!(s, vec![0x04, 0x02, b'h', b'i']);
let big = vec![0u8; 200];
let enc = encode_octet_string(&big);
assert_eq!(&enc[..2], &[0x04, 0x81]);
assert_eq!(enc[2], 200);
let (tag, content, _) = parse_tlv(&enc).unwrap();
assert_eq!(tag, 0x04);
assert_eq!(content.len(), 200);
}
#[test]
fn sequence_children_iterate() {
let seq = encode_sequence(&[encode_integer(5), encode_octet_string(b"x")]);
let (tag, content, _) = parse_tlv(&seq).unwrap();
assert_eq!(tag, 0x30);
let kids: Vec<_> = Children::new(content).map(Result::unwrap).collect();
assert_eq!(kids.len(), 2);
assert_eq!(kids[0].0, 0x02);
assert_eq!(parse_integer(kids[0].1).unwrap(), 5);
assert_eq!(kids[1].0, 0x04);
assert_eq!(kids[1].1, b"x");
}
#[test]
fn rejects_truncated_and_indefinite() {
assert!(parse_tlv(&[0x04, 0x05, b'a']).is_err()); assert!(parse_tlv(&[0x30, 0x80]).is_err()); assert!(parse_tlv(&[]).is_err());
}
#[test]
fn bool_parse() {
assert!(parse_bool(&[0xff]).unwrap());
assert!(!parse_bool(&[0x00]).unwrap());
assert!(parse_bool(&[0x00, 0x00]).is_err());
}
}