diff --git a/Changelog.md b/Changelog.md index fcc46f398..abe3ea14c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -13,10 +13,15 @@ Improvements Bug fixes +* Changed the `FromStr` impl for `SecurityAlgorithm` to also accept + mnemonics. This also means that these are now accepted by the zonefile + parser. ([#656]) + Other changes [#641]: https://github.com/NLnetLabs/domain/pull/641 +[#656]: https://github.com/NLnetLabs/domain/pull/656 [@soywod]: https://github.com/soywod diff --git a/src/base/iana/class.rs b/src/base/iana/class.rs index ae859d7e2..83aa9945b 100644 --- a/src/base/iana/class.rs +++ b/src/base/iana/class.rs @@ -2,7 +2,10 @@ //------------ Class --------------------------------------------------------- -int_enum! { +use crate::base::iana::macros::FromStrError; +use crate::base::iana::macros::IanaEnum; + +iana_enum! { /// DNS CLASSes. /// /// The domain name space is partitioned into separate classes for different @@ -25,6 +28,11 @@ int_enum! { /// [DNS CLASSes IANA registry]: http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2 => Class, u16; + display_mnemonic_fallback_prefix_integer, + parse_from_mnemonic_or_prefix_integer, + serialize_to_mnemonic_fallback_prefix_integer, + deserialize_from_mnemonic_or_prefix_integer, + "CLASS"; /// Internet (IN). /// @@ -56,7 +64,6 @@ int_enum! { (ANY => 0xFF, "*") } -int_enum_str_with_prefix!(Class, "CLASS", b"CLASS", u16, "unknown class"); int_enum_zonefile_fmt_with_prefix!(Class, "CLASS"); //============ Tests ========================================================= diff --git a/src/base/iana/digestalg.rs b/src/base/iana/digestalg.rs index 7498bde40..486cd2812 100644 --- a/src/base/iana/digestalg.rs +++ b/src/base/iana/digestalg.rs @@ -2,7 +2,10 @@ //------------ DigestAlgorithm ----------------------------------------------- -int_enum! { +use crate::base::iana::macros::FromStrError; +use crate::base::iana::macros::IanaEnum; + +iana_enum! { /// Delegation signer digest algorithm numbers. /// /// These numbers are used in the DS resource record to specify how the @@ -14,6 +17,11 @@ int_enum! { /// [IANA registration]: https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml#ds-rr-types-1 => DigestAlgorithm, u8; + display_integer, + parse_from_integer, + serialize_to_integer, + deserialize_from_integer, + ""; /// Specifies that the SHA-1 hash function is used. /// @@ -42,7 +50,6 @@ int_enum! { (SHA384 => 4, "SHA-384") } -int_enum_str_decimal!(DigestAlgorithm, u8); int_enum_zonefile_fmt_decimal!(DigestAlgorithm, "digest type"); //============ Tests ========================================================= diff --git a/src/base/iana/iana_trait.rs b/src/base/iana/iana_trait.rs new file mode 100644 index 000000000..e69de29bb diff --git a/src/base/iana/ipseckey.rs b/src/base/iana/ipseckey.rs index 0dc85c84c..91f592be0 100644 --- a/src/base/iana/ipseckey.rs +++ b/src/base/iana/ipseckey.rs @@ -5,7 +5,10 @@ //------------ IpseckeyAlgorithm --------------------------------------------- -int_enum! { +use crate::base::iana::macros::FromStrError; +use crate::base::iana::macros::IanaEnum; + +iana_enum! { /// IPSECKEY Algorithms. /// /// This type identifies the public key's cryptographic algorithm of the @@ -18,6 +21,11 @@ int_enum! { /// [IANA registration]: https://www.iana.org/assignments/ipseckey-rr-parameters/ipseckey-rr-parameters.xhtml#ipseckey-rr-parameters-1 => IpseckeyAlgorithm, u8; + display_integer, + parse_from_mnemonic_or_integer, + serialize_to_integer, + deserialize_from_integer, + ""; /// Specified that no Public key is present. (NONE => 0, "NONE") @@ -35,12 +43,11 @@ int_enum! { (EDDSA => 4, "EdDSA") } -int_enum_str_decimal!(IpseckeyAlgorithm, u8); int_enum_zonefile_fmt_decimal!(IpseckeyAlgorithm, "ipseckey algorithm"); //------------ IpseckeyGateway ----------------------------------------------- -int_enum! { +iana_enum! { /// IPSECKEY Gateway Types. /// /// This type indicates the format of the information that is stored in @@ -53,6 +60,11 @@ int_enum! { /// [IANA registration]: https://www.iana.org/assignments/ipseckey-rr-parameters/ipseckey-rr-parameters.xhtml#ipseckey-rr-parameters-2 => IpseckeyGatewayType, u8; + display_integer, + parse_from_mnemonic_or_integer, + serialize_to_integer, + deserialize_from_integer, + ""; /// Specified that No gateway is present. (NONE => 0, "NONE") @@ -67,5 +79,4 @@ int_enum! { (NAME => 3, "NAME") } -int_enum_str_decimal!(IpseckeyGatewayType, u8); int_enum_zonefile_fmt_decimal!(IpseckeyGatewayType, "ipseckey gateway type"); diff --git a/src/base/iana/macros.rs b/src/base/iana/macros.rs index f2dcfdfcc..556e30d4e 100644 --- a/src/base/iana/macros.rs +++ b/src/base/iana/macros.rs @@ -136,206 +136,6 @@ macro_rules! int_enum { } } -/* -/// Adds impls for `FromStr` and `Display` to the type given as first argument. -/// -/// The `FromStr` impl matches only well known mnemonics ignoring case, -/// otherwise it returns an error of the second argument. -/// -/// For `Display`, it will display a decimal number for values without -/// mnemonic. -macro_rules! int_enum_str_mnemonics_only { - ($ianatype:ident, $error:expr) => { - impl ::std::str::FromStr for $ianatype { - type Err = FromStrError; - - fn from_str(s: &str) -> Result { - // We assume all mnemonics are always ASCII, so using - // the bytes representation of `s` is safe. - $ianatype::from_mnemonic(s.as_bytes()).ok_or(FromStrError) - } - } - - impl ::std::fmt::Display for $ianatype { - fn fmt(&self, f: &mut ::std::fmt::Formatter) - -> ::std::fmt::Result { - use ::std::fmt::Write; - - match self.to_mnemonic() { - Some(m) => { - for ch in m { - f.write_char(*ch as char)? - } - Ok(()) - } - None => { - write!(f, "{}", self.to_int()) - } - } - } - } - - from_str_error!($error); - } -} -*/ - -/// Adds impls for `FromStr` and `Display` to the type given as first argument. -/// -/// For `FromStr`, recognizes only the decimal values. For `Display`, it will -/// only print the decimal values. -/// -/// If the `serde` feature is enabled, also adds implementation for -/// `Serialize` and `Deserialize`, serializing values as their decimal values. -macro_rules! int_enum_str_decimal { - ($ianatype:ident, $inttype:ident) => { - impl $ianatype { - #[must_use] - pub fn from_bytes(bytes: &[u8]) -> Option { - core::str::from_utf8(bytes) - .ok() - .and_then(|r| r.parse().ok().map($ianatype::from_int)) - } - } - - impl core::str::FromStr for $ianatype { - type Err = core::num::ParseIntError; - - fn from_str(s: &str) -> Result { - s.parse().map($ianatype::from_int) - } - } - - scan_impl!($ianatype); - - impl core::fmt::Display for $ianatype { - fn fmt( - &self, - f: &mut core::fmt::Formatter<'_>, - ) -> core::fmt::Result { - write!(f, "{}", self.to_int()) - } - } - - #[cfg(feature = "serde")] - impl serde::Serialize for $ianatype { - fn serialize( - &self, - serializer: S, - ) -> Result { - self.to_int().serialize(serializer) - } - } - - #[cfg(feature = "serde")] - impl<'de> serde::Deserialize<'de> for $ianatype { - fn deserialize>( - deserializer: D, - ) -> Result { - $inttype::deserialize(deserializer).map(Into::into) - } - } - }; -} - -/// Adds impls for `FromStr` and `Display` to the type given as first argument. -/// -/// For `FromStr`, recognizes all mnemonics case-insensitively as well as a -/// decimal number representing any value. -/// -/// For `Display`, it will display a decimal number for values without -/// mnemonic. -/// -/// If the `serde` feature is enabled, also adds implementation for -/// `Serialize` and `Deserialize`. Values will be serialized using the -/// mnemonic if availbale or otherwise the integer value for human readable -/// formats and the integer value for compact formats. Both mnemonics and -/// integer values can be deserialized. -macro_rules! int_enum_str_with_decimal { - ($ianatype:ident, $inttype:ident, $error:expr) => { - impl $ianatype { - #[must_use] - pub fn from_bytes(bytes: &[u8]) -> Option { - $ianatype::from_mnemonic(bytes).or_else(|| { - core::str::from_utf8(bytes) - .ok() - .and_then(|r| r.parse().ok().map($ianatype::from_int)) - }) - } - } - - impl core::str::FromStr for $ianatype { - type Err = FromStrError; - - fn from_str(s: &str) -> Result { - // We assume all mnemonics are always ASCII, so using - // the bytes representation of `s` is safe. - match $ianatype::from_mnemonic(s.as_bytes()) { - Some(res) => Ok(res), - None => { - if let Ok(res) = s.parse() { - Ok($ianatype::from_int(res)) - } else { - Err(FromStrError(())) - } - } - } - } - } - - impl core::fmt::Display for $ianatype { - fn fmt( - &self, - f: &mut core::fmt::Formatter<'_>, - ) -> core::fmt::Result { - match self.to_mnemonic_str() { - Some(m) => { - write!(f, "{m}({})", self.to_int()) - } - None => { - write!(f, "{}", self.to_int()) - } - } - } - } - - scan_impl!($ianatype); - - #[cfg(feature = "serde")] - impl serde::Serialize for $ianatype { - fn serialize( - &self, - serializer: S, - ) -> Result { - if serializer.is_human_readable() { - match self - .to_mnemonic() - .and_then(|value| core::str::from_utf8(value).ok()) - { - Some(value) => value.serialize(serializer), - None => self.to_int().serialize(serializer), - } - } else { - self.to_int().serialize(serializer) - } - } - } - - #[cfg(feature = "serde")] - impl<'de> serde::Deserialize<'de> for $ianatype { - fn deserialize>( - deserializer: D, - ) -> Result { - use crate::base::serde::DeserializeNativeOrStr; - - $inttype::deserialize_native_or_str(deserializer) - } - } - - from_str_error!($error); - }; -} - /// Adds impls for `FromStr` and `Display` to the type given as first argument. /// /// For `FromStr` recognizes all defined mnemonics ignoring case. Additionally @@ -536,3 +336,469 @@ macro_rules! from_str_error { } }; } + +// --- TODO: NEW VERSION, FINISH IT! + +macro_rules! iana_enum { + ( $(#[$attr:meta])* => + $ianatype:ident, $inttype:path; + $display_function:tt, + $parse_function:tt, + $serde_serialize:tt, + $serde_deserialize:tt, + $prefix:expr; + $( $(#[$variant_attr:meta])* ( $variant:ident => + $value:expr, $mnemonic:expr) )* ) => { + $(#[$attr])* + #[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] + pub struct $ianatype($inttype); + + impl $ianatype { + $( + $(#[$variant_attr])* + pub const $variant: $ianatype = $ianatype($value); + )* + } + + impl IanaEnum <'_>for $ianatype { + type INT = $inttype; + type ParseError = FromStrError; + fn get_prefix() -> &'static str { + $prefix + } + + /// Returns the raw integer value for a value. + fn get_integer(&self) -> Self::INT { + self.0 + } + + /// Returns the raw integer value for a value. + fn from_integer(value: Self::INT) -> Self { + Self(value) + } + + /// Returns a value from a well-defined mnemonic. + fn from_mnemonic(m: &[u8]) -> Option { + $( + if m.eq_ignore_ascii_case($mnemonic.as_bytes()) { + return Some($ianatype::$variant) + } + )* + None + } + + /// Returns the mnemonic as a `&str` for this value if there is one + fn get_mnemonic_str(&self) -> Option<&'static str> { + match self { + $( + &$ianatype::$variant => { + Some($mnemonic) + } + )* + _ => None + } + } + + } + impl $ianatype { + + /// Returns a value from its raw integer value. + pub fn from_int(value: $inttype) -> Self { + Self(value) + } + + pub fn to_int(self) -> $inttype { + self.0 + } + + #[must_use] + pub fn from_bytes(bytes: &[u8]) -> Option { + $ianatype::from_mnemonic(bytes).or_else(|| { + if bytes.len() <= $prefix.len() { + return None; + } + let (l, r) = bytes.split_at($prefix.len()); + if !l.eq_ignore_ascii_case($prefix.as_bytes()) { + return None; + } + let r = match core::str::from_utf8(r) { + Ok(r) => r, + Err(_) => return None, + }; + r.parse().ok().map($ianatype::from_int) + }) + } + pub fn parse<'a, Octs: AsRef<[u8]> + ?Sized> ( + parser: &mut octseq::parse::Parser<'a, Octs> + ) -> Result { + <$inttype as $crate::base::wire::Parse<'a, Octs>>::parse( + parser + ).map(Self::from_int) + } + + pub const COMPOSE_LEN: u16 = + <$inttype as $crate::base::wire::Compose>::COMPOSE_LEN; + pub fn compose( + &self, + target: &mut Target, + ) -> Result<(), Target::AppendError> { + crate::base::wire::Compose::compose(&self.get_integer(), target) + } + + + } + + + //--- From + + impl From<$inttype> for $ianatype { + fn from(value: $inttype) -> Self { + $ianatype::from_int(value) + } + } + + impl From<$ianatype> for $inttype { + fn from(value: $ianatype) -> Self { + value.get_integer() + } + } + + impl<'a> From<&'a $ianatype> for $inttype { + fn from(value: &'a $ianatype) -> Self { + value.get_integer() + } + } + + impl core::str::FromStr for $ianatype { + type Err = FromStrError; + + fn from_str(s: &str) -> Result { + $ianatype::$parse_function(s) + } + } + + scan_impl!($ianatype); + + //--- Display + impl core::fmt::Display for $ianatype { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.$display_function()) + } + } + + //--- Debug + + impl core::fmt::Debug for $ianatype { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self.get_mnemonic_bytes().and_then(|bytes| { + core::str::from_utf8(bytes).ok() + }) { + Some(mnemonic) => { + write!( + f, + concat!(stringify!($ianatype), "::{}"), + mnemonic + ) + } + None => { + f.debug_tuple(stringify!($ianatype)) + .field(&self.0) + .finish() + } + } + } + } + + //--- Serde + #[cfg(feature = "serde")] + impl serde::Serialize for $ianatype{ + fn serialize( + &self, + serializer: S, + ) -> Result { + self.$serde_serialize(serializer) + } + } + + #[cfg(feature = "serde")] + impl<'de> serde::Deserialize<'de> for $ianatype{ + fn deserialize>( + deserializer: D, + ) -> Result { + Self::$serde_deserialize(deserializer) + } + } + } +} +#[derive(Clone, Debug)] +pub struct FromStrError(()); + +impl core::error::Error for FromStrError { + fn description(&self) -> &str { + "unknown TODO" + } +} + +impl core::fmt::Display for FromStrError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + "unkown TODO".fmt(f) + } +} +iana_enum! { + => + JannisTestEnum1, u8; + display_integer, + parse_from_integer, + serialize_to_integer, + deserialize_from_integer, + ""; + (A => 0, "A") + (B => 1, "B") +} +iana_enum! { + => + JannisTestEnum2, u8; + display_mnemonic_fallback_prefix_integer, + parse_from_mnemonic_or_prefix_integer, + serialize_to_mnemonic_fallback_prefix_integer, + deserialize_from_mnemonic_or_prefix_integer, + "J"; + (A => 0, "A") + (B => 1, "B") +} +iana_enum! { + => + JannisTestEnum3, u8; + display_mnemonic_with_integer, + parse_from_mnemonic_or_integer, + serialize_to_mnemonic_fallback_integer, + deserialize_from_mnemonic_or_integer, + ""; + (A => 0, "A") + (B => 1, "B") +} +iana_enum! { + => + JannisTestEnum4, u8; + display_mnemonic_with_integer, + parse_from_integer, + serialize_to_integer, + deserialize_from_integer, + ""; + (A => 0, "A") + (B => 1, "B") +} + +use core::fmt::Display; +use std::string::String; +use std::string::ToString; + +#[cfg(feature = "serde")] +use serde::Deserialize; +#[cfg(feature = "serde")] +use serde::Serialize; + +#[cfg(feature = "serde")] +use crate::base::serde::DeserializeNativeOrStr; + +pub trait IanaEnum<'de>: Sized { + #[cfg(not(feature = "serde"))] + type INT: Default + + std::string::ToString + + crate::base::wire::Compose + + core::str::FromStr + + Into + + Display; + #[cfg(feature = "serde")] + type INT: Default + + std::string::ToString + + crate::base::wire::Compose + + core::str::FromStr + + Deserialize<'de> + + Into + + Serialize + + Display + + DeserializeNativeOrStr<'de, Self>; + // + core::str::FromStr + + type ParseError; + + fn get_prefix() -> &'static str; + fn from_integer(value: Self::INT) -> Self; + fn from_mnemonic(m: &[u8]) -> Option; + fn get_mnemonic_str(&self) -> Option<&'static str>; + fn get_mnemonic_bytes(&self) -> Option<&'static [u8]> { + match self.get_mnemonic_str() { + Some(m) => Some(m.as_bytes()), + None => None, + } + } + fn to_mnemonic_str(self) -> Option<&'static str> { + self.get_mnemonic_str() + } + + fn get_integer(&self) -> Self::INT; + + //--- Display + fn display_integer(&self) -> String { + self.get_integer().to_string() + } + fn display_mnemonic_fallback_integer(&self) -> String { + match self.get_mnemonic_str() { + Some(m) => m.to_string(), + None => self.get_integer().to_string(), + } + } + fn display_mnemonic_fallback_prefix_integer(&self) -> String { + match self.get_mnemonic_str() { + Some(m) => m.to_string(), + None => format!("{}{}", Self::get_prefix(), self.get_integer()), + } + } + fn display_mnemonic_with_integer(&self) -> String { + match self.get_mnemonic_str() { + Some(m) => format!("{}({})", m, self.get_integer()), + None => format!("{}", self.get_integer()), + } + } + + //--- PARSING + fn parse_from_integer(value: &str) -> Result { + match value.parse().map(Self::from_integer) { + Ok(v) => Ok(v), + Err(_) => Err(FromStrError(())), + } + } + fn parse_from_mnemonic_or_integer( + value: &str, + ) -> Result { + match Self::from_mnemonic(value.as_bytes()) { + Some(v) => Ok(v), + None => match value.parse().map(Self::from_integer) { + Ok(v) => Ok(v), + Err(_) => Err(FromStrError(())), + }, + } + } + + fn parse_from_mnemonic_or_prefix_integer( + value: &str, + ) -> Result { + match Self::from_mnemonic(value.as_bytes()) { + Some(res) => Ok(res), + None => { + if let Some((n, _)) = + value.char_indices().nth(Self::get_prefix().len()) + { + let (l, r) = value.split_at(n); + if l.eq_ignore_ascii_case(Self::get_prefix()) { + let value = match r.parse() { + Ok(x) => x, + Err(..) => return Err(FromStrError(())), + }; + Ok(Self::from_integer(value)) + } else { + Err(FromStrError(())) + } + } else { + Err(FromStrError(())) + } + } + } + } + + //--- serde::Serialize + #[cfg(feature = "serde")] + fn serialize_to_integer( + &self, + serializer: S, + ) -> Result { + self.get_integer().serialize(serializer) + } + + #[cfg(feature = "serde")] + fn serialize_to_mnemonic_fallback_integer( + &self, + serializer: S, + ) -> Result { + if !serializer.is_human_readable() { + return self.get_integer().serialize(serializer); + } + + match self.get_mnemonic_str() { + Some(m) => m.serialize(serializer), + None => self.get_integer().serialize(serializer), + } + } + + #[cfg(feature = "serde")] + fn serialize_to_mnemonic_fallback_prefix_integer( + &self, + serializer: S, + ) -> Result { + if !serializer.is_human_readable() { + return self.get_integer().serialize(serializer); + } + + match self.get_mnemonic_str() { + Some(m) => m.serialize(serializer), + None => format!("{}{}", Self::get_prefix(), self.get_integer()) + .serialize(serializer), + } + } + + //--- serde::Deserialize + #[cfg(feature = "serde")] + fn deserialize_from_integer>( + deserializer: D, + ) -> Result>::Error> { + Self::INT::deserialize(deserializer).map(Self::from_integer) + } + + #[cfg(feature = "serde")] + fn deserialize_from_mnemonic_or_integer>( + deserializer: D, + ) -> Result>::Error> { + Self::INT::deserialize_native_or_str(deserializer) + } + + #[cfg(feature = "serde")] + fn deserialize_from_mnemonic_or_prefix_integer< + D: serde::Deserializer<'de>, + >( + deserializer: D, + ) -> Result>::Error> { + Self::INT::deserialize_native_or_str(deserializer) + } +} + +int_enum_zonefile_fmt_decimal!(JannisTestEnum1, "jannis1"); +int_enum_zonefile_fmt_with_prefix!(JannisTestEnum2, "J"); +int_enum_zonefile_fmt_with_decimal!(JannisTestEnum3); +int_enum_zonefile_fmt_decimal!(JannisTestEnum4, "jannis4"); + +//------------ Tests --------------------------------------------------------- + +#[cfg(test)] +mod test { + #[cfg(feature = "serde")] + #[test] + fn security_algorithm_to_json_string() { + use crate::base::iana::SecurityAlgorithm; + use alloc::string::String; + let secalg: SecurityAlgorithm = SecurityAlgorithm::DELETE; + + let secalg_json_str: String = serde_json::to_string(&secalg).unwrap(); + + println!( + "#{secalg}#{secalg:?}#: #{secalg_json_str}#{secalg_json_str:?}#" + ); + + let secalg_from_str: Result = + serde_json::from_str(&secalg_json_str); + + println!("{:?}", secalg_from_str); + assert!(secalg_from_str.is_ok()) + } +} diff --git a/src/base/iana/mod.rs b/src/base/iana/mod.rs index 1c734ccee..23d030ba7 100644 --- a/src/base/iana/mod.rs +++ b/src/base/iana/mod.rs @@ -24,11 +24,30 @@ //! While each parameter type has a module of its own, they are all //! re-exported here. This is mostly so we can have associated types like //! `FromStrError` without having to resort to devilishly long names. +//! + +// TODO; This is an example. +//! ## Representation of Variables +//! +//! The following table defines the output of for the variables in this module. +//! ### Type [`SecurityAlgorithm`] +//! +//! * [`Display`] returns mnemonic ("0") +//! * [`FromStr`] parses text with number or mnemonic ("0", "DELETE") +//! * [`ZonefileFmt`] returns text number ("0") +//! * [`serde::Serialize`] returns number (5) +//! * [`serde::Deserialize`] reads mnemonic ("RSASHA1") or number (5) +// ----- Aliases +//! +//! [`ZonefileFmt`]: crate::base::zonefile_fmt::ZonefileFmt +//! [`FromStr`]: core::str::FromStr +//! [`Display`]: std::fmt::Display pub use self::class::Class; pub use self::digestalg::DigestAlgorithm; pub use self::exterr::ExtendedErrorCode; pub use self::ipseckey::{IpseckeyAlgorithm, IpseckeyGatewayType}; +pub use self::macros::IanaEnum; pub use self::nsec3::Nsec3HashAlgorithm; pub use self::opcode::Opcode; pub use self::opt::OptionCode; @@ -57,3 +76,584 @@ pub mod sshfp; pub mod svcb; pub mod tlsa; pub mod zonemd; + +#[cfg(feature = "serde")] +#[cfg(test)] +mod test { + use crate::base::iana::class::Class; + use crate::base::iana::digestalg::DigestAlgorithm; + // use crate::base::iana::exterr::ExtendedErrorCode; + use crate::base::iana::ipseckey::IpseckeyAlgorithm; + use crate::base::iana::ipseckey::IpseckeyGatewayType; + use crate::base::iana::nsec3::Nsec3HashAlgorithm; + use crate::base::iana::opcode::Opcode; + use crate::base::iana::opt::OptionCode; + // use crate::base::iana::rcode::OptRcode; + // use crate::base::iana::rcode::Rcode; + use crate::base::iana::rcode::TsigRcode; + use crate::base::iana::rtype::Rtype; + use crate::base::iana::secalg::SecurityAlgorithm; + use crate::base::iana::sshfp::SshfpAlgorithm; + use crate::base::iana::sshfp::SshfpType; + use crate::base::iana::svcb::SvcParamKey; + use crate::base::iana::tlsa::TlsaCertificateUsage; + use crate::base::iana::tlsa::TlsaMatchingType; + use crate::base::iana::tlsa::TlsaSelector; + use crate::base::iana::zonemd::ZonemdAlgorithm; + use crate::base::iana::zonemd::ZonemdScheme; + + // TODO: REMOVE + use crate::base::iana::macros::JannisTestEnum1; + use crate::base::iana::macros::JannisTestEnum2; + use crate::base::iana::macros::JannisTestEnum3; + use crate::base::iana::macros::JannisTestEnum4; + + use core::fmt::Debug; + use core::fmt::Display; + use core::str::FromStr; + use std::string::String; + + use crate::base::zonefile_fmt::DisplayKind; + use crate::base::zonefile_fmt::ZonefileFmt; + + #[track_caller] + fn validate_generic_representation( + test_value: T, // This value is used as desired value + display_repr: String, // Display MUST result in this String + debug_repr: String, // Debug MUST result in this String + fromstr_list: &[&str], // `&[&str]` `FromStr`s MUST result in `test_value` + zonefile_fmt_value: String, // ZonefileFmt MUST result in this String + serde_serialize_value: String, // ZonefileFmt MUST result in this String + ) where + T: Display + + Debug + + FromStr + + PartialEq + + ZonefileFmt + + for<'a> serde::Deserialize<'a> + + serde::Serialize, + ::Err: core::fmt::Debug, + { + // Display + println!("assert fmt::Display"); + assert_eq!( + display_repr, + format!("{test_value}"), + "Display representation" + ); + + // Debug + println!("assert fmt::Debug"); + assert_eq!( + debug_repr, + format!("{test_value:?}"), + "Debug representation" + ); + + for value in fromstr_list { + // FromStr mnemonic + println!("assert FromStr with {}", value); + assert_eq!( + value.parse::().unwrap_or_else(|_| panic!( + "FromStr failed with {value}" + )), + test_value, + "FromStr representation" + ); + } + + // ZonefileFmt + println!("assert ZonefileFmt"); + let display_zonefile = + test_value.display_zonefile(DisplayKind::Simple); + assert_eq!( + zonefile_fmt_value, + format!("{display_zonefile}"), + "ZonefileFmt representation" + ); + + // serde::Serialize + println!("assert Serialize"); + assert_eq!( + serde_serialize_value, + serde_json::to_string(&test_value).unwrap(), + "serde_json::to_string(&test_value)" + ); + + // serde::Deserialize + let value_as_json_string = + serde_json::to_string(&test_value).unwrap(); + + println!("assert Deserialize from #{}#", value_as_json_string); + assert_eq!( + test_value, + serde_json::from_str(&value_as_json_string).unwrap(), + "serde_json::from_str(&value_as_json_string)" + ); + + // TODO: Missing is the non-human-readable testing of serde! + } + + #[test] + fn validate_jannis1_representation() { + validate_generic_representation( + JannisTestEnum1::A, + "0".into(), + "JannisTestEnum1::A".into(), + &["0"], + "0".into(), + r#"0"#.into(), + ); + validate_generic_representation( + JannisTestEnum1::from_int(42), + "42".into(), + "JannisTestEnum1(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); + // use serde_test::{assert_tokens, Configure, Token}; + // assert_tokens(&JannisTestEnum1::A.readable(), &[Token::Str("A")]); + // assert_tokens(&JannisTestEnum1::from_int(42).readable(), &[Token::U8(42)]); + // + // assert_tokens(&JannisTestEnum1::A.compact(), &[Token::U8(0)]); + // assert_tokens(&JannisTestEnum1::from_int(42).compact(), &[Token::U8(42)]); + } + + #[test] + fn validate_jannis2_representation() { + validate_generic_representation( + JannisTestEnum2::A, + "A".into(), + "JannisTestEnum2::A".into(), + &["A", "J0"], + "A".into(), + r#""A""#.into(), + ); + validate_generic_representation( + JannisTestEnum2::from_int(42), + "J42".into(), + "JannisTestEnum2(42)".into(), + &["J42"], + "J42".into(), + r#""J42""#.into(), + ); + } + + #[test] + fn validate_jannis3_representation() { + validate_generic_representation( + JannisTestEnum3::A, + "A(0)".into(), + "JannisTestEnum3::A".into(), + &["A", "0"], + "A".into(), + r#""A""#.into(), + ); + validate_generic_representation( + JannisTestEnum3::from_int(42), + "42".into(), + "JannisTestEnum3(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); + } + + #[test] + fn validate_jannis4_representation() { + validate_generic_representation( + JannisTestEnum4::A, + "A(0)".into(), + "JannisTestEnum4::A".into(), + &["0"], + "0".into(), + r#"0"#.into(), + ); + validate_generic_representation( + JannisTestEnum4::from_int(42), + "42".into(), + "JannisTestEnum4(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); + } + #[test] + fn validate_class_representation() { + validate_generic_representation( + Class::IN, + "IN".into(), + "Class::IN".into(), + &["IN", "CLASS1"], + "IN".into(), + r#""IN""#.into(), + ); + validate_generic_representation( + Class::from_int(42), + "CLASS42".into(), + "Class(42)".into(), + &["CLASS42"], + "CLASS42".into(), + r#""CLASS42""#.into(), + ); + } + + #[test] + fn validate_digest_algorithm_representation() { + validate_generic_representation( + DigestAlgorithm::SHA256, + "2".into(), + "DigestAlgorithm::SHA-256".into(), + &["2"], + "2".into(), + r#"2"#.into(), + ); + validate_generic_representation( + DigestAlgorithm::from_int(42), + "42".into(), + "DigestAlgorithm(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); + } + + #[test] + #[ignore = "not yet implemented"] + fn validate_extended_error_code_representation() { + todo!() + } + + #[test] + fn validate_ipseckey_algorithm_representation() { + validate_generic_representation( + IpseckeyAlgorithm::ECDSA, + "3".into(), + "IpseckeyAlgorithm::ECDSA".into(), + &["3", "ECDSA"], + "3".into(), + r#"3"#.into(), + ); + validate_generic_representation( + IpseckeyAlgorithm::from_int(42), + "42".into(), + "IpseckeyAlgorithm(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); + } + + #[test] + fn validate_ipseckey_gateway_type_representation() { + validate_generic_representation( + IpseckeyGatewayType::NONE, + "0".into(), + "IpseckeyGatewayType::NONE".into(), + &["0", "NONE"], + "0".into(), + r#"0"#.into(), + ); + validate_generic_representation( + IpseckeyGatewayType::from_int(42), + "42".into(), + "IpseckeyGatewayType(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); + } + + #[test] + fn validate_nsec3_hash_algorithm_representation() { + validate_generic_representation( + Nsec3HashAlgorithm::SHA1, + "1".into(), + "Nsec3HashAlgorithm::SHA-1".into(), + &["1", "SHA-1"], + "1".into(), + r#"1"#.into(), + ); + validate_generic_representation( + Nsec3HashAlgorithm::from_int(42), + "42".into(), + "Nsec3HashAlgorithm(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); + } + + #[test] + fn validate_opcode_representation() { + validate_generic_representation( + Opcode::QUERY, + "QUERY(0)".into(), + "Opcode::QUERY".into(), + &["QUERY", "0"], + "QUERY".into(), + r#""QUERY""#.into(), + ); + validate_generic_representation( + Opcode::from_int(42), + "42".into(), + "Opcode(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); + } + + #[test] + fn validate_option_code_representation() { + validate_generic_representation( + OptionCode::COOKIE, + "COOKIE(10)".into(), + "OptionCode::COOKIE".into(), + &["COOKIE", "10"], + "COOKIE".into(), + r#""COOKIE""#.into(), + ); + validate_generic_representation( + OptionCode::from_int(42), + "42".into(), + "OptionCode(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); + } + + #[test] + #[ignore = "not yet implemented"] + fn validate_opt_rcode_representation() { + todo!() + } + + #[test] + #[ignore = "not yet implemented"] + fn validate_rcode_representation() { + todo!() + } + + #[test] + fn validate_tsig_rcode_representation() { + validate_generic_representation( + TsigRcode::BADCOOKIE, + "BADCOOKIE(23)".into(), + "TsigRcode::BADCOOKIE".into(), + &["23", "BADCOOKIE"], + "BADCOOKIE".into(), + r#""BADCOOKIE""#.into(), + ); + validate_generic_representation( + TsigRcode::from_int(42), + "42".into(), + "TsigRcode(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); + } + + #[test] + fn validate_rtype_representation() { + validate_generic_representation( + Rtype::MX, + "MX".into(), + "Rtype::MX".into(), + &["MX", "TYPE15"], + "MX".into(), + r#""MX""#.into(), + ); + validate_generic_representation( + Rtype::from_int(842), + "TYPE842".into(), + "Rtype(842)".into(), + &["TYPE842"], + "TYPE842".into(), + r#""TYPE842""#.into(), + ); + } + + #[test] + fn validate_security_algorithm_representation() { + validate_generic_representation( + SecurityAlgorithm::DELETE, + "DELETE(0)".into(), + "SecurityAlgorithm::DELETE".into(), + &["0", "DELETE"], // SPECIAL, read from mnemonic and int + "0".into(), // ...but print as integer + r#"0"#.into(), + ); + validate_generic_representation( + SecurityAlgorithm::from_int(42), + "42".into(), + "SecurityAlgorithm(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); + } + + #[test] + fn validate_sshfp_algorithm_representation() { + validate_generic_representation( + SshfpAlgorithm::ED25519, + "4".into(), + "SshfpAlgorithm::Ed25519".into(), + &["4"], + "4".into(), + r#"4"#.into(), + ); + validate_generic_representation( + SshfpAlgorithm::from_int(42), + "42".into(), + "SshfpAlgorithm(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); + } + + #[test] + fn validate_sshfp_type_representation() { + validate_generic_representation( + SshfpType::SHA256, + "2".into(), + "SshfpType::SHA-256".into(), + &["2"], + "2".into(), + r#"2"#.into(), + ); + validate_generic_representation( + SshfpType::from_int(42), + "42".into(), + "SshfpType(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); + } + + #[test] + fn validate_svc_param_key_representation() { + validate_generic_representation( + SvcParamKey::ALPN, + "alpn".into(), + "SvcParamKey::alpn".into(), + &["alpn", "KEY1"], + "alpn".into(), + r#""alpn""#.into(), + ); + validate_generic_representation( + SvcParamKey::from_int(42), + "key42".into(), + "SvcParamKey(42)".into(), + &["KEY42"], + "key42".into(), + r#""key42""#.into(), + ); + } + + #[test] + fn validate_tlsa_certificate_usage_representation() { + validate_generic_representation( + TlsaCertificateUsage::DANE_EE, + "3".into(), + "TlsaCertificateUsage::DANE-EE".into(), + &["3"], + "3".into(), + r#"3"#.into(), + ); + validate_generic_representation( + TlsaCertificateUsage::from_int(42), + "42".into(), + "TlsaCertificateUsage(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); + } + + #[test] + fn validate_tlsa_matching_type_representation() { + validate_generic_representation( + TlsaMatchingType::FULL, + "0".into(), + "TlsaMatchingType::Full".into(), + &["0"], + "0".into(), + r#"0"#.into(), + ); + validate_generic_representation( + TlsaMatchingType::from_int(42), + "42".into(), + "TlsaMatchingType(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); + } + + #[test] + fn validate_tlsa_selector_representation() { + validate_generic_representation( + TlsaSelector::CERT, + "0".into(), + "TlsaSelector::Cert".into(), + &["0"], + "0".into(), + r#"0"#.into(), + ); + validate_generic_representation( + TlsaSelector::from_int(42), + "42".into(), + "TlsaSelector(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); + } + + #[test] + fn validate_zonemd_algorithm_representation() { + validate_generic_representation( + ZonemdAlgorithm::SHA512, + "2".into(), + "ZonemdAlgorithm::SHA512".into(), + &["2"], + "2".into(), + "2".into(), + ); + validate_generic_representation( + ZonemdAlgorithm::from_int(42), + "42".into(), + "ZonemdAlgorithm(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); + } + + #[test] + fn validate_zonemd_scheme_representation() { + validate_generic_representation( + ZonemdScheme::SIMPLE, + "1".into(), + "ZonemdScheme::SIMPLE".into(), + &["1"], + "1".into(), + "1".into(), + ); + validate_generic_representation( + ZonemdScheme::from_int(42), + "42".into(), + "ZonemdScheme(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); + } +} diff --git a/src/base/iana/nsec3.rs b/src/base/iana/nsec3.rs index 650fbdb4e..1abae3e19 100644 --- a/src/base/iana/nsec3.rs +++ b/src/base/iana/nsec3.rs @@ -2,7 +2,10 @@ //------------ Nsec3HashAlgorithm -------------------------------------------- -int_enum! { +use crate::base::iana::macros::FromStrError; +use crate::base::iana::macros::IanaEnum; + +iana_enum! { /// NSEC3 hash algorithm numbers. /// /// This type selects the algorithm used to hash domain names for use with @@ -15,10 +18,14 @@ int_enum! { /// [IANA registration]: https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml#dnssec-nsec3-parameters-3 => Nsec3HashAlgorithm, u8; + display_integer, + parse_from_mnemonic_or_integer, + serialize_to_integer, + deserialize_from_integer, + ""; /// Specifies that the SHA-1 hash function is used. (SHA1 => 1, "SHA-1") } -int_enum_str_decimal!(Nsec3HashAlgorithm, u8); int_enum_zonefile_fmt_decimal!(Nsec3HashAlgorithm, "hash algorithm"); diff --git a/src/base/iana/opcode.rs b/src/base/iana/opcode.rs index 7a4869202..eb15ec5d5 100644 --- a/src/base/iana/opcode.rs +++ b/src/base/iana/opcode.rs @@ -2,7 +2,10 @@ //------------ Opcode -------------------------------------------------------- -int_enum! { +use crate::base::iana::macros::FromStrError; +use crate::base::iana::macros::IanaEnum; + +iana_enum! { /// DNS OpCodes. /// /// The opcode specifies the kind of query to be performed. @@ -16,6 +19,11 @@ int_enum! { /// [IANA registry]: http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-5 => Opcode, u8; + display_mnemonic_with_integer, + parse_from_mnemonic_or_integer, + serialize_to_mnemonic_fallback_integer, + deserialize_from_mnemonic_or_integer, + ""; /// A standard query (0). /// @@ -81,5 +89,4 @@ int_enum! { (DSO => 6, "DSO") } -int_enum_str_with_decimal!(Opcode, u8, "unknown opcode"); int_enum_zonefile_fmt_with_decimal!(Opcode); diff --git a/src/base/iana/opt.rs b/src/base/iana/opt.rs index 0012353cc..708e7f102 100644 --- a/src/base/iana/opt.rs +++ b/src/base/iana/opt.rs @@ -2,7 +2,10 @@ //------------ OptionCode ---------------------------------------------------- -int_enum! { +use crate::base::iana::macros::FromStrError; +use crate::base::iana::macros::IanaEnum; + +iana_enum! { /// DNS EDNS0 option codes. /// /// The record data of [OPT] records is a sequence of options. The type of @@ -16,6 +19,11 @@ int_enum! { /// [IANA registry]: http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-11 => OptionCode, u16; + display_mnemonic_with_integer, + parse_from_mnemonic_or_integer, + serialize_to_mnemonic_fallback_integer, + deserialize_from_mnemonic_or_integer, + ""; /// Long-Lived Queries (LLQ, 1). /// @@ -170,7 +178,6 @@ int_enum! { (DEVICE_ID => 26946, "DeviceId") } -int_enum_str_with_decimal!(OptionCode, u16, "unknown option code"); int_enum_zonefile_fmt_with_decimal!(OptionCode); //============ Tests ========================================================= diff --git a/src/base/iana/rcode.rs b/src/base/iana/rcode.rs index 6651c72d4..4cedeb200 100644 --- a/src/base/iana/rcode.rs +++ b/src/base/iana/rcode.rs @@ -18,6 +18,8 @@ // Note: Rcode and OptRcode don’t use the macros since they don’t use all the // bits of the wrapped integer. +use crate::base::iana::macros::FromStrError; +use crate::base::iana::macros::IanaEnum; use core::fmt; use core::str::FromStr; @@ -669,7 +671,7 @@ impl fmt::Debug for OptRcode { //------------ TsigRcode ---------------------------------------------------- -int_enum! { +iana_enum! { /// Response codes for transaction authentication (TSIG). /// /// TSIG and TKEY resource records contain a 16 bit wide error field whose @@ -687,6 +689,11 @@ int_enum! { /// [IANA DNS RCODEs]: http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 => TsigRcode, u16; + display_mnemonic_with_integer, + parse_from_mnemonic_or_integer, + serialize_to_mnemonic_fallback_integer, + deserialize_from_mnemonic_or_integer, + ""; /// No error condition. /// @@ -896,7 +903,6 @@ impl From for TsigRcode { } } -int_enum_str_with_decimal!(TsigRcode, u16, "unknown TSIG error"); int_enum_zonefile_fmt_with_decimal!(TsigRcode); //============ Error Types =================================================== diff --git a/src/base/iana/rtype.rs b/src/base/iana/rtype.rs index a6546476a..22a87c9fc 100644 --- a/src/base/iana/rtype.rs +++ b/src/base/iana/rtype.rs @@ -2,7 +2,10 @@ //------------ Rtype --------------------------------------------------------- -int_enum! { +use crate::base::iana::macros::FromStrError; +use crate::base::iana::macros::IanaEnum; + +iana_enum! { /// Resource Record Types. /// /// Each resource records has a 16 bit type value indicating what kind of @@ -22,6 +25,11 @@ int_enum! { /// guidelines. => Rtype, u16; + display_mnemonic_fallback_prefix_integer, + parse_from_mnemonic_or_prefix_integer, + serialize_to_mnemonic_fallback_prefix_integer, + deserialize_from_mnemonic_or_prefix_integer, + "TYPE"; /// A host address. (A => 1, "A") @@ -429,7 +437,6 @@ int_enum! { (DLV => 32769, "DLV") } -int_enum_str_with_prefix!(Rtype, "TYPE", b"TYPE", u16, "unknown record type"); int_enum_zonefile_fmt_with_prefix!(Rtype, "TYPE"); impl Rtype { diff --git a/src/base/iana/secalg.rs b/src/base/iana/secalg.rs index 82d222395..f887fd67b 100644 --- a/src/base/iana/secalg.rs +++ b/src/base/iana/secalg.rs @@ -2,7 +2,10 @@ //------------ SecurityAlgorithm --------------------------------------------- -int_enum! { +use crate::base::iana::macros::FromStrError; +use crate::base::iana::macros::IanaEnum; + +iana_enum! { /// Security Algorithm Numbers. /// /// These numbers are used in various security related record types. @@ -12,6 +15,11 @@ int_enum! { /// [IANA registration]: http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml#dns-sec-alg-numbers-1]. => SecurityAlgorithm, u8; + display_mnemonic_with_integer, + parse_from_mnemonic_or_integer, + serialize_to_integer, + deserialize_from_mnemonic_or_integer, + "CLASS"; /// Delete DS /// @@ -116,5 +124,4 @@ int_enum! { (PRIVATEOID => 254, "PRIVATEOID") } -int_enum_str_decimal!(SecurityAlgorithm, u8); int_enum_zonefile_fmt_decimal!(SecurityAlgorithm, "algorithm"); diff --git a/src/base/iana/sshfp.rs b/src/base/iana/sshfp.rs index ff31d67f2..a4b900707 100644 --- a/src/base/iana/sshfp.rs +++ b/src/base/iana/sshfp.rs @@ -10,7 +10,10 @@ //------------ SshfpType ----------------------------------------------------- -int_enum! { +use crate::base::iana::macros::FromStrError; +use crate::base::iana::macros::IanaEnum; + +iana_enum! { /// SSHFP fingerprint type. /// /// This type selects the digest algorithm used for the fingerprint in the @@ -23,6 +26,11 @@ int_enum! { /// [IANA registration]: https://www.iana.org/assignments/dns-sshfp-rr-parameters/dns-sshfp-rr-parameters.xhtml#dns-sshfp-rr-parameters-2 => SshfpType, u8; + display_integer, + parse_from_integer, + serialize_to_integer, + deserialize_from_integer, + ""; (RESERVED => 0, "Reserved") @@ -38,12 +46,11 @@ int_enum! { } -int_enum_str_decimal!(SshfpType, u8); int_enum_zonefile_fmt_decimal!(SshfpType, "fingerprint type"); //------------ SshfpAlgorithm ------------------------------------------------ -int_enum! { +iana_enum! { /// SSHFP public key algorithms. /// /// This type selects the algorithm of the public key associated with the [`Sshfp`]. @@ -55,6 +62,11 @@ int_enum! { /// [IANA registration]: https://www.iana.org/assignments/dns-sshfp-rr-parameters/dns-sshfp-rr-parameters.xhtml#dns-sshfp-rr-parameters-1 => SshfpAlgorithm, u8; + display_integer, + parse_from_integer, + serialize_to_integer, + deserialize_from_integer, + ""; /// Specified that the Reserved algorithm is used. [RFC4255] /// @@ -87,5 +99,4 @@ int_enum! { (ED448 => 6, "Ed448") } -int_enum_str_decimal!(SshfpAlgorithm, u8); int_enum_zonefile_fmt_decimal!(SshfpAlgorithm, "public key algorithm"); diff --git a/src/base/iana/svcb.rs b/src/base/iana/svcb.rs index 58e651d22..3f31e0b97 100644 --- a/src/base/iana/svcb.rs +++ b/src/base/iana/svcb.rs @@ -1,8 +1,16 @@ //! Service Binding (SVCB) Parameter Registry -int_enum! { +use crate::base::iana::macros::FromStrError; +use crate::base::iana::macros::IanaEnum; + +iana_enum! { => SvcParamKey, u16; + display_mnemonic_fallback_prefix_integer, + parse_from_mnemonic_or_prefix_integer, + serialize_to_mnemonic_fallback_prefix_integer, + deserialize_from_mnemonic_or_prefix_integer, + "key"; (MANDATORY => 0, "mandatory") (ALPN => 1, "alpn") @@ -20,7 +28,6 @@ int_enum! { // TODO: docpath https://datatracker.ietf.org/doc/draft-ietf-core-dns-over-coap/ } -int_enum_str_with_prefix!(SvcParamKey, "key", b"key", u16, "unknown key"); int_enum_zonefile_fmt_with_prefix!(SvcParamKey, "key"); impl SvcParamKey { diff --git a/src/base/iana/tlsa.rs b/src/base/iana/tlsa.rs index b320e4c2d..fa0e9d5c9 100644 --- a/src/base/iana/tlsa.rs +++ b/src/base/iana/tlsa.rs @@ -2,7 +2,10 @@ //------------ TlsaCertificateUsage ------------------------------------------ -int_enum! { +use crate::base::iana::macros::FromStrError; +use crate::base::iana::macros::IanaEnum; + +iana_enum! { /// TLSA Certificate Usage type. /// /// This type specifies the provided association that will be used to match the certificate @@ -15,6 +18,11 @@ int_enum! { /// [IANA registration]: https://www.iana.org/assignments/dane-parameters/dane-parameters.xhtml#certificate-usages => TlsaCertificateUsage, u8; + display_integer, + parse_from_integer, + serialize_to_integer, + deserialize_from_integer, + ""; /// CA constraint (PKIX_TA => 0, "PKIX-TA") @@ -32,7 +40,6 @@ int_enum! { (PRIVCERT => 255, "PrivCert") } -int_enum_str_decimal!(TlsaCertificateUsage, u8); int_enum_zonefile_fmt_decimal!( TlsaCertificateUsage, "certificate usage type" @@ -40,7 +47,7 @@ int_enum_zonefile_fmt_decimal!( //------------ TlsaSelector -------------------------------------------------- -int_enum! { +iana_enum! { /// TLSA Selector type. /// /// This type specifies which part of the TLS certificate presented by the server will be @@ -53,6 +60,11 @@ int_enum! { /// [IANA registration]: https://www.iana.org/assignments/dane-parameters/dane-parameters.xhtml#selectors => TlsaSelector, u8; + display_integer, + parse_from_integer, + serialize_to_integer, + deserialize_from_integer, + ""; /// Full certificate (CERT => 0, "Cert") @@ -64,12 +76,11 @@ int_enum! { (PRIVSEL => 255, "PrivSel") } -int_enum_str_decimal!(TlsaSelector, u8); int_enum_zonefile_fmt_decimal!(TlsaSelector, "selector"); //------------ TlsaMatchingType ---------------------------------------------- -int_enum! { +iana_enum! { /// TLSA Matching Type type. /// /// This type specifies how the certificate association is presented. @@ -81,6 +92,11 @@ int_enum! { /// [IANA registration]: https://www.iana.org/assignments/dane-parameters/dane-parameters.xhtml#matching-types => TlsaMatchingType, u8; + display_integer, + parse_from_integer, + serialize_to_integer, + deserialize_from_integer, + ""; /// No hash used (FULL => 0, "Full") @@ -95,5 +111,4 @@ int_enum! { (PRIVMATCH => 255, "PrivMatch") } -int_enum_str_decimal!(TlsaMatchingType, u8); int_enum_zonefile_fmt_decimal!(TlsaMatchingType, "matching type"); diff --git a/src/base/iana/zonemd.rs b/src/base/iana/zonemd.rs index 49ce637e3..6c8a04787 100644 --- a/src/base/iana/zonemd.rs +++ b/src/base/iana/zonemd.rs @@ -2,7 +2,10 @@ //------------ ZonemdScheme -------------------------------------------------- -int_enum! { +use crate::base::iana::macros::FromStrError; +use crate::base::iana::macros::IanaEnum; + +iana_enum! { /// ZONEMD schemes. /// /// This type selects the method by which data is collated and presented @@ -15,17 +18,21 @@ int_enum! { /// [IANA registration]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#zonemd-schemes => ZonemdScheme, u8; + display_integer, + parse_from_integer, + serialize_to_integer, + deserialize_from_integer, + ""; /// Specifies that the SIMPLE scheme is used. (SIMPLE => 1, "SIMPLE") } -int_enum_str_decimal!(ZonemdScheme, u8); int_enum_zonefile_fmt_decimal!(ZonemdScheme, "scheme"); //------------ ZonemdAlgorithm ----------------------------------------------- -int_enum! { +iana_enum! { /// ZONEMD algorithms. /// /// This type selects the algorithm used to hash domain names for use with @@ -38,6 +45,11 @@ int_enum! { /// [IANA registration]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#zonemd-hash-algorithms => ZonemdAlgorithm, u8; + display_integer, + parse_from_integer, + serialize_to_integer, + deserialize_from_integer, + ""; /// Specifies that the SHA-384 algorithm is used. (SHA384 => 1, "SHA384") @@ -46,5 +58,4 @@ int_enum! { (SHA512 => 2, "SHA512") } -int_enum_str_decimal!(ZonemdAlgorithm, u8); int_enum_zonefile_fmt_decimal!(ZonemdAlgorithm, "hash algorithm"); diff --git a/src/base/serde.rs b/src/base/serde.rs index 801120ad6..67729768b 100644 --- a/src/base/serde.rs +++ b/src/base/serde.rs @@ -43,20 +43,15 @@ where write!(f, "a u8 or string") } - fn visit_u8(self, v: u8) -> Result { - Ok(T::from(v)) + fn visit_u64(self, v: u64) -> Result { + Ok(T::from(v as u8)) } fn visit_str(self, v: &str) -> Result { T::from_str(v).map_err(E::custom) } } - - if deserializer.is_human_readable() { - deserializer.deserialize_str(Visitor(PhantomData)) - } else { - deserializer.deserialize_u8(Visitor(PhantomData)) - } + deserializer.deserialize_any(Visitor(PhantomData)) } } @@ -81,20 +76,15 @@ where write!(f, "a u16 or string") } - fn visit_u16(self, v: u16) -> Result { - Ok(T::from(v)) + fn visit_u64(self, v: u64) -> Result { + Ok(T::from(v as u16)) } fn visit_str(self, v: &str) -> Result { T::from_str(v).map_err(E::custom) } } - - if deserializer.is_human_readable() { - deserializer.deserialize_str(Visitor(PhantomData)) - } else { - deserializer.deserialize_u16(Visitor(PhantomData)) - } + deserializer.deserialize_any(Visitor(PhantomData)) } } @@ -119,19 +109,14 @@ where write!(f, "a u32 or string") } - fn visit_u32(self, v: u32) -> Result { - Ok(T::from(v)) + fn visit_u64(self, v: u64) -> Result { + Ok(T::from(v as u32)) } fn visit_str(self, v: &str) -> Result { T::from_str(v).map_err(E::custom) } } - - if deserializer.is_human_readable() { - deserializer.deserialize_str(Visitor(PhantomData)) - } else { - deserializer.deserialize_u32(Visitor(PhantomData)) - } + deserializer.deserialize_any(Visitor(PhantomData)) } } diff --git a/src/zonefile/inplace.rs b/src/zonefile/inplace.rs index 72dd7036e..f0890b008 100644 --- a/src/zonefile/inplace.rs +++ b/src/zonefile/inplace.rs @@ -1982,4 +1982,11 @@ mod test { "../../test-data/zonefiles/rfc_1035_ttl_class_type_rdata.yaml" )) } + + #[test] + fn test_security_algorithm_as_int_and_str_in_cds_rrsig_dnskey() { + TestCase::test(include_str!( + "../../test-data/zonefiles/security_algorithm_as_int_and_str_in_cds_rrsig_dnskey.yaml" + )) + } } diff --git a/test-data/zonefiles/security_algorithm_as_int_and_str_in_cds_rrsig_dnskey.yaml b/test-data/zonefiles/security_algorithm_as_int_and_str_in_cds_rrsig_dnskey.yaml new file mode 100644 index 000000000..fc00108e0 --- /dev/null +++ b/test-data/zonefiles/security_algorithm_as_int_and_str_in_cds_rrsig_dnskey.yaml @@ -0,0 +1,107 @@ +origin: example.com. +zonefile: | + v01.example.com. 3600 IN CDS 0 0 0 00 + v02.example.com. 3600 IN CDS 0 DELETE 0 00 + + v03.example.com. 3600 IN RRSIG A 5 3 3600 20060825081644 20060728081644 44537 Example.com. ONq5uFbBsvt6XRZdGA7TdZkRy9DcV3ZXS1CXL1AMZ78+MaS+YufBes9wUo/8rBJPoGrBx1u6HRiLOUjc+7LpLJa0NSGCCwPsispbAgNwBKNBYBHd2ftxWImchh1qp5OfFvjkSp9jftr4DlQOW3Sjz8/kvzNU3b+Cr+ERF6vlJks= + v04.example.com. 3600 IN RRSIG A RSASHA1 3 3600 20060825081644 20060728081644 44537 Example.com. ONq5uFbBsvt6XRZdGA7TdZkRy9DcV3ZXS1CXL1AMZ78+MaS+YufBes9wUo/8rBJPoGrBx1u6HRiLOUjc+7LpLJa0NSGCCwPsispbAgNwBKNBYBHd2ftxWImchh1qp5OfFvjkSp9jftr4DlQOW3Sjz8/kvzNU3b+Cr+ERF6vlJks= + + v05.example.com. 3600 IN DNSKEY 257 3 8 AQO5v4qLMhH88u+O2rSXA349FO48DlVX8cCQCW3P8edee/4moLd3wLwGm4SoUwX/TyP9HLoyMjCw1gGJPyvlR6IA4u1NAE7Ik2Vpj8NtA9y1evpOd6AYBYlRKon0SAsl5x7QqcN0lKaE2zklXh3lUdQTKrh94xAyXu+SsiSdaaVy+w== + v06.example.com. 3600 IN DNSKEY 257 3 RSASHA256 AQO5v4qLMhH88u+O2rSXA349FO48DlVX8cCQCW3P8edee/4moLd3wLwGm4SoUwX/TyP9HLoyMjCw1gGJPyvlR6IA4u1NAE7Ik2Vpj8NtA9y1evpOd6AYBYlRKon0SAsl5x7QqcN0lKaE2zklXh3lUdQTKrh94xAyXu+SsiSdaaVy+w== + + v07.example.com. 3600 IN DNSKEY 257 3 10 AQO5v4qLMhH88u+O2rSXA349FO48DlVX8cCQCW3P8edee/4moLd3wLwGm4SoUwX/TyP9HLoyMjCw1gGJPyvlR6IA4u1NAE7Ik2Vpj8NtA9y1evpOd6AYBYlRKon0SAsl5x7QqcN0lKaE2zklXh3lUdQTKrh94xAyXu+SsiSdaaVy+w== + v08.example.com. 3600 IN DNSKEY 257 3 RSASHA512 AQO5v4qLMhH88u+O2rSXA349FO48DlVX8cCQCW3P8edee/4moLd3wLwGm4SoUwX/TyP9HLoyMjCw1gGJPyvlR6IA4u1NAE7Ik2Vpj8NtA9y1evpOd6AYBYlRKon0SAsl5x7QqcN0lKaE2zklXh3lUdQTKrh94xAyXu+SsiSdaaVy+w== +result: + + # --- Code 0 / DELETE + # https://datatracker.ietf.org/doc/html/rfc8078#section-4 + - owner: v01.example.com. + class: IN + ttl: 3600 + data: !Cds + rtype: Ds + key_tag: 0 + algorithm: 0 + digest_type: 0 + digest: AA== # 0 in Base64 + + # https://datatracker.ietf.org/doc/html/rfc8078#section-4 + - owner: v02.example.com. + class: IN + ttl: 3600 + data: !Cds + rtype: Ds + key_tag: 0 + algorithm: 0 + digest_type: 0 + digest: AA== # 0 in Base64 + + - owner: v03.example.com. + class: IN + ttl: 3600 + data: !Rrsig + rtype: Rrsig + type_covered: A + algorithm: 5 # RSA/SHA-1 [RSASHA1] https://datatracker.ietf.org/doc/html/rfc4034#appendix-A.1 + labels: 3 + original_ttl: 3600 + expiration: 1156493804 + inception: 1154074604 + key_tag: 44537 + signer_name: example.com. + signature: ONq5uFbBsvt6XRZdGA7TdZkRy9DcV3ZXS1CXL1AMZ78+MaS+YufBes9wUo/8rBJPoGrBx1u6HRiLOUjc+7LpLJa0NSGCCwPsispbAgNwBKNBYBHd2ftxWImchh1qp5OfFvjkSp9jftr4DlQOW3Sjz8/kvzNU3b+Cr+ERF6vlJks= + + - owner: v04.example.com. + class: IN + ttl: 3600 + data: !Rrsig + rtype: Rrsig + type_covered: A + algorithm: 5 # RSA/SHA-1 [RSASHA1] https://datatracker.ietf.org/doc/html/rfc4034#appendix-A.1 + labels: 3 + original_ttl: 3600 + expiration: 1156493804 + inception: 1154074604 + key_tag: 44537 + signer_name: example.com. + signature: ONq5uFbBsvt6XRZdGA7TdZkRy9DcV3ZXS1CXL1AMZ78+MaS+YufBes9wUo/8rBJPoGrBx1u6HRiLOUjc+7LpLJa0NSGCCwPsispbAgNwBKNBYBHd2ftxWImchh1qp5OfFvjkSp9jftr4DlQOW3Sjz8/kvzNU3b+Cr+ERF6vlJks= + + - owner: v05.example.com. + class: IN + ttl: 3600 + data: !Dnskey + rtype: Dnskey + flags: 257 + protocol: 3 + algorithm: 8 # RSA/SHA-256 [RSASHA256] https://datatracker.ietf.org/doc/html/rfc5702#section-7 + public_key: AQO5v4qLMhH88u+O2rSXA349FO48DlVX8cCQCW3P8edee/4moLd3wLwGm4SoUwX/TyP9HLoyMjCw1gGJPyvlR6IA4u1NAE7Ik2Vpj8NtA9y1evpOd6AYBYlRKon0SAsl5x7QqcN0lKaE2zklXh3lUdQTKrh94xAyXu+SsiSdaaVy+w== + + - owner: v06.example.com. + class: IN + ttl: 3600 + data: !Dnskey + rtype: Dnskey + flags: 257 + protocol: 3 + algorithm: 8 # RSA/SHA-256 [RSASHA256] https://datatracker.ietf.org/doc/html/rfc5702#section-7 + public_key: AQO5v4qLMhH88u+O2rSXA349FO48DlVX8cCQCW3P8edee/4moLd3wLwGm4SoUwX/TyP9HLoyMjCw1gGJPyvlR6IA4u1NAE7Ik2Vpj8NtA9y1evpOd6AYBYlRKon0SAsl5x7QqcN0lKaE2zklXh3lUdQTKrh94xAyXu+SsiSdaaVy+w== + + - owner: v07.example.com. + class: IN + ttl: 3600 + data: !Dnskey + rtype: Dnskey + flags: 257 + protocol: 3 + algorithm: 10 # RSA/SHA-512 [RSASHA512] https://datatracker.ietf.org/doc/html/rfc5702#section-7 + public_key: AQO5v4qLMhH88u+O2rSXA349FO48DlVX8cCQCW3P8edee/4moLd3wLwGm4SoUwX/TyP9HLoyMjCw1gGJPyvlR6IA4u1NAE7Ik2Vpj8NtA9y1evpOd6AYBYlRKon0SAsl5x7QqcN0lKaE2zklXh3lUdQTKrh94xAyXu+SsiSdaaVy+w== + + - owner: v08.example.com. + class: IN + ttl: 3600 + data: !Dnskey + rtype: Dnskey + flags: 257 + protocol: 3 + algorithm: 10 # RSA/SHA-512 [RSASHA512] https://datatracker.ietf.org/doc/html/rfc5702#section-7 + public_key: AQO5v4qLMhH88u+O2rSXA349FO48DlVX8cCQCW3P8edee/4moLd3wLwGm4SoUwX/TyP9HLoyMjCw1gGJPyvlR6IA4u1NAE7Ik2Vpj8NtA9y1evpOd6AYBYlRKon0SAsl5x7QqcN0lKaE2zklXh3lUdQTKrh94xAyXu+SsiSdaaVy+w== \ No newline at end of file