#![cfg_attr(feature = "arbitrary", allow(clippy::arithmetic_side_effects))]
mod class;
mod mode;
mod number;
pub use self::{class::Class, mode::TagMode, number::TagNumber};
use crate::{Decode, DerOrd, Encode, Error, ErrorKind, Length, Reader, Result, Writer};
use core::{cmp::Ordering, fmt};
#[cfg(feature = "alloc")]
use alloc::borrow::{Cow, ToOwned};
const CONSTRUCTED_FLAG: u8 = 0b100000;
pub trait FixedTag {
const TAG: Tag;
}
#[cfg(feature = "alloc")]
impl<'a, T> FixedTag for Cow<'a, T>
where
T: ToOwned + ?Sized,
&'a T: FixedTag,
{
const TAG: Tag = <&'a T>::TAG;
}
#[diagnostic::on_unimplemented(note = "Consider adding impl of `FixedTag` to `{Self}`")]
pub trait Tagged {
fn tag(&self) -> Tag;
}
impl<T: FixedTag + ?Sized> Tagged for T {
fn tag(&self) -> Tag {
T::TAG
}
}
#[diagnostic::on_unimplemented(note = "Consider adding impl of `FixedTag` to `{Self}`")]
pub trait IsConstructed {
const CONSTRUCTED: bool;
}
impl<T: FixedTag + ?Sized> IsConstructed for T {
const CONSTRUCTED: bool = T::TAG.is_constructed();
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Tag {
Boolean,
Integer,
BitString,
OctetString,
Null,
ObjectIdentifier,
Real,
Enumerated,
Utf8String,
RelativeOid,
Sequence,
Set,
NumericString,
PrintableString,
TeletexString,
VideotexString,
Ia5String,
UtcTime,
GeneralizedTime,
VisibleString,
GeneralString,
BmpString,
Application {
constructed: bool,
number: TagNumber,
},
ContextSpecific {
constructed: bool,
number: TagNumber,
},
Private {
constructed: bool,
number: TagNumber,
},
}
impl Tag {
pub(crate) const MAX_SIZE: usize = 6;
pub(crate) fn decode_with_constructed_bit<'a>(
reader: &mut impl Reader<'a>,
) -> Result<(Self, bool)> {
let first_byte = reader.read_byte()?;
let is_constructed = first_byte & CONSTRUCTED_FLAG != 0;
let tag = match first_byte {
0x01 => Tag::Boolean,
0x02 => Tag::Integer,
0x03 => Tag::BitString,
0x04 => Tag::OctetString,
0x05 => Tag::Null,
0x06 => Tag::ObjectIdentifier,
0x09 => Tag::Real,
0x0A => Tag::Enumerated,
0x0C => Tag::Utf8String,
0x0D => Tag::RelativeOid,
0x12 => Tag::NumericString,
0x13 => Tag::PrintableString,
0x14 => Tag::TeletexString,
0x15 => Tag::VideotexString,
0x16 => Tag::Ia5String,
0x17 => Tag::UtcTime,
0x18 => Tag::GeneralizedTime,
0x1A => Tag::VisibleString,
0x1B => Tag::GeneralString,
0x1E => Tag::BmpString,
#[cfg(feature = "ber")]
0x24 if reader.encoding_rules().is_ber() => Tag::OctetString,
0x30 => Tag::Sequence, 0x31 => Tag::Set, 0x40..=0x7F => {
let (constructed, number) = parse_parts(first_byte, reader)?;
Tag::Application {
constructed,
number,
}
}
0x80..=0xBF => {
let (constructed, number) = parse_parts(first_byte, reader)?;
Tag::ContextSpecific {
constructed,
number,
}
}
0xC0..=0xFF => {
let (constructed, number) = parse_parts(first_byte, reader)?;
Tag::Private {
constructed,
number,
}
}
0x1F => return Err(reader.error(ErrorKind::TagNumberInvalid)),
byte => return Err(reader.error(ErrorKind::TagUnknown { byte })),
};
Ok((tag, is_constructed))
}
pub fn peek<'a>(reader: &impl Reader<'a>) -> Result<Self> {
Self::decode(&mut reader.clone())
}
pub(crate) fn peek_matches<'a, R: Reader<'a>>(
reader: &mut R,
expected_class: Class,
expected_tag_number: TagNumber,
) -> Result<bool> {
if reader.is_finished() {
return Ok(false);
}
let tag = Self::peek(reader)?;
Ok(tag.class() == expected_class && tag.number() == expected_tag_number)
}
pub fn assert_eq(self, expected: Tag) -> Result<Tag> {
if self == expected {
Ok(self)
} else {
Err(self.unexpected_error(Some(expected)).into())
}
}
#[must_use]
pub const fn class(self) -> Class {
match self {
Tag::Application { .. } => Class::Application,
Tag::ContextSpecific { .. } => Class::ContextSpecific,
Tag::Private { .. } => Class::Private,
_ => Class::Universal,
}
}
#[must_use]
pub const fn number(self) -> TagNumber {
match self {
Tag::Boolean => TagNumber(1),
Tag::Integer => TagNumber(2),
Tag::BitString => TagNumber(3),
Tag::OctetString => TagNumber(4),
Tag::Null => TagNumber(5),
Tag::ObjectIdentifier => TagNumber(6),
Tag::Real => TagNumber(9),
Tag::Enumerated => TagNumber(10),
Tag::Utf8String => TagNumber(12),
Tag::RelativeOid => TagNumber(13),
Tag::Sequence => TagNumber(16),
Tag::Set => TagNumber(17),
Tag::NumericString => TagNumber(18),
Tag::PrintableString => TagNumber(19),
Tag::TeletexString => TagNumber(20),
Tag::VideotexString => TagNumber(21),
Tag::Ia5String => TagNumber(22),
Tag::UtcTime => TagNumber(23),
Tag::GeneralizedTime => TagNumber(24),
Tag::VisibleString => TagNumber(26),
Tag::GeneralString => TagNumber(27),
Tag::BmpString => TagNumber(30),
Tag::Application { number, .. } => number,
Tag::ContextSpecific { number, .. } => number,
Tag::Private { number, .. } => number,
}
}
#[must_use]
pub const fn is_constructed(self) -> bool {
match self {
Tag::Sequence | Tag::Set => true,
Tag::Application { constructed, .. }
| Tag::ContextSpecific { constructed, .. }
| Tag::Private { constructed, .. } => constructed,
_ => false,
}
}
#[must_use]
pub const fn is_application(self) -> bool {
matches!(self.class(), Class::Application)
}
#[must_use]
pub const fn is_context_specific(self) -> bool {
matches!(self.class(), Class::ContextSpecific)
}
#[must_use]
pub const fn is_private(self) -> bool {
matches!(self.class(), Class::Private)
}
#[must_use]
pub const fn is_universal(self) -> bool {
matches!(self.class(), Class::Universal)
}
#[must_use]
pub fn length_error(self) -> ErrorKind {
ErrorKind::Length { tag: self }
}
#[must_use]
pub fn non_canonical_error(self) -> ErrorKind {
ErrorKind::Noncanonical { tag: self }
}
#[must_use]
pub fn unexpected_error(self, expected: Option<Self>) -> ErrorKind {
ErrorKind::TagUnexpected {
expected,
actual: self,
}
}
#[must_use]
pub fn value_error(self) -> ErrorKind {
ErrorKind::Value { tag: self }
}
}
impl<'a> Decode<'a> for Tag {
type Error = Error;
fn decode<R: Reader<'a>>(reader: &mut R) -> Result<Self> {
Self::decode_with_constructed_bit(reader).map(|(tag, _)| tag)
}
}
fn parse_parts<'a, R: Reader<'a>>(first_byte: u8, reader: &mut R) -> Result<(bool, TagNumber)> {
let constructed = first_byte & CONSTRUCTED_FLAG != 0;
let first_number_part = first_byte & TagNumber::MASK;
if first_number_part != TagNumber::MASK {
return Ok((constructed, TagNumber(first_number_part.into())));
}
let mut multi_byte_tag_number = 0;
for i in 0..Tag::MAX_SIZE - 1 {
let byte = reader.read_byte()?;
multi_byte_tag_number |= u32::from(byte & 0x7F);
if byte & 0x80 == 0 {
if multi_byte_tag_number < u32::from(TagNumber::MASK) {
return Err(reader.error(ErrorKind::TagNumberInvalid));
}
return Ok((constructed, TagNumber(multi_byte_tag_number)));
} else if i == 0 && multi_byte_tag_number == 0 {
return Err(reader.error(ErrorKind::TagNumberInvalid));
}
if multi_byte_tag_number.leading_zeros() < 7 {
return Err(reader.error(ErrorKind::TagNumberInvalid));
}
multi_byte_tag_number <<= 7;
}
Err(reader.error(ErrorKind::TagNumberInvalid))
}
impl Encode for Tag {
#[allow(clippy::cast_possible_truncation)]
fn encoded_len(&self) -> Result<Length> {
let number = self.number().value();
let length = if number <= 30 {
Length::ONE
} else {
Length::new(number.ilog2() / 7 + 2)
};
Ok(length)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
let mut first_byte = (self.class() as u8) | (u8::from(self.is_constructed()) << 5);
let number = self.number().value();
if number < u32::from(TagNumber::MASK) {
first_byte |= (number & 0x1F) as u8;
writer.write_byte(first_byte)?;
} else {
first_byte |= TagNumber::MASK;
writer.write_byte(first_byte)?;
let extra_bytes = number.ilog2() / 7 + 1;
for shift in (0..extra_bytes).rev() {
let mut byte = ((number >> (shift * 7)) & 0x7f) as u8;
if shift != 0 {
byte |= 0x80;
}
writer.write_byte(byte)?;
}
}
Ok(())
}
}
impl DerOrd for Tag {
fn der_cmp(&self, other: &Self) -> Result<Ordering> {
Ok((self.class().cmp(&other.class()))
.then_with(|| self.number().cmp(&other.number()))
.then_with(|| self.is_constructed().cmp(&other.is_constructed())))
}
}
impl fmt::Display for Tag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
const FIELD_TYPE: [&str; 2] = ["primitive", "constructed"];
match *self {
Tag::Boolean => f.write_str("BOOLEAN"),
Tag::Integer => f.write_str("INTEGER"),
Tag::BitString => f.write_str("BIT STRING"),
Tag::OctetString => f.write_str("OCTET STRING"),
Tag::Null => f.write_str("NULL"),
Tag::ObjectIdentifier => f.write_str("OBJECT IDENTIFIER"),
Tag::Real => f.write_str("REAL"),
Tag::Enumerated => f.write_str("ENUMERATED"),
Tag::Utf8String => f.write_str("UTF8String"),
Tag::RelativeOid => f.write_str("RELATIVE OID"),
Tag::Set => f.write_str("SET"),
Tag::NumericString => f.write_str("NumericString"),
Tag::PrintableString => f.write_str("PrintableString"),
Tag::TeletexString => f.write_str("TeletexString"),
Tag::VideotexString => f.write_str("VideotexString"),
Tag::Ia5String => f.write_str("IA5String"),
Tag::UtcTime => f.write_str("UTCTime"),
Tag::GeneralizedTime => f.write_str("GeneralizedTime"),
Tag::VisibleString => f.write_str("VisibleString"),
Tag::GeneralString => f.write_str("GeneralString"),
Tag::BmpString => f.write_str("BMPString"),
Tag::Sequence => f.write_str("SEQUENCE"),
Tag::Application {
constructed,
number,
} => write!(
f,
"APPLICATION [{}] ({})",
number,
FIELD_TYPE[usize::from(constructed)]
),
Tag::ContextSpecific {
constructed,
number,
} => write!(
f,
"CONTEXT-SPECIFIC [{}] ({})",
number,
FIELD_TYPE[usize::from(constructed)]
),
Tag::Private {
constructed,
number,
} => write!(
f,
"PRIVATE [{}] ({})",
number,
FIELD_TYPE[usize::from(constructed)]
),
}
}
}
impl fmt::Debug for Tag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Tag(0x{:02x}: {})", self.number().value(), self)
}
}
#[cfg(test)]
mod tests {
use core::cmp::Ordering;
use hex_literal::hex;
use super::{Class, Tag, TagNumber};
use crate::{Decode, DerOrd, ErrorKind, Length, Reader, SliceReader};
#[test]
fn tag_class() {
assert_eq!(Tag::Boolean.class(), Class::Universal);
assert_eq!(Tag::Integer.class(), Class::Universal);
assert_eq!(Tag::BitString.class(), Class::Universal);
assert_eq!(Tag::OctetString.class(), Class::Universal);
assert_eq!(Tag::Null.class(), Class::Universal);
assert_eq!(Tag::ObjectIdentifier.class(), Class::Universal);
assert_eq!(Tag::Real.class(), Class::Universal);
assert_eq!(Tag::Enumerated.class(), Class::Universal);
assert_eq!(Tag::Utf8String.class(), Class::Universal);
assert_eq!(Tag::RelativeOid.class(), Class::Universal);
assert_eq!(Tag::Set.class(), Class::Universal);
assert_eq!(Tag::NumericString.class(), Class::Universal);
assert_eq!(Tag::PrintableString.class(), Class::Universal);
assert_eq!(Tag::TeletexString.class(), Class::Universal);
assert_eq!(Tag::VideotexString.class(), Class::Universal);
assert_eq!(Tag::Ia5String.class(), Class::Universal);
assert_eq!(Tag::UtcTime.class(), Class::Universal);
assert_eq!(Tag::GeneralizedTime.class(), Class::Universal);
assert_eq!(Tag::Sequence.class(), Class::Universal);
for num in 0..=30 {
for &constructed in &[false, true] {
let number = TagNumber(num);
assert_eq!(
Tag::Application {
constructed,
number
}
.class(),
Class::Application
);
assert_eq!(
Tag::ContextSpecific {
constructed,
number
}
.class(),
Class::ContextSpecific
);
assert_eq!(
Tag::Private {
constructed,
number
}
.class(),
Class::Private
);
}
}
}
#[test]
fn decoding() {
assert_eq!(
Tag::Application {
constructed: false,
number: TagNumber(0x4001)
},
Tag::from_der(&hex!("5F818001")).expect("bits 7 to 1 are zero")
);
assert_eq!(
Tag::ContextSpecific {
constructed: false,
number: TagNumber(0x200001)
},
Tag::from_der(&hex!("9F81808001")).expect("bits 7 to 1 are zero two times")
);
assert_eq!(
Tag::Private {
constructed: false,
number: TagNumber(u32::MAX)
},
Tag::from_der(&hex!("DF8FFFFFFF7F")).expect("private tag 2^32-1")
);
assert_eq!(
ErrorKind::TagNumberInvalid,
Tag::from_der(&hex!("FF03"))
.expect_err("valid tag number but must be in short form")
.kind()
);
assert_eq!(
ErrorKind::TagNumberInvalid,
Tag::from_der(&hex!("1FFF"))
.expect_err("universal tag with long form")
.kind()
);
assert_eq!(
ErrorKind::TagNumberInvalid,
Tag::from_der(&hex!("5F8020"))
.expect_err("leading zeros in long form")
.kind()
);
assert_eq!(
ErrorKind::TagNumberInvalid,
Tag::from_der(&hex!("DF9F8F8F8F0F"))
.expect_err("tag number larger than 32 bits")
.kind()
);
assert_eq!(
ErrorKind::Incomplete {
expected_len: Length::new(3),
actual_len: Length::new(2)
},
Tag::from_der(&hex!("5F9E"))
.expect_err("incomplete tag in long form")
.kind()
);
}
#[test]
fn tag_order() {
assert_eq!(Tag::Boolean.der_cmp(&Tag::Integer), Ok(Ordering::Less));
assert_eq!(Tag::Integer.der_cmp(&Tag::Null), Ok(Ordering::Less));
assert_eq!(Tag::Null.der_cmp(&Tag::Sequence), Ok(Ordering::Less));
assert_eq!(Tag::Sequence.der_cmp(&Tag::Ia5String), Ok(Ordering::Less));
assert_eq!(Tag::Ia5String.der_cmp(&Tag::BmpString), Ok(Ordering::Less));
assert_eq!(
Tag::BmpString.der_cmp(&Tag::Application {
constructed: true,
number: TagNumber(0)
}),
Ok(Ordering::Less)
);
assert_eq!(
Tag::Application {
constructed: true,
number: TagNumber(0)
}
.der_cmp(&Tag::Application {
constructed: true,
number: TagNumber(1)
}),
Ok(Ordering::Less)
);
assert_eq!(
Tag::Application {
constructed: true,
number: TagNumber(1)
}
.der_cmp(&Tag::Application {
constructed: false,
number: TagNumber(2)
}),
Ok(Ordering::Less)
);
assert_eq!(
Tag::Application {
constructed: false,
number: TagNumber(2)
}
.der_cmp(&Tag::Application {
constructed: true,
number: TagNumber(2)
}),
Ok(Ordering::Less)
);
assert_eq!(
Tag::Application {
constructed: true,
number: TagNumber(2)
}
.der_cmp(&Tag::ContextSpecific {
constructed: true,
number: TagNumber(0)
}),
Ok(Ordering::Less)
);
assert_eq!(
Tag::ContextSpecific {
constructed: true,
number: TagNumber(10)
}
.der_cmp(&Tag::Private {
constructed: true,
number: TagNumber(0)
}),
Ok(Ordering::Less)
);
}
#[test]
fn peek() {
let reader = SliceReader::new(&[0x02]).expect("valid reader");
assert_eq!(reader.position(), Length::ZERO);
assert_eq!(Tag::peek(&reader).expect("peeked tag"), Tag::Integer);
assert_eq!(reader.position(), Length::ZERO); }
#[test]
fn peek_long_tags() {
let reader = SliceReader::new(&hex!("DF8FFFFFFF7F")).expect("valid reader");
let tag = Tag::peek(&reader).expect("peeked tag");
assert!(!tag.is_context_specific());
assert!(!tag.is_application());
assert!(tag.is_private());
assert_eq!(
tag,
Tag::Private {
constructed: false,
number: TagNumber(u32::MAX)
}
);
}
#[test]
fn negative_peek_long_tags() {
let reader = SliceReader::new(&hex!("DF8FFFFFFFFF")).expect("valid reader");
assert_eq!(
Tag::peek(&reader)
.expect_err("tag ends in 0xFF, so 7+ bytes needed")
.kind(),
ErrorKind::TagNumberInvalid,
);
}
}