use crate::errors::{RCodeFromStrError, UnknownRCodeName};
use core::{
cmp::Ordering,
fmt::{self, Display, Formatter, Write},
str::FromStr,
};
const UNKNOWN_RCODE: &str = "__UNKNOWN_RCODE__";
const RFC3597_PFX: &str = "RCODE";
#[rustfmt::skip]
static NAMES: [&str; 17] = [
"NOERROR", "FORMERR", "SERVFAIL", "NXDOMAIN", "NOTIMP", "REFUSED", UNKNOWN_RCODE, UNKNOWN_RCODE, UNKNOWN_RCODE, UNKNOWN_RCODE, UNKNOWN_RCODE, UNKNOWN_RCODE, UNKNOWN_RCODE, UNKNOWN_RCODE, UNKNOWN_RCODE, UNKNOWN_RCODE, "BADVERS", ];
static KNOWN: [u8; 17] = [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
pub struct RCode(u16);
impl RCode {
pub const NOERROR: RCode = RCode::new(0);
pub const FORMERR: RCode = RCode::new(1);
pub const SERVFAIL: RCode = RCode::new(2);
pub const NXDOMAIN: RCode = RCode::new(3);
pub const NOTIMP: RCode = RCode::new(4);
pub const REFUSED: RCode = RCode::new(5);
pub const BADVERS: RCode = RCode::new(16);
#[cfg(test)]
#[allow(missing_docs)]
pub const VALUES: [RCode; 7] = [
Self::NOERROR,
Self::FORMERR,
Self::SERVFAIL,
Self::NXDOMAIN,
Self::NOTIMP,
Self::REFUSED,
Self::BADVERS,
];
#[inline]
const fn new(v: u16) -> Self {
Self(v)
}
#[inline]
pub fn name(self) -> &'static str {
let val = self.0 as usize;
if val < NAMES.len() {
NAMES[val]
} else {
UNKNOWN_RCODE
}
}
#[inline]
pub const fn value(self) -> u16 {
self.0
}
#[inline]
pub fn extended(base: RCode, extension: u8) -> RCode {
RCode((base.0 & 0xF) | ((extension as u16) << 4))
}
pub fn from_name(name: &str) -> Result<Self, UnknownRCodeName> {
match name.len() {
6 => match name {
"NOTIMP" => Ok(RCode::NOTIMP),
_ => Err(UnknownRCodeName),
},
7 => match name {
"NOERROR" => Ok(RCode::NOERROR),
"FORMERR" => Ok(RCode::FORMERR),
"REFUSED" => Ok(RCode::REFUSED),
"BADVERS" => Ok(RCode::BADVERS),
_ => Err(UnknownRCodeName),
},
8 => match name {
"SERVFAIL" => Ok(RCode::SERVFAIL),
"NXDOMAIN" => Ok(RCode::NXDOMAIN),
_ => Err(UnknownRCodeName),
},
_ => Err(UnknownRCodeName),
}
}
#[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 RCode {
#[inline]
fn from(value: u16) -> Self {
Self(value)
}
}
impl Display for RCode {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let name = self.name();
match name {
UNKNOWN_RCODE => {
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 RCode {
#[inline]
fn eq(&self, other: &u16) -> bool {
self.0 == *other
}
}
impl PartialOrd<u16> for RCode {
#[inline]
fn partial_cmp(&self, other: &u16) -> Option<Ordering> {
self.0.partial_cmp(other)
}
}
impl FromStr for RCode {
type Err = RCodeFromStrError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(rc) = Self::from_name(s) {
return Ok(rc);
}
if let Some(sfx) = s.strip_prefix(RFC3597_PFX) {
match sfx.parse::<u16>() {
Ok(v) => Ok(RCode::from(v)),
_ => Err(RCodeFromStrError),
}
} else {
Err(RCodeFromStrError)
}
}
}
#[cfg(test)]
mod test {
pub use super::*;
#[test]
fn test_name() {
assert_eq!(RCode::NOERROR.name(), "NOERROR");
assert_eq!(RCode::FORMERR.name(), "FORMERR");
assert_eq!(RCode::SERVFAIL.name(), "SERVFAIL");
assert_eq!(RCode::NXDOMAIN.name(), "NXDOMAIN");
assert_eq!(RCode::NOTIMP.name(), "NOTIMP");
assert_eq!(RCode::REFUSED.name(), "REFUSED");
assert_eq!(RCode::BADVERS.name(), "BADVERS");
for (i, v) in NAMES.iter().enumerate() {
assert_eq!(RCode::from(i as u16).name(), *v);
}
}
#[test]
fn test_from_name() {
assert_eq!(RCode::from_name("NOERROR").unwrap(), RCode::NOERROR);
assert_eq!(RCode::from_name("FORMERR").unwrap(), RCode::FORMERR);
assert_eq!(RCode::from_name("SERVFAIL").unwrap(), RCode::SERVFAIL);
assert_eq!(RCode::from_name("NXDOMAIN").unwrap(), RCode::NXDOMAIN);
assert_eq!(RCode::from_name("NOTIMP").unwrap(), RCode::NOTIMP);
assert_eq!(RCode::from_name("REFUSED").unwrap(), RCode::REFUSED);
assert_eq!(RCode::from_name("BADVERS").unwrap(), RCode::BADVERS);
for (i, name) in NAMES.iter().enumerate() {
if *name != UNKNOWN_RCODE {
assert_eq!(RCode::from(i as u16), RCode::from_name(name).unwrap());
assert!(RCode::from_name(&name.to_lowercase()).is_err());
} else {
assert!(RCode::from_name(name).is_err());
}
}
}
#[test]
fn test_from_str() {
assert_eq!(RCode::from_str("NOERROR").unwrap(), RCode::NOERROR);
assert_eq!(RCode::from_str("FORMERR").unwrap(), RCode::FORMERR);
assert_eq!(RCode::from_str("SERVFAIL").unwrap(), RCode::SERVFAIL);
assert_eq!(RCode::from_str("NXDOMAIN").unwrap(), RCode::NXDOMAIN);
assert_eq!(RCode::from_str("NOTIMP").unwrap(), RCode::NOTIMP);
assert_eq!(RCode::from_str("REFUSED").unwrap(), RCode::REFUSED);
assert_eq!(RCode::from_str("BADVERS").unwrap(), RCode::BADVERS);
for (i, name) in NAMES.iter().enumerate() {
if *name != UNKNOWN_RCODE {
assert_eq!(RCode::from(i as u16), RCode::from_str(name).unwrap());
assert!(RCode::from_str(&name.to_lowercase()).is_err());
}
}
for i in 0..=u16::MAX {
let s = format!("RCODE{i}");
assert_eq!(RCode::from_str(&s).unwrap(), RCode::from(i));
assert!(RCode::from_str(&s.to_lowercase()).is_err());
}
assert!(RCode::from_str("RCODE65536").is_err());
}
#[test]
fn test_is_defined() {
assert!(RCode::NOERROR.is_defined());
assert!(RCode::FORMERR.is_defined());
assert!(RCode::SERVFAIL.is_defined());
assert!(RCode::NXDOMAIN.is_defined());
assert!(RCode::NOTIMP.is_defined());
assert!(RCode::REFUSED.is_defined());
assert!(RCode::BADVERS.is_defined());
for v in RCode::VALUES {
assert!(v.is_defined());
}
for (i, name) in NAMES.iter().enumerate() {
assert_eq!(RCode::from(i as u16).is_defined(), *name != UNKNOWN_RCODE);
}
for i in 0..=u8::MAX {
let rcode = RCode::from(i as u16);
assert_eq!(rcode.is_defined(), RCode::VALUES.contains(&rcode));
}
}
}