use crate::error::DecodeError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Scan {
pub top_tag: Option<u64>,
}
const MAX_DEPTH: usize = 32;
pub fn scan(bytes: &[u8]) -> Result<Scan, DecodeError> {
let top_tag = match bytes.first() {
Some(b) if b >> 5 == 6 => {
let (tag, _) = read_head(bytes, 0)?;
Some(tag)
}
Some(_) => None,
None => return Err(DecodeError::Malformed),
};
let end = scan_item(bytes, 0, 0)?;
if end == bytes.len() {
Ok(Scan { top_tag })
} else {
Err(DecodeError::Malformed)
}
}
fn read_head(bytes: &[u8], pos: usize) -> Result<(u64, usize), DecodeError> {
let initial = *bytes.get(pos).ok_or(DecodeError::Malformed)?;
let ai = initial & 0x1f;
match ai {
0..=23 => Ok((u64::from(ai), pos + 1)),
24 => {
let v = *bytes.get(pos + 1).ok_or(DecodeError::Malformed)?;
let min = if initial >> 5 == 7 { 32 } else { 24 };
if v < min {
return Err(DecodeError::NonMinimalEncoding);
}
Ok((u64::from(v), pos + 2))
}
25 => {
let raw = bytes.get(pos + 1..pos + 3).ok_or(DecodeError::Malformed)?;
let v = u64::from(u16::from_be_bytes(
raw.try_into().map_err(|_| DecodeError::Malformed)?,
));
if initial >> 5 != 7 && v < 256 {
return Err(DecodeError::NonMinimalEncoding);
}
Ok((v, pos + 3))
}
26 => {
let raw = bytes.get(pos + 1..pos + 5).ok_or(DecodeError::Malformed)?;
let v = u64::from(u32::from_be_bytes(
raw.try_into().map_err(|_| DecodeError::Malformed)?,
));
if initial >> 5 != 7 && v < 65_536 {
return Err(DecodeError::NonMinimalEncoding);
}
Ok((v, pos + 5))
}
27 => {
let raw = bytes.get(pos + 1..pos + 9).ok_or(DecodeError::Malformed)?;
let v = u64::from_be_bytes(raw.try_into().map_err(|_| DecodeError::Malformed)?);
if initial >> 5 != 7 && v < 4_294_967_296 {
return Err(DecodeError::NonMinimalEncoding);
}
Ok((v, pos + 9))
}
28..=30 => Err(DecodeError::Malformed),
_ => Err(DecodeError::IndefiniteLength),
}
}
fn scan_item(bytes: &[u8], pos: usize, depth: usize) -> Result<usize, DecodeError> {
if depth > MAX_DEPTH {
return Err(DecodeError::Malformed);
}
let initial = *bytes.get(pos).ok_or(DecodeError::Malformed)?;
let major = initial >> 5;
if initial & 0x1f == 31 {
return match major {
2..=5 => Err(DecodeError::IndefiniteLength),
_ => Err(DecodeError::Malformed),
};
}
let (arg, mut next) = read_head(bytes, pos)?;
match major {
0 | 1 | 7 => Ok(next),
2 | 3 => {
let len = usize::try_from(arg).map_err(|_| DecodeError::Malformed)?;
let end = next.checked_add(len).ok_or(DecodeError::Malformed)?;
if end > bytes.len() {
return Err(DecodeError::Malformed);
}
Ok(end)
}
4 => {
for _ in 0..arg {
next = scan_item(bytes, next, depth + 1)?;
}
Ok(next)
}
5 => {
let pairs = arg.checked_mul(2).ok_or(DecodeError::Malformed)?;
for _ in 0..pairs {
next = scan_item(bytes, next, depth + 1)?;
}
Ok(next)
}
_ => scan_item(bytes, next, depth + 1),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_minimal_encodings() {
assert!(scan(&[0x17]).is_ok());
assert!(scan(&[0x18, 0x18]).is_ok());
let s = scan(&[0xD2, 0x82, 0x01, 0x41, 0xAA]).unwrap();
assert_eq!(s.top_tag, Some(18));
}
#[test]
fn rejects_non_minimal_int() {
assert_eq!(
scan(&[0x19, 0x00, 0x18]),
Err(DecodeError::NonMinimalEncoding)
);
assert_eq!(scan(&[0x18, 0x0A]), Err(DecodeError::NonMinimalEncoding));
assert_eq!(
scan(&[0x58, 0x01, 0xAA]),
Err(DecodeError::NonMinimalEncoding)
);
}
#[test]
fn rejects_indefinite_length() {
assert_eq!(
scan(&[0x9F, 0x01, 0xFF]),
Err(DecodeError::IndefiniteLength)
);
assert_eq!(
scan(&[0x5F, 0x41, 0xAA, 0xFF]),
Err(DecodeError::IndefiniteLength)
);
assert_eq!(
scan(&[0xBF, 0x01, 0x02, 0xFF]),
Err(DecodeError::IndefiniteLength)
);
}
#[test]
fn rejects_truncation_and_trailing() {
assert_eq!(scan(&[]), Err(DecodeError::Malformed));
assert_eq!(scan(&[0x82, 0x01]), Err(DecodeError::Malformed));
assert_eq!(scan(&[0x01, 0x02]), Err(DecodeError::Malformed));
assert_eq!(scan(&[0x43, 0x01, 0x02]), Err(DecodeError::Malformed));
}
#[test]
fn rejects_deep_nesting() {
let mut bytes = alloc::vec![0x81u8; 64];
bytes.push(0x01);
assert_eq!(scan(&bytes), Err(DecodeError::Malformed));
}
#[test]
fn top_tag_reported() {
let s = scan(&[0xD8, 0x60, 0x80]).unwrap();
assert_eq!(s.top_tag, Some(96));
assert_eq!(scan(&[0x80]).unwrap().top_tag, None);
}
#[test]
fn simple_values_in_invalid_range_rejected() {
assert_eq!(scan(&[0xF8, 0x1F]), Err(DecodeError::NonMinimalEncoding));
assert!(scan(&[0xF8, 0x20]).is_ok());
assert!(scan(&[0xF6]).is_ok());
}
}