#![cfg_attr(feature = "arbitrary", allow(clippy::integer_arithmetic))]
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};
const CONSTRUCTED_FLAG: u8 = 0b100000;
pub trait FixedTag {
const TAG: Tag;
}
pub trait Tagged {
fn tag(&self) -> Tag;
}
impl<T: FixedTag> Tagged for T {
fn tag(&self) -> Tag {
T::TAG
}
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Tag {
Boolean,
Integer,
BitString,
OctetString,
Null,
ObjectIdentifier,
Real,
Enumerated,
Utf8String,
Sequence,
Set,
NumericString,
PrintableString,
TeletexString,
VideotexString,
Ia5String,
UtcTime,
GeneralizedTime,
VisibleString,
BmpString,
Application {
constructed: bool,
number: TagNumber,
},
ContextSpecific {
constructed: bool,
number: TagNumber,
},
Private {
constructed: bool,
number: TagNumber,
},
}
impl Tag {
pub fn assert_eq(self, expected: Tag) -> Result<Tag> {
if self == expected {
Ok(self)
} else {
Err(self.unexpected_error(Some(expected)))
}
}
pub fn class(self) -> Class {
match self {
Tag::Application { .. } => Class::Application,
Tag::ContextSpecific { .. } => Class::ContextSpecific,
Tag::Private { .. } => Class::Private,
_ => Class::Universal,
}
}
pub fn number(self) -> TagNumber {
TagNumber(self.octet() & TagNumber::MASK)
}
pub fn is_constructed(self) -> bool {
self.octet() & CONSTRUCTED_FLAG != 0
}
pub fn is_application(self) -> bool {
self.class() == Class::Application
}
pub fn is_context_specific(self) -> bool {
self.class() == Class::ContextSpecific
}
pub fn is_private(self) -> bool {
self.class() == Class::Private
}
pub fn is_universal(self) -> bool {
self.class() == Class::Universal
}
pub fn octet(self) -> u8 {
match self {
Tag::Boolean => 0x01,
Tag::Integer => 0x02,
Tag::BitString => 0x03,
Tag::OctetString => 0x04,
Tag::Null => 0x05,
Tag::ObjectIdentifier => 0x06,
Tag::Real => 0x09,
Tag::Enumerated => 0x0A,
Tag::Utf8String => 0x0C,
Tag::Sequence => 0x10 | CONSTRUCTED_FLAG,
Tag::Set => 0x11 | CONSTRUCTED_FLAG,
Tag::NumericString => 0x12,
Tag::PrintableString => 0x13,
Tag::TeletexString => 0x14,
Tag::VideotexString => 0x15,
Tag::Ia5String => 0x16,
Tag::UtcTime => 0x17,
Tag::GeneralizedTime => 0x18,
Tag::VisibleString => 0x1A,
Tag::BmpString => 0x1E,
Tag::Application {
constructed,
number,
}
| Tag::ContextSpecific {
constructed,
number,
}
| Tag::Private {
constructed,
number,
} => self.class().octet(constructed, number),
}
}
pub fn length_error(self) -> Error {
ErrorKind::Length { tag: self }.into()
}
pub fn non_canonical_error(self) -> Error {
ErrorKind::Noncanonical { tag: self }.into()
}
pub fn unexpected_error(self, expected: Option<Self>) -> Error {
ErrorKind::TagUnexpected {
expected,
actual: self,
}
.into()
}
pub fn value_error(self) -> Error {
ErrorKind::Value { tag: self }.into()
}
}
impl TryFrom<u8> for Tag {
type Error = Error;
fn try_from(byte: u8) -> Result<Tag> {
let constructed = byte & CONSTRUCTED_FLAG != 0;
let number = TagNumber::try_from(byte & TagNumber::MASK)?;
match byte {
0x01 => Ok(Tag::Boolean),
0x02 => Ok(Tag::Integer),
0x03 => Ok(Tag::BitString),
0x04 => Ok(Tag::OctetString),
0x05 => Ok(Tag::Null),
0x06 => Ok(Tag::ObjectIdentifier),
0x09 => Ok(Tag::Real),
0x0A => Ok(Tag::Enumerated),
0x0C => Ok(Tag::Utf8String),
0x12 => Ok(Tag::NumericString),
0x13 => Ok(Tag::PrintableString),
0x14 => Ok(Tag::TeletexString),
0x15 => Ok(Tag::VideotexString),
0x16 => Ok(Tag::Ia5String),
0x17 => Ok(Tag::UtcTime),
0x18 => Ok(Tag::GeneralizedTime),
0x1A => Ok(Tag::VisibleString),
0x1E => Ok(Tag::BmpString),
0x30 => Ok(Tag::Sequence), 0x31 => Ok(Tag::Set), 0x40..=0x7E => Ok(Tag::Application {
constructed,
number,
}),
0x80..=0xBE => Ok(Tag::ContextSpecific {
constructed,
number,
}),
0xC0..=0xFE => Ok(Tag::Private {
constructed,
number,
}),
_ => Err(ErrorKind::TagUnknown { byte }.into()),
}
}
}
impl From<Tag> for u8 {
fn from(tag: Tag) -> u8 {
tag.octet()
}
}
impl From<&Tag> for u8 {
fn from(tag: &Tag) -> u8 {
u8::from(*tag)
}
}
impl<'a> Decode<'a> for Tag {
fn decode<R: Reader<'a>>(reader: &mut R) -> Result<Self> {
reader.read_byte().and_then(Self::try_from)
}
}
impl Encode for Tag {
fn encoded_len(&self) -> Result<Length> {
Ok(Length::ONE)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
writer.write_byte(self.into())
}
}
impl DerOrd for Tag {
fn der_cmp(&self, other: &Self) -> Result<Ordering> {
Ok(self.octet().cmp(&other.octet()))
}
}
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::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::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}: {})", u8::from(*self), self)
}
}
#[cfg(test)]
mod tests {
use super::TagNumber;
use super::{Class, Tag};
#[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::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::new(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
);
}
}
}
}