use crate::{
Result,
bytes::{Cursor, Reader},
errors::{ClassFromStrError, UnknownClassName},
};
use core::{
cmp::Ordering,
fmt::{self, Display, Formatter, Write},
str::FromStr,
};
const UNKNOWN_CLASS: &str = "__UNKNOWN_CLASS__";
const RFC3597_PFX: &str = "CLASS";
#[rustfmt::skip]
static NAMES: [&str; 256] = [
"", "IN", "CS", "CH", "HS", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "ANY",
];
#[rustfmt::skip]
static KNOWN: [u8; 256] = [
0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
];
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
pub struct Class(u16);
impl Class {
pub const IN: Class = Class::new(1);
pub const CS: Class = Class::new(2);
pub const CH: Class = Class::new(3);
pub const HS: Class = Class::new(4);
pub const ANY: Class = Class::new(255);
#[cfg(test)]
#[allow(missing_docs)]
pub const VALUES: [Class; 5] = [Self::IN, Self::CS, Self::CH, Self::HS, Self::ANY];
#[inline]
const fn new(c: u16) -> Self {
Self(c)
}
#[inline]
pub fn name(self) -> &'static str {
let val = self.value() as usize;
let name = if val < NAMES.len() { NAMES[val] } else { "" };
if name.is_empty() { UNKNOWN_CLASS } else { name }
}
#[inline]
pub const fn value(self) -> u16 {
self.0
}
#[inline]
pub const fn is_data_class(self) -> bool {
0x0001 <= self.0 && self.0 <= 0x007F
}
#[inline]
pub const fn is_meta_class(self) -> bool {
0x0080 <= self.0 && self.0 <= 0x00FF
}
pub fn from_name(name: &str) -> core::result::Result<Self, UnknownClassName> {
match name.len() {
2 => match name {
"IN" => Ok(Self::IN),
"CS" => Ok(Self::CS),
"CH" => Ok(Self::CH),
"HS" => Ok(Self::HS),
_ => Err(UnknownClassName),
},
3 => match name {
"ANY" => Ok(Self::ANY),
_ => Err(UnknownClassName),
},
_ => Err(UnknownClassName),
}
}
#[inline]
pub fn is_defined(self) -> bool {
let val = self.value() as usize;
if val < KNOWN.len() {
KNOWN[val] != 0
} else {
false
}
}
}
impl From<u16> for Class {
#[inline]
fn from(value: u16) -> Self {
Self(value)
}
}
impl Display for Class {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let val = self.value() as usize;
let name = if val < NAMES.len() { NAMES[val] } else { "" };
match name {
"" => {
let mut buf = arrayvec::ArrayString::<32>::new();
write!(&mut buf, "{}{}", RFC3597_PFX, self.0)?;
f.pad(buf.as_str())
}
_ => f.pad(name),
}
}
}
impl PartialEq<u16> for Class {
#[inline]
fn eq(&self, other: &u16) -> bool {
self.0 == *other
}
}
impl PartialOrd<u16> for Class {
#[inline]
fn partial_cmp(&self, other: &u16) -> Option<Ordering> {
self.0.partial_cmp(other)
}
}
impl Reader<Class> for Cursor<'_> {
#[inline]
fn read(&mut self) -> Result<Class> {
Ok(Class::from(self.u16_be()?))
}
}
impl Default for Class {
#[inline]
fn default() -> Self {
Self::IN
}
}
impl FromStr for Class {
type Err = ClassFromStrError;
fn from_str(s: &str) -> core::result::Result<Self, ClassFromStrError> {
if let Ok(c) = Self::from_name(s) {
return Ok(c);
}
if let Some(sfx) = s.strip_prefix(RFC3597_PFX) {
match sfx.parse::<u16>() {
Ok(v) => Ok(Class::from(v)),
_ => Err(ClassFromStrError),
}
} else {
Err(ClassFromStrError)
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_default() {
assert_eq!(Class::default(), Class::IN);
}
#[test]
fn test_name() {
for (i, name) in NAMES.iter().enumerate() {
let class = Class::from(i as u16);
match class {
Class::IN => assert_eq!(Class::IN.name(), *name),
Class::CH => assert_eq!(Class::CH.name(), *name),
Class::CS => assert_eq!(Class::CS.name(), *name),
Class::HS => assert_eq!(Class::HS.name(), *name),
Class::ANY => assert_eq!(Class::ANY.name(), *name),
_ => assert_eq!(class.name(), UNKNOWN_CLASS),
}
}
assert_eq!(Class::from(u16::MAX).name(), UNKNOWN_CLASS);
}
#[test]
fn test_from_name() {
assert_eq!(Class::from_name("IN").unwrap(), Class::IN);
assert_eq!(Class::from_name("CS").unwrap(), Class::CS);
assert_eq!(Class::from_name("CH").unwrap(), Class::CH);
assert_eq!(Class::from_name("HS").unwrap(), Class::HS);
assert_eq!(Class::from_name("ANY").unwrap(), Class::ANY);
for (i, name) in NAMES.iter().enumerate() {
if !name.is_empty() {
assert_eq!(Class::from_name(name).unwrap(), Class::from(i as u16));
assert!(Class::from_name(&name.to_lowercase()).is_err());
}
}
}
#[test]
fn test_from_str() {
assert_eq!(Class::from_str("IN").unwrap(), Class::IN);
assert_eq!(Class::from_str("CS").unwrap(), Class::CS);
assert_eq!(Class::from_str("CH").unwrap(), Class::CH);
assert_eq!(Class::from_str("HS").unwrap(), Class::HS);
assert_eq!(Class::from_str("ANY").unwrap(), Class::ANY);
for (i, name) in NAMES.iter().enumerate() {
if !name.is_empty() {
assert_eq!(Class::from_str(name).unwrap(), Class::from(i as u16));
assert!(Class::from_str(&name.to_lowercase()).is_err());
}
}
for i in 0..=u16::MAX {
let s = format!("CLASS{i}");
assert_eq!(Class::from_str(&s).unwrap(), Class::from(i));
assert!(Class::from_str(&s.to_lowercase()).is_err());
}
assert!(Class::from_str("CLASS65536").is_err());
}
#[test]
fn test_is_defined() {
assert!(Class::IN.is_defined());
assert!(Class::CS.is_defined());
assert!(Class::CH.is_defined());
assert!(Class::HS.is_defined());
assert!(Class::ANY.is_defined());
for (i, name) in NAMES.iter().enumerate() {
assert_eq!(Class::from(i as u16).is_defined(), !name.is_empty());
}
for i in 0..=u16::MAX {
assert_eq!(
Class::from(i).is_defined(),
Class::VALUES.iter().any(|v| *v == i)
);
}
}
}