From 8c7a2e8fe44fdbb352270e8c6fe67d931a7ef5a6 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 15 May 2026 11:31:04 +0200 Subject: [PATCH 01/13] Fix display of SecurityAlgorithm by splitting display and fromstr macro --- src/base/iana/digestalg.rs | 3 +- src/base/iana/ipseckey.rs | 6 ++-- src/base/iana/macros.rs | 60 +++++++++++++++++++++----------------- src/base/iana/nsec3.rs | 3 +- src/base/iana/opcode.rs | 3 +- src/base/iana/opt.rs | 3 +- src/base/iana/rcode.rs | 3 +- src/base/iana/secalg.rs | 6 +++- src/base/iana/sshfp.rs | 6 ++-- src/base/iana/tlsa.rs | 9 ++++-- src/base/iana/zonemd.rs | 6 ++-- 11 files changed, 67 insertions(+), 41 deletions(-) diff --git a/src/base/iana/digestalg.rs b/src/base/iana/digestalg.rs index 7498bde40..d307e13e6 100644 --- a/src/base/iana/digestalg.rs +++ b/src/base/iana/digestalg.rs @@ -42,7 +42,8 @@ int_enum! { (SHA384 => 4, "SHA-384") } -int_enum_str_decimal!(DigestAlgorithm, u8); +int_enum_fromstr_decimal!(DigestAlgorithm, u8); +int_enum_display_decimal!(DigestAlgorithm, u8); int_enum_zonefile_fmt_decimal!(DigestAlgorithm, "digest type"); //============ Tests ========================================================= diff --git a/src/base/iana/ipseckey.rs b/src/base/iana/ipseckey.rs index 0dc85c84c..2cb61c4bb 100644 --- a/src/base/iana/ipseckey.rs +++ b/src/base/iana/ipseckey.rs @@ -35,7 +35,8 @@ int_enum! { (EDDSA => 4, "EdDSA") } -int_enum_str_decimal!(IpseckeyAlgorithm, u8); +int_enum_fromstr_decimal!(IpseckeyAlgorithm, u8); +int_enum_display_decimal!(IpseckeyAlgorithm, u8); int_enum_zonefile_fmt_decimal!(IpseckeyAlgorithm, "ipseckey algorithm"); //------------ IpseckeyGateway ----------------------------------------------- @@ -67,5 +68,6 @@ int_enum! { (NAME => 3, "NAME") } -int_enum_str_decimal!(IpseckeyGatewayType, u8); +int_enum_fromstr_decimal!(IpseckeyGatewayType, u8); +int_enum_display_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..4b9b904df 100644 --- a/src/base/iana/macros.rs +++ b/src/base/iana/macros.rs @@ -187,7 +187,7 @@ macro_rules! int_enum_str_mnemonics_only { /// /// 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 { +macro_rules! int_enum_fromstr_decimal { ($ianatype:ident, $inttype:ident) => { impl $ianatype { #[must_use] @@ -208,6 +208,19 @@ macro_rules! int_enum_str_decimal { scan_impl!($ianatype); + #[cfg(feature = "serde")] + impl<'de> serde::Deserialize<'de> for $ianatype { + fn deserialize>( + deserializer: D, + ) -> Result { + $inttype::deserialize(deserializer).map(Into::into) + } + } + }; +} + +macro_rules! int_enum_display_decimal { + ($ianatype:ident, $inttype:ident) => { impl core::fmt::Display for $ianatype { fn fmt( &self, @@ -226,15 +239,6 @@ macro_rules! int_enum_str_decimal { 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) - } - } }; } @@ -251,7 +255,7 @@ macro_rules! int_enum_str_decimal { /// 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 { +macro_rules! int_enum_fromstr_mnemonic { ($ianatype:ident, $inttype:ident, $error:expr) => { impl $ianatype { #[must_use] @@ -283,6 +287,25 @@ macro_rules! int_enum_str_with_decimal { } } + scan_impl!($ianatype); + + #[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); + }; +} + +macro_rules! int_enum_display_mnemonic { + ($ianatype:ident, $inttype:ident, $error:expr) => { impl core::fmt::Display for $ianatype { fn fmt( &self, @@ -299,8 +322,6 @@ macro_rules! int_enum_str_with_decimal { } } - scan_impl!($ianatype); - #[cfg(feature = "serde")] impl serde::Serialize for $ianatype { fn serialize( @@ -320,19 +341,6 @@ macro_rules! int_enum_str_with_decimal { } } } - - #[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); }; } diff --git a/src/base/iana/nsec3.rs b/src/base/iana/nsec3.rs index 650fbdb4e..a2d4a8334 100644 --- a/src/base/iana/nsec3.rs +++ b/src/base/iana/nsec3.rs @@ -20,5 +20,6 @@ int_enum! { (SHA1 => 1, "SHA-1") } -int_enum_str_decimal!(Nsec3HashAlgorithm, u8); +int_enum_fromstr_decimal!(Nsec3HashAlgorithm, u8); +int_enum_display_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..e48718e49 100644 --- a/src/base/iana/opcode.rs +++ b/src/base/iana/opcode.rs @@ -81,5 +81,6 @@ int_enum! { (DSO => 6, "DSO") } -int_enum_str_with_decimal!(Opcode, u8, "unknown opcode"); +int_enum_fromstr_mnemonic!(Opcode, u8, "unknown opcode"); +int_enum_display_mnemonic!(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..f1370f7ff 100644 --- a/src/base/iana/opt.rs +++ b/src/base/iana/opt.rs @@ -170,7 +170,8 @@ int_enum! { (DEVICE_ID => 26946, "DeviceId") } -int_enum_str_with_decimal!(OptionCode, u16, "unknown option code"); +int_enum_fromstr_mnemonic!(OptionCode, u16, "unknown option code"); +int_enum_display_mnemonic!(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..d75d0b34f 100644 --- a/src/base/iana/rcode.rs +++ b/src/base/iana/rcode.rs @@ -896,7 +896,8 @@ impl From for TsigRcode { } } -int_enum_str_with_decimal!(TsigRcode, u16, "unknown TSIG error"); +int_enum_fromstr_mnemonic!(TsigRcode, u16, "unknown TSIG error"); +int_enum_display_mnemonic!(TsigRcode, u16, "unknown TSIG error"); int_enum_zonefile_fmt_with_decimal!(TsigRcode); //============ Error Types =================================================== diff --git a/src/base/iana/secalg.rs b/src/base/iana/secalg.rs index d19e30e15..061e4dcce 100644 --- a/src/base/iana/secalg.rs +++ b/src/base/iana/secalg.rs @@ -116,9 +116,13 @@ int_enum! { (PRIVATEOID => 254, "PRIVATEOID") } -int_enum_str_with_decimal!( +int_enum_fromstr_mnemonic!( SecurityAlgorithm, u8, "unknown security algorithm" ); + +// We want SecurityAlgorithm to parse from a mnemonic from compatibility with +// some zonefiles, but always print as a number. +int_enum_display_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..fd5abfa40 100644 --- a/src/base/iana/sshfp.rs +++ b/src/base/iana/sshfp.rs @@ -38,7 +38,8 @@ int_enum! { } -int_enum_str_decimal!(SshfpType, u8); +int_enum_fromstr_decimal!(SshfpType, u8); +int_enum_display_decimal!(SshfpType, u8); int_enum_zonefile_fmt_decimal!(SshfpType, "fingerprint type"); //------------ SshfpAlgorithm ------------------------------------------------ @@ -87,5 +88,6 @@ int_enum! { (ED448 => 6, "Ed448") } -int_enum_str_decimal!(SshfpAlgorithm, u8); +int_enum_fromstr_decimal!(SshfpAlgorithm, u8); +int_enum_display_decimal!(SshfpAlgorithm, u8); int_enum_zonefile_fmt_decimal!(SshfpAlgorithm, "public key algorithm"); diff --git a/src/base/iana/tlsa.rs b/src/base/iana/tlsa.rs index b320e4c2d..ca20d50ee 100644 --- a/src/base/iana/tlsa.rs +++ b/src/base/iana/tlsa.rs @@ -32,7 +32,8 @@ int_enum! { (PRIVCERT => 255, "PrivCert") } -int_enum_str_decimal!(TlsaCertificateUsage, u8); +int_enum_fromstr_decimal!(TlsaCertificateUsage, u8); +int_enum_display_decimal!(TlsaCertificateUsage, u8); int_enum_zonefile_fmt_decimal!( TlsaCertificateUsage, "certificate usage type" @@ -64,7 +65,8 @@ int_enum! { (PRIVSEL => 255, "PrivSel") } -int_enum_str_decimal!(TlsaSelector, u8); +int_enum_fromstr_decimal!(TlsaSelector, u8); +int_enum_display_decimal!(TlsaSelector, u8); int_enum_zonefile_fmt_decimal!(TlsaSelector, "selector"); //------------ TlsaMatchingType ---------------------------------------------- @@ -95,5 +97,6 @@ int_enum! { (PRIVMATCH => 255, "PrivMatch") } -int_enum_str_decimal!(TlsaMatchingType, u8); +int_enum_fromstr_decimal!(TlsaMatchingType, u8); +int_enum_display_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..693e92c18 100644 --- a/src/base/iana/zonemd.rs +++ b/src/base/iana/zonemd.rs @@ -20,7 +20,8 @@ int_enum! { (SIMPLE => 1, "SIMPLE") } -int_enum_str_decimal!(ZonemdScheme, u8); +int_enum_fromstr_decimal!(ZonemdScheme, u8); +int_enum_display_decimal!(ZonemdScheme, u8); int_enum_zonefile_fmt_decimal!(ZonemdScheme, "scheme"); //------------ ZonemdAlgorithm ----------------------------------------------- @@ -46,5 +47,6 @@ int_enum! { (SHA512 => 2, "SHA512") } -int_enum_str_decimal!(ZonemdAlgorithm, u8); +int_enum_fromstr_decimal!(ZonemdAlgorithm, u8); +int_enum_display_decimal!(ZonemdAlgorithm, u8); int_enum_zonefile_fmt_decimal!(ZonemdAlgorithm, "hash algorithm"); From 47f16c3c4a9a455efc54ad1802bb5fb88797d295 Mon Sep 17 00:00:00 2001 From: withjannisNLnetLabs Date: Sat, 16 May 2026 10:03:33 +0100 Subject: [PATCH 02/13] added PoC for import export bug --- src/base/iana/macros.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/base/iana/macros.rs b/src/base/iana/macros.rs index 4b9b904df..99420159d 100644 --- a/src/base/iana/macros.rs +++ b/src/base/iana/macros.rs @@ -544,3 +544,29 @@ macro_rules! from_str_error { } }; } + +//------------ Tests --------------------------------------------------------- + +#[cfg(test)] +mod test { + use crate::base::iana::SecurityAlgorithm; + use alloc::string::String; + + #[test] + fn security_algorithm_to_json_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()) + + } +} From 0e8501394f6e61437bef728ef31b7ea42abbc321 Mon Sep 17 00:00:00 2001 From: withjannisNLnetLabs Date: Mon, 18 May 2026 20:56:22 +0100 Subject: [PATCH 03/13] Added tests to `domain::base::iana` to verify impl --- src/base/iana/mod.rs | 288 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 288 insertions(+) diff --git a/src/base/iana/mod.rs b/src/base/iana/mod.rs index 1c734ccee..3bdfc3a0e 100644 --- a/src/base/iana/mod.rs +++ b/src/base/iana/mod.rs @@ -24,6 +24,24 @@ //! 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; @@ -57,3 +75,273 @@ pub mod sshfp; pub mod svcb; pub mod tlsa; pub mod zonemd; + +#[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; + + 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 + fromstr_list: &[&str], // `&[&str]` `FromStr`s MUST result in `test_value` + zonefile_fmt_value: String, // ZonefileFmt MUST result in this String + ) -> () + where + T: Display + ZonefileFmt + FromStr + PartialEq + Debug, + ::Err: core::fmt::Debug, + { + // Display + assert_eq!( + display_repr, + format!("{test_value}"), + "Display representation" + ); + + for value in fromstr_list { + // FromStr mnemonic + assert_eq!( + value + .parse::() + .expect(&format!("FromStr failed with {value}")), + test_value, + "FromStr representation" + ); + } + + // ZonefileFmt + let display_zonefile = + test_value.display_zonefile(DisplayKind::Simple); + assert_eq!( + zonefile_fmt_value, + format!("{display_zonefile}"), + "ZonefileFmt representation" + ); + } + + #[test] + fn validate_class_representation() { + validate_generic_representation( + Class::IN, + "IN".into(), + &["IN", "CLASS1"], + "IN".into(), + ); + } + + #[test] + fn validate_digest_algorithm_representation() { + validate_generic_representation( + DigestAlgorithm::SHA256, + "2".into(), + &["2"], + "2".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(), + &["3"], + "3".into(), + ); + } + + #[test] + fn validate_ipseckey_gateway_type_representation() { + validate_generic_representation( + IpseckeyGatewayType::NONE, + "0".into(), + &["0"], + "0".into(), + ); + } + + #[test] + fn validate_nsec3_hash_algorithm_representation() { + validate_generic_representation( + Nsec3HashAlgorithm::SHA1, + "1".into(), + &["1"], + "1".into(), + ); + } + + #[test] + fn validate_opcode_representation() { + validate_generic_representation( + Opcode::QUERY, + "QUERY(0)".into(), + &["0"], + "QUERY".into(), + ); + } + + #[test] + fn validate_option_code_representation() { + validate_generic_representation( + OptionCode::COOKIE, + "COOKIE(10)".into(), + &["10"], + "COOKIE".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(), + &["23", "BADCOOKIE"], + "BADCOOKIE".into(), + ); + } + + #[test] + fn validate_rtype_representation() { + validate_generic_representation( + Rtype::MX, + "MX".into(), + &["MX", "TYPE15"], + "MX".into(), + ); + } + + #[test] + fn validate_security_algorithm_representation() { + validate_generic_representation( + SecurityAlgorithm::DELETE, + "0".into(), + &["0", "DELETE"], + "0".into(), + ); + } + + #[test] + fn validate_sshfp_algorithm_representation() { + validate_generic_representation( + SshfpAlgorithm::ED25519, + "4".into(), + &["4"], + "4".into(), + ); + } + + #[test] + fn validate_sshfp_type_representation() { + validate_generic_representation( + SshfpType::SHA256, + "2".into(), + &["2"], + "2".into(), + ); + } + + #[test] + fn validate_svc_param_key_representation() { + validate_generic_representation( + SvcParamKey::ALPN, + "alpn".into(), + &["KEY1"], + "alpn".into(), + ); + } + + #[test] + fn validate_tlsa_certificate_usage_representation() { + validate_generic_representation( + TlsaCertificateUsage::DANE_EE, + "3".into(), + &["3"], + "3".into(), + ); + } + + #[test] + fn validate_tlsa_matching_type_representation() { + validate_generic_representation( + TlsaMatchingType::FULL, + "0".into(), + &["0"], + "0".into(), + ); + } + + #[test] + fn validate_tlsa_selector_representation() { + validate_generic_representation( + TlsaSelector::CERT, + "0".into(), + &["0"], + "0".into(), + ); + } + + #[test] + fn validate_zonemd_algorithm_representation() { + validate_generic_representation( + ZonemdAlgorithm::SHA512, + "2".into(), + &["2"], + "2".into(), + ); + } + + #[test] + fn validate_zonemd_scheme_representation() { + validate_generic_representation( + ZonemdScheme::SIMPLE, + "1".into(), + &["1"], + "1".into(), + ); + } +} From 2b0a6929ba9a4bf99f448ef24df3542e9ea79a93 Mon Sep 17 00:00:00 2001 From: withjannisNLnetLabs Date: Mon, 18 May 2026 21:15:31 +0100 Subject: [PATCH 04/13] Minor fixes in formatting and feature for test --- src/base/iana/macros.rs | 2 +- src/base/iana/mod.rs | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/base/iana/macros.rs b/src/base/iana/macros.rs index 99420159d..035660312 100644 --- a/src/base/iana/macros.rs +++ b/src/base/iana/macros.rs @@ -552,6 +552,7 @@ mod test { use crate::base::iana::SecurityAlgorithm; use alloc::string::String; + #[cfg(feature = "serde")] #[test] fn security_algorithm_to_json_string() { let secalg: SecurityAlgorithm = SecurityAlgorithm::DELETE; @@ -567,6 +568,5 @@ mod test { 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 3bdfc3a0e..b026fb413 100644 --- a/src/base/iana/mod.rs +++ b/src/base/iana/mod.rs @@ -114,8 +114,7 @@ mod test { display_repr: String, // Display 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 - ) -> () - where + ) where T: Display + ZonefileFmt + FromStr + PartialEq + Debug, ::Err: core::fmt::Debug, { @@ -129,9 +128,9 @@ mod test { for value in fromstr_list { // FromStr mnemonic assert_eq!( - value - .parse::() - .expect(&format!("FromStr failed with {value}")), + value.parse::().unwrap_or_else(|_| panic!( + "FromStr failed with {value}" + )), test_value, "FromStr representation" ); From 4dc876e7314dd2297264865ea822f36525c47351 Mon Sep 17 00:00:00 2001 From: withjannisNLnetLabs Date: Mon, 11 May 2026 15:27:52 +0200 Subject: [PATCH 05/13] Fix `SecurityAlgorithm` to parse int and mnemonic (#656) This PR fixes loading of zones that contain a SecurityAlgorithm given as a mnemonic rather than a number as mandated by RFC 4034. --- src/base/iana/secalg.rs | 1 - src/zonefile/inplace.rs | 7 ++ ...hm_as_int_and_str_in_cds_rrsig_dnskey.yaml | 107 ++++++++++++++++++ 3 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 test-data/zonefiles/security_algorithm_as_int_and_str_in_cds_rrsig_dnskey.yaml diff --git a/src/base/iana/secalg.rs b/src/base/iana/secalg.rs index 061e4dcce..1a7b3eff7 100644 --- a/src/base/iana/secalg.rs +++ b/src/base/iana/secalg.rs @@ -121,7 +121,6 @@ int_enum_fromstr_mnemonic!( u8, "unknown security algorithm" ); - // We want SecurityAlgorithm to parse from a mnemonic from compatibility with // some zonefiles, but always print as a number. int_enum_display_decimal!(SecurityAlgorithm, u8); 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 From 273ef7593bac5ee0839ea4ec5f77daff7fef233c Mon Sep 17 00:00:00 2001 From: Martin Hoffmann Date: Mon, 11 May 2026 15:29:39 +0200 Subject: [PATCH 06/13] Update changelog. --- Changelog.md | 5 +++++ 1 file changed, 5 insertions(+) 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 From 1b5bc9b506a911f4234702be9d090226a13b0c9d Mon Sep 17 00:00:00 2001 From: withjannisNLnetLabs Date: Thu, 21 May 2026 17:44:10 +0200 Subject: [PATCH 07/13] Added JSON test and started moving to new macros --- src/base/iana/class.rs | 17 ++- src/base/iana/macros.rs | 253 +++++++++++++++++++++++++++++++++++++++- src/base/iana/mod.rs | 56 ++++++++- src/base/iana/secalg.rs | 24 ++-- 4 files changed, 335 insertions(+), 15 deletions(-) diff --git a/src/base/iana/class.rs b/src/base/iana/class.rs index ae859d7e2..7c814e890 100644 --- a/src/base/iana/class.rs +++ b/src/base/iana/class.rs @@ -56,9 +56,24 @@ int_enum! { (ANY => 0xFF, "*") } -int_enum_str_with_prefix!(Class, "CLASS", b"CLASS", u16, "unknown class"); +scan_impl!(Class); + int_enum_zonefile_fmt_with_prefix!(Class, "CLASS"); +// Display +int_enum_impl_display_mnemonics_fallback_integer!(Class); + +// FromStrError +instantiate_fromstrerror_with_error_description!("unknown class"); + +// serde::Serialize / serde::Deserialize +int_enum_impl_serde_to_and_from_mnemonic!(Class, u16); + +// core::str::FromStr / from_bytes() +int_enum_impl_fromstr_frombytes_from_mnemonics_or_prefix!( + Class, "CLASS", b"CLASS" +); + //============ Tests ========================================================= #[cfg(test)] diff --git a/src/base/iana/macros.rs b/src/base/iana/macros.rs index 035660312..e9dde7d9f 100644 --- a/src/base/iana/macros.rs +++ b/src/base/iana/macros.rs @@ -545,16 +545,263 @@ macro_rules! from_str_error { }; } +// --- TODO: NEW VERSION, FINISH IT! + +/// --- FromStrError --- +/// This macro defines a new struct which is used as an error type in +/// core::str::FromStr. The description is used to std::fmt::Display the +/// error. +macro_rules! instantiate_fromstrerror_with_error_description { + ($error_description:expr) => { + #[derive(Clone, Debug)] + pub struct FromStrError(()); + + impl core::error::Error for FromStrError { + fn description(&self) -> &str { + $error_description + } + } + + impl core::fmt::Display for FromStrError { + fn fmt( + &self, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + $error_description.fmt(f) + } + } + }; +} +// --- + +/// --- FromBytes / FromStr --- COMBINATION VERSION +/// This macro implements core::str::FromStr and the function from_bytes(). It +/// tries to parse the bytes as mnemonic first and falls back to parsing it as +/// an integer if that is not possible. +macro_rules! int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer { + ($enum_type:ident) => { + impl $enum_type { + #[must_use] + pub fn from_bytes(bytes: &[u8]) -> Option { + $enum_type::from_mnemonic(bytes).or_else(|| { + core::str::from_utf8(bytes).ok().and_then(|r| { + r.parse().ok().map($enum_type::from_int) + }) + }) + } + } + + impl core::str::FromStr for $enum_type { + 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 $enum_type::from_mnemonic(s.as_bytes()) { + Some(res) => Ok(res), + None => { + if let Ok(res) = s.parse() { + Ok($enum_type::from_int(res)) + } else { + Err(FromStrError(())) + } + } + } + } + } + }; +} +// --- + +/// --- FromBytes / FromStr --- PREFIX VERSION +/// This macro implements core::str::FromStr and the function from_bytes(). It +/// tries to parse the bytes as mnemonic first and falls back to parsing it as +/// an integer with a specific prefix if that is not possible. +macro_rules! int_enum_impl_fromstr_frombytes_from_mnemonics_or_prefix { + ($enum_type:ident, $str_prefix:expr, $prefix_bytes:expr) => { + impl $enum_type { + #[must_use] + pub fn from_bytes(bytes: &[u8]) -> Option { + $enum_type::from_mnemonic(bytes).or_else(|| { + if bytes.len() <= $prefix_bytes.len() { + return None; + } + let (l, r) = bytes.split_at($prefix_bytes.len()); + if !l.eq_ignore_ascii_case($prefix_bytes) { + return None; + } + let r = match core::str::from_utf8(r) { + Ok(r) => r, + Err(_) => return None, + }; + r.parse().ok().map($enum_type::from_int) + }) + } + } + + impl core::str::FromStr for $enum_type { + 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 $enum_type::from_mnemonic(s.as_bytes()) { + Some(res) => Ok(res), + None => { + if let Some((n, _)) = + s.char_indices().nth($str_prefix.len()) + { + let (l, r) = s.split_at(n); + if l.eq_ignore_ascii_case($str_prefix) { + let value = match r.parse() { + Ok(x) => x, + Err(..) => return Err(FromStrError(())), + }; + Ok($enum_type::from_int(value)) + } else { + Err(FromStrError(())) + } + } else { + Err(FromStrError(())) + } + } + } + } + } + }; +} +// --- + +/// --- Display --- COMBINATION VERSION +/// This macro implements std::fmt::Display. If the value has a mnemonic it +/// formats the string as "()" including the interger in +/// parenthesis. If the value does not have a mnemonic it falls back to the +/// integer and formats the String as "". +macro_rules! int_enum_impl_display_mnemonics_with_integer_fallback_integer { + ($enum_type:ident) => { + impl std::fmt::Display for $enum_type { + 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()) + } + } + } + } + }; +} +// --- + +/// --- Display --- COMBINATION VERSION +/// This macro implements std::fmt::Display. If the value has a mnemonic it +/// formats the string as "". If the value does not have a mnemonic +/// it falls back to the integer and formats the String as "". +macro_rules! int_enum_impl_display_mnemonics_fallback_integer { + ($enum_type:ident) => { + impl std::fmt::Display for $enum_type { + fn fmt( + &self, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + match self.to_mnemonic_str() { + Some(m) => { + write!(f, "{}", m) + } + None => { + write!(f, "{}", self.to_int()) + } + } + } + } + }; +} +// --- + +/// --- Serialization / Deserialization --- INTEGER VERSION +/// This macro implements serde::Serialize and serde::Deserialize. The token +/// represented in the JSON string is an integer without quotes surrounding +/// it. +/// { +/// "value": 42 +/// } +macro_rules! int_enum_impl_serde_to_and_from_integer { + ($enum_type:ident) => { + #[cfg(feature = "serde")] + impl serde::Serialize for $enum_type { + fn serialize( + &self, + serializer: S, + ) -> Result { + self.to_int().serialize(serializer) + } + } + + #[cfg(feature = "serde")] + impl<'de> serde::Deserialize<'de> for $enum_type { + fn deserialize>( + deserializer: D, + ) -> Result { + u8::deserialize(deserializer).map(Into::into) + } + } + }; +} +// --- + +/// --- Serialization / Deserialization --- MNEMONIC VERSION +/// This macro implements serde::Serialize and serde::Deserialize. The token +/// represented in the JSON string is a mnemonic in quotes if available or an +/// integer without quotes surrounding +/// it. +/// { +/// "value": "SHA1", +/// "value2": 42 +/// } +macro_rules! int_enum_impl_serde_to_and_from_mnemonic { + ($enum_type:ident, $enum_integer_type:ident) => { + #[cfg(feature = "serde")] + impl serde::Serialize for $enum_type { + fn serialize( + &self, + serializer: S, + ) -> Result { + if serializer.is_human_readable() { + serializer.collect_str(&format_args!("{}", self)) + } else { + self.to_int().serialize(serializer) + } + } + } + + #[cfg(feature = "serde")] + impl<'de> serde::Deserialize<'de> for $enum_type { + fn deserialize>( + deserializer: D, + ) -> Result { + use crate::base::serde::DeserializeNativeOrStr; + + $enum_integer_type::deserialize_native_or_str(deserializer) + } + } + }; +} +// --- + //------------ Tests --------------------------------------------------------- #[cfg(test)] mod test { - use crate::base::iana::SecurityAlgorithm; - use alloc::string::String; - #[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(); diff --git a/src/base/iana/mod.rs b/src/base/iana/mod.rs index b026fb413..02c6792f7 100644 --- a/src/base/iana/mod.rs +++ b/src/base/iana/mod.rs @@ -76,6 +76,7 @@ pub mod svcb; pub mod tlsa; pub mod zonemd; +#[cfg(feature = "serde")] #[cfg(test)] mod test { use crate::base::iana::class::Class; @@ -114,8 +115,15 @@ mod test { display_repr: String, // Display 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 + ZonefileFmt + FromStr + PartialEq + Debug, + T: Display + + ZonefileFmt + + FromStr + + PartialEq + + Debug + + serde::Serialize + + for<'a> serde::Deserialize<'a>, ::Err: core::fmt::Debug, { // Display @@ -144,6 +152,28 @@ mod test { format!("{display_zonefile}"), "ZonefileFmt representation" ); + + // serde::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!( + "test_value as json string is #{}# without the #", + 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)" + ); } #[test] @@ -153,6 +183,7 @@ mod test { "IN".into(), &["IN", "CLASS1"], "IN".into(), + r#""IN""#.into(), ); } @@ -163,6 +194,7 @@ mod test { "2".into(), &["2"], "2".into(), + r#"2"#.into(), ); } @@ -179,6 +211,7 @@ mod test { "3".into(), &["3"], "3".into(), + r#"3"#.into(), ); } @@ -189,6 +222,7 @@ mod test { "0".into(), &["0"], "0".into(), + r#"0"#.into(), ); } @@ -199,6 +233,7 @@ mod test { "1".into(), &["1"], "1".into(), + r#"1"#.into(), ); } @@ -209,6 +244,7 @@ mod test { "QUERY(0)".into(), &["0"], "QUERY".into(), + r#""QUERY""#.into(), ); } @@ -219,6 +255,7 @@ mod test { "COOKIE(10)".into(), &["10"], "COOKIE".into(), + r#""COOKIE""#.into(), ); } @@ -241,6 +278,7 @@ mod test { "BADCOOKIE(23)".into(), &["23", "BADCOOKIE"], "BADCOOKIE".into(), + r#""BADCOOKIE""#.into(), ); } @@ -251,6 +289,7 @@ mod test { "MX".into(), &["MX", "TYPE15"], "MX".into(), + r#""MX""#.into(), ); } @@ -258,9 +297,10 @@ mod test { fn validate_security_algorithm_representation() { validate_generic_representation( SecurityAlgorithm::DELETE, - "0".into(), - &["0", "DELETE"], - "0".into(), + "DELETE(0)".into(), + &["0", "DELETE"], // SPECIAL, read from mnemonic and int + "0".into(), // ...but print as integer + r#"0"#.into(), ); } @@ -271,6 +311,7 @@ mod test { "4".into(), &["4"], "4".into(), + r#"4"#.into(), ); } @@ -281,6 +322,7 @@ mod test { "2".into(), &["2"], "2".into(), + r#"2"#.into(), ); } @@ -291,6 +333,7 @@ mod test { "alpn".into(), &["KEY1"], "alpn".into(), + r#""alpn""#.into(), ); } @@ -301,6 +344,7 @@ mod test { "3".into(), &["3"], "3".into(), + r#"3"#.into(), ); } @@ -311,6 +355,7 @@ mod test { "0".into(), &["0"], "0".into(), + r#"0"#.into(), ); } @@ -321,6 +366,7 @@ mod test { "0".into(), &["0"], "0".into(), + r#"0"#.into(), ); } @@ -331,6 +377,7 @@ mod test { "2".into(), &["2"], "2".into(), + "2".into(), ); } @@ -341,6 +388,7 @@ mod test { "1".into(), &["1"], "1".into(), + "1".into(), ); } } diff --git a/src/base/iana/secalg.rs b/src/base/iana/secalg.rs index 1a7b3eff7..a7a42304e 100644 --- a/src/base/iana/secalg.rs +++ b/src/base/iana/secalg.rs @@ -116,12 +116,22 @@ int_enum! { (PRIVATEOID => 254, "PRIVATEOID") } -int_enum_fromstr_mnemonic!( - SecurityAlgorithm, - u8, +scan_impl!(SecurityAlgorithm); + +int_enum_zonefile_fmt_decimal!(SecurityAlgorithm, "algorithm"); + +// Display +int_enum_impl_display_mnemonics_with_integer_fallback_integer!( + SecurityAlgorithm +); + +// FromStrError +instantiate_fromstrerror_with_error_description!( "unknown security algorithm" ); -// We want SecurityAlgorithm to parse from a mnemonic from compatibility with -// some zonefiles, but always print as a number. -int_enum_display_decimal!(SecurityAlgorithm, u8); -int_enum_zonefile_fmt_decimal!(SecurityAlgorithm, "algorithm"); + +// serde::Serialize / serde::Deserialize +int_enum_impl_serde_to_and_from_integer!(SecurityAlgorithm); + +// core::str::FromStr / from_bytes() +int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!(SecurityAlgorithm); From 5829e4af9d1d7caa9af0b615473a2f555bef0acd Mon Sep 17 00:00:00 2001 From: withjannisNLnetLabs Date: Fri, 22 May 2026 14:13:22 +0200 Subject: [PATCH 08/13] Added new macros and testcases for iana enums --- src/base/iana/macros.rs | 90 +++++++++++++++++-- src/base/iana/mod.rs | 195 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 269 insertions(+), 16 deletions(-) diff --git a/src/base/iana/macros.rs b/src/base/iana/macros.rs index e9dde7d9f..c0aaf4794 100644 --- a/src/base/iana/macros.rs +++ b/src/base/iana/macros.rs @@ -612,6 +612,44 @@ macro_rules! int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer { }; } // --- +// +/// --- FromBytes / FromStr --- INTEGER VERSION +/// This macro implements core::str::FromStr and the function from_bytes(). It +/// parses the value from the integer associated with it. +macro_rules! int_enum_impl_fromstr_frombytes_from_integer { + ($enum_type:ident) => { + impl $enum_type { + #[must_use] + pub fn from_bytes(bytes: &[u8]) -> Option { + $enum_type::from_mnemonic(bytes).or_else(|| { + core::str::from_utf8(bytes).ok().and_then(|r| { + r.parse().ok().map($enum_type::from_int) + }) + }) + } + } + + impl core::str::FromStr for $enum_type { + 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 $enum_type::from_mnemonic(s.as_bytes()) { + Some(res) => Ok(res), + None => { + if let Ok(res) = s.parse() { + Ok($enum_type::from_int(res)) + } else { + Err(FromStrError(())) + } + } + } + } + } + }; +} +// --- /// --- FromBytes / FromStr --- PREFIX VERSION /// This macro implements core::str::FromStr and the function from_bytes(). It @@ -698,7 +736,32 @@ macro_rules! int_enum_impl_display_mnemonics_with_integer_fallback_integer { } // --- -/// --- Display --- COMBINATION VERSION +/// --- Display --- MNEMONIC PREFIX VERSION +/// This macro implements std::fmt::Display. If the value has a mnemonic it +/// formats the string as "". If the value does not have a mnemonic +/// it falls back to the integer and formats the String as "". +macro_rules! int_enum_impl_display_mnemonics_fallback_prefix_integer { + ($enum_type:ident, $prefix:expr) => { + impl std::fmt::Display for $enum_type { + fn fmt( + &self, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + match self.to_mnemonic_str() { + Some(m) => { + write!(f, "{}", m) + } + None => { + write!(f, "{}{}", $prefix, self.to_int()) + } + } + } + } + }; +} +// --- + +/// --- Display --- MNEMONIC VERSION /// This macro implements std::fmt::Display. If the value has a mnemonic it /// formats the string as "". If the value does not have a mnemonic /// it falls back to the integer and formats the String as "". @@ -722,6 +785,23 @@ macro_rules! int_enum_impl_display_mnemonics_fallback_integer { }; } // --- +// +/// --- Display --- COMBINATION VERSION +/// This macro implements std::fmt::Display. The value will always be formated +/// with the integer associated with it. +macro_rules! int_enum_impl_display_integer { + ($enum_type:ident) => { + impl std::fmt::Display for $enum_type { + fn fmt( + &self, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + write!(f, "{}", self.to_int()) + } + } + }; +} +// --- /// --- Serialization / Deserialization --- INTEGER VERSION /// This macro implements serde::Serialize and serde::Deserialize. The token @@ -754,11 +834,11 @@ macro_rules! int_enum_impl_serde_to_and_from_integer { } // --- -/// --- Serialization / Deserialization --- MNEMONIC VERSION +/// --- Serialization / Deserialization --- DISPLAY VERSION /// This macro implements serde::Serialize and serde::Deserialize. The token -/// represented in the JSON string is a mnemonic in quotes if available or an -/// integer without quotes surrounding -/// it. +/// represented in the JSON string is the same as the Display implementation +/// in quotes. In the compact format the value is represented as the +/// associated integer. /// { /// "value": "SHA1", /// "value2": 42 diff --git a/src/base/iana/mod.rs b/src/base/iana/mod.rs index 02c6792f7..65ab571ae 100644 --- a/src/base/iana/mod.rs +++ b/src/base/iana/mod.rs @@ -113,28 +113,39 @@ mod test { 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 - + ZonefileFmt + + Debug + FromStr + PartialEq - + Debug - + serde::Serialize - + for<'a> serde::Deserialize<'a>, + + 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}" @@ -145,6 +156,7 @@ mod test { } // ZonefileFmt + println!("assert ZonefileFmt"); let display_zonefile = test_value.display_zonefile(DisplayKind::Simple); assert_eq!( @@ -154,6 +166,7 @@ mod test { ); // serde::Serialize + println!("assert Serialize"); assert_eq!( serde_serialize_value, serde_json::to_string(&test_value).unwrap(), @@ -164,16 +177,14 @@ mod test { let value_as_json_string = serde_json::to_string(&test_value).unwrap(); - println!( - "test_value as json string is #{}# without the #", - value_as_json_string - ); - + 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] @@ -181,10 +192,19 @@ mod test { 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] @@ -192,10 +212,19 @@ mod test { 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] @@ -209,10 +238,19 @@ mod test { validate_generic_representation( IpseckeyAlgorithm::ECDSA, "3".into(), + "IpseckeyAlgorithm::ECDSA".into(), &["3"], "3".into(), r#"3"#.into(), ); + validate_generic_representation( + IpseckeyAlgorithm::from_int(42), + "42".into(), + "IpseckeyAlgorithm(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); } #[test] @@ -220,10 +258,19 @@ mod test { validate_generic_representation( IpseckeyGatewayType::NONE, "0".into(), + "IpseckeyGatewayType::NONE".into(), &["0"], "0".into(), r#"0"#.into(), ); + validate_generic_representation( + IpseckeyGatewayType::from_int(42), + "42".into(), + "IpseckeyGatewayType(42)".into(), + &["42"], + "42".into(), + r#"42"#.into(), + ); } #[test] @@ -231,10 +278,19 @@ mod test { validate_generic_representation( Nsec3HashAlgorithm::SHA1, "1".into(), + "Nsec3HashAlgorithm::SHA-1".into(), &["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] @@ -242,10 +298,19 @@ mod test { validate_generic_representation( Opcode::QUERY, "QUERY(0)".into(), - &["0"], + "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] @@ -253,10 +318,19 @@ mod test { validate_generic_representation( OptionCode::COOKIE, "COOKIE(10)".into(), - &["10"], + "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] @@ -276,10 +350,19 @@ mod test { 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] @@ -287,10 +370,19 @@ mod test { 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] @@ -298,10 +390,19 @@ mod test { 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] @@ -309,10 +410,19 @@ mod test { 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] @@ -320,10 +430,19 @@ mod test { 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] @@ -331,10 +450,19 @@ mod test { validate_generic_representation( SvcParamKey::ALPN, "alpn".into(), + "SvcParamKey::alpn".into(), &["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] @@ -342,10 +470,19 @@ mod test { 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] @@ -353,10 +490,19 @@ mod test { 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] @@ -364,10 +510,19 @@ mod test { 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] @@ -375,10 +530,19 @@ mod test { 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] @@ -386,9 +550,18 @@ mod test { 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(), + ); } } From bf88fdfeed814c74d752f0dba0661236ec184c64 Mon Sep 17 00:00:00 2001 From: withjannisNLnetLabs Date: Fri, 22 May 2026 14:14:44 +0200 Subject: [PATCH 09/13] Moved more iana enums to new macros --- src/base/iana/class.rs | 4 +++- src/base/iana/digestalg.rs | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/base/iana/class.rs b/src/base/iana/class.rs index 7c814e890..24afa021e 100644 --- a/src/base/iana/class.rs +++ b/src/base/iana/class.rs @@ -56,12 +56,14 @@ int_enum! { (ANY => 0xFF, "*") } +// int_enum_str_with_prefix!(Class, "CLASS", b"CLASS", u16, "unknown class"); + scan_impl!(Class); int_enum_zonefile_fmt_with_prefix!(Class, "CLASS"); // Display -int_enum_impl_display_mnemonics_fallback_integer!(Class); +int_enum_impl_display_mnemonics_fallback_prefix_integer!(Class, "CLASS"); // FromStrError instantiate_fromstrerror_with_error_description!("unknown class"); diff --git a/src/base/iana/digestalg.rs b/src/base/iana/digestalg.rs index d307e13e6..7d9456f14 100644 --- a/src/base/iana/digestalg.rs +++ b/src/base/iana/digestalg.rs @@ -42,10 +42,25 @@ int_enum! { (SHA384 => 4, "SHA-384") } -int_enum_fromstr_decimal!(DigestAlgorithm, u8); -int_enum_display_decimal!(DigestAlgorithm, u8); +// int_enum_fromstr_decimal!(DigestAlgorithm, u8); +// int_enum_display_decimal!(DigestAlgorithm, u8); +// int_enum_zonefile_fmt_decimal!(DigestAlgorithm, "digest type"); +scan_impl!(DigestAlgorithm); + int_enum_zonefile_fmt_decimal!(DigestAlgorithm, "digest type"); +// Display +int_enum_impl_display_integer!(DigestAlgorithm); + +// FromStrError +instantiate_fromstrerror_with_error_description!("unknown digest type"); + +// serde::Serialize / serde::Deserialize +int_enum_impl_serde_to_and_from_integer!(DigestAlgorithm); + +// core::str::FromStr / from_bytes() +int_enum_impl_fromstr_frombytes_from_integer!(DigestAlgorithm); + //============ Tests ========================================================= #[cfg(test)] From 6ef8aa38ea447b32397ccd5e27bb3293dc4d3ed2 Mon Sep 17 00:00:00 2001 From: withjannisNLnetLabs Date: Tue, 26 May 2026 14:25:49 +0200 Subject: [PATCH 10/13] Macro implementation on hold in favor of trait --- NOTES.md | 75 ++++++++++++++++ src/base/iana/digestalg.rs | 34 +++---- src/base/iana/ipseckey.rs | 52 +++++++++-- src/base/iana/macros.rs | 176 +++++++++++++++++++++++++++++++++++-- src/base/iana/mod.rs | 105 ++++++++++++++++++++-- src/base/iana/nsec3.rs | 26 +++++- src/base/iana/opcode.rs | 24 ++++- src/base/iana/opt.rs | 24 ++++- src/base/iana/rcode.rs | 19 +++- src/base/iana/secalg.rs | 2 +- 10 files changed, 490 insertions(+), 47 deletions(-) create mode 100644 NOTES.md diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 000000000..b4e130cec --- /dev/null +++ b/NOTES.md @@ -0,0 +1,75 @@ + +# Notes + +## MNEMONIC PREFIX + +- Class +- Rtype +- SvcParamKey + +## INTEGER + +- DigestAlgorithm +- IpseckeyAlgorithm +- IpseccGatewayType +- Nsec3HashAlgorithm +- SshfpAlgorithm +- SshfpType +- TlsaCertificateUsage +- TlsaMatchingType +- TlsaSelector +- ZonemdAlgorithm +- ZonemdScheme + + +## MNEMONIC with integer in parentheses for display + +- OpCode +- OptionCode +- TsigRcode + +## INTEGER with integer in parentheses for display + +- SecurityAlgorithm + + +fn( + type: struct + integer_type: [u8, u16] + default_represent: (Mnemonics, Integer, MnemonicPrefixed) + display_trait: (Mnemonics, Integer, MnemonicWithIntegerInParentheses) + prefix: str +) + + + + +trait IanaEnum { + + fn display_as_number() { + + } + + fn display_mnemonic() + + fn display_with_prefix() { + const PREFIX: STRING = "CLASS" + + } + +} + + +macro_rules! hello_world +... => { + impl fmt::Display for $iana_type { + $iana_type::$display_callable(f) + } + + impl str::FromStr for $iana_type { + $fromstr_callable(something: &str) + } + +} + +iana_enum_implemenation!(SecAlg, IanaEnum::display_as_number, |f|) diff --git a/src/base/iana/digestalg.rs b/src/base/iana/digestalg.rs index 7d9456f14..1cf9badae 100644 --- a/src/base/iana/digestalg.rs +++ b/src/base/iana/digestalg.rs @@ -42,24 +42,24 @@ int_enum! { (SHA384 => 4, "SHA-384") } -// int_enum_fromstr_decimal!(DigestAlgorithm, u8); -// int_enum_display_decimal!(DigestAlgorithm, u8); -// int_enum_zonefile_fmt_decimal!(DigestAlgorithm, "digest type"); -scan_impl!(DigestAlgorithm); - +int_enum_fromstr_decimal!(DigestAlgorithm, u8); +int_enum_display_decimal!(DigestAlgorithm, u8); int_enum_zonefile_fmt_decimal!(DigestAlgorithm, "digest type"); - -// Display -int_enum_impl_display_integer!(DigestAlgorithm); - -// FromStrError -instantiate_fromstrerror_with_error_description!("unknown digest type"); - -// serde::Serialize / serde::Deserialize -int_enum_impl_serde_to_and_from_integer!(DigestAlgorithm); - -// core::str::FromStr / from_bytes() -int_enum_impl_fromstr_frombytes_from_integer!(DigestAlgorithm); +// scan_impl!(DigestAlgorithm); +// +// int_enum_zonefile_fmt_decimal!(DigestAlgorithm, "digest type"); +// +// // Display +// int_enum_impl_display_integer!(DigestAlgorithm); +// +// // FromStrError +// instantiate_fromstrerror_with_error_description!("unknown digest type"); +// +// // serde::Serialize / serde::Deserialize +// int_enum_impl_serde_to_and_from_integer!(DigestAlgorithm, u8); +// +// // core::str::FromStr / from_bytes() +// int_enum_impl_fromstr_frombytes_from_integer!(DigestAlgorithm); //============ Tests ========================================================= diff --git a/src/base/iana/ipseckey.rs b/src/base/iana/ipseckey.rs index 2cb61c4bb..b31ae64dd 100644 --- a/src/base/iana/ipseckey.rs +++ b/src/base/iana/ipseckey.rs @@ -35,9 +35,29 @@ int_enum! { (EDDSA => 4, "EdDSA") } -int_enum_fromstr_decimal!(IpseckeyAlgorithm, u8); -int_enum_display_decimal!(IpseckeyAlgorithm, u8); -int_enum_zonefile_fmt_decimal!(IpseckeyAlgorithm, "ipseckey algorithm"); +// int_enum_fromstr_decimal!(IpseckeyAlgorithm, u8); +// int_enum_display_decimal!(IpseckeyAlgorithm, u8); +// int_enum_zonefile_fmt_decimal!(IpseckeyAlgorithm, "ipseckey algorithm"); + +scan_impl!(IpseckeyAlgorithm); + +int_enum_zonefile_fmt_decimal!(IpseckeyAlgorithm, "algorithm"); + +// Display +int_enum_impl_display_integer!( + IpseckeyAlgorithm +); + +// FromStrError +instantiate_fromstrerror_with_error_description!( + "unknown ipseckey algorithm" +); + +// serde::Serialize / serde::Deserialize +int_enum_impl_serde_to_and_from_integer!(IpseckeyAlgorithm, u8); + +// core::str::FromStr / from_bytes() +int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!(IpseckeyAlgorithm); //------------ IpseckeyGateway ----------------------------------------------- @@ -68,6 +88,26 @@ int_enum! { (NAME => 3, "NAME") } -int_enum_fromstr_decimal!(IpseckeyGatewayType, u8); -int_enum_display_decimal!(IpseckeyGatewayType, u8); -int_enum_zonefile_fmt_decimal!(IpseckeyGatewayType, "ipseckey gateway type"); +// int_enum_fromstr_decimal!(IpseckeyGatewayType, u8); +// int_enum_display_decimal!(IpseckeyGatewayType, u8); +// int_enum_zonefile_fmt_decimal!(IpseckeyGatewayType, "ipseckey gateway type"); + +scan_impl!(IpseckeyGatewayType); + +int_enum_zonefile_fmt_decimal!(IpseckeyGatewayType, "algorithm"); + +// Display +int_enum_impl_display_integer!( + IpseckeyGatewayType +); + +// FromStrError +// instantiate_fromstrerror_with_error_description!( +// "unknown ipseckey algorithm" +// ); + +// serde::Serialize / serde::Deserialize +int_enum_impl_serde_to_and_from_integer!(IpseckeyGatewayType, u8); + +// core::str::FromStr / from_bytes() +int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!(IpseckeyGatewayType); diff --git a/src/base/iana/macros.rs b/src/base/iana/macros.rs index c0aaf4794..0828446a9 100644 --- a/src/base/iana/macros.rs +++ b/src/base/iana/macros.rs @@ -786,7 +786,7 @@ macro_rules! int_enum_impl_display_mnemonics_fallback_integer { } // --- // -/// --- Display --- COMBINATION VERSION +/// --- Display --- INTEGER VERSION /// This macro implements std::fmt::Display. The value will always be formated /// with the integer associated with it. macro_rules! int_enum_impl_display_integer { @@ -796,7 +796,7 @@ macro_rules! int_enum_impl_display_integer { &self, f: &mut core::fmt::Formatter<'_>, ) -> core::fmt::Result { - write!(f, "{}", self.to_int()) + write!(f, "{}", self.to_int()) } } }; @@ -811,7 +811,7 @@ macro_rules! int_enum_impl_display_integer { /// "value": 42 /// } macro_rules! int_enum_impl_serde_to_and_from_integer { - ($enum_type:ident) => { + ($enum_type:ident, $enum_integer_type:ident) => { #[cfg(feature = "serde")] impl serde::Serialize for $enum_type { fn serialize( @@ -827,17 +827,16 @@ macro_rules! int_enum_impl_serde_to_and_from_integer { fn deserialize>( deserializer: D, ) -> Result { - u8::deserialize(deserializer).map(Into::into) + $enum_integer_type::deserialize(deserializer).map(Into::into) } } }; } // --- -/// --- Serialization / Deserialization --- DISPLAY VERSION +/// --- Serialization / Deserialization --- MNEMONIC FALLBACK INTEGER /// This macro implements serde::Serialize and serde::Deserialize. The token -/// represented in the JSON string is the same as the Display implementation -/// in quotes. In the compact format the value is represented as the +/// represented in the JSON is the mnemonic version if possible otherwise the /// associated integer. /// { /// "value": "SHA1", @@ -852,7 +851,58 @@ macro_rules! int_enum_impl_serde_to_and_from_mnemonic { serializer: S, ) -> Result { if serializer.is_human_readable() { - serializer.collect_str(&format_args!("{}", self)) + match self.to_mnemonic_str() { + Some(m) => { + serializer.collect_str(&format_args!("{}", m)) + } + None => self.to_int().serialize(serializer), + } + } else { + self.to_int().serialize(serializer) + } + } + } + + #[cfg(feature = "serde")] + impl<'de> serde::Deserialize<'de> for $enum_type { + fn deserialize>( + deserializer: D, + ) -> Result { + use crate::base::serde::DeserializeNativeOrStr; + + $enum_integer_type::deserialize_native_or_str(deserializer) + } + } + }; +} + +/// --- Serialization / Deserialization --- MNEMONIC FALLBACK PREFIX INTEGER +/// This macro implements serde::Serialize and serde::Deserialize. The token +/// represented in the JSON is the mnemonic version if possible otherwise the +/// associated integer with a prefix. +/// { +/// "value": "SHA1", +/// "value2": 42 +/// } +macro_rules! int_enum_impl_serde_to_and_from_mnemonic_fallback_prefix { + ($enum_type:ident, $enum_integer_type:ident, $int_prefix:expr) => { + #[cfg(feature = "serde")] + impl serde::Serialize for $enum_type { + fn serialize( + &self, + serializer: S, + ) -> Result { + if serializer.is_human_readable() { + match self.to_mnemonic_str() { + Some(m) => { + serializer.collect_str(&format_args!("{}", m)) + } + None => serializer.collect_str(&format_args!( + "{}{}", + $int_prefix, + self.to_int() + )), + } } else { self.to_int().serialize(serializer) } @@ -873,6 +923,116 @@ macro_rules! int_enum_impl_serde_to_and_from_mnemonic { } // --- +// --- TRY AGAIN VERSION 3 :) +// Plan now is to only make one gianc function + +// (=> $name:ident, $int:ident; fmt:decimal, zonefile:decimal($zname:expr); $($variants:tt)*) => { +macro_rules! iana_enum_implementation { + // INTEGER ONLY + (=> $name:ident, $int_type:ident; + fmt:integer) => { + instantiate_fromstrerror_with_error_description!(concat!( + "unknown ", + stringify!($ianatype), + )); + + // fmt::Display INTEGER ONLY + int_enum_impl_display_integer!($name); + // FromStr and frombytes() INTEGER ONLY + int_enum_impl_fromstr_frombytes_from_integer!($name); + // JSON INTEGER ONLY + int_enum_impl_serde_to_and_from_integer!($name, $int_type); + // Zonefile Format INTEGER + int_enum_zonefile_fmt_decimal!($name, stringify!($name)); + }; + + // MNEMONICS WITH PREFIX + (=> $name:ident, $int_type:ident; + fmt:mnemonic, prefix($int_prefix:expr, $int_byte_prefix:expr)) => { + // fmt::Display MNEMONICS fallback integer with prefix + int_enum_impl_display_mnemonics_fallback_prefix_integer!( + $name, + $int_prefix + ); + // READ MNEMONICS fallback integer with prefix + int_enum_impl_fromstr_frombytes_from_mnemonics_or_prefix!( + $name, + $int_prefix, + $int_byte_prefix + ); + // JSON MNEMONICS fallback integer with prefix + int_enum_impl_serde_to_and_from_mnemonic_fallback_prefix!( + $name, + $int_type, + $int_prefix + ); + // Zonefile Format INTEGER + int_enum_zonefile_fmt_with_prefix!($name, $int_prefix); + }; + + // MNEMONICS BUT `fmt::Display` WITH INTEGER IN PARENTHESES + (=> $name:ident, $int_type:ident; + fmt:mnemonic_display_with_combination) => { + // fmt::Display MNEMONIC with () fallback integer only + int_enum_impl_display_mnemonics_with_integer_fallback_integer!($name); + // READ MNEMONIC + int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!($name); + // JSON MNEMONIC + int_enum_impl_serde_to_and_from_mnemonic!($name, $int_type); + // Zonefile Format MNEMONIC + int_enum_zonefile_fmt_with_decimal!($name); + }; + + // INTEGER with integer in parentheses for display + (=> $name:ident, $int_type:ident; + fmt:integer_display_with_combination) => { + // fmt::Display MNEMONIC with () fallback integer only + int_enum_impl_display_mnemonics_with_integer_fallback_integer!($name); + // READ MNEMONIC + int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!($name); + // JSON MNEMONIC + int_enum_impl_serde_to_and_from_integer!($name, $int_type); + // Zonefile Format MNEMONIC + int_enum_zonefile_fmt_decimal!($name, stringify!($name)); + }; +} + +int_enum! { + => + JannisTestEnum1, u8; + (A => 0, "A") + (B => 1, "B") +} +int_enum! { + => + JannisTestEnum2, u8; + (A => 0, "A") + (B => 1, "B") +} +int_enum! { + => + JannisTestEnum3, u8; + (A => 0, "A") + (B => 1, "B") +} +int_enum! { + => + JannisTestEnum4, u8; + (A => 0, "A") + (B => 1, "B") +} + +iana_enum_implementation!(=> JannisTestEnum1, u8; + fmt:integer); + +iana_enum_implementation!(=> JannisTestEnum2, u8; + fmt:mnemonic, prefix("J", b"J")); + +iana_enum_implementation!(=> JannisTestEnum3, u8; + fmt:mnemonic_display_with_combination); + +iana_enum_implementation!(=> JannisTestEnum4, u8; + fmt:integer_display_with_combination); //------------ Tests --------------------------------------------------------- #[cfg(test)] diff --git a/src/base/iana/mod.rs b/src/base/iana/mod.rs index 65ab571ae..f08878b31 100644 --- a/src/base/iana/mod.rs +++ b/src/base/iana/mod.rs @@ -101,6 +101,13 @@ mod test { 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; @@ -187,6 +194,92 @@ mod test { // 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(), + &["A", "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( @@ -239,7 +332,7 @@ mod test { IpseckeyAlgorithm::ECDSA, "3".into(), "IpseckeyAlgorithm::ECDSA".into(), - &["3"], + &["3", "ECDSA"], "3".into(), r#"3"#.into(), ); @@ -259,7 +352,7 @@ mod test { IpseckeyGatewayType::NONE, "0".into(), "IpseckeyGatewayType::NONE".into(), - &["0"], + &["0", "NONE"], "0".into(), r#"0"#.into(), ); @@ -274,12 +367,12 @@ mod test { } #[test] - fn validate_nsec3_hash_algorithm_representation() { + fn validate_nsec3_hash_algorithm_representation() { validate_generic_representation( Nsec3HashAlgorithm::SHA1, "1".into(), "Nsec3HashAlgorithm::SHA-1".into(), - &["1"], + &["1", "SHA-1"], "1".into(), r#"1"#.into(), ); @@ -451,7 +544,7 @@ mod test { SvcParamKey::ALPN, "alpn".into(), "SvcParamKey::alpn".into(), - &["KEY1"], + &["alpn", "KEY1"], "alpn".into(), r#""alpn""#.into(), ); @@ -459,7 +552,7 @@ mod test { SvcParamKey::from_int(42), "key42".into(), "SvcParamKey(42)".into(), - &["key42"], + &["KEY42"], "key42".into(), r#""key42""#.into(), ); diff --git a/src/base/iana/nsec3.rs b/src/base/iana/nsec3.rs index a2d4a8334..c19be42db 100644 --- a/src/base/iana/nsec3.rs +++ b/src/base/iana/nsec3.rs @@ -20,6 +20,26 @@ int_enum! { (SHA1 => 1, "SHA-1") } -int_enum_fromstr_decimal!(Nsec3HashAlgorithm, u8); -int_enum_display_decimal!(Nsec3HashAlgorithm, u8); -int_enum_zonefile_fmt_decimal!(Nsec3HashAlgorithm, "hash algorithm"); +// int_enum_fromstr_decimal!(Nsec3HashAlgorithm, u8); +// int_enum_display_decimal!(Nsec3HashAlgorithm, u8); +// int_enum_zonefile_fmt_decimal!(Nsec3HashAlgorithm, "hash algorithm"); + +scan_impl!(Nsec3HashAlgorithm); + +int_enum_zonefile_fmt_decimal!(Nsec3HashAlgorithm, "algorithm"); + +// Display +int_enum_impl_display_integer!( + Nsec3HashAlgorithm +); + +// FromStrError +instantiate_fromstrerror_with_error_description!( + "unknown Nsec3HashAlgorithm" +); + +// serde::Serialize / serde::Deserialize +int_enum_impl_serde_to_and_from_integer!(Nsec3HashAlgorithm, u8); + +// core::str::FromStr / from_bytes() +int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!(Nsec3HashAlgorithm); diff --git a/src/base/iana/opcode.rs b/src/base/iana/opcode.rs index e48718e49..a85dd6530 100644 --- a/src/base/iana/opcode.rs +++ b/src/base/iana/opcode.rs @@ -81,6 +81,26 @@ int_enum! { (DSO => 6, "DSO") } -int_enum_fromstr_mnemonic!(Opcode, u8, "unknown opcode"); -int_enum_display_mnemonic!(Opcode, u8, "unknown opcode"); +// int_enum_fromstr_mnemonic!(Opcode, u8, "unknown opcode"); +// int_enum_display_mnemonic!(Opcode, u8, "unknown opcode"); +// int_enum_zonefile_fmt_with_decimal!(Opcode); + +scan_impl!(Opcode); + int_enum_zonefile_fmt_with_decimal!(Opcode); + +// Display +int_enum_impl_display_mnemonics_with_integer_fallback_integer!( + Opcode +); + +// FromStrError +instantiate_fromstrerror_with_error_description!( + "unknown Opcode" +); + +// serde::Serialize / serde::Deserialize +int_enum_impl_serde_to_and_from_mnemonic!(Opcode, u8); + +// core::str::FromStr / from_bytes() +int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!(Opcode); diff --git a/src/base/iana/opt.rs b/src/base/iana/opt.rs index f1370f7ff..1aa2e9f5a 100644 --- a/src/base/iana/opt.rs +++ b/src/base/iana/opt.rs @@ -170,10 +170,30 @@ int_enum! { (DEVICE_ID => 26946, "DeviceId") } -int_enum_fromstr_mnemonic!(OptionCode, u16, "unknown option code"); -int_enum_display_mnemonic!(OptionCode, u16, "unknown option code"); +// int_enum_fromstr_mnemonic!(OptionCode, u16, "unknown option code"); +// int_enum_display_mnemonic!(OptionCode, u16, "unknown option code"); +// int_enum_zonefile_fmt_with_decimal!(OptionCode); + +scan_impl!(OptionCode); + int_enum_zonefile_fmt_with_decimal!(OptionCode); +// Display +int_enum_impl_display_mnemonics_with_integer_fallback_integer!( + OptionCode +); + +// FromStrError +instantiate_fromstrerror_with_error_description!( + "unknown OptionCode" +); + +// serde::Serialize / serde::Deserialize +int_enum_impl_serde_to_and_from_integer!(OptionCode, u16); + +// core::str::FromStr / from_bytes() +int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!(OptionCode); + //============ Tests ========================================================= #[cfg(test)] diff --git a/src/base/iana/rcode.rs b/src/base/iana/rcode.rs index d75d0b34f..e8fce6c3c 100644 --- a/src/base/iana/rcode.rs +++ b/src/base/iana/rcode.rs @@ -896,10 +896,25 @@ impl From for TsigRcode { } } -int_enum_fromstr_mnemonic!(TsigRcode, u16, "unknown TSIG error"); -int_enum_display_mnemonic!(TsigRcode, u16, "unknown TSIG error"); +// int_enum_fromstr_mnemonic!(TsigRcode, u16, "unknown TSIG error"); +// int_enum_display_mnemonic!(TsigRcode, u16, "unknown TSIG error"); +// int_enum_zonefile_fmt_with_decimal!(TsigRcode); +scan_impl!(TsigRcode); + int_enum_zonefile_fmt_with_decimal!(TsigRcode); +// Display +int_enum_impl_display_mnemonics_fallback_integer!(TsigRcode); + +// FromStrError +instantiate_fromstrerror_with_error_description!("unknown tsig rcode"); + +// serde::Serialize / serde::Deserialize +int_enum_impl_serde_to_and_from_mnemonic!(TsigRcode, u16); + +// core::str::FromStr / from_bytes() +int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!(TsigRcode); + //============ Error Types =================================================== /// An integer couldn’t be converted into an rcode. diff --git a/src/base/iana/secalg.rs b/src/base/iana/secalg.rs index a7a42304e..63d6a81bf 100644 --- a/src/base/iana/secalg.rs +++ b/src/base/iana/secalg.rs @@ -131,7 +131,7 @@ instantiate_fromstrerror_with_error_description!( ); // serde::Serialize / serde::Deserialize -int_enum_impl_serde_to_and_from_integer!(SecurityAlgorithm); +int_enum_impl_serde_to_and_from_integer!(SecurityAlgorithm, u8); // core::str::FromStr / from_bytes() int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!(SecurityAlgorithm); From 6e23bf4b61e0ec46d810de7fa0dbf97c6e38b803 Mon Sep 17 00:00:00 2001 From: withjannisNLnetLabs Date: Tue, 26 May 2026 22:09:02 +0200 Subject: [PATCH 11/13] Initial IanaEnum trait finished starting migration --- NOTES.md | 75 ------- src/base/iana/class.rs | 28 +-- src/base/iana/iana_trait.rs | 0 src/base/iana/ipseckey.rs | 12 +- src/base/iana/macros.rs | 381 ++++++++++++++++++++++++++++++++++-- src/base/iana/mod.rs | 7 +- src/base/iana/nsec3.rs | 8 +- src/base/iana/opcode.rs | 8 +- src/base/iana/opt.rs | 8 +- src/base/serde.rs | 33 +--- 10 files changed, 403 insertions(+), 157 deletions(-) delete mode 100644 NOTES.md create mode 100644 src/base/iana/iana_trait.rs diff --git a/NOTES.md b/NOTES.md deleted file mode 100644 index b4e130cec..000000000 --- a/NOTES.md +++ /dev/null @@ -1,75 +0,0 @@ - -# Notes - -## MNEMONIC PREFIX - -- Class -- Rtype -- SvcParamKey - -## INTEGER - -- DigestAlgorithm -- IpseckeyAlgorithm -- IpseccGatewayType -- Nsec3HashAlgorithm -- SshfpAlgorithm -- SshfpType -- TlsaCertificateUsage -- TlsaMatchingType -- TlsaSelector -- ZonemdAlgorithm -- ZonemdScheme - - -## MNEMONIC with integer in parentheses for display - -- OpCode -- OptionCode -- TsigRcode - -## INTEGER with integer in parentheses for display - -- SecurityAlgorithm - - -fn( - type: struct - integer_type: [u8, u16] - default_represent: (Mnemonics, Integer, MnemonicPrefixed) - display_trait: (Mnemonics, Integer, MnemonicWithIntegerInParentheses) - prefix: str -) - - - - -trait IanaEnum { - - fn display_as_number() { - - } - - fn display_mnemonic() - - fn display_with_prefix() { - const PREFIX: STRING = "CLASS" - - } - -} - - -macro_rules! hello_world -... => { - impl fmt::Display for $iana_type { - $iana_type::$display_callable(f) - } - - impl str::FromStr for $iana_type { - $fromstr_callable(something: &str) - } - -} - -iana_enum_implemenation!(SecAlg, IanaEnum::display_as_number, |f|) diff --git a/src/base/iana/class.rs b/src/base/iana/class.rs index 24afa021e..251e88c37 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, + "J"; /// Internet (IN). /// @@ -56,26 +64,8 @@ int_enum! { (ANY => 0xFF, "*") } -// int_enum_str_with_prefix!(Class, "CLASS", b"CLASS", u16, "unknown class"); - -scan_impl!(Class); - int_enum_zonefile_fmt_with_prefix!(Class, "CLASS"); -// Display -int_enum_impl_display_mnemonics_fallback_prefix_integer!(Class, "CLASS"); - -// FromStrError -instantiate_fromstrerror_with_error_description!("unknown class"); - -// serde::Serialize / serde::Deserialize -int_enum_impl_serde_to_and_from_mnemonic!(Class, u16); - -// core::str::FromStr / from_bytes() -int_enum_impl_fromstr_frombytes_from_mnemonics_or_prefix!( - Class, "CLASS", b"CLASS" -); - //============ Tests ========================================================= #[cfg(test)] 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 b31ae64dd..8e5b2bf7d 100644 --- a/src/base/iana/ipseckey.rs +++ b/src/base/iana/ipseckey.rs @@ -44,9 +44,7 @@ scan_impl!(IpseckeyAlgorithm); int_enum_zonefile_fmt_decimal!(IpseckeyAlgorithm, "algorithm"); // Display -int_enum_impl_display_integer!( - IpseckeyAlgorithm -); +int_enum_impl_display_integer!(IpseckeyAlgorithm); // FromStrError instantiate_fromstrerror_with_error_description!( @@ -97,9 +95,7 @@ scan_impl!(IpseckeyGatewayType); int_enum_zonefile_fmt_decimal!(IpseckeyGatewayType, "algorithm"); // Display -int_enum_impl_display_integer!( - IpseckeyGatewayType -); +int_enum_impl_display_integer!(IpseckeyGatewayType); // FromStrError // instantiate_fromstrerror_with_error_description!( @@ -110,4 +106,6 @@ int_enum_impl_display_integer!( int_enum_impl_serde_to_and_from_integer!(IpseckeyGatewayType, u8); // core::str::FromStr / from_bytes() -int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!(IpseckeyGatewayType); +int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!( + IpseckeyGatewayType +); diff --git a/src/base/iana/macros.rs b/src/base/iana/macros.rs index 0828446a9..d49010f95 100644 --- a/src/base/iana/macros.rs +++ b/src/base/iana/macros.rs @@ -997,42 +997,399 @@ macro_rules! iana_enum_implementation { }; } -int_enum! { +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 a value from its raw integer value. + fn from_int(value: $inttype) -> Self { + Self(value) + } + + /// Returns the raw integer value for a value. + fn get_integer(&self) -> $inttype { + self.0 + } + + /// 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 { + 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; + + } + + + //--- 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") } -int_enum! { +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") } -int_enum! { +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") } -int_enum! { +iana_enum! { => JannisTestEnum4, u8; + display_mnemonic_with_integer, + parse_from_integer, + serialize_to_integer, + deserialize_from_integer, + ""; (A => 0, "A") (B => 1, "B") } -iana_enum_implementation!(=> JannisTestEnum1, u8; - fmt:integer); +use core::fmt::Display; +use std::string::String; +use std::string::ToString; + +use serde::Deserialize; +use serde::Serialize; + +use crate::base::serde::DeserializeNativeOrStr; + +pub trait IanaEnum<'de>: Sized { + 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_int(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; + fn to_int(self) -> Self::INT { + self.get_integer() + } + + fn compose( + &self, + target: &mut Target, + ) -> Result<(), Target::AppendError> { + crate::base::wire::Compose::compose(&self.get_integer(), target) + } + + //--- 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().to_string() + ), + } + } + 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_int) { + 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_int) { + 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_int(value)) + } else { + Err(FromStrError(())) + } + } else { + Err(FromStrError(())) + } + } + } + } + + //--- serde::Serialize + fn serialize_to_integer( + &self, + serializer: S, + ) -> Result { + self.get_integer().serialize(serializer) + } + + fn serialize_to_mnemonic_fallback_integer( + &self, + serializer: S, + ) -> Result { + match self.get_mnemonic_str() { + Some(m) => m.serialize(serializer), + None => self.get_integer().serialize(serializer), + } + } + + fn serialize_to_mnemonic_fallback_prefix_integer( + &self, + serializer: S, + ) -> Result { + match self.get_mnemonic_str() { + Some(m) => m.serialize(serializer), + None => format!("{}{}", Self::get_prefix(), self.get_integer()) + .serialize(serializer), + } + } -iana_enum_implementation!(=> JannisTestEnum2, u8; - fmt:mnemonic, prefix("J", b"J")); + //--- serde::Deserialize + fn deserialize_from_integer>( + deserializer: D, + ) -> Result>::Error> { + Self::INT::deserialize(deserializer).map(Self::from_int) + } + + fn deserialize_from_mnemonic_or_integer>( + deserializer: D, + ) -> Result>::Error> { + Self::INT::deserialize_native_or_str(deserializer) + } + + fn deserialize_from_mnemonic_or_prefix_integer< + D: serde::Deserializer<'de>, + >( + deserializer: D, + ) -> Result>::Error> { + Self::INT::deserialize_native_or_str(deserializer) + } +} -iana_enum_implementation!(=> JannisTestEnum3, u8; - fmt:mnemonic_display_with_combination); +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"); -iana_enum_implementation!(=> JannisTestEnum4, u8; - fmt:integer_display_with_combination); //------------ Tests --------------------------------------------------------- #[cfg(test)] diff --git a/src/base/iana/mod.rs b/src/base/iana/mod.rs index f08878b31..8fe56838a 100644 --- a/src/base/iana/mod.rs +++ b/src/base/iana/mod.rs @@ -84,6 +84,7 @@ mod test { // use crate::base::iana::exterr::ExtendedErrorCode; use crate::base::iana::ipseckey::IpseckeyAlgorithm; use crate::base::iana::ipseckey::IpseckeyGatewayType; + use crate::base::iana::macros::IanaEnum; use crate::base::iana::nsec3::Nsec3HashAlgorithm; use crate::base::iana::opcode::Opcode; use crate::base::iana::opt::OptionCode; @@ -101,7 +102,6 @@ mod test { 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; @@ -266,7 +266,7 @@ mod test { JannisTestEnum4::A, "A(0)".into(), "JannisTestEnum4::A".into(), - &["A", "0"], + &["0"], "0".into(), r#"0"#.into(), ); @@ -279,7 +279,6 @@ mod test { r#"42"#.into(), ); } - #[test] fn validate_class_representation() { validate_generic_representation( @@ -367,7 +366,7 @@ mod test { } #[test] - fn validate_nsec3_hash_algorithm_representation() { + fn validate_nsec3_hash_algorithm_representation() { validate_generic_representation( Nsec3HashAlgorithm::SHA1, "1".into(), diff --git a/src/base/iana/nsec3.rs b/src/base/iana/nsec3.rs index c19be42db..7c3115697 100644 --- a/src/base/iana/nsec3.rs +++ b/src/base/iana/nsec3.rs @@ -29,9 +29,7 @@ scan_impl!(Nsec3HashAlgorithm); int_enum_zonefile_fmt_decimal!(Nsec3HashAlgorithm, "algorithm"); // Display -int_enum_impl_display_integer!( - Nsec3HashAlgorithm -); +int_enum_impl_display_integer!(Nsec3HashAlgorithm); // FromStrError instantiate_fromstrerror_with_error_description!( @@ -42,4 +40,6 @@ instantiate_fromstrerror_with_error_description!( int_enum_impl_serde_to_and_from_integer!(Nsec3HashAlgorithm, u8); // core::str::FromStr / from_bytes() -int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!(Nsec3HashAlgorithm); +int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!( + Nsec3HashAlgorithm +); diff --git a/src/base/iana/opcode.rs b/src/base/iana/opcode.rs index a85dd6530..317ca7c60 100644 --- a/src/base/iana/opcode.rs +++ b/src/base/iana/opcode.rs @@ -90,14 +90,10 @@ scan_impl!(Opcode); int_enum_zonefile_fmt_with_decimal!(Opcode); // Display -int_enum_impl_display_mnemonics_with_integer_fallback_integer!( - Opcode -); +int_enum_impl_display_mnemonics_with_integer_fallback_integer!(Opcode); // FromStrError -instantiate_fromstrerror_with_error_description!( - "unknown Opcode" -); +instantiate_fromstrerror_with_error_description!("unknown Opcode"); // serde::Serialize / serde::Deserialize int_enum_impl_serde_to_and_from_mnemonic!(Opcode, u8); diff --git a/src/base/iana/opt.rs b/src/base/iana/opt.rs index 1aa2e9f5a..77680e166 100644 --- a/src/base/iana/opt.rs +++ b/src/base/iana/opt.rs @@ -179,14 +179,10 @@ scan_impl!(OptionCode); int_enum_zonefile_fmt_with_decimal!(OptionCode); // Display -int_enum_impl_display_mnemonics_with_integer_fallback_integer!( - OptionCode -); +int_enum_impl_display_mnemonics_with_integer_fallback_integer!(OptionCode); // FromStrError -instantiate_fromstrerror_with_error_description!( - "unknown OptionCode" -); +instantiate_fromstrerror_with_error_description!("unknown OptionCode"); // serde::Serialize / serde::Deserialize int_enum_impl_serde_to_and_from_integer!(OptionCode, u16); 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)) } } From bf086fd29c4d6dc41333bc745254a19fa7fe6aab Mon Sep 17 00:00:00 2001 From: withjannisNLnetLabs Date: Tue, 26 May 2026 23:26:26 +0200 Subject: [PATCH 12/13] All test passed but not yet finished with IanaEnum --- src/base/iana/class.rs | 2 +- src/base/iana/digestalg.rs | 27 +- src/base/iana/ipseckey.rs | 63 +--- src/base/iana/macros.rs | 731 +++---------------------------------- src/base/iana/mod.rs | 2 +- src/base/iana/nsec3.rs | 33 +- src/base/iana/opcode.rs | 28 +- src/base/iana/opt.rs | 28 +- src/base/iana/rcode.rs | 26 +- src/base/iana/rtype.rs | 11 +- src/base/iana/secalg.rs | 28 +- src/base/iana/sshfp.rs | 21 +- src/base/iana/svcb.rs | 11 +- src/base/iana/tlsa.rs | 30 +- src/base/iana/zonemd.rs | 21 +- 15 files changed, 195 insertions(+), 867 deletions(-) diff --git a/src/base/iana/class.rs b/src/base/iana/class.rs index 251e88c37..83aa9945b 100644 --- a/src/base/iana/class.rs +++ b/src/base/iana/class.rs @@ -32,7 +32,7 @@ iana_enum! { parse_from_mnemonic_or_prefix_integer, serialize_to_mnemonic_fallback_prefix_integer, deserialize_from_mnemonic_or_prefix_integer, - "J"; + "CLASS"; /// Internet (IN). /// diff --git a/src/base/iana/digestalg.rs b/src/base/iana/digestalg.rs index 1cf9badae..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,24 +50,7 @@ int_enum! { (SHA384 => 4, "SHA-384") } -int_enum_fromstr_decimal!(DigestAlgorithm, u8); -int_enum_display_decimal!(DigestAlgorithm, u8); int_enum_zonefile_fmt_decimal!(DigestAlgorithm, "digest type"); -// scan_impl!(DigestAlgorithm); -// -// int_enum_zonefile_fmt_decimal!(DigestAlgorithm, "digest type"); -// -// // Display -// int_enum_impl_display_integer!(DigestAlgorithm); -// -// // FromStrError -// instantiate_fromstrerror_with_error_description!("unknown digest type"); -// -// // serde::Serialize / serde::Deserialize -// int_enum_impl_serde_to_and_from_integer!(DigestAlgorithm, u8); -// -// // core::str::FromStr / from_bytes() -// int_enum_impl_fromstr_frombytes_from_integer!(DigestAlgorithm); //============ Tests ========================================================= diff --git a/src/base/iana/ipseckey.rs b/src/base/iana/ipseckey.rs index 8e5b2bf7d..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,31 +43,11 @@ int_enum! { (EDDSA => 4, "EdDSA") } -// int_enum_fromstr_decimal!(IpseckeyAlgorithm, u8); -// int_enum_display_decimal!(IpseckeyAlgorithm, u8); -// int_enum_zonefile_fmt_decimal!(IpseckeyAlgorithm, "ipseckey algorithm"); - -scan_impl!(IpseckeyAlgorithm); - -int_enum_zonefile_fmt_decimal!(IpseckeyAlgorithm, "algorithm"); - -// Display -int_enum_impl_display_integer!(IpseckeyAlgorithm); - -// FromStrError -instantiate_fromstrerror_with_error_description!( - "unknown ipseckey algorithm" -); - -// serde::Serialize / serde::Deserialize -int_enum_impl_serde_to_and_from_integer!(IpseckeyAlgorithm, u8); - -// core::str::FromStr / from_bytes() -int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!(IpseckeyAlgorithm); +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 @@ -72,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") @@ -86,26 +79,4 @@ int_enum! { (NAME => 3, "NAME") } -// int_enum_fromstr_decimal!(IpseckeyGatewayType, u8); -// int_enum_display_decimal!(IpseckeyGatewayType, u8); -// int_enum_zonefile_fmt_decimal!(IpseckeyGatewayType, "ipseckey gateway type"); - -scan_impl!(IpseckeyGatewayType); - -int_enum_zonefile_fmt_decimal!(IpseckeyGatewayType, "algorithm"); - -// Display -int_enum_impl_display_integer!(IpseckeyGatewayType); - -// FromStrError -// instantiate_fromstrerror_with_error_description!( -// "unknown ipseckey algorithm" -// ); - -// serde::Serialize / serde::Deserialize -int_enum_impl_serde_to_and_from_integer!(IpseckeyGatewayType, u8); - -// core::str::FromStr / from_bytes() -int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!( - IpseckeyGatewayType -); +int_enum_zonefile_fmt_decimal!(IpseckeyGatewayType, "ipseckey gateway type"); diff --git a/src/base/iana/macros.rs b/src/base/iana/macros.rs index d49010f95..2ad8380ff 100644 --- a/src/base/iana/macros.rs +++ b/src/base/iana/macros.rs @@ -136,214 +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_fromstr_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); - - #[cfg(feature = "serde")] - impl<'de> serde::Deserialize<'de> for $ianatype { - fn deserialize>( - deserializer: D, - ) -> Result { - $inttype::deserialize(deserializer).map(Into::into) - } - } - }; -} - -macro_rules! int_enum_display_decimal { - ($ianatype:ident, $inttype:ident) => { - 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) - } - } - }; -} - -/// 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_fromstr_mnemonic { - ($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(())) - } - } - } - } - } - - scan_impl!($ianatype); - - #[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); - }; -} - -macro_rules! int_enum_display_mnemonic { - ($ianatype:ident, $inttype:ident, $error:expr) => { - 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()) - } - } - } - } - - #[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) - } - } - } - }; -} - /// Adds impls for `FromStr` and `Display` to the type given as first argument. /// /// For `FromStr` recognizes all defined mnemonics ignoring case. Additionally @@ -547,456 +339,6 @@ macro_rules! from_str_error { // --- TODO: NEW VERSION, FINISH IT! -/// --- FromStrError --- -/// This macro defines a new struct which is used as an error type in -/// core::str::FromStr. The description is used to std::fmt::Display the -/// error. -macro_rules! instantiate_fromstrerror_with_error_description { - ($error_description:expr) => { - #[derive(Clone, Debug)] - pub struct FromStrError(()); - - impl core::error::Error for FromStrError { - fn description(&self) -> &str { - $error_description - } - } - - impl core::fmt::Display for FromStrError { - fn fmt( - &self, - f: &mut core::fmt::Formatter<'_>, - ) -> core::fmt::Result { - $error_description.fmt(f) - } - } - }; -} -// --- - -/// --- FromBytes / FromStr --- COMBINATION VERSION -/// This macro implements core::str::FromStr and the function from_bytes(). It -/// tries to parse the bytes as mnemonic first and falls back to parsing it as -/// an integer if that is not possible. -macro_rules! int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer { - ($enum_type:ident) => { - impl $enum_type { - #[must_use] - pub fn from_bytes(bytes: &[u8]) -> Option { - $enum_type::from_mnemonic(bytes).or_else(|| { - core::str::from_utf8(bytes).ok().and_then(|r| { - r.parse().ok().map($enum_type::from_int) - }) - }) - } - } - - impl core::str::FromStr for $enum_type { - 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 $enum_type::from_mnemonic(s.as_bytes()) { - Some(res) => Ok(res), - None => { - if let Ok(res) = s.parse() { - Ok($enum_type::from_int(res)) - } else { - Err(FromStrError(())) - } - } - } - } - } - }; -} -// --- -// -/// --- FromBytes / FromStr --- INTEGER VERSION -/// This macro implements core::str::FromStr and the function from_bytes(). It -/// parses the value from the integer associated with it. -macro_rules! int_enum_impl_fromstr_frombytes_from_integer { - ($enum_type:ident) => { - impl $enum_type { - #[must_use] - pub fn from_bytes(bytes: &[u8]) -> Option { - $enum_type::from_mnemonic(bytes).or_else(|| { - core::str::from_utf8(bytes).ok().and_then(|r| { - r.parse().ok().map($enum_type::from_int) - }) - }) - } - } - - impl core::str::FromStr for $enum_type { - 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 $enum_type::from_mnemonic(s.as_bytes()) { - Some(res) => Ok(res), - None => { - if let Ok(res) = s.parse() { - Ok($enum_type::from_int(res)) - } else { - Err(FromStrError(())) - } - } - } - } - } - }; -} -// --- - -/// --- FromBytes / FromStr --- PREFIX VERSION -/// This macro implements core::str::FromStr and the function from_bytes(). It -/// tries to parse the bytes as mnemonic first and falls back to parsing it as -/// an integer with a specific prefix if that is not possible. -macro_rules! int_enum_impl_fromstr_frombytes_from_mnemonics_or_prefix { - ($enum_type:ident, $str_prefix:expr, $prefix_bytes:expr) => { - impl $enum_type { - #[must_use] - pub fn from_bytes(bytes: &[u8]) -> Option { - $enum_type::from_mnemonic(bytes).or_else(|| { - if bytes.len() <= $prefix_bytes.len() { - return None; - } - let (l, r) = bytes.split_at($prefix_bytes.len()); - if !l.eq_ignore_ascii_case($prefix_bytes) { - return None; - } - let r = match core::str::from_utf8(r) { - Ok(r) => r, - Err(_) => return None, - }; - r.parse().ok().map($enum_type::from_int) - }) - } - } - - impl core::str::FromStr for $enum_type { - 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 $enum_type::from_mnemonic(s.as_bytes()) { - Some(res) => Ok(res), - None => { - if let Some((n, _)) = - s.char_indices().nth($str_prefix.len()) - { - let (l, r) = s.split_at(n); - if l.eq_ignore_ascii_case($str_prefix) { - let value = match r.parse() { - Ok(x) => x, - Err(..) => return Err(FromStrError(())), - }; - Ok($enum_type::from_int(value)) - } else { - Err(FromStrError(())) - } - } else { - Err(FromStrError(())) - } - } - } - } - } - }; -} -// --- - -/// --- Display --- COMBINATION VERSION -/// This macro implements std::fmt::Display. If the value has a mnemonic it -/// formats the string as "()" including the interger in -/// parenthesis. If the value does not have a mnemonic it falls back to the -/// integer and formats the String as "". -macro_rules! int_enum_impl_display_mnemonics_with_integer_fallback_integer { - ($enum_type:ident) => { - impl std::fmt::Display for $enum_type { - 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()) - } - } - } - } - }; -} -// --- - -/// --- Display --- MNEMONIC PREFIX VERSION -/// This macro implements std::fmt::Display. If the value has a mnemonic it -/// formats the string as "". If the value does not have a mnemonic -/// it falls back to the integer and formats the String as "". -macro_rules! int_enum_impl_display_mnemonics_fallback_prefix_integer { - ($enum_type:ident, $prefix:expr) => { - impl std::fmt::Display for $enum_type { - fn fmt( - &self, - f: &mut core::fmt::Formatter<'_>, - ) -> core::fmt::Result { - match self.to_mnemonic_str() { - Some(m) => { - write!(f, "{}", m) - } - None => { - write!(f, "{}{}", $prefix, self.to_int()) - } - } - } - } - }; -} -// --- - -/// --- Display --- MNEMONIC VERSION -/// This macro implements std::fmt::Display. If the value has a mnemonic it -/// formats the string as "". If the value does not have a mnemonic -/// it falls back to the integer and formats the String as "". -macro_rules! int_enum_impl_display_mnemonics_fallback_integer { - ($enum_type:ident) => { - impl std::fmt::Display for $enum_type { - fn fmt( - &self, - f: &mut core::fmt::Formatter<'_>, - ) -> core::fmt::Result { - match self.to_mnemonic_str() { - Some(m) => { - write!(f, "{}", m) - } - None => { - write!(f, "{}", self.to_int()) - } - } - } - } - }; -} -// --- -// -/// --- Display --- INTEGER VERSION -/// This macro implements std::fmt::Display. The value will always be formated -/// with the integer associated with it. -macro_rules! int_enum_impl_display_integer { - ($enum_type:ident) => { - impl std::fmt::Display for $enum_type { - fn fmt( - &self, - f: &mut core::fmt::Formatter<'_>, - ) -> core::fmt::Result { - write!(f, "{}", self.to_int()) - } - } - }; -} -// --- - -/// --- Serialization / Deserialization --- INTEGER VERSION -/// This macro implements serde::Serialize and serde::Deserialize. The token -/// represented in the JSON string is an integer without quotes surrounding -/// it. -/// { -/// "value": 42 -/// } -macro_rules! int_enum_impl_serde_to_and_from_integer { - ($enum_type:ident, $enum_integer_type:ident) => { - #[cfg(feature = "serde")] - impl serde::Serialize for $enum_type { - fn serialize( - &self, - serializer: S, - ) -> Result { - self.to_int().serialize(serializer) - } - } - - #[cfg(feature = "serde")] - impl<'de> serde::Deserialize<'de> for $enum_type { - fn deserialize>( - deserializer: D, - ) -> Result { - $enum_integer_type::deserialize(deserializer).map(Into::into) - } - } - }; -} -// --- - -/// --- Serialization / Deserialization --- MNEMONIC FALLBACK INTEGER -/// This macro implements serde::Serialize and serde::Deserialize. The token -/// represented in the JSON is the mnemonic version if possible otherwise the -/// associated integer. -/// { -/// "value": "SHA1", -/// "value2": 42 -/// } -macro_rules! int_enum_impl_serde_to_and_from_mnemonic { - ($enum_type:ident, $enum_integer_type:ident) => { - #[cfg(feature = "serde")] - impl serde::Serialize for $enum_type { - fn serialize( - &self, - serializer: S, - ) -> Result { - if serializer.is_human_readable() { - match self.to_mnemonic_str() { - Some(m) => { - serializer.collect_str(&format_args!("{}", m)) - } - None => self.to_int().serialize(serializer), - } - } else { - self.to_int().serialize(serializer) - } - } - } - - #[cfg(feature = "serde")] - impl<'de> serde::Deserialize<'de> for $enum_type { - fn deserialize>( - deserializer: D, - ) -> Result { - use crate::base::serde::DeserializeNativeOrStr; - - $enum_integer_type::deserialize_native_or_str(deserializer) - } - } - }; -} - -/// --- Serialization / Deserialization --- MNEMONIC FALLBACK PREFIX INTEGER -/// This macro implements serde::Serialize and serde::Deserialize. The token -/// represented in the JSON is the mnemonic version if possible otherwise the -/// associated integer with a prefix. -/// { -/// "value": "SHA1", -/// "value2": 42 -/// } -macro_rules! int_enum_impl_serde_to_and_from_mnemonic_fallback_prefix { - ($enum_type:ident, $enum_integer_type:ident, $int_prefix:expr) => { - #[cfg(feature = "serde")] - impl serde::Serialize for $enum_type { - fn serialize( - &self, - serializer: S, - ) -> Result { - if serializer.is_human_readable() { - match self.to_mnemonic_str() { - Some(m) => { - serializer.collect_str(&format_args!("{}", m)) - } - None => serializer.collect_str(&format_args!( - "{}{}", - $int_prefix, - self.to_int() - )), - } - } else { - self.to_int().serialize(serializer) - } - } - } - - #[cfg(feature = "serde")] - impl<'de> serde::Deserialize<'de> for $enum_type { - fn deserialize>( - deserializer: D, - ) -> Result { - use crate::base::serde::DeserializeNativeOrStr; - - $enum_integer_type::deserialize_native_or_str(deserializer) - } - } - }; -} -// --- - -// --- TRY AGAIN VERSION 3 :) -// Plan now is to only make one gianc function - -// (=> $name:ident, $int:ident; fmt:decimal, zonefile:decimal($zname:expr); $($variants:tt)*) => { -macro_rules! iana_enum_implementation { - // INTEGER ONLY - (=> $name:ident, $int_type:ident; - fmt:integer) => { - instantiate_fromstrerror_with_error_description!(concat!( - "unknown ", - stringify!($ianatype), - )); - - // fmt::Display INTEGER ONLY - int_enum_impl_display_integer!($name); - // FromStr and frombytes() INTEGER ONLY - int_enum_impl_fromstr_frombytes_from_integer!($name); - // JSON INTEGER ONLY - int_enum_impl_serde_to_and_from_integer!($name, $int_type); - // Zonefile Format INTEGER - int_enum_zonefile_fmt_decimal!($name, stringify!($name)); - }; - - // MNEMONICS WITH PREFIX - (=> $name:ident, $int_type:ident; - fmt:mnemonic, prefix($int_prefix:expr, $int_byte_prefix:expr)) => { - // fmt::Display MNEMONICS fallback integer with prefix - int_enum_impl_display_mnemonics_fallback_prefix_integer!( - $name, - $int_prefix - ); - // READ MNEMONICS fallback integer with prefix - int_enum_impl_fromstr_frombytes_from_mnemonics_or_prefix!( - $name, - $int_prefix, - $int_byte_prefix - ); - // JSON MNEMONICS fallback integer with prefix - int_enum_impl_serde_to_and_from_mnemonic_fallback_prefix!( - $name, - $int_type, - $int_prefix - ); - // Zonefile Format INTEGER - int_enum_zonefile_fmt_with_prefix!($name, $int_prefix); - }; - - // MNEMONICS BUT `fmt::Display` WITH INTEGER IN PARENTHESES - (=> $name:ident, $int_type:ident; - fmt:mnemonic_display_with_combination) => { - // fmt::Display MNEMONIC with () fallback integer only - int_enum_impl_display_mnemonics_with_integer_fallback_integer!($name); - // READ MNEMONIC - int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!($name); - // JSON MNEMONIC - int_enum_impl_serde_to_and_from_mnemonic!($name, $int_type); - // Zonefile Format MNEMONIC - int_enum_zonefile_fmt_with_decimal!($name); - }; - - // INTEGER with integer in parentheses for display - (=> $name:ident, $int_type:ident; - fmt:integer_display_with_combination) => { - // fmt::Display MNEMONIC with () fallback integer only - int_enum_impl_display_mnemonics_with_integer_fallback_integer!($name); - // READ MNEMONIC - int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!($name); - // JSON MNEMONIC - int_enum_impl_serde_to_and_from_integer!($name, $int_type); - // Zonefile Format MNEMONIC - int_enum_zonefile_fmt_decimal!($name, stringify!($name)); - }; -} - macro_rules! iana_enum { ( $(#[$attr:meta])* => $ianatype:ident, $inttype:path; @@ -1025,16 +367,17 @@ macro_rules! iana_enum { fn get_prefix() -> &'static str { $prefix } - /// Returns a value from its raw integer value. - fn from_int(value: $inttype) -> Self { - Self(value) - } /// Returns the raw integer value for a value. - fn get_integer(&self) -> $inttype { + 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 { $( @@ -1059,6 +402,33 @@ macro_rules! iana_enum { } 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 { @@ -1069,6 +439,13 @@ macro_rules! iana_enum { 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) + } + } @@ -1237,7 +614,7 @@ pub trait IanaEnum<'de>: Sized { type ParseError; fn get_prefix() -> &'static str; - fn from_int(value: Self::INT) -> Self; + 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]> { @@ -1251,16 +628,6 @@ pub trait IanaEnum<'de>: Sized { } fn get_integer(&self) -> Self::INT; - fn to_int(self) -> Self::INT { - self.get_integer() - } - - fn compose( - &self, - target: &mut Target, - ) -> Result<(), Target::AppendError> { - crate::base::wire::Compose::compose(&self.get_integer(), target) - } //--- Display fn display_integer(&self) -> String { @@ -1291,7 +658,7 @@ pub trait IanaEnum<'de>: Sized { //--- PARSING fn parse_from_integer(value: &str) -> Result { - match value.parse().map(Self::from_int) { + match value.parse().map(Self::from_integer) { Ok(v) => Ok(v), Err(_) => Err(FromStrError(())), } @@ -1301,7 +668,7 @@ pub trait IanaEnum<'de>: Sized { ) -> Result { match Self::from_mnemonic(value.as_bytes()) { Some(v) => Ok(v), - None => match value.parse().map(Self::from_int) { + None => match value.parse().map(Self::from_integer) { Ok(v) => Ok(v), Err(_) => Err(FromStrError(())), }, @@ -1323,7 +690,7 @@ pub trait IanaEnum<'de>: Sized { Ok(x) => x, Err(..) => return Err(FromStrError(())), }; - Ok(Self::from_int(value)) + Ok(Self::from_integer(value)) } else { Err(FromStrError(())) } @@ -1346,6 +713,10 @@ pub trait IanaEnum<'de>: Sized { &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), @@ -1356,6 +727,10 @@ pub trait IanaEnum<'de>: Sized { &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()) @@ -1367,7 +742,7 @@ pub trait IanaEnum<'de>: Sized { fn deserialize_from_integer>( deserializer: D, ) -> Result>::Error> { - Self::INT::deserialize(deserializer).map(Self::from_int) + Self::INT::deserialize(deserializer).map(Self::from_integer) } fn deserialize_from_mnemonic_or_integer>( diff --git a/src/base/iana/mod.rs b/src/base/iana/mod.rs index 8fe56838a..8686b88be 100644 --- a/src/base/iana/mod.rs +++ b/src/base/iana/mod.rs @@ -57,6 +57,7 @@ pub use self::sshfp::{SshfpAlgorithm, SshfpType}; pub use self::svcb::SvcParamKey; pub use self::tlsa::{TlsaCertificateUsage, TlsaMatchingType, TlsaSelector}; pub use self::zonemd::{ZonemdAlgorithm, ZonemdScheme}; +pub use self::macros::{IanaEnum}; #[macro_use] mod macros; @@ -84,7 +85,6 @@ mod test { // use crate::base::iana::exterr::ExtendedErrorCode; use crate::base::iana::ipseckey::IpseckeyAlgorithm; use crate::base::iana::ipseckey::IpseckeyGatewayType; - use crate::base::iana::macros::IanaEnum; use crate::base::iana::nsec3::Nsec3HashAlgorithm; use crate::base::iana::opcode::Opcode; use crate::base::iana::opt::OptionCode; diff --git a/src/base/iana/nsec3.rs b/src/base/iana/nsec3.rs index 7c3115697..f077f7b42 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,31 +18,15 @@ 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_fromstr_decimal!(Nsec3HashAlgorithm, u8); -// int_enum_display_decimal!(Nsec3HashAlgorithm, u8); -// int_enum_zonefile_fmt_decimal!(Nsec3HashAlgorithm, "hash algorithm"); - -scan_impl!(Nsec3HashAlgorithm); - -int_enum_zonefile_fmt_decimal!(Nsec3HashAlgorithm, "algorithm"); - -// Display -int_enum_impl_display_integer!(Nsec3HashAlgorithm); - -// FromStrError -instantiate_fromstrerror_with_error_description!( - "unknown Nsec3HashAlgorithm" -); - -// serde::Serialize / serde::Deserialize -int_enum_impl_serde_to_and_from_integer!(Nsec3HashAlgorithm, u8); +int_enum_zonefile_fmt_decimal!(Nsec3HashAlgorithm, "hash algorithm"); -// core::str::FromStr / from_bytes() -int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!( - Nsec3HashAlgorithm -); diff --git a/src/base/iana/opcode.rs b/src/base/iana/opcode.rs index 317ca7c60..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,22 +89,4 @@ int_enum! { (DSO => 6, "DSO") } -// int_enum_fromstr_mnemonic!(Opcode, u8, "unknown opcode"); -// int_enum_display_mnemonic!(Opcode, u8, "unknown opcode"); -// int_enum_zonefile_fmt_with_decimal!(Opcode); - -scan_impl!(Opcode); - int_enum_zonefile_fmt_with_decimal!(Opcode); - -// Display -int_enum_impl_display_mnemonics_with_integer_fallback_integer!(Opcode); - -// FromStrError -instantiate_fromstrerror_with_error_description!("unknown Opcode"); - -// serde::Serialize / serde::Deserialize -int_enum_impl_serde_to_and_from_mnemonic!(Opcode, u8); - -// core::str::FromStr / from_bytes() -int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!(Opcode); diff --git a/src/base/iana/opt.rs b/src/base/iana/opt.rs index 77680e166..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,26 +178,8 @@ int_enum! { (DEVICE_ID => 26946, "DeviceId") } -// int_enum_fromstr_mnemonic!(OptionCode, u16, "unknown option code"); -// int_enum_display_mnemonic!(OptionCode, u16, "unknown option code"); -// int_enum_zonefile_fmt_with_decimal!(OptionCode); - -scan_impl!(OptionCode); - int_enum_zonefile_fmt_with_decimal!(OptionCode); -// Display -int_enum_impl_display_mnemonics_with_integer_fallback_integer!(OptionCode); - -// FromStrError -instantiate_fromstrerror_with_error_description!("unknown OptionCode"); - -// serde::Serialize / serde::Deserialize -int_enum_impl_serde_to_and_from_integer!(OptionCode, u16); - -// core::str::FromStr / from_bytes() -int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!(OptionCode); - //============ Tests ========================================================= #[cfg(test)] diff --git a/src/base/iana/rcode.rs b/src/base/iana/rcode.rs index e8fce6c3c..7ad6a6a59 100644 --- a/src/base/iana/rcode.rs +++ b/src/base/iana/rcode.rs @@ -20,6 +20,8 @@ use core::fmt; use core::str::FromStr; +use crate::base::iana::macros::FromStrError; +use crate::base::iana::macros::IanaEnum; //------------ Rcode --------------------------------------------------------- @@ -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,25 +903,8 @@ impl From for TsigRcode { } } -// int_enum_fromstr_mnemonic!(TsigRcode, u16, "unknown TSIG error"); -// int_enum_display_mnemonic!(TsigRcode, u16, "unknown TSIG error"); -// int_enum_zonefile_fmt_with_decimal!(TsigRcode); -scan_impl!(TsigRcode); - int_enum_zonefile_fmt_with_decimal!(TsigRcode); -// Display -int_enum_impl_display_mnemonics_fallback_integer!(TsigRcode); - -// FromStrError -instantiate_fromstrerror_with_error_description!("unknown tsig rcode"); - -// serde::Serialize / serde::Deserialize -int_enum_impl_serde_to_and_from_mnemonic!(TsigRcode, u16); - -// core::str::FromStr / from_bytes() -int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!(TsigRcode); - //============ Error Types =================================================== /// An integer couldn’t be converted into an rcode. 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 63d6a81bf..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,22 +124,4 @@ int_enum! { (PRIVATEOID => 254, "PRIVATEOID") } -scan_impl!(SecurityAlgorithm); - int_enum_zonefile_fmt_decimal!(SecurityAlgorithm, "algorithm"); - -// Display -int_enum_impl_display_mnemonics_with_integer_fallback_integer!( - SecurityAlgorithm -); - -// FromStrError -instantiate_fromstrerror_with_error_description!( - "unknown security algorithm" -); - -// serde::Serialize / serde::Deserialize -int_enum_impl_serde_to_and_from_integer!(SecurityAlgorithm, u8); - -// core::str::FromStr / from_bytes() -int_enum_impl_fromstr_frombytes_from_mnemonics_or_integer!(SecurityAlgorithm); diff --git a/src/base/iana/sshfp.rs b/src/base/iana/sshfp.rs index fd5abfa40..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,13 +46,11 @@ int_enum! { } -int_enum_fromstr_decimal!(SshfpType, u8); -int_enum_display_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`]. @@ -56,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] /// @@ -88,6 +99,4 @@ int_enum! { (ED448 => 6, "Ed448") } -int_enum_fromstr_decimal!(SshfpAlgorithm, u8); -int_enum_display_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 ca20d50ee..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,8 +40,6 @@ int_enum! { (PRIVCERT => 255, "PrivCert") } -int_enum_fromstr_decimal!(TlsaCertificateUsage, u8); -int_enum_display_decimal!(TlsaCertificateUsage, u8); int_enum_zonefile_fmt_decimal!( TlsaCertificateUsage, "certificate usage type" @@ -41,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 @@ -54,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") @@ -65,13 +76,11 @@ int_enum! { (PRIVSEL => 255, "PrivSel") } -int_enum_fromstr_decimal!(TlsaSelector, u8); -int_enum_display_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. @@ -83,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") @@ -97,6 +111,4 @@ int_enum! { (PRIVMATCH => 255, "PrivMatch") } -int_enum_fromstr_decimal!(TlsaMatchingType, u8); -int_enum_display_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 693e92c18..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,18 +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_fromstr_decimal!(ZonemdScheme, u8); -int_enum_display_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 @@ -39,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") @@ -47,6 +58,4 @@ int_enum! { (SHA512 => 2, "SHA512") } -int_enum_fromstr_decimal!(ZonemdAlgorithm, u8); -int_enum_display_decimal!(ZonemdAlgorithm, u8); int_enum_zonefile_fmt_decimal!(ZonemdAlgorithm, "hash algorithm"); From 0a40dd7be7d9eda826b7145a9a68db98067d656d Mon Sep 17 00:00:00 2001 From: withjannisNLnetLabs Date: Tue, 26 May 2026 23:39:14 +0200 Subject: [PATCH 13/13] Fixed linting errors and cfg attr --- src/base/iana/macros.rs | 27 ++++++++++++++++++++------- src/base/iana/mod.rs | 2 +- src/base/iana/nsec3.rs | 1 - src/base/iana/rcode.rs | 4 ++-- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/base/iana/macros.rs b/src/base/iana/macros.rs index 2ad8380ff..556e30d4e 100644 --- a/src/base/iana/macros.rs +++ b/src/base/iana/macros.rs @@ -594,12 +594,23 @@ 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 @@ -642,11 +653,7 @@ pub trait IanaEnum<'de>: Sized { 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().to_string() - ), + None => format!("{}{}", Self::get_prefix(), self.get_integer()), } } fn display_mnemonic_with_integer(&self) -> String { @@ -702,6 +709,7 @@ pub trait IanaEnum<'de>: Sized { } //--- serde::Serialize + #[cfg(feature = "serde")] fn serialize_to_integer( &self, serializer: S, @@ -709,12 +717,13 @@ pub trait IanaEnum<'de>: Sized { 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) + return self.get_integer().serialize(serializer); } match self.get_mnemonic_str() { @@ -723,12 +732,13 @@ pub trait IanaEnum<'de>: Sized { } } + #[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) + return self.get_integer().serialize(serializer); } match self.get_mnemonic_str() { @@ -739,18 +749,21 @@ pub trait IanaEnum<'de>: Sized { } //--- 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>, >( diff --git a/src/base/iana/mod.rs b/src/base/iana/mod.rs index 8686b88be..23d030ba7 100644 --- a/src/base/iana/mod.rs +++ b/src/base/iana/mod.rs @@ -47,6 +47,7 @@ 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,7 +58,6 @@ pub use self::sshfp::{SshfpAlgorithm, SshfpType}; pub use self::svcb::SvcParamKey; pub use self::tlsa::{TlsaCertificateUsage, TlsaMatchingType, TlsaSelector}; pub use self::zonemd::{ZonemdAlgorithm, ZonemdScheme}; -pub use self::macros::{IanaEnum}; #[macro_use] mod macros; diff --git a/src/base/iana/nsec3.rs b/src/base/iana/nsec3.rs index f077f7b42..1abae3e19 100644 --- a/src/base/iana/nsec3.rs +++ b/src/base/iana/nsec3.rs @@ -29,4 +29,3 @@ iana_enum! { } int_enum_zonefile_fmt_decimal!(Nsec3HashAlgorithm, "hash algorithm"); - diff --git a/src/base/iana/rcode.rs b/src/base/iana/rcode.rs index 7ad6a6a59..4cedeb200 100644 --- a/src/base/iana/rcode.rs +++ b/src/base/iana/rcode.rs @@ -18,10 +18,10 @@ // Note: Rcode and OptRcode don’t use the macros since they don’t use all the // bits of the wrapped integer. -use core::fmt; -use core::str::FromStr; use crate::base::iana::macros::FromStrError; use crate::base::iana::macros::IanaEnum; +use core::fmt; +use core::str::FromStr; //------------ Rcode ---------------------------------------------------------