use crate::CryptoError;
use crate::error::FormatDefect;
use crate::format::{EXT_LEN_MAX, read_u16_be, read_u32_be};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TlvClass {
Ignorable,
Critical,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct RawTlv<'a> {
pub(crate) tag: u16,
pub(crate) class: TlvClass,
#[allow(dead_code)]
pub(crate) value: &'a [u8],
}
pub(crate) fn classify_tlv_tag(tag: u16) -> Result<TlvClass, CryptoError> {
if tag == 0x0000 || tag == 0x8000 {
Err(CryptoError::InvalidFormat(FormatDefect::MalformedTlv))
} else if tag < 0x8000 {
Ok(TlvClass::Ignorable)
} else {
Ok(TlvClass::Critical)
}
}
const ENTRY_HEADER_SIZE: usize = 6;
pub(crate) fn scan_tlv_region(
bytes: &[u8],
max_region_len: u32,
max_value_len: u32,
) -> Result<Vec<RawTlv<'_>>, CryptoError> {
let region_len = u32::try_from(bytes.len()).unwrap_or(u32::MAX);
if region_len > max_region_len {
return Err(CryptoError::InvalidFormat(FormatDefect::ExtTooLarge {
len: region_len,
}));
}
let mut out = Vec::new();
let mut cursor = 0;
let mut prev_tag: Option<u16> = None;
while cursor < bytes.len() {
if bytes.len() - cursor < ENTRY_HEADER_SIZE {
return Err(CryptoError::InvalidFormat(FormatDefect::MalformedTlv));
}
let tag = read_u16_be(bytes, cursor)?;
let len = read_u32_be(bytes, cursor + size_of::<u16>())?;
cursor += ENTRY_HEADER_SIZE;
let class = classify_tlv_tag(tag)?;
if let Some(prev) = prev_tag {
if tag <= prev {
return Err(CryptoError::InvalidFormat(FormatDefect::MalformedTlv));
}
}
prev_tag = Some(tag);
if len > max_value_len {
return Err(CryptoError::InvalidFormat(FormatDefect::MalformedTlv));
}
let len = len as usize;
if bytes.len() - cursor < len {
return Err(CryptoError::InvalidFormat(FormatDefect::MalformedTlv));
}
out.push(RawTlv {
tag,
class,
value: &bytes[cursor..cursor + len],
});
cursor += len;
}
Ok(out)
}
pub(crate) fn reject_unknown_critical(tlvs: &[RawTlv<'_>]) -> Result<(), CryptoError> {
for tlv in tlvs {
if matches!(tlv.class, TlvClass::Critical) {
return Err(CryptoError::InvalidFormat(
FormatDefect::UnknownCriticalTag { tag: tlv.tag },
));
}
}
Ok(())
}
pub(crate) fn validate_no_known_critical(
bytes: &[u8],
max_region_len: u32,
max_value_len: u32,
) -> Result<(), CryptoError> {
let tlvs = scan_tlv_region(bytes, max_region_len, max_value_len)?;
reject_unknown_critical(&tlvs)
}
pub fn validate_tlv(ext_bytes: &[u8]) -> Result<(), CryptoError> {
validate_no_known_critical(ext_bytes, EXT_LEN_MAX, EXT_LEN_MAX)
}
#[cfg(test)]
pub(crate) fn tlv_bytes(tag: u16, value: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(ENTRY_HEADER_SIZE + value.len());
out.extend_from_slice(&tag.to_be_bytes());
out.extend_from_slice(&(value.len() as u32).to_be_bytes());
out.extend_from_slice(value);
out
}
#[cfg(test)]
mod tests {
use super::*;
use tlv_bytes as tlv;
#[test]
fn validate_tlv_accepts_empty_region() {
assert!(validate_tlv(&[]).is_ok());
}
#[test]
fn validate_tlv_accepts_single_ignorable_tag() {
let region = tlv(0x0001, &[0xAA; 8]);
assert!(validate_tlv(®ion).is_ok());
}
#[test]
fn validate_tlv_accepts_zero_length_ignorable_tag() {
let region = tlv(0x0001, &[]);
assert!(validate_tlv(®ion).is_ok());
}
#[test]
fn validate_tlv_accepts_ascending_ignorable_tags() {
let mut region = tlv(0x0001, &[0xAA]);
region.extend_from_slice(&tlv(0x0002, &[0xBB; 3]));
region.extend_from_slice(&tlv(0x7FFF, &[0xCC; 16]));
assert!(validate_tlv(®ion).is_ok());
}
#[test]
fn validate_tlv_rejects_unknown_critical_tag() {
let region = tlv(0x8001, &[0xAA; 4]);
match validate_tlv(®ion) {
Err(CryptoError::InvalidFormat(FormatDefect::UnknownCriticalTag { tag: 0x8001 })) => {}
other => panic!("expected UnknownCriticalTag(0x8001), got {other:?}"),
}
}
#[test]
fn validate_tlv_rejects_reserved_tag_0x0000() {
let region = tlv(0x0000, &[]);
match validate_tlv(®ion) {
Err(CryptoError::InvalidFormat(FormatDefect::MalformedTlv)) => {}
other => panic!("expected MalformedTlv for 0x0000, got {other:?}"),
}
}
#[test]
fn validate_tlv_rejects_reserved_tag_0x8000() {
let region = tlv(0x8000, &[]);
match validate_tlv(®ion) {
Err(CryptoError::InvalidFormat(FormatDefect::MalformedTlv)) => {}
other => panic!("expected MalformedTlv for 0x8000, got {other:?}"),
}
}
#[test]
fn validate_tlv_rejects_descending_tags() {
let mut region = tlv(0x0002, &[0xAA]);
region.extend_from_slice(&tlv(0x0001, &[0xBB]));
match validate_tlv(®ion) {
Err(CryptoError::InvalidFormat(FormatDefect::MalformedTlv)) => {}
other => panic!("expected MalformedTlv for descending, got {other:?}"),
}
}
#[test]
fn validate_tlv_rejects_duplicate_tags() {
let mut region = tlv(0x0001, &[0xAA]);
region.extend_from_slice(&tlv(0x0001, &[0xBB]));
match validate_tlv(®ion) {
Err(CryptoError::InvalidFormat(FormatDefect::MalformedTlv)) => {}
other => panic!("expected MalformedTlv for duplicate, got {other:?}"),
}
}
#[test]
fn validate_tlv_rejects_len_past_end() {
let mut region = Vec::new();
region.extend_from_slice(&0x0001u16.to_be_bytes());
region.extend_from_slice(&100u32.to_be_bytes());
region.extend_from_slice(&[0xAA; 3]);
match validate_tlv(®ion) {
Err(CryptoError::InvalidFormat(FormatDefect::MalformedTlv)) => {}
other => panic!("expected MalformedTlv for len-past-end, got {other:?}"),
}
}
#[test]
fn validate_tlv_rejects_truncated_entry_header() {
let region = vec![0x00, 0x01, 0x00, 0x00, 0x00];
match validate_tlv(®ion) {
Err(CryptoError::InvalidFormat(FormatDefect::MalformedTlv)) => {}
other => panic!("expected MalformedTlv for truncated header, got {other:?}"),
}
}
#[test]
fn validate_tlv_rejects_len_above_region_cap() {
let mut region = Vec::new();
region.extend_from_slice(&0x0001u16.to_be_bytes());
region.extend_from_slice(&u32::MAX.to_be_bytes());
match validate_tlv(®ion) {
Err(CryptoError::InvalidFormat(FormatDefect::MalformedTlv)) => {}
other => panic!("expected MalformedTlv for len-over-cap, got {other:?}"),
}
}
#[test]
fn validate_tlv_rejects_region_over_cap() {
let region = vec![0u8; EXT_LEN_MAX as usize + 1];
match validate_tlv(®ion) {
Err(CryptoError::InvalidFormat(FormatDefect::ExtTooLarge { .. })) => {}
other => panic!("expected ExtTooLarge, got {other:?}"),
}
}
#[test]
fn scan_tlv_region_returns_classified_tlvs() {
let mut region = tlv(0x0001, &[0xAA]);
region.extend_from_slice(&tlv(0x0042, &[0xBB; 3]));
let tlvs = scan_tlv_region(®ion, EXT_LEN_MAX, EXT_LEN_MAX).unwrap();
assert_eq!(tlvs.len(), 2);
assert_eq!(tlvs[0].tag, 0x0001);
assert_eq!(tlvs[0].class, TlvClass::Ignorable);
assert_eq!(tlvs[0].value, &[0xAA]);
assert_eq!(tlvs[1].tag, 0x0042);
assert_eq!(tlvs[1].class, TlvClass::Ignorable);
assert_eq!(tlvs[1].value, &[0xBB; 3]);
}
#[test]
fn scan_tlv_region_classifies_critical_without_rejecting() {
let region = tlv(0x8001, &[0xAA]);
let tlvs = scan_tlv_region(®ion, EXT_LEN_MAX, EXT_LEN_MAX).unwrap();
assert_eq!(tlvs.len(), 1);
assert_eq!(tlvs[0].class, TlvClass::Critical);
}
#[test]
fn reject_unknown_critical_rejects_critical_tag() {
let region = tlv(0x8001, &[0xAA]);
let tlvs = scan_tlv_region(®ion, EXT_LEN_MAX, EXT_LEN_MAX).unwrap();
match reject_unknown_critical(&tlvs) {
Err(CryptoError::InvalidFormat(FormatDefect::UnknownCriticalTag { tag: 0x8001 })) => {}
other => panic!("expected UnknownCriticalTag(0x8001), got {other:?}"),
}
}
#[test]
fn scan_tlv_region_honors_caller_region_cap() {
let small_cap: u32 = 64;
let region = vec![0u8; small_cap as usize + 1];
match scan_tlv_region(®ion, small_cap, EXT_LEN_MAX) {
Err(CryptoError::InvalidFormat(FormatDefect::ExtTooLarge { len })) => {
assert_eq!(len, small_cap + 1);
}
other => panic!("expected ExtTooLarge, got {other:?}"),
}
}
#[test]
fn scan_tlv_region_honors_caller_value_cap() {
let mut region = Vec::new();
region.extend_from_slice(&0x0001u16.to_be_bytes());
region.extend_from_slice(&200u32.to_be_bytes());
region.extend_from_slice(&[0u8; 200]);
match scan_tlv_region(®ion, 1024, 100) {
Err(CryptoError::InvalidFormat(FormatDefect::MalformedTlv)) => {}
other => panic!("expected MalformedTlv for value-over-cap, got {other:?}"),
}
}
#[test]
fn scan_tlv_region_value_at_cap_admissible() {
let region = tlv(0x0001, &[0xAA; 100]);
let tlvs = scan_tlv_region(®ion, 1024, 100).unwrap();
assert_eq!(tlvs.len(), 1);
assert_eq!(tlvs[0].value.len(), 100);
let oversize = tlv(0x0001, &[0xBB; 101]);
assert!(scan_tlv_region(&oversize, 1024, 100).is_err());
}
#[test]
fn scan_tlv_region_region_at_cap_admissible() {
let region = tlv(0x0001, &[0xAA; 94]);
assert_eq!(region.len(), 100);
let tlvs = scan_tlv_region(®ion, 100, 100).unwrap();
assert_eq!(tlvs.len(), 1);
let mut oversize = region.clone();
oversize.push(0);
match scan_tlv_region(&oversize, 100, 100) {
Err(CryptoError::InvalidFormat(FormatDefect::ExtTooLarge { .. })) => {}
other => panic!("expected ExtTooLarge, got {other:?}"),
}
}
#[test]
fn classify_tlv_tag_rejects_reserved() {
assert!(classify_tlv_tag(0x0000).is_err());
assert!(classify_tlv_tag(0x8000).is_err());
assert_eq!(classify_tlv_tag(0x0001).unwrap(), TlvClass::Ignorable);
assert_eq!(classify_tlv_tag(0x7FFF).unwrap(), TlvClass::Ignorable);
assert_eq!(classify_tlv_tag(0x8001).unwrap(), TlvClass::Critical);
assert_eq!(classify_tlv_tag(0xFFFF).unwrap(), TlvClass::Critical);
}
}