diff --git a/src/new/base/charstr.rs b/src/new/base/charstr.rs index f7be9d404..468650cec 100644 --- a/src/new/base/charstr.rs +++ b/src/new/base/charstr.rs @@ -167,6 +167,13 @@ impl Clone for alloc::boxed::Box { //--- Equality +/// [Section 2.3.3 of RFC1035] (Character Case) states: +/// +/// > For all parts of the DNS that are part of the official protocol, all +/// > comparisons between character strings (e.g., labels, domain names, etc.) +/// > are done in a case-insensitive manner. +/// +/// [Section 2.3.3 of RFC1035]: https://datatracker.ietf.org/doc/html/rfc1035#section-2.3.3 impl PartialEq for CharStr { fn eq(&self, other: &Self) -> bool { self.octets.eq_ignore_ascii_case(&other.octets) diff --git a/src/new/base/question.rs b/src/new/base/question.rs index 99f8d7c40..b7ff907c0 100644 --- a/src/new/base/question.rs +++ b/src/new/base/question.rs @@ -128,6 +128,11 @@ where //----------- QType ---------------------------------------------------------- /// The type of a question. +/// +/// IANA maintains [the registry][iana-qtype] of assignments for Question +/// Types. +/// +/// [iana-qtype]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4 #[derive( Copy, Clone, @@ -150,150 +155,170 @@ pub struct QType { pub code: U16, } -//--- Associated Constants - impl QType { /// Create a new [`QType`]. - const fn new(value: u16) -> Self { + pub const fn new(value: u16) -> Self { Self { code: U16::new(value), } } +} - /// The type of queries for [`A`](crate::new::rdata::A) records. - pub const A: Self = Self::new(1); +//--- Associated Constants - /// The type of queries for [`Ns`](crate::new::rdata::Ns) records. - pub const NS: Self = Self::new(2); +known_values_define! ( + QType::(pub TYPES, pub MNEMONICS) = [ + /// The type of queries for [`A`](crate::new::rdata::A) records. + "A" as A = Self::new(1), - /// The type of queries for [`CName`](crate::new::rdata::CName) records. - pub const CNAME: Self = Self::new(5); + /// The type of queries for [`Ns`](crate::new::rdata::Ns) records. + "NS" as NS = Self::new(2), - /// The type of queries for [`Soa`](crate::new::rdata::Soa) records. - pub const SOA: Self = Self::new(6); + /// The type of queries for [`CName`](crate::new::rdata::CName) records. + "CNAME" as CNAME = Self::new(5), - /// The type of queries for [`Ptr`](crate::new::rdata::Ptr) records. - pub const PTR: Self = Self::new(12); + /// The type of queries for [`Soa`](crate::new::rdata::Soa) records. + "SOA" as SOA = Self::new(6), - /// The type of queries for [`HInfo`](crate::new::rdata::HInfo) records. - pub const HINFO: Self = Self::new(13); + /// The type of queries for [`Ptr`](crate::new::rdata::Ptr) records. + "PTR" as PTR = Self::new(12), - /// The type of queries for [`Mx`](crate::new::rdata::Mx) records. - pub const MX: Self = Self::new(15); + /// The type of queries for [`HInfo`](crate::new::rdata::HInfo) records. + "HINFO" as HINFO = Self::new(13), - /// The type of queries for [`Txt`](crate::new::rdata::Txt) records. - pub const TXT: Self = Self::new(16); + /// The type of queries for [`Mx`](crate::new::rdata::Mx) records. + "MX" as MX = Self::new(15), - /// The type of queries for [`Rp`](crate::new::rdata::Rp) records. - pub const RP: Self = Self::new(17); + /// The type of queries for [`Txt`](crate::new::rdata::Txt) records. + "TXT" as TXT = Self::new(16), - /// The type of queries for [`Aaaa`](crate::new::rdata::Aaaa) records. - pub const AAAA: Self = Self::new(28); + /// The type of queries for [`Rp`](crate::new::rdata::Rp) records. + "RP" as RP = Self::new(17), - /// The type of queries for [`Srv`](crate::new::rdata::Srv) records. - pub const SRV: Self = Self::new(33); + /// The type of queries for [`Aaaa`](crate::new::rdata::Aaaa) records. + "AAAA" as AAAA = Self::new(28), - /// The type of queries for [`DName`](crate::new::rdata::DName) records. - pub const DNAME: Self = Self::new(39); + /// The type of queries for [`Srv`](crate::new::rdata::Srv) records. + "SRV" as SRV = Self::new(33), - /// The type of queries for [`Opt`](crate::new::rdata::Opt) records. - pub const OPT: Self = Self::new(41); + /// The type of queries for [`DName`](crate::new::rdata::DName) records. + "DNAME" as DNAME = Self::new(39), - /// The type of queries for [`Ds`](crate::new::rdata::Ds) records. - pub const DS: Self = Self::new(43); + /// The type of queries for [`Opt`](crate::new::rdata::Opt) records. + "OPT" as OPT = Self::new(41), - /// The type of queries for [`Rrsig`](crate::new::rdata::Rrsig) records. - pub const RRSIG: Self = Self::new(46); + /// The type of queries for [`Ds`](crate::new::rdata::Ds) records. + "DS" as DS = Self::new(43), - /// The type of queries for [`Nsec`](crate::new::rdata::Nsec) records. - pub const NSEC: Self = Self::new(47); + /// The type of queries for [`Rrsig`](crate::new::rdata::Rrsig) records. + "RRSIG" as RRSIG = Self::new(46), - /// The type of queries for [`DNSKey`](crate::new::rdata::DNSKey) records. - pub const DNSKEY: Self = Self::new(48); + /// The type of queries for [`Nsec`](crate::new::rdata::Nsec) records. + "NSEC" as NSEC = Self::new(47), - /// The type of queries for [`Nsec3`](crate::new::rdata::Nsec3) records. - pub const NSEC3: Self = Self::new(50); + /// The type of queries for [`DNSKey`](crate::new::rdata::DNSKey) records. + "DNSKEY" as DNSKEY = Self::new(48), - /// The type of queries for [`Nsec3Param`](crate::new::rdata::Nsec3Param) records. - pub const NSEC3PARAM: Self = Self::new(51); + /// The type of queries for [`Nsec3`](crate::new::rdata::Nsec3) records. + "NSEC3" as NSEC3 = Self::new(50), - /// The type of querios for `Cds` records. - pub const CDS: Self = Self::new(59); + /// The type of queries for [`Nsec3Param`](crate::new::rdata::Nsec3Param) records. + "NSEC3PARAM" as NSEC3PARAM = Self::new(51), - /// The type of querios for `CDNSKey` records. - pub const CDNSKEY: Self = Self::new(60); + /// The type of queries for `Cds` records. + "CDS" as CDS = Self::new(59), - /// The type of querios for [`ZoneMD`](crate::new::rdata::ZoneMD) records. - pub const ZONEMD: Self = Self::new(63); + /// The type of queries for `CDNSKey` records. + "CDNSKEY" as CDNSKEY = Self::new(60), - /// The type of querios for `TSig` records. - pub const TSIG: Self = Self::new(250); + /// The type of queries for [`ZoneMD`](crate::new::rdata::ZoneMD) records. + "ZONEMD" as ZONEMD = Self::new(63), - /// The type of requests for incremental zone transfers (IXFRs). - pub const IXFR: Self = Self::new(251); + /// The type of queries for `TSig` records. + "TSIG" as TSIG = Self::new(250), - /// The type of requests for authoritative zone transfers (AXFRs). - pub const AXFR: Self = Self::new(252); + //----- QType specific - /// The type of queries for all available records. - pub const ANY: Self = Self::new(255); -} + /// The type of requests for incremental zone transfers (IXFRs). + "IXFR" as IXFR = Self::new(251), -//--- Conversion to and from 'u16' + /// The type of requests for authoritative zone transfers (AXFRs). + "AXFR" as AXFR = Self::new(252), -impl From for QType { - fn from(value: u16) -> Self { - Self { - code: U16::new(value), - } - } -} + /// The type of queries for all available records. + "ANY" as ANY = Self::new(255), + ]; -impl From for u16 { - fn from(value: QType) -> Self { - value.code.get() - } -} +); + +//--- Conversion to and from 'u16' + +known_values_from_and_to_primitive!(QType, u16); //--- Formatting +/// Format a [`QType`] for debugging. +/// +/// The output displays the mnemonic, if known, and the code associated to the +/// [`QType`]. +/// +/// ``` +/// # use domain::new::base::QType; +/// // Known Question Type. +/// assert_eq!( +/// "QType::A(1)", +/// format!("{:?}", QType::A) +/// ); +/// // Unknown Question Type. +/// assert_eq!( +/// "QType(42)", +/// format!("{:?}", QType::from(42)) +/// ); +/// ``` impl fmt::Debug for QType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - Self::A => "QType::A", - Self::NS => "QType::NS", - Self::CNAME => "QType::CNAME", - Self::SOA => "QType::SOA", - Self::PTR => "QType::PTR", - Self::HINFO => "QType::HINFO", - Self::MX => "QType::MX", - Self::TXT => "QType::TXT", - Self::RP => "QType::RP", - Self::AAAA => "QType::AAAA", - Self::SRV => "QType::SRV", - Self::DNAME => "QType::DNAME", - Self::OPT => "QType::OPT", - Self::DS => "QType::DS", - Self::RRSIG => "QType::RRSIG", - Self::NSEC => "QType::NSEC", - Self::DNSKEY => "QType::DNSKEY", - Self::NSEC3 => "QType::NSEC3", - Self::NSEC3PARAM => "QType::NSEC3PARAM", - Self::CDS => "QType::CDS", - Self::CDNSKEY => "QType::CDNSKEY", - Self::ZONEMD => "QType::ZONEMD", - Self::TSIG => "QType::TSIG", - Self::IXFR => "QType::IXFR", - Self::AXFR => "QType::AXFR", - Self::ANY => "QType::ANY", - _ => return write!(f, "QType({})", self.code), - }) + match self.get_mnemonic() { + Some(m) => write!(f, "QType::{}({})", m, self.code), + None => write!(f, "QType({})", self.code), + } + } +} + +/// Format a [`QType`] in a human-readable way. +/// +/// Return the mnemonic of [`QType`]. If [`QType`] is unknown, then the +/// returned string contains the type in the unknown format as defined in +/// [Section 5 of RFC3597]. +/// +/// The mnemonics are consolidated by [IANA]. +/// +/// ``` +/// # use domain::new::base::QType; +/// // Known Question Type with mnemonic. +/// assert_eq!("A", format!("{}", QType::A)); +/// // Unknown Question Type. +/// assert_eq!("TYPE265", format!("{}", QType::from(265))); +/// ``` +/// +/// [Section 5 of RFC3597]: https://datatracker.ietf.org/doc/html/rfc3597#section-5 +/// [IANA]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4 +impl fmt::Display for QType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.get_mnemonic() { + Some(m) => write!(f, "{}", m), + None => write!(f, "TYPE{}", self.code), + } } } //----------- QClass --------------------------------------------------------- /// The class of a question. +/// +/// IANA maintains [the registry][iana-qclass] of assignments for Question +/// Classes. +/// +/// [iana-qclass]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2 #[derive( Copy, Clone, @@ -316,32 +341,83 @@ pub struct QClass { pub code: U16, } -//--- Associated Constants - impl QClass { /// Create a new [`QClass`]. - const fn new(value: u16) -> Self { + pub const fn new(value: u16) -> Self { Self { code: U16::new(value), } } +} - /// The type of queries in the Internet class. - pub const IN: Self = Self::new(1); +//--- Associated Constants - /// The type of queries in the CHAOS class. - pub const CH: Self = Self::new(3); -} +known_values_define! ( + QClass::(pub CLASSES, pub MNEMONICS) = [ + /// The Internet class. + "IN" as IN = Self::new(1), + /// The CHAOS class. + "CH" as CH = Self::new(3), + ]; +); + +//--- Conversion to and from 'u16' + +known_values_from_and_to_primitive!(QClass, u16); //--- Formatting +/// Format a [`QClass`] for debugging. +/// +/// The output displays the mnemonic, if known, and the code associated to the +/// [`QClass`]. +/// +/// ``` +/// # use domain::new::base::QClass; +/// // Known Question Class. +/// assert_eq!( +/// "QClass::IN(1)", +/// format!("{:?}", QClass::IN) +/// ); +/// // Unknown Question Class. +/// assert_eq!( +/// "QClass(42)", +/// format!("{:?}", QClass::from(42)) +/// ); +/// ``` impl fmt::Debug for QClass { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - Self::IN => "QClass::IN", - Self::CH => "QClass::CH", - _ => return write!(f, "QClass({})", self.code), - }) + match self.get_mnemonic() { + Some(m) => write!(f, "QClass::{}({})", m, self.code), + None => write!(f, "QClass({})", self.code), + } + } +} + +/// Format a [`QClass`] in a human-readable way. +/// +/// Return the mnemonic of [`QClass`]. If [`QClass`] is unknown, then the +/// returned string contains the class in the unknown format as defined in +/// [Section 5 of RFC3597]. +/// +/// The mnemonics are consolidated by [IANA]. +/// +/// ``` +/// # use domain::new::base::QClass; +/// // Known Question Class with mnemonic. +/// assert_eq!("IN", format!("{}", QClass::IN)); +/// // Unknown Question Class. +/// assert_eq!("CLASS42", format!("{}", QClass::from(42))); +/// ``` +/// +/// [Section 5 of RFC3597]: https://datatracker.ietf.org/doc/html/rfc3597#section-5 +/// [IANA]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2 +impl fmt::Display for QClass { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.get_mnemonic() { + Some(m) => write!(f, "{}", m), + None => write!(f, "CLASS{}", self.code), + } } } @@ -375,4 +451,38 @@ mod test { ); assert_eq!(buffer, &bytes[..9]); } + + #[test] + fn test_qclass_from() { + let qclass: QClass = 1.into(); + assert_eq!(qclass, QClass::IN); + + let number: u16 = qclass.into(); + assert_eq!(number, 1); + } + + #[test] + fn test_qtype_from() { + let qtype: QType = 6.into(); + assert_eq!(qtype, QType::SOA); + + let number: u16 = qtype.into(); + assert_eq!(number, 6); + } + + #[test] + fn test_qtype_from_mnemonic() { + assert_eq!(QType::from_mnemonic("A").unwrap(), QType::A); + assert_eq!(QType::from_mnemonic("MX").unwrap(), QType::MX); + // Make sure from_mnemonic does NOT parse unknown format. + assert!(QType::from_mnemonic("TYPE10").is_none()); + } + + #[test] + fn test_qclass_from_mnemonic() { + assert_eq!(QClass::from_mnemonic("IN").unwrap(), QClass::IN); + assert_eq!(QClass::from_mnemonic("CH").unwrap(), QClass::CH); + // Make sure from_mnemonic does NOT parse unknown format. + assert!(QClass::from_mnemonic("CLASS10").is_none()); + } } diff --git a/src/new/base/record.rs b/src/new/base/record.rs index a3a8f1f13..cacfef058 100644 --- a/src/new/base/record.rs +++ b/src/new/base/record.rs @@ -255,6 +255,10 @@ where //----------- RType ---------------------------------------------------------- /// The type of a record. +/// +/// IANA maintains [the registry][iana-rtype] of assignments for Record Types. +/// +/// [iana-rtype]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4 #[derive( Copy, Clone, @@ -277,85 +281,91 @@ pub struct RType { pub code: U16, } -//--- Associated Constants - impl RType { /// Create a new [`RType`]. - const fn new(value: u16) -> Self { + pub const fn new(value: u16) -> Self { Self { code: U16::new(value), } } +} + +//--- Associated Constants - /// The type of an [`A`](crate::new::rdata::A) record. - pub const A: Self = Self::new(1); +// [`RType`] implementation using macro. See macro for implementation details. +known_values_define! ( + RType::(pub TYPES, pub MNEMONICS) = [ + /// The type of an [`A`](crate::new::rdata::A) record. + "A" as A = Self::new(1), - /// The type of an [`Ns`](crate::new::rdata::Ns) record. - pub const NS: Self = Self::new(2); + /// The type of an [`Ns`](crate::new::rdata::Ns) record. + "NS" as NS = Self::new(2), - /// The type of a [`CName`](crate::new::rdata::CName) record. - pub const CNAME: Self = Self::new(5); + /// The type of a [`CName`](crate::new::rdata::CName) record. + "CNAME" as CNAME = Self::new(5), - /// The type of an [`Soa`](crate::new::rdata::Soa) record. - pub const SOA: Self = Self::new(6); + /// The type of an [`Soa`](crate::new::rdata::Soa) record. + "SOA" as SOA = Self::new(6), - /// The type of a [`Ptr`](crate::new::rdata::Ptr) record. - pub const PTR: Self = Self::new(12); + /// The type of a [`Ptr`](crate::new::rdata::Ptr) record. + "PTR" as PTR = Self::new(12), - /// The type of a [`HInfo`](crate::new::rdata::HInfo) record. - pub const HINFO: Self = Self::new(13); + /// The type of a [`HInfo`](crate::new::rdata::HInfo) record. + "HINFO" as HINFO = Self::new(13), - /// The type of a [`Mx`](crate::new::rdata::Mx) record. - pub const MX: Self = Self::new(15); + /// The type of a [`Mx`](crate::new::rdata::Mx) record. + "MX" as MX = Self::new(15), - /// The type of a [`Txt`](crate::new::rdata::Txt) record. - pub const TXT: Self = Self::new(16); + /// The type of a [`Txt`](crate::new::rdata::Txt) record. + "TXT" as TXT = Self::new(16), - /// The type of an [`Rp`](crate::new::rdata::Rp) record. - pub const RP: Self = Self::new(17); + /// The type of an [`Rp`](crate::new::rdata::Rp) record. + "RP" as RP = Self::new(17), - /// The type of an [`Aaaa`](crate::new::rdata::Aaaa) record. - pub const AAAA: Self = Self::new(28); + /// The type of an [`Aaaa`](crate::new::rdata::Aaaa) record. + "AAAA" as AAAA = Self::new(28), - /// The type of an [`Srv`](crate::new::rdata::Srv) record. - pub const SRV: Self = Self::new(33); + /// The type of an [`Srv`](crate::new::rdata::Srv) record. + "SRV" as SRV = Self::new(33), - /// The type of a [`DName`](crate::new::rdata::DName) record. - pub const DNAME: Self = Self::new(39); + /// The type of a [`DName`](crate::new::rdata::DName) record. + "DNAME" as DNAME = Self::new(39), - /// The type of an [`Opt`](crate::new::rdata::Opt) record. - pub const OPT: Self = Self::new(41); + /// The type of an [`Opt`](crate::new::rdata::Opt) record. + "OPT" as OPT = Self::new(41), - /// The type of a [`Ds`](crate::new::rdata::Ds) record. - pub const DS: Self = Self::new(43); + /// The type of a [`Ds`](crate::new::rdata::Ds) record. + "DS" as DS = Self::new(43), - /// The type of an [`Rrsig`](crate::new::rdata::Rrsig) record. - pub const RRSIG: Self = Self::new(46); + /// The type of an [`Rrsig`](crate::new::rdata::Rrsig) record. + "RRSIG" as RRSIG = Self::new(46), - /// The type of an [`Nsec`](crate::new::rdata::Nsec) record. - pub const NSEC: Self = Self::new(47); + /// The type of an [`Nsec`](crate::new::rdata::Nsec) record. + "NSEC" as NSEC = Self::new(47), - /// The type of a [`DNSKey`](crate::new::rdata::DNSKey) record. - pub const DNSKEY: Self = Self::new(48); + /// The type of a [`DNSKey`](crate::new::rdata::DNSKey) record. + "DNSKEY" as DNSKEY = Self::new(48), - /// The type of an [`Nsec3`](crate::new::rdata::Nsec3) record. - pub const NSEC3: Self = Self::new(50); + /// The type of an [`Nsec3`](crate::new::rdata::Nsec3) record. + "NSEC3" as NSEC3 = Self::new(50), - /// The type of an [`Nsec3Param`](crate::new::rdata::Nsec3Param) record. - pub const NSEC3PARAM: Self = Self::new(51); + /// The type of an [`Nsec3Param`](crate::new::rdata::Nsec3Param) record. + "NSEC3PARAM" as NSEC3PARAM = Self::new(51), - /// The type of a `Cds` record. - pub const CDS: Self = Self::new(59); + /// The type of a `Cds` record. + "CDS" as CDS = Self::new(59), - /// The type of a `CDNSKey` record. - pub const CDNSKEY: Self = Self::new(60); + /// The type of a `CDNSKey` record. + "CDNSKEY" as CDNSKEY = Self::new(60), - /// The type of a [`ZoneMD`](crate::new::rdata::ZoneMD) record. - pub const ZONEMD: Self = Self::new(63); + /// The type of a [`ZoneMD`](crate::new::rdata::ZoneMD) record. + "ZONEMD" as ZONEMD = Self::new(63), - /// The type of a `TSig` record. - pub const TSIG: Self = Self::new(250); -} + /// The type of a `TSig` record. + "TSIG" as TSIG = Self::new(250), + ]; + +); //--- Interaction @@ -411,105 +421,72 @@ impl RType { //--- Conversion to and from 'u16' -impl From for RType { - fn from(value: u16) -> Self { - Self { - code: U16::new(value), - } - } -} - -impl From for u16 { - fn from(value: RType) -> Self { - value.code.get() - } -} +known_values_from_and_to_primitive!(RType, u16); //--- Formatting +/// Format a [`RType`] for debugging. +/// +/// The output displays the mnemonic, if known, and the code associated to the +/// [`RType`]. +/// +/// ``` +/// # use domain::new::base::RType; +/// // Known Record Type. +/// assert_eq!( +/// "RType::A(1)", +/// format!("{:?}", RType::A) +/// ); +/// // Unknown Record Type. +/// assert_eq!( +/// "RType(42)", +/// format!("{:?}", RType::from(42)) +/// ); +/// ``` impl fmt::Debug for RType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - Self::A => "RType::A", - Self::NS => "RType::NS", - Self::CNAME => "RType::CNAME", - Self::SOA => "RType::SOA", - Self::PTR => "RType::PTR", - Self::HINFO => "RType::HINFO", - Self::MX => "RType::MX", - Self::TXT => "RType::TXT", - Self::RP => "RType::RP", - Self::AAAA => "RType::AAAA", - Self::SRV => "RType::SRV", - Self::DNAME => "RType::DNAME", - Self::OPT => "RType::OPT", - Self::DS => "RType::DS", - Self::RRSIG => "RType::RRSIG", - Self::NSEC => "RType::NSEC", - Self::DNSKEY => "RType::DNSKEY", - Self::NSEC3 => "RType::NSEC3", - Self::NSEC3PARAM => "RType::NSEC3PARAM", - Self::CDS => "RType::CDS", - Self::CDNSKEY => "RType::CDNSKEY", - Self::ZONEMD => "RType::ZONEMD", - Self::TSIG => "RType::TSIG", - _ => return write!(f, "RType({})", self.code), - }) - } -} - -/// Format an [`RType`] in a human-readable way + match self.get_mnemonic() { + Some(m) => write!(f, "RType::{}({})", m, self.code), + None => write!(f, "RType({})", self.code), + } + } +} + +/// Format a [`RType`] in a human-readable way. /// /// Return the mnemonic of [`RType`]. If [`RType`] is unknown, then the /// returned string contains the type in the unknown format as defined in -/// Section 5 in [RFC3597]. +/// [Section 5 of RFC3597]. /// /// The mnemonics are consolidated by [IANA]. /// /// ``` /// # use domain::new::base::RType; -/// // Known RType with mnemonic +/// // Known Record Type with mnemonic. /// assert_eq!("A", format!("{}", RType::A)); -/// // Unknown RType +/// // Unknown Record Type. /// assert_eq!("TYPE265", format!("{}", RType::from(265))); /// ``` /// -/// [RFC3597]: https://datatracker.ietf.org/doc/html/rfc3597#section-5 -/// [IANA]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml +/// [Section 5 of RFC3597]: https://datatracker.ietf.org/doc/html/rfc3597#section-5 +/// [IANA]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4 impl fmt::Display for RType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - Self::A => "A", - Self::NS => "NS", - Self::CNAME => "CNAME", - Self::SOA => "SOA", - Self::PTR => "PTR", - Self::HINFO => "HINFO", - Self::MX => "MX", - Self::TXT => "TXT", - Self::RP => "RP", - Self::AAAA => "AAAA", - Self::SRV => "SRV", - Self::DNAME => "DNAME", - Self::OPT => "OPT", - Self::DS => "DS", - Self::RRSIG => "RRSIG", - Self::NSEC => "NSEC", - Self::DNSKEY => "DNSKEY", - Self::NSEC3 => "NSEC3", - Self::NSEC3PARAM => "NSEC3PARAM", - Self::CDS => "CDS", - Self::CDNSKEY => "CDNSKEY", - Self::ZONEMD => "ZONEMD", - Self::TSIG => "TSIG", - _ => return write!(f, "TYPE{}", self.code), - }) + match self.get_mnemonic() { + Some(m) => write!(f, "{}", m), + None => write!(f, "TYPE{}", self.code), + } } } //----------- RClass --------------------------------------------------------- /// The class of a record. +/// +/// IANA maintains [the registry][iana-rclass] of assignments for Record +/// Classes. +/// +/// [iana-rclass]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2 #[derive( Copy, Clone, @@ -536,72 +513,79 @@ pub struct RClass { impl RClass { /// Create a new [`RType`]. - const fn new(value: u16) -> Self { + pub const fn new(value: u16) -> Self { Self { code: U16::new(value), } } - - /// The Internet class. - pub const IN: Self = Self::new(1); - - /// The CHAOS class. - pub const CH: Self = Self::new(3); } -//--- Conversion to and from 'u16' +known_values_define! ( + RClass::(pub CLASSES, pub MNEMONICS) = [ + /// The Internet class. + "IN" as IN = Self::new(1), + /// The CHAOS class. + "CH" as CH = Self::new(3), + ]; +); -impl From for RClass { - fn from(value: u16) -> Self { - Self { - code: U16::new(value), - } - } -} +//--- Conversion to and from 'u16' -impl From for u16 { - fn from(value: RClass) -> Self { - value.code.get() - } -} +known_values_from_and_to_primitive!(RClass, u16); //--- Formatting +/// Format a [`RClass`] for debugging. +/// +/// The output displays the mnemonic, if known, and the code associated to the +/// [`RClass`]. +/// +/// ``` +/// # use domain::new::base::RClass; +/// // Known Record Class. +/// assert_eq!( +/// "RClass::IN(1)", +/// format!("{:?}", RClass::IN) +/// ); +/// // Unknown Record Class. +/// assert_eq!( +/// "RClass(42)", +/// format!("{:?}", RClass::from(42)) +/// ); +/// ``` impl fmt::Debug for RClass { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - Self::IN => "RClass::IN", - Self::CH => "RClass::CH", - _ => return write!(f, "RClass({})", self.code), - }) + match self.get_mnemonic() { + Some(m) => write!(f, "RClass::{}({})", m, self.code), + None => write!(f, "RClass({})", self.code), + } } } -/// Format an [`RClass`] in a human-readable way +/// Format a [`RClass`] in a human-readable way. /// /// Return the mnemonic of [`RClass`]. If [`RClass`] is unknown, then the /// returned string contains the class in the unknown format as defined in -/// Section 5 in [RFC3597]. +/// [Section 5 of RFC3597]. /// /// The mnemonics are consolidated by [IANA]. /// /// ``` /// # use domain::new::base::RClass; -/// // Known RClass with mnemonic +/// // Known Record Class with mnemonic. /// assert_eq!("IN", format!("{}", RClass::IN)); -/// // Unknown RClass +/// // Unknown Record Class. /// assert_eq!("CLASS42", format!("{}", RClass::from(42))); /// ``` /// -/// [RFC3597]: https://datatracker.ietf.org/doc/html/rfc3597#section-5 -/// [IANA]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml +/// [Section 5 of RFC3597]: https://datatracker.ietf.org/doc/html/rfc3597#section-5 +/// [IANA]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2 impl fmt::Display for RClass { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - Self::IN => "IN", - Self::CH => "CH", - _ => return write!(f, "CLASS{}", self.code), - }) + match self.get_mnemonic() { + Some(m) => write!(f, "{}", m), + None => write!(f, "CLASS{}", self.code), + } } } @@ -873,12 +857,10 @@ impl Record { #[cfg(test)] mod test { - #[cfg(feature = "alloc")] - use alloc::format; - use super::{RClass, RType, Record, TTL, UnparsedRecordData}; use crate::new::base::{ + QClass, QType, name::Name, wire::{AsBytes, BuildBytes, ParseBytes, SplitBytes}, }; @@ -923,19 +905,97 @@ mod test { assert_eq!(number, 6); } - #[cfg(feature = "alloc")] #[test] - fn test_rclass_display() { - assert_eq!("IN", format!("{}", RClass::IN)); - assert_eq!("CH", format!("{}", RClass::CH)); - assert_eq!("CLASS42", format!("{}", RClass::from(42))); + fn test_rtype_from_mnemonic() { + assert_eq!(RType::from_mnemonic("A").unwrap(), RType::A); + assert_eq!(RType::from_mnemonic("MX").unwrap(), RType::MX); + // Make sure from_mnemonic does NOT parse unknown format. + assert!(RType::from_mnemonic("TYPE10").is_none()); } - #[cfg(feature = "alloc")] #[test] - fn test_rtype_display() { - assert_eq!("A", format!("{}", RType::A)); - assert_eq!("MX", format!("{}", RType::MX)); - assert_eq!("TYPE265", format!("{}", RType::from(265))); + fn test_rclass_from_mnemonic() { + assert_eq!(RClass::from_mnemonic("IN").unwrap(), RClass::IN); + assert_eq!(RClass::from_mnemonic("CH").unwrap(), RClass::CH); + // Make sure from_mnemonic does NOT parse unknown format. + assert!(RClass::from_mnemonic("CLASS10").is_none()); + } + + // Currently `RType` (Record Type) and `QType` (Question Type) are + // separate. Reason is that the overlap between the two is big but not + // complete. See IANA definition below. + // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4 + // + // To prevent drift of the implementation this test confirms the overlap + // but keeps track of exceptions where there is no overlap. + #[test] + fn test_rtype_qtype_sync() { + // Specify the special cases + const RTYPE_ONLY: &[RType] = &[]; + const QTYPE_ONLY: &[QType] = &[QType::IXFR, QType::AXFR, QType::ANY]; + + let mut rtype: Option<&str>; + let mut qtype: Option<&str>; + + // Iterate over the entire Type space + for i in 0..=u16::MAX { + rtype = RType::from(i).get_mnemonic(); + qtype = QType::from(i).get_mnemonic(); + match (rtype, qtype) { + // Both implement this type. This should be the most common + // case. Now make sure the resulting mnemonic is the same. + (Some(rtype), Some(qtype)) => { + assert_eq!(rtype, qtype); + } + // Both do not implement this Type. This is ok. + (None, None) => (), + + // RType does not implement this Type, but QType does. This is + // _only_ in specific cases ok. + (None, Some(qtype)) => { + // check if the QType is allowed. + assert!( + QTYPE_ONLY.contains(&i.into()), + "Failed for {qtype:?}" + ); + } + // RType does implement this Type, but QType does not. This is + // _only_ in specific cases ok. + (Some(rtype), None) => { + assert!( + RTYPE_ONLY.contains(&i.into()), + "Failed for {rtype:?}" + ); + } + } + } + } + + // The same (as for QType and RType, see above) applies for RClass and + // QClass. But currently there are no differences in the defined Classes + // therefore a quick comparision is enough. + #[test] + fn test_rclass_qclass_sync() { + let mut rclass: Option<&str>; + let mut qclass: Option<&str>; + for i in 0..=u16::MAX { + rclass = RClass::from(i).get_mnemonic(); + qclass = QClass::from(i).get_mnemonic(); + match (rclass, qclass) { + // Both implement this type. This should be the most common + // case. Now make sure the resulting mnemonic is the same. + (Some(rtype), Some(qtype)) => { + assert_eq!(rtype, qtype); + } + // Both do not implement this Type. This is ok. + (None, None) => (), + (None, Some(qclass)) => { + panic!("{qclass:?} not correctly implemented for both") + } + (Some(rclass), None) => { + panic!("{rclass:?} not correctly implemented for both") + } + } + } } } diff --git a/src/new/macros.rs b/src/new/macros.rs new file mode 100644 index 000000000..35cb43e50 --- /dev/null +++ b/src/new/macros.rs @@ -0,0 +1,136 @@ +//! Macro rules used in `domain::new`. + +/// Define known DNS constants with an associated name and value. +/// +/// The constants are defined on top of an existing struct. The macro defines +/// two lists `$values` & `$names`. The visibility of both lists can be +/// defined. These lists are used to implement two basic methods for the +/// struct automatically: +/// +/// - `get_mnemonic()` +/// - `from_mnemonic()` +/// +/// The visibility of the above functions is the same as the visibility of the +/// `$names` list. If the `$names` list doesn't contain mnemonics the +/// functions serve no purpose and should be hidden to avoid confusion. +macro_rules! known_values_define { + ( + $(#[$type_attr:meta])* + $type:ident:: ( + $values_vis:vis $values:ident , + $names_vis:vis $names:ident + ) = [ $( + $(#[$value_attr:meta])* + $value_name:literal as $value_constant:ident = $value:expr, + )* ]; + ) => { + $(#[$type_attr])* + /// This implementation block contains all constant values. + impl $type { + $( + $(#[$value_attr])* + pub const $value_constant: $type = $value; + )* + } + + // This block writes the list called $values. Containing all defined + // values. + $(#[$type_attr])* + impl $type { + /// The + #[doc = concat!("[`", stringify!($type), "::", stringify!($values), "`]")] + /// list contains all constant values associated to the type. + /// + /// *This list is autogenerated through macro invocation.* + $values_vis const $values: &'static [$type] = &[ + $($value,)* + ]; + + /// The + #[doc = concat!("[`", stringify!($type), "::", stringify!($names), "`]")] + /// list contains all names/mnemonics associated to the type. The + /// position of any name in this list correlates with the position + /// of the value in + #[doc = concat!("[`", stringify!($type), "::", stringify!($values), "`].")] + /// + /// *For constants that do not contain defined mnemonics this list + /// is hidden.* + /// + /// *This list is autogenerated through macro invocation.* + $names_vis const $names: &'static [&'static str] = &[ + $( + $value_name , + )* + ]; + + /// Mnemonic representation of `self` if it's defined. + /// + /// *For constants that don't have defined mnemonics this function + /// is hidden.* + /// + /// *This function is autogenerated through macro invocation.* + #[must_use] + $names_vis fn get_mnemonic(&self) -> Option<&'static str> { + if let Some(pos) = + Self::$values.iter().position(|t| t == self) + { + return Self::$names.get(pos).map(|s| *s); + }; + None + } + + /// Associated constant value of + #[doc = concat!("[`", stringify!($type), "`]")] + /// if `mnemonic` is recognised. + /// + /// *For constants that don't have defined mnemonics this function + /// is hidden.* + /// + /// *This function is autogenerated through macro invocation.* + #[must_use] + // For functions that do not have defined mnemonics this function + // is hidden and possibly unused. + #[allow(unused)] + $names_vis fn from_mnemonic(mnemonic: &str) -> Option { + if let Some(pos) = Self::$names + .iter() + .position(|s| mnemonic.eq_ignore_ascii_case(s)) + { + return Self::$values.get(pos).map(|s| *s); + }; + None + } + } + }; +} + +/// From implementations for DNS constants. +/// +/// This macro implements conversions from the primitive type into the wrapper +/// type and vice versa. +/// +/// The macro requires the struct to have a `code` field containing the +/// associated value with a From conversion into the primitive type, if +/// needed. And a `new()` method which takes the primitive type and converts +/// it into the struct. +/// +/// The following implementations result from this macro: +/// +/// - fn from(value: $value_type) -> $type +/// - fn from(value: $type) -> $value_type +macro_rules! known_values_from_and_to_primitive { + ($type:ident, $value_type:ident) => { + //--- Conversion to and from primitive + impl From<$value_type> for $type { + fn from(value: $value_type) -> Self { + Self::new(value) + } + } + + impl From<$type> for $value_type { + fn from(value: $type) -> Self { + value.code.into() + } + } + }; +} diff --git a/src/new/mod.rs b/src/new/mod.rs index c9390575e..384963fc9 100644 --- a/src/new/mod.rs +++ b/src/new/mod.rs @@ -61,6 +61,9 @@ //! can hold any known or unknown record data type. Together, these modules //! provide the basic essence of DNS that any operation can be built around. +#[macro_use] +mod macros; + pub mod base; pub mod edns; pub mod rdata; diff --git a/src/new/rdata/dnssec/mod.rs b/src/new/rdata/dnssec/mod.rs index 3d0b55542..61e4cf6e0 100644 --- a/src/new/rdata/dnssec/mod.rs +++ b/src/new/rdata/dnssec/mod.rs @@ -24,6 +24,11 @@ pub use ds::{DigestType, Ds}; //----------- SecAlg --------------------------------------------------------- /// A cryptographic algorithm for DNS security. +/// +/// IANA maintains [the registry][iana-secalg] of assignments for Security +/// Algorithms. +/// +/// [iana-secalg]: https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml#dns-sec-alg-numbers-1 #[derive( Copy, Clone, @@ -46,24 +51,102 @@ pub struct SecAlg { pub code: u8, } +impl SecAlg { + /// Create a new [`SecAlg`]. + pub const fn new(value: u8) -> Self { + Self { code: value } + } +} + //--- Associated Constants -impl SecAlg { - /// The DSA/SHA-1 algorithm. - pub const DSA_SHA1: Self = Self { code: 3 }; +known_values_define! ( + SecAlg::(pub ALGS, pub MNEMONICS) = [ + /// The DSA/SHA-1 algorithm. + "DSA" as DSA_SHA1 = Self { code: 3 }, - /// The RSA/SHA-1 algorithm. - pub const RSA_SHA1: Self = Self { code: 5 }; -} + /// The RSA/SHA-1 algorithm. + "RSASHA1" as RSA_SHA1 = Self { code: 5 }, + ]; +); + +//--- Conversion to and from 'u8' + +known_values_from_and_to_primitive!(SecAlg, u8); //--- Formatting +/// Format a [`SecAlg`] for debugging. +/// +/// The output displays the mnemonic, if known, and the code associated to the +/// [`SecAlg`]. +/// +/// ``` +/// # use domain::new::rdata::SecAlg; +/// // Known Security Algorithm. +/// assert_eq!("SecAlg::DSA(3)", format!("{:?}", SecAlg::DSA_SHA1)); +/// // Unknown Security Algorithm. +/// assert_eq!("SecAlg(42)", format!("{:?}", SecAlg::from(42))); +/// ``` impl fmt::Debug for SecAlg { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - Self::DSA_SHA1 => "SecAlg::DSA_SHA1", - Self::RSA_SHA1 => "SecAlg::RSA_SHA1", - _ => return write!(f, "SecAlg({})", self.code), - }) + match self.get_mnemonic() { + Some(m) => write!(f, "SecAlg::{}({})", m, self.code), + None => write!(f, "SecAlg({})", self.code), + } + } +} + +/// Format a [`SecAlg`] in a human-readable way. +/// +/// Return the mnemonic of [`SecAlg`]. If [`SecAlg`] is unknown, then the +/// returned string contains the number of the Algorithm. +/// +/// [Section 2.2 of RFC4034] states: +/// +/// > The Algorithm field MUST be represented either as an unsigned decimal +/// > integer or as an algorithm mnemonic as specified in Appendix A.1. +/// +/// The algorithms are consolidated by [IANA]. +/// +/// ``` +/// # use domain::new::rdata::SecAlg; +/// // Known Security Algorithm with mnemonic. +/// assert_eq!("DSA", format!("{}", SecAlg::DSA_SHA1)); +/// // Unknown Security Algorithm. +/// assert_eq!("42", format!("{}", SecAlg::from(42))); +/// ``` +/// +/// [Section 2.2 of RFC4034]: https://datatracker.ietf.org/doc/html/rfc4034#section-2.2 +/// [IANA]: https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml#dns-sec-alg-numbers-1 +impl fmt::Display for SecAlg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.get_mnemonic() { + Some(m) => write!(f, "{}", m), + None => write!(f, "{}", self.code), + } + } +} + +#[cfg(test)] +mod test { + use crate::new::rdata::SecAlg; + + #[test] + fn test_secalg_from() { + let secalg: SecAlg = 3.into(); + assert_eq!(secalg, SecAlg::DSA_SHA1); + + let number: u8 = secalg.into(); + assert_eq!(number, 3); + } + + #[test] + fn test_secalg_from_mnemonic() { + assert_eq!(SecAlg::from_mnemonic("DSA").unwrap(), SecAlg::DSA_SHA1); + assert_eq!( + SecAlg::from_mnemonic("RSASHA1").unwrap(), + SecAlg::RSA_SHA1 + ); } } diff --git a/src/new/rdata/dnssec/nsec3.rs b/src/new/rdata/dnssec/nsec3.rs index 4443fb1f7..6c98360d9 100644 --- a/src/new/rdata/dnssec/nsec3.rs +++ b/src/new/rdata/dnssec/nsec3.rs @@ -183,6 +183,11 @@ impl Hash for Nsec3Param { //----------- Nsec3HashAlgorithm --------------------------------------------- /// The hash algorithm used with [`Nsec3`] records. +/// +/// IANA maintains [the registry][iana-nsec3hashalgorithm] of assignments for +/// NSEC3 Hash Algorithms. +/// +/// [iana-nsec3hashalgorithm]: https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml#dnssec-nsec3-parameters-3 #[derive( Copy, Clone, @@ -205,21 +210,81 @@ pub struct Nsec3HashAlgorithm { pub code: u8, } -//--- Associated Constants - impl Nsec3HashAlgorithm { - /// The SHA-1 algorithm. - pub const SHA1: Self = Self { code: 1 }; + /// Create a new [`Nsec3HashAlgorithm`]. + pub const fn new(value: u8) -> Self { + Self { code: value } + } } +//--- Associated Constants + +// The Nsec3 Hash Algorithms have no official mnemonics. Therefore the list of +// names is NOT called "MNEMONICS" and it is NOT public. +known_values_define! ( + Nsec3HashAlgorithm::(pub ALGS, NAMES) = [ + /// The SHA-1 algorithm. + "SHA1" as SHA1 = Self { code: 1 }, + ]; +); + +//--- Conversion to and from 'u8' + +known_values_from_and_to_primitive!(Nsec3HashAlgorithm, u8); + //--- Formatting +/// Format a [`Nsec3HashAlgorithm`] for debugging. +/// +/// The output displays the mnemonic, if known, and the code associated to the +/// [`Nsec3HashAlgorithm`]. +/// +/// ``` +/// # use domain::new::rdata::Nsec3HashAlgorithm; +/// // Known Nsec3 Hash Algorithm. +/// assert_eq!( +/// "Nsec3HashAlgorithm::SHA1(1)", +/// format!("{:?}", Nsec3HashAlgorithm::SHA1) +/// ); +/// // Unknown Nsec3 Hash Algorithm. +/// assert_eq!( +/// "Nsec3HashAlgorithm(42)", +/// format!("{:?}", Nsec3HashAlgorithm::from(42)) +/// ); +/// ``` impl fmt::Debug for Nsec3HashAlgorithm { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - Self::SHA1 => "Nsec3HashAlgorithm::SHA1", - _ => return write!(f, "Nsec3HashAlgorithm({})", self.code), - }) + match self.get_mnemonic() { + Some(m) => write!(f, "Nsec3HashAlgorithm::{}({})", m, self.code), + None => write!(f, "Nsec3HashAlgorithm({})", self.code), + } + } +} + +/// Format a [`Nsec3HashAlgorithm`] in a human-readable way. +/// +/// [Section 4.3 of RFC5155] states: +/// +/// > The presentation format of the RDATA portion is as follows: +/// > o The Hash Algorithm field is represented as an unsigned decimal +/// > integer. The value has a maximum of 255. +/// > ... +/// +/// The algorithms are consolidated by [IANA]. +/// +/// ``` +/// # use domain::new::rdata::Nsec3HashAlgorithm; +/// // Known Nsec3 Hash Algorithm, but still represented as number. +/// assert_eq!("1", format!("{}", Nsec3HashAlgorithm::SHA1)); +/// // Unknown Nsec3 Hash Algorithm. +/// assert_eq!("42", format!("{}", Nsec3HashAlgorithm::from(42))); +/// ``` +/// +/// [Section 4.3 of RFC5155]: https://datatracker.ietf.org/doc/html/rfc5155#section-4.3 +/// [IANA]: https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml#dnssec-nsec3-parameters-3 +impl fmt::Display for Nsec3HashAlgorithm { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.code) } } @@ -352,3 +417,25 @@ impl Nsec3Param { &self.salt } } + +#[cfg(test)] +mod test { + use crate::new::rdata::Nsec3HashAlgorithm; + + #[test] + fn test_nsec3_hash_algorithm_from() { + let nsec3_hash_algorithm: Nsec3HashAlgorithm = 1.into(); + assert_eq!(nsec3_hash_algorithm, Nsec3HashAlgorithm::SHA1); + + let number: u8 = nsec3_hash_algorithm.into(); + assert_eq!(number, 1); + } + + #[test] + fn test_nsec3_hash_algorithm_from_mnemonic() { + assert_eq!( + Nsec3HashAlgorithm::from_mnemonic("SHA1").unwrap(), + Nsec3HashAlgorithm::SHA1 + ); + } +} diff --git a/src/new/rdata/zonemd.rs b/src/new/rdata/zonemd.rs index cf12f583e..3a2cd957a 100644 --- a/src/new/rdata/zonemd.rs +++ b/src/new/rdata/zonemd.rs @@ -332,32 +332,88 @@ pub struct ZoneMDScheme { pub code: u8, } -//--- Associated Constants - impl ZoneMDScheme { - /// The SIMPLE scheme. - /// - /// SIMPLE is, as expected, a simple scheme for computing the ZONEMD - /// digest. It is specified by [RFC 8976, section 3.3.1]. At present, - /// implementations are required to support it. - /// - /// [RFC 8976, section 3.3.1]: https://www.rfc-editor.org/rfc/rfc8976.html#section-3.3.1 - /// - /// SIMPLE includes glue records and occluded data in the zone, sorts it - /// in DNSSEC canonical order, and passes the entire zone (i.e. records - /// serialized in the DNSSEC canonical wire format, concatenated together) - /// into a single invocation of the hash function. - pub const SIMPLE: Self = Self { code: 1 }; + /// Create a new [`ZoneMDScheme`]. + pub const fn new(value: u8) -> Self { + Self { code: value } + } } +//--- Associated Constants + +// [`ZoneMDScheme`] implementation using macro. See macro for implementation +// details. +known_values_define! ( + ZoneMDScheme::(pub SCHEMES, pub MNEMONICS) = [ + /// The SIMPLE scheme. + /// + /// SIMPLE is, as expected, a simple scheme for computing the ZONEMD + /// digest. It is specified by [RFC 8976, section 3.3.1]. At present, + /// implementations are required to support it. + /// + /// [RFC 8976, section 3.3.1]: https://www.rfc-editor.org/rfc/rfc8976.html#section-3.3.1 + /// + /// SIMPLE includes glue records and occluded data in the zone, sorts + /// it in DNSSEC canonical order, and passes the entire zone (i.e. + /// records serialized in the DNSSEC canonical wire format, + /// concatenated together) into a single invocation of the hash + /// function. + "SIMPLE" as SIMPLE = Self { code: 1 }, + ]; +); + +//--- Conversion to and from 'u8' + +known_values_from_and_to_primitive!(ZoneMDScheme, u8); + //--- Formatting +/// Format a [`ZoneMDScheme`] for debugging. +/// +/// The output displays the mnemonic, if known, and the code associated to the +/// [`ZoneMDScheme`]. +/// +/// ``` +/// # use domain::new::rdata::ZoneMDScheme; +/// // Known ZoneMD Scheme. +/// assert_eq!( +/// "ZoneMDScheme::SIMPLE(1)", +/// format!("{:?}", ZoneMDScheme::SIMPLE) +/// ); +/// // Unknown ZoneMD Scheme. +/// assert_eq!("ZoneMDScheme(42)", format!("{:?}", ZoneMDScheme::from(42))); +/// ``` impl fmt::Debug for ZoneMDScheme { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - Self::SIMPLE => "ZoneMDScheme::SIMPLE", - _ => return write!(f, "ZoneMDScheme({})", self.code), - }) + match self.get_mnemonic() { + Some(m) => write!(f, "ZoneMDScheme::{}({})", m, self.code), + None => write!(f, "ZoneMDScheme({})", self.code), + } + } +} + +/// Format a [`ZoneMDScheme`] in a human-readable way. +/// +/// The [`ZoneMDScheme`] is always displayed as its defined number. [Section +/// 2.3 of RFC8976] defines the presentation format as follows: +/// +/// > The Scheme field is represented as an unsigned decimal integer. +/// +/// The schemes are consolidated by [IANA]. +/// +/// ``` +/// # use domain::new::rdata::ZoneMDScheme; +/// // Known ZoneMD Scheme, but still represented as number. +/// assert_eq!("1", format!("{}", ZoneMDScheme::SIMPLE)); +/// // Unknown ZoneMD Scheme. +/// assert_eq!("42", format!("{}", ZoneMDScheme::from(42))); +/// ``` +/// +/// [Section 2.3 of RFC8976]: https://datatracker.ietf.org/doc/html/rfc8976#name-zonemd-presentation-format +/// [IANA]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#zonemd-schemes +impl fmt::Display for ZoneMDScheme { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.code) } } @@ -396,30 +452,126 @@ pub struct ZoneMDHashAlg { pub code: u8, } +impl ZoneMDHashAlg { + /// Create a new [`ZoneMDHashAlg`]. + pub const fn new(value: u8) -> Self { + Self { code: value } + } +} + //--- Associated Constants -impl ZoneMDHashAlg { - /// The SHA384 algorithm. - /// - /// The resulting digest is 48 bytes in size, and must not be truncated. - /// At present, implementations are required to support it. - pub const SHA384: Self = Self { code: 1 }; +// [`ZoneMDHashAlg`] implementation using macro. See macro for implementation +// details. +known_values_define! ( + ZoneMDHashAlg::(pub ALGS, pub MNEMONICS) = [ + /// The SHA384 algorithm. + /// + /// The resulting digest is 48 bytes in size, and must not be truncated. + /// At present, implementations are required to support it. + "SHA384" as SHA384 = Self { code: 1 }, - /// The SHA512 algorithm. - /// - /// The resulting digest is 64 bytes in size, and must not be truncated. - /// At present, implementations are recommended to support it. - pub const SHA512: Self = Self { code: 2 }; -} + /// The SHA512 algorithm. + /// + /// The resulting digest is 64 bytes in size, and must not be truncated. + /// At present, implementations are recommended to support it. + "SHA512" as SHA512 = Self { code: 2 }, + ]; +); + +//--- Conversion to and from 'u8' + +known_values_from_and_to_primitive!(ZoneMDHashAlg, u8); //--- Formatting +/// Format a [`ZoneMDHashAlg`] for debugging. +/// +/// The output displays the mnemonic, if known, and the code associated to the +/// [`ZoneMDHashAlg`]. +/// +/// ``` +/// # use domain::new::rdata::ZoneMDHashAlg; +/// // Known ZoneMD Hash Algorithm. +/// assert_eq!( +/// "ZoneMDHashAlg::SHA384(1)", +/// format!("{:?}", ZoneMDHashAlg::SHA384) +/// ); +/// // Unknown ZoneMD Hash Algorithm. +/// assert_eq!( +/// "ZoneMDHashAlg(42)", +/// format!("{:?}", ZoneMDHashAlg::from(42)) +/// ); +/// ``` impl fmt::Debug for ZoneMDHashAlg { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - Self::SHA384 => "ZoneMDHashAlg::SHA384", - Self::SHA512 => "ZoneMDHashAlg::SHA512", - _ => return write!(f, "ZoneMDHashAlg({})", self.code), - }) + match self.get_mnemonic() { + Some(m) => write!(f, "ZoneMDHashAlg::{}({})", m, self.code), + None => write!(f, "ZoneMDHashAlg({})", self.code), + } + } +} + +/// Format a [`ZoneMDHashAlg`] in a human-readable way. +/// +/// The [`ZoneMDHashAlg`] is always displayed as its defined number. [Section +/// 2.3 of RFC8976] defines the presentation format as follows: +/// +/// > The Hash Algorithm field is represented as an unsigned decimal integer. +/// +/// The algorithms are consolidated by [IANA]. +/// +/// ``` +/// # use domain::new::rdata::ZoneMDHashAlg; +/// // Known ZoneMD Hash Algorithm, but still represented as number. +/// assert_eq!("1", format!("{}", ZoneMDHashAlg::SHA384)); +/// // Unknown ZoneMD Hash Algorithm. +/// assert_eq!("42", format!("{}", ZoneMDHashAlg::from(42))); +/// ``` +/// +/// [Section 2.3 of RFC8976]: https://datatracker.ietf.org/doc/html/rfc8976#name-zonemd-presentation-format +/// [IANA]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#zonemd-hash-algorithms +impl fmt::Display for ZoneMDHashAlg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.code) + } +} + +#[cfg(test)] +mod test { + use crate::new::rdata::{ZoneMDHashAlg, ZoneMDScheme}; + + #[test] + fn test_zonemd_scheme_from() { + let zonemd_scheme: ZoneMDScheme = 1.into(); + assert_eq!(zonemd_scheme, ZoneMDScheme::SIMPLE); + + let number: u8 = zonemd_scheme.into(); + assert_eq!(number, 1); + } + + #[test] + fn test_zonemd_hash_alg_from() { + let zonemd_hash_alg: ZoneMDHashAlg = 1.into(); + assert_eq!(zonemd_hash_alg, ZoneMDHashAlg::SHA384); + + let number: u8 = zonemd_hash_alg.into(); + assert_eq!(number, 1); + } + + #[test] + fn test_zonemd_scheme_from_mnemonic() { + assert_eq!( + ZoneMDScheme::from_mnemonic("SIMPLE").unwrap(), + ZoneMDScheme::SIMPLE + ); + } + + #[test] + fn test_zonemd_hash_alg_from_mnemonic() { + assert_eq!( + ZoneMDHashAlg::from_mnemonic("SHA384").unwrap(), + ZoneMDHashAlg::SHA384 + ); } }