use core::fmt;
use alloc::vec::Vec;
pub use crate::ber::{
ber_length_size, ber_length_size_for, decode_ber_length, encode_ber_length,
encode_ber_length_as,
};
use crate::error::{BufferTooShort, InvalidValue, OutputBufferTooSmall};
use crate::len::{need_in, need_out};
use crate::{Parse, Serialize};
pub const UNIVERSAL_LABEL_LEN: usize = 16;
pub type UniversalLabel = [u8; UNIVERSAL_LABEL_LEN];
fn universal_label_from_prefix(bytes: &[u8]) -> UniversalLabel {
let mut out = [0u8; UNIVERSAL_LABEL_LEN];
let mut i = 0;
while i < UNIVERSAL_LABEL_LEN {
out[i] = bytes[i];
i += 1;
}
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum KlvError {
BufferTooShort(BufferTooShort),
OutputBufferTooSmall(OutputBufferTooSmall),
BerIndefiniteLength,
BerLengthTooLong {
bytes: usize,
},
FixedBerLengthTooSmall {
len: u64,
width: u8,
},
InvalidLength(InvalidValue),
}
impl fmt::Display for KlvError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BufferTooShort(e) => write!(f, "{e}"),
Self::OutputBufferTooSmall(e) => write!(f, "{e}"),
Self::BerIndefiniteLength => {
f.write_str("BER length used the reserved indefinite-length token (0x80)")
}
Self::BerLengthTooLong { bytes } => write!(
f,
"BER long-form length has {bytes} following bytes, exceeding the 8-byte cap"
),
Self::FixedBerLengthTooSmall { len, width } => write!(
f,
"length {len} does not fit in the fixed BER width of {width} byte(s)"
),
Self::InvalidLength(e) => write!(f, "{e}"),
}
}
}
impl core::error::Error for KlvError {}
impl From<BufferTooShort> for KlvError {
fn from(e: BufferTooShort) -> Self {
Self::BufferTooShort(e)
}
}
impl From<OutputBufferTooSmall> for KlvError {
fn from(e: OutputBufferTooSmall) -> Self {
Self::OutputBufferTooSmall(e)
}
}
pub type Result<T> = core::result::Result<T, KlvError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum BerLength {
#[default]
Minimal,
Fixed(core::num::NonZeroU8),
}
impl BerLength {
#[must_use]
pub fn fixed_from_consumed(consumed: usize) -> Self {
Self::Fixed(
u8::try_from(consumed)
.ok()
.and_then(core::num::NonZeroU8::new)
.unwrap_or(core::num::NonZeroU8::MIN),
)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct KlvItem<'a> {
pub key: UniversalLabel,
pub value: &'a [u8],
pub len_size: BerLength,
}
impl PartialEq for KlvItem<'_> {
fn eq(&self, other: &Self) -> bool {
self.key == other.key && self.value == other.value
}
}
impl Eq for KlvItem<'_> {}
impl<'a> KlvItem<'a> {
#[must_use]
pub fn new(key: UniversalLabel, value: &'a [u8]) -> Self {
Self {
key,
value,
len_size: BerLength::Minimal,
}
}
}
impl<'a> KlvItem<'a> {
pub fn parse_prefix(bytes: &'a [u8]) -> Result<(Self, usize)> {
need_in(bytes, UNIVERSAL_LABEL_LEN, "KLV key")?;
let key: UniversalLabel = universal_label_from_prefix(bytes);
let (len, len_token_size) = decode_ber_length(&bytes[UNIVERSAL_LABEL_LEN..])?;
let value_start = UNIVERSAL_LABEL_LEN + len_token_size;
let len = usize::try_from(len).map_err(|_| {
KlvError::BufferTooShort(BufferTooShort {
need: usize::MAX,
have: bytes.len(),
what: "KLV value (length exceeds platform usize)",
})
})?;
let value_end = value_start
.checked_add(len)
.ok_or(KlvError::BufferTooShort(BufferTooShort {
need: usize::MAX,
have: bytes.len(),
what: "KLV value (length overflow)",
}))?;
need_in(bytes, value_end, "KLV value")?;
Ok((
KlvItem {
key,
value: &bytes[value_start..value_end],
len_size: BerLength::fixed_from_consumed(len_token_size),
},
value_end,
))
}
}
impl<'a> Parse<'a> for KlvItem<'a> {
type Error = KlvError;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let (item, consumed) = Self::parse_prefix(bytes)?;
if consumed != bytes.len() {
return Err(KlvError::BufferTooShort(BufferTooShort {
need: consumed,
have: bytes.len(),
what: "KLV item (trailing bytes after exact-fit parse)",
}));
}
Ok(item)
}
}
impl Serialize for KlvItem<'_> {
type Error = KlvError;
fn serialized_len(&self) -> usize {
UNIVERSAL_LABEL_LEN
+ ber_length_size_for(
u64::try_from(self.value.len()).unwrap_or(u64::MAX),
self.len_size,
)
+ self.value.len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let total = self.serialized_len();
need_out(buf, total)?;
buf[..UNIVERSAL_LABEL_LEN].copy_from_slice(&self.key);
let len_size = encode_ber_length_as(
u64::try_from(self.value.len()).unwrap_or(u64::MAX),
self.len_size,
&mut buf[UNIVERSAL_LABEL_LEN..],
)?;
let value_start = UNIVERSAL_LABEL_LEN + len_size;
buf[value_start..value_start + self.value.len()].copy_from_slice(self.value);
Ok(total)
}
}
pub fn walk_klv_items<'a>(
mut bytes: &'a [u8],
mut f: impl FnMut(usize, KlvItem<'a>) -> Result<()>,
) -> Result<()> {
let mut offset = 0usize;
while !bytes.is_empty() {
let (item, consumed) = KlvItem::parse_prefix(bytes)?;
f(offset, item)?;
offset += consumed;
bytes = &bytes[consumed..];
}
Ok(())
}
pub fn collect_klv_items(bytes: &[u8]) -> Result<Vec<(usize, KlvItem<'_>)>> {
let mut out = Vec::new();
walk_klv_items(bytes, |offset, item| {
out.push((offset, item));
Ok(())
})?;
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serialize_into_short_output_is_output_buffer_too_small() {
let item = KlvItem::new([0xAAu8; 16], &[1, 2, 3, 4]);
let need = item.serialized_len();
let mut buf = alloc::vec![0u8; need - 1];
assert_eq!(
item.serialize_into(&mut buf),
Err(KlvError::OutputBufferTooSmall(OutputBufferTooSmall {
need,
have: need - 1
}))
);
}
#[test]
fn klv_item_round_trip_short_form() {
let item = KlvItem::new([0xAAu8; 16], &[1, 2, 3, 4]);
let mut buf = alloc::vec![0u8; item.serialized_len()];
item.serialize_into(&mut buf).unwrap();
assert_eq!(buf.len(), 16 + 1 + 4);
let reparsed = KlvItem::parse(&buf).unwrap();
assert_eq!(reparsed.key, item.key);
assert_eq!(reparsed.value, item.value);
}
#[test]
fn klv_item_round_trip_long_form() {
let value = alloc::vec![0x42u8; 200];
let item = KlvItem::new([0xBBu8; 16], &value);
let mut buf = alloc::vec![0u8; item.serialized_len()];
item.serialize_into(&mut buf).unwrap();
assert_eq!(buf.len(), 16 + 2 + 200);
let reparsed = KlvItem::parse(&buf).unwrap();
assert_eq!(reparsed.key, item.key);
assert_eq!(reparsed.value, item.value);
}
#[test]
fn walk_multiple_items() {
let a = KlvItem::new([1u8; 16], &[10, 20]);
let b = KlvItem::new([2u8; 16], &[30, 40, 50]);
let mut buf = Vec::new();
buf.extend(a.try_to_bytes().unwrap());
buf.extend(b.try_to_bytes().unwrap());
let items = collect_klv_items(&buf).unwrap();
assert_eq!(items.len(), 2);
assert_eq!(items[0].0, 0);
assert_eq!(items[0].1.key, a.key);
assert_eq!(items[0].1.value, a.value);
assert_eq!(items[1].1.key, b.key);
assert_eq!(items[1].1.value, b.value);
}
#[test]
fn truncated_key_is_error() {
assert!(matches!(
KlvItem::parse(&[0u8; 10]),
Err(KlvError::BufferTooShort(..))
));
}
#[test]
fn non_minimal_length_token_round_trips_byte_identical() {
let mut original = alloc::vec![0xAAu8; 16];
original.extend_from_slice(&[0x82, 0x00, 0x04]);
original.extend_from_slice(&[1, 2, 3, 4]);
let item = KlvItem::parse(&original).unwrap();
assert_eq!(
item.len_size,
BerLength::Fixed(core::num::NonZeroU8::new(3).unwrap()),
"parse must record the true 3-byte on-wire length-token width"
);
let mut out = alloc::vec![0u8; item.serialized_len()];
item.serialize_into(&mut out).unwrap();
assert_eq!(
out, original,
"re-serializing a parsed item must reproduce its exact original bytes, \
including a non-minimal length token"
);
}
#[test]
fn freshly_built_item_uses_minimal_form() {
let item = KlvItem::new([0xAAu8; 16], &[1, 2, 3, 4]);
assert_eq!(item.len_size, BerLength::Minimal);
let bytes = item.try_to_bytes().unwrap();
assert_eq!(&bytes[16..17], &[0x04], "value 4 must use short form");
}
}