use crate::error::ErrorCode;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Class {
Universal,
Application,
ContextSpecific,
Private,
}
impl Class {
fn from_bits(bits: u8) -> Self {
match bits >> 6 {
0 => Self::Universal,
1 => Self::Application,
2 => Self::ContextSpecific,
_ => Self::Private,
}
}
}
pub mod universal {
pub const BOOLEAN: u32 = 1;
pub const INTEGER: u32 = 2;
pub const BIT_STRING: u32 = 3;
pub const OCTET_STRING: u32 = 4;
pub const NULL: u32 = 5;
pub const OID: u32 = 6;
pub const UTF8_STRING: u32 = 12;
pub const SEQUENCE: u32 = 16;
pub const SET: u32 = 17;
pub const PRINTABLE_STRING: u32 = 19;
pub const IA5_STRING: u32 = 22;
pub const UTC_TIME: u32 = 23;
pub const GENERALIZED_TIME: u32 = 24;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Tag {
class: Class,
constructed: bool,
number: Option<u32>,
}
impl Tag {
#[must_use]
pub fn new(class: Class, constructed: bool, number: u32) -> Self {
Self {
class,
constructed,
number: Some(number),
}
}
#[must_use]
pub fn beyond_naming(class: Class, constructed: bool) -> Self {
Self {
class,
constructed,
number: None,
}
}
#[must_use]
pub fn primitive(number: u32) -> Self {
Self::new(Class::Universal, false, number)
}
#[must_use]
pub fn constructed(number: u32) -> Self {
Self::new(Class::Universal, true, number)
}
#[must_use]
pub fn context(constructed: bool, number: u32) -> Self {
Self::new(Class::ContextSpecific, constructed, number)
}
#[must_use]
pub fn class(self) -> Class {
self.class
}
#[must_use]
pub fn is_constructed(self) -> bool {
self.constructed
}
#[must_use]
pub fn number(self) -> Option<u32> {
self.number
}
#[must_use]
pub fn is_end_of_contents(self) -> bool {
self.class == Class::Universal && !self.constructed && self.number == Some(0)
}
}
pub(crate) fn read_accounted(
input: &[u8],
offset: u64,
mut charge: impl FnMut(u64) -> Result<(), ErrorCode>,
) -> Result<(Tag, usize), (ErrorCode, u64)> {
let fail = |code| (code, offset);
charge(1).map_err(fail)?;
let first = *input
.first()
.ok_or_else(|| fail(ErrorCode::UnexpectedEnd))?;
let class = Class::from_bits(first);
let constructed = first & 0x20 != 0;
let low = first & 0x1f;
if low != 0x1f {
if class == Class::Universal && low == 0 && constructed {
return Err(fail(ErrorCode::MalformedTag));
}
return Ok((Tag::new(class, constructed, u32::from(low)), 1));
}
let mut number: Option<u64> = Some(0);
let mut taken = 1;
loop {
charge(1).map_err(fail)?;
let byte = *input
.get(taken)
.ok_or_else(|| fail(ErrorCode::UnexpectedEnd))?;
taken += 1;
if taken == 2 && byte == 0x80 {
return Err(fail(ErrorCode::MalformedTag));
}
number =
number.and_then(|value| value.checked_mul(128)?.checked_add(u64::from(byte & 0x7f)));
if byte & 0x80 == 0 {
break;
}
if taken > TAG_NUMBER_BYTES {
return Err(fail(ErrorCode::TagTooLarge));
}
}
if number.is_some_and(|number| number < 0x1f) {
return Err(fail(ErrorCode::MalformedTag));
}
let tag = match number.and_then(|number| u32::try_from(number).ok()) {
Some(number) => Tag::new(class, constructed, number),
None => Tag::beyond_naming(class, constructed),
};
Ok((tag, taken))
}
#[cfg(test)]
fn read(input: &[u8], offset: u64) -> Result<(Tag, usize), (ErrorCode, u64)> {
read_accounted(input, offset, |_| Ok(()))
}
const TAG_NUMBER_BYTES: usize = 16;
#[cfg(test)]
mod tests;