diff --git a/src/base/iana/class.rs b/src/base/iana/class.rs index 51caef05e..ae859d7e2 100644 --- a/src/base/iana/class.rs +++ b/src/base/iana/class.rs @@ -30,33 +30,34 @@ int_enum! { /// /// This class is defined in RFC 1035 and really the only one relevant /// at all. - (IN => 1, b"IN") + (IN => 1, "IN") /// Chaosnet (CH). /// /// A network protocol developed at MIT in the 1970s. Reused by BIND for /// built-in server information zones.", - (CH => 3, b"CH") + (CH => 3, "CH") /// Hesiod (HS). /// /// A system information protocol part of MIT's Project Athena.", - (HS => 4, b"HS") + (HS => 4, "HS") /// Query class None. /// /// Defined in RFC 2136, this class is used in UPDATE queries to /// require that an RRset does not exist prior to the update.", - (NONE => 0xFE, b"NONE") + (NONE => 0xFE, "NONE") /// Query class * (ANY). /// /// This class can be used in a query to indicate that records for the /// given name from any class are requested.", - (ANY => 0xFF, b"*") + (ANY => 0xFF, "*") } int_enum_str_with_prefix!(Class, "CLASS", b"CLASS", u16, "unknown class"); +int_enum_zonefile_fmt_with_prefix!(Class, "CLASS"); //============ Tests ========================================================= diff --git a/src/base/iana/digestalg.rs b/src/base/iana/digestalg.rs index 75557c177..66c3c4bee 100644 --- a/src/base/iana/digestalg.rs +++ b/src/base/iana/digestalg.rs @@ -18,12 +18,12 @@ int_enum! { /// Specifies that the SHA-1 hash function is used. /// /// Implementation of this function is currently mandatory. - (SHA1 => 1, b"SHA-1") + (SHA1 => 1, "SHA-1") /// Specifies that the SHA-256 hash function is used. /// /// Implementation of this function is currently mandatory. - (SHA256 => 2, b"SHA-256") + (SHA256 => 2, "SHA-256") /// Specifies that the GOST R 34.11-94 hash function is used. /// @@ -31,7 +31,7 @@ int_enum! { /// the function is optional. /// /// [RFC 5933]: https://tools.ietf.org/html/rfc5933 - (GOST => 3, b"GOST R 34.11-94") + (GOST => 3, "GOST R 34.11-94") /// Specifies that the SHA-384 hash function is used. /// @@ -39,10 +39,11 @@ int_enum! { /// the function is optional. /// /// [RFC 6605]: https://tools.ietf.org/html/rfc6605 - (SHA384 => 4, b"SHA-384") + (SHA384 => 4, "SHA-384") } int_enum_str_decimal!(DigestAlg, u8); +int_enum_zonefile_fmt_decimal!(DigestAlg, "digest type"); //============ Tests ========================================================= diff --git a/src/base/iana/exterr.rs b/src/base/iana/exterr.rs index 1307c2d15..5f6da5f4d 100644 --- a/src/base/iana/exterr.rs +++ b/src/base/iana/exterr.rs @@ -19,15 +19,15 @@ int_enum! { /// match known extended error codes. Implementations SHOULD /// include an EXTRA-TEXT value to augment this error code with /// additional information. - (OTHER => 0, b"Other Error") + (OTHER => 0, "Other Error") /// The resolver attempted to perform DNSSEC validation, but a DNSKEY /// RRset contained only unsupported DNSSEC algorithms. - (UNSUPPORTED_DNSKEY_ALGORITHM => 1, b"Unsupported DNSKEY Algorithm") + (UNSUPPORTED_DNSKEY_ALGORITHM => 1, "Unsupported DNSKEY Algorithm") /// The resolver attempted to perform DNSSEC validation, but a DS /// RRset contained only unsupported Digest Types. - (UNSUPPORTED_DS_DIGEST_TYPE => 2, b"Unsupported DS Digest Type") + (UNSUPPORTED_DS_DIGEST_TYPE => 2, "Unsupported DS Digest Type") /// The resolver was unable to resolve the answer within its time /// limits and decided to answer with previously cached data @@ -35,7 +35,7 @@ int_enum! { /// by problems communicating with an authoritative server, /// possibly as result of a denial of service (DoS) attack against /// another network. (See also Code 19.) - (STALE_ANSWER => 3, b"Stale Answer") + (STALE_ANSWER => 3, "Stale Answer") /// For policy reasons (legal obligation or malware filtering, for /// instance), an answer was forged. Note that this should be @@ -43,58 +43,58 @@ int_enum! { /// codes are returned instead. See Blocked (15), Censored /// (16), and Filtered (17) for use when returning other /// response codes. - (FORGED_ANSWER => 4, b"Forged Answer") + (FORGED_ANSWER => 4, "Forged Answer") /// The resolver attempted to perform DNSSEC validation, but /// validation ended in the Indeterminate state [RFC 4035]. /// /// [RFC 4035]: https://tools.ietf.org/html/rfc4035 - (DNSSEC_INDETERMINATE => 5, b"DNSSEC Indeterminate") + (DNSSEC_INDETERMINATE => 5, "DNSSEC Indeterminate") /// The resolver attempted to perform DNSSEC validation, but /// validation ended in the Bogus state. - (DNSSEC_BOGUS => 6, b"DNSSEC Bogus") + (DNSSEC_BOGUS => 6, "DNSSEC Bogus") /// The resolver attempted to perform DNSSEC validation, but no /// signatures are presently valid and some (often all) are /// expired. - (SIGNATURE_EXPIRED => 7, b"Signature Expired") + (SIGNATURE_EXPIRED => 7, "Signature Expired") /// The resolver attempted to perform DNSSEC validation, but no /// signatures are presently valid and at least some are not yet /// valid. - (SIGNATURE_NOT_YET_VALID => 8, b"Signature Not Yet Valid") + (SIGNATURE_NOT_YET_VALID => 8, "Signature Not Yet Valid") /// A DS record existed at a parent, but no supported matching /// DNSKEY record could be found for the child. - (DNSKEY_MISSING => 9, b"DNSKEY Missing") + (DNSKEY_MISSING => 9, "DNSKEY Missing") /// The resolver attempted to perform DNSSEC validation, but no /// RRSIGs could be found for at least one RRset where RRSIGs were /// expected. - (RRSIGS_MISSING => 10, b"RRSIGs Missing") + (RRSIGS_MISSING => 10, "RRSIGs Missing") /// The resolver attempted to perform DNSSEC validation, but no /// Zone Key Bit was set in a DNSKEY. - (NO_ZONE_KEY_BIT_SET => 11, b"No Zone Key Bit Set") + (NO_ZONE_KEY_BIT_SET => 11, "No Zone Key Bit Set") /// The resolver attempted to perform DNSSEC validation, but the /// requested data was missing and a covering NSEC or NSEC3 was /// not provided. - (NSEC_MISSING => 12, b"NSEC Missing") + (NSEC_MISSING => 12, "NSEC Missing") /// The resolver is returning the SERVFAIL RCODE from its cache. - (CACHED_ERROR => 13, b"Cached Error") + (CACHED_ERROR => 13, "Cached Error") /// The server is unable to answer the query, as it was not fully /// functional when the query was received. - (NOT_READY => 14, b"Not Ready") + (NOT_READY => 14, "Not Ready") /// The server is unable to respond to the request because the /// domain is on a blocklist due to an internal security policy /// imposed by the operator of the server resolving or forwarding /// the query. - (BLOCKED => 15, b"Blocked") + (BLOCKED => 15, "Blocked") /// The server is unable to respond to the request because the /// domain is on a blocklist due to an external requirement @@ -102,20 +102,20 @@ int_enum! { /// resolving or forwarding the query. Note that how the imposed /// policy is applied is irrelevant (in-band DNS filtering, court /// order, etc.). - (CENSORED => 16, b"Censored") + (CENSORED => 16, "Censored") /// The server is unable to respond to the request because the /// domain is on a blocklist as requested by the client. /// Functionally, this amounts to "you requested that we filter /// domains like this one." - (FILTERED => 17, b"Filtered") + (FILTERED => 17, "Filtered") /// An authoritative server or recursive resolver that receives a /// query from an "unauthorized" client can annotate its REFUSED /// message with this code. Examples of "unauthorized" clients are /// recursive queries from IP addresses outside the network, /// blocklisted IP addresses, local policy, etc. - (PROHIBITED => 18, b"Prohibited") + (PROHIBITED => 18, "Prohibited") /// The resolver was unable to resolve an answer within its /// configured time limits and decided to answer with a previously @@ -124,7 +124,7 @@ int_enum! { /// with an authoritative server, possibly as result of a denial /// of service (DoS) attack against another network. (See also /// Code 3.) - (STALE_NXDOMAIN_ANSWER => 19, b"Stale NXDomain Answer") + (STALE_NXDOMAIN_ANSWER => 19, "Stale NXDomain Answer") /// An authoritative server that receives a query with the /// Recursion Desired (RD) bit clear, or when it is not configured @@ -132,26 +132,26 @@ int_enum! { /// SHOULD include this EDE code in the REFUSED response. A /// resolver that receives a query with the RD bit clear SHOULD /// include this EDE code in the REFUSED response. - (NOT_AUTHORITATIVE => 20, b"Not Authoritative") + (NOT_AUTHORITATIVE => 20, "Not Authoritative") /// The requested operation or query is not supported. - (NOT_SUPPORTED => 21, b"Not Supported") + (NOT_SUPPORTED => 21, "Not Supported") /// The resolver could not reach any of the authoritative name /// servers (or they potentially refused to reply). - (NO_REACHABLE_AUTHORITY => 22, b"No Reachable Authority") + (NO_REACHABLE_AUTHORITY => 22, "No Reachable Authority") /// An unrecoverable error occurred while communicating with /// another server. - (NETWORK_ERROR => 23, b"Network Error") + (NETWORK_ERROR => 23, "Network Error") /// The authoritative server cannot answer with data for a zone it /// is otherwise configured to support. Examples of this include /// its most recent zone being too old or having expired. - (INVALID_DATA => 24, b"Invalid Data") + (INVALID_DATA => 24, "Invalid Data") /// The requested resource record type should not appear in a query. - (INVALID_QUERY_TYPE => 30, b"Invalid Query Type") + (INVALID_QUERY_TYPE => 30, "Invalid Query Type") } /// Start of the private range for EDE codes. diff --git a/src/base/iana/macros.rs b/src/base/iana/macros.rs index 93c1c2b2a..2a7549de0 100644 --- a/src/base/iana/macros.rs +++ b/src/base/iana/macros.rs @@ -39,7 +39,7 @@ macro_rules! int_enum { #[must_use] pub fn from_mnemonic(m: &[u8]) -> Option { $( - if m.eq_ignore_ascii_case($mnemonic) { + if m.eq_ignore_ascii_case($mnemonic.as_bytes()) { return Some($ianatype::$variant) } )* @@ -52,6 +52,14 @@ macro_rules! int_enum { /// is hidden in a `Int` variant. #[must_use] pub const fn to_mnemonic(self) -> Option<&'static [u8]> { + match self.to_mnemonic_str() { + Some(m) => Some(m.as_bytes()), + None => None, + } + } + + /// Returns the mnemonic as a `&str` for this value if there is one + pub const fn to_mnemonic_str(self) -> Option<&'static str> { match self { $( $ianatype::$variant => { @@ -201,7 +209,12 @@ macro_rules! int_enum_str_decimal { impl core::fmt::Display for $ianatype { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - write!(f, "{}", self.to_int()) + write!(f, "{}", self.to_int())?; + + if let Some(m) = self.to_mnemonic_str() { + write!(f, "({m})")?; + } + Ok(()) } } @@ -273,14 +286,9 @@ macro_rules! int_enum_str_with_decimal { impl core::fmt::Display for $ianatype { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - use core::fmt::Write; - - match self.to_mnemonic() { + match self.to_mnemonic_str() { Some(m) => { - for ch in m { - f.write_char(*ch as char)? - } - Ok(()) + write!(f, "{m}({})", self.to_int()) } None => { write!(f, "{}", self.to_int()) @@ -389,15 +397,8 @@ macro_rules! int_enum_str_with_prefix { impl core::fmt::Display for $ianatype { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - use core::fmt::Write; - - match self.to_mnemonic() { - Some(m) => { - for ch in m { - f.write_char(*ch as char)? - } - Ok(()) - } + match self.to_mnemonic_str() { + Some(m) => f.write_str(m), None => { write!(f, "{}{}", $str_prefix, self.to_int()) } @@ -436,6 +437,56 @@ macro_rules! int_enum_str_with_prefix { }; } +macro_rules! int_enum_zonefile_fmt_decimal { + ($ianatype:ident, $name:expr) => { + impl $crate::base::zonefile_fmt::ZonefileFmt for $ianatype { + fn fmt( + &self, + p: &mut impl $crate::base::zonefile_fmt::Formatter, + ) -> $crate::base::zonefile_fmt::Result { + p.write_token(self.to_int())?; + p.write_comment(format_args!("{}: {}", $name, self)) + } + } + }; +} + +macro_rules! int_enum_zonefile_fmt_with_decimal { + ($ianatype:ident) => { + impl $crate::base::zonefile_fmt::ZonefileFmt for $ianatype { + fn fmt( + &self, + p: &mut impl $crate::base::zonefile_fmt::Formatter, + ) -> $crate::base::zonefile_fmt::Result { + match self.to_mnemonic_str() { + Some(m) => p.write_token(m), + None => p.write_token(self.to_int()), + } + } + } + }; +} + +macro_rules! int_enum_zonefile_fmt_with_prefix { + ($ianatype:ident, $str_prefix:expr) => { + impl $crate::base::zonefile_fmt::ZonefileFmt for $ianatype { + fn fmt( + &self, + p: &mut impl $crate::base::zonefile_fmt::Formatter, + ) -> $crate::base::zonefile_fmt::Result { + match self.to_mnemonic_str() { + Some(m) => p.write_token(m), + None => p.write_token(format_args!( + "{}{}", + $str_prefix, + self.to_int() + )), + } + } + } + }; +} + macro_rules! scan_impl { ($ianatype:ident) => { impl $ianatype { diff --git a/src/base/iana/nsec3.rs b/src/base/iana/nsec3.rs index 44a9ea5a8..3714d6b42 100644 --- a/src/base/iana/nsec3.rs +++ b/src/base/iana/nsec3.rs @@ -17,7 +17,8 @@ int_enum! { Nsec3HashAlg, u8; /// Specifies that the SHA-1 hash function is used. - (SHA1 => 1, b"SHA-1") + (SHA1 => 1, "SHA-1") } int_enum_str_decimal!(Nsec3HashAlg, u8); +int_enum_zonefile_fmt_decimal!(Nsec3HashAlg, "hash algorithm"); diff --git a/src/base/iana/opcode.rs b/src/base/iana/opcode.rs index 0ca287136..7a4869202 100644 --- a/src/base/iana/opcode.rs +++ b/src/base/iana/opcode.rs @@ -25,7 +25,7 @@ int_enum! { /// This value is defined in [RFC 1035]. /// /// [RFC 1035]: https://tools.ietf.org/html/rfc1035 - (QUERY => 0, b"QUERY") + (QUERY => 0, "QUERY") /// An inverse query (IQUERY) (1, obsolete). /// @@ -38,7 +38,7 @@ int_enum! { /// /// [RFC 1035]: https://tools.ietf.org/html/rfc1035 /// [RFC 3425]: https://tools.ietf.org/html/rfc3425 - (IQUERY => 1, b"IQUERY") + (IQUERY => 1, "IQUERY") /// A server status request (2). /// @@ -48,7 +48,7 @@ int_enum! { /// /// [RFC 1034]: https://tools.ietf.org/html/rfc1034 /// [RFC 1035]: https://tools.ietf.org/html/rfc1035 - (STATUS => 2, b"STATUS") + (STATUS => 2, "STATUS") /// A NOTIFY query (4). /// @@ -58,7 +58,7 @@ int_enum! { /// This value and the NOTIFY query are defined in [RFC 1996]. /// /// [RFC 1996]: https://tools.ietf.org/html/rfc1996 - (NOTIFY => 4, b"NOTIFY") + (NOTIFY => 4, "NOTIFY") /// An UPDATE query (5). /// @@ -68,7 +68,7 @@ int_enum! { /// This value and the UPDATE query are defined in [RFC 2136]. /// /// [RFC 2136]: https://tools.ietf.org/html/rfc2136 - (UPDATE => 5, b"UPDATE") + (UPDATE => 5, "UPDATE") /// DNS Stateful operations (DSO) (6). /// @@ -78,7 +78,8 @@ int_enum! { /// This value and the DOS query are defined in [RFC 8490]. /// /// [RFC 8490]: https://tools.ietf.org/html/rfc8490 - (DSO => 6, b"DSO") + (DSO => 6, "DSO") } int_enum_str_with_decimal!(Opcode, u8, "unknown opcode"); +int_enum_zonefile_fmt_with_decimal!(Opcode); diff --git a/src/base/iana/opt.rs b/src/base/iana/opt.rs index c8d3bfbdd..0012353cc 100644 --- a/src/base/iana/opt.rs +++ b/src/base/iana/opt.rs @@ -27,7 +27,7 @@ int_enum! { /// currently [draft-sekar-dns-llq]. /// /// [draft-sekar-dns-llq]: https://datatracker.ietf.org/doc/draft-sekar-dns-llq/ - (LLQ => 1, b"LLQ") + (LLQ => 1, "LLQ") /// Update lease (UL, 2). /// @@ -36,7 +36,7 @@ int_enum! { /// has since expired. The code is considered ‘on hold.’ /// /// [draft-sekar-dns-ul]: http://files.dns-sd.org/draft-sekar-dns-ul.txt - (UL => 2, b"UL") + (UL => 2, "UL") /// Name server identifier (NSID, 3). /// @@ -44,7 +44,7 @@ int_enum! { /// answer for diagnostic purposes. The options in defined in [RFC 5001]. /// /// [RFC 5001]: https://tools.ietf.org/html/rfc5001 - (NSID => 3, b"NSID") + (NSID => 3, "NSID") /// DNSSEC algorithm understood (DAU, 5). /// @@ -53,7 +53,7 @@ int_enum! { /// in [RFC 6975]. /// /// [RFC 6075]: https://tools.ietf.org/html/rfc6975 - (DAU => 5, b"DAU") + (DAU => 5, "DAU") /// DS hash understood (DHU, 6). /// @@ -62,7 +62,7 @@ int_enum! { /// in [RFC 6975]. /// /// [RFC 6075]: https://tools.ietf.org/html/rfc6975 - (DHU => 6, b"DHU") + (DHU => 6, "DHU") /// NSEC3 hash understood (N3U, 7). /// @@ -71,7 +71,7 @@ int_enum! { /// in [RFC 6975]. /// /// [RFC 6075]: https://tools.ietf.org/html/rfc6975 - (N3U => 7, b"N3U") + (N3U => 7, "N3U") /// EDNS client subnet (8), /// @@ -80,7 +80,7 @@ int_enum! { /// answer. This option is defined in [RFC 7871]. /// /// [RFC 7871]: https://tools.ietf.org/html/rfc7871 - (CLIENT_SUBNET => 8, b"edns-client-subnet") + (CLIENT_SUBNET => 8, "edns-client-subnet") /// Expire (9). /// @@ -89,7 +89,7 @@ int_enum! { /// primary. The option is defined in [RFC 7314]. /// /// [RFC 7314]: https://tools.ietf.org/html/rfc7314 - (EXPIRE => 9, b"EDNS EXPIRE") + (EXPIRE => 9, "EDNS EXPIRE") /// DNS Cookie (10). /// @@ -98,7 +98,7 @@ int_enum! { /// amplification attacks. The option is defined in [RFC 7873]. /// /// [RFC 7873]: https://tools.ietf.org/html/rfc7873 - (COOKIE => 10, b"COOKIE") + (COOKIE => 10, "COOKIE") /// edns-tcp-keepalive (11). /// @@ -106,7 +106,7 @@ int_enum! { /// may hold open a TCP connection. The option is defined in [RFC 7828]. /// /// [RFC 7828]: https://tools.ietf.org/html/rfc7828 - (TCP_KEEPALIVE => 11, b"edns-tcp-keepalive") + (TCP_KEEPALIVE => 11, "edns-tcp-keepalive") /// Padding (12). /// @@ -115,7 +115,7 @@ int_enum! { /// The option is defined in [RFC 7830]. /// /// [RFC 7830]: https://tools.ietf.org/html/rfc7830 - (PADDING => 12, b"Padding") + (PADDING => 12, "Padding") /// CHAIN query requests (13). /// @@ -124,7 +124,7 @@ int_enum! { /// the answer. The option is defined in [RFC 7901]. /// /// [RFC 7901]: https://tools.ietf.org/html/rfc7901 - (CHAIN => 13, b"CHAIN") + (CHAIN => 13, "CHAIN") /// EDNS key tag (14). /// @@ -133,7 +133,7 @@ int_enum! { /// [RFC 8145]. /// /// [RFC 8145]: https://tools.ietf.org/html/rfc8145 - (KEY_TAG => 14, b"edns-key-tag") + (KEY_TAG => 14, "edns-key-tag") /// Extended DNS Error (15). /// @@ -142,7 +142,7 @@ int_enum! { /// processing of RCODEs. The option is defined in [RFC 8914]. /// /// [RFC 8914]: https://tools.ietf.org/html/rfc8914 - (EXTENDED_ERROR => 15, b"Extended DNS Error") + (EXTENDED_ERROR => 15, "Extended DNS Error") /// EDNS client tag (16). /// @@ -151,7 +151,7 @@ int_enum! { /// [draft-bellis-dnsop-edns-tags]. /// /// [draft-bellis-dnsop-edns-tags]: https://datatracker.ietf.org/doc/draft-bellis-dnsop-edns-tags/ - (CLIENT_TAG => 16, b"EDNS-Client-Tag") + (CLIENT_TAG => 16, "EDNS-Client-Tag") /// EDNS server tag (16). /// @@ -160,17 +160,18 @@ int_enum! { /// [draft-bellis-dnsop-edns-tags]. /// /// [draft-bellis-dnsop-edns-tags]: https://datatracker.ietf.org/doc/draft-bellis-dnsop-edns-tags/ - (SERVER_TAG => 17, b"EDNS-Server-Tag") + (SERVER_TAG => 17, "EDNS-Server-Tag") /// DeviceID (26946). /// /// Ths option is used by the [Cisco Umbrella network device API]. /// /// [Cisco Umbrella network device API]: https://docs.umbrella.com/developer/networkdevices-api/identifying-dns-traffic2 - (DEVICE_ID => 26946, b"DeviceId") + (DEVICE_ID => 26946, "DeviceId") } int_enum_str_with_decimal!(OptionCode, u16, "unknown option code"); +int_enum_zonefile_fmt_with_decimal!(OptionCode); //============ Tests ========================================================= diff --git a/src/base/iana/rcode.rs b/src/base/iana/rcode.rs index 64252aed0..460c8c36b 100644 --- a/src/base/iana/rcode.rs +++ b/src/base/iana/rcode.rs @@ -695,7 +695,7 @@ int_enum! { /// Defined in [RFC 1035]. /// /// [RFC 1035]: https://tools.ietf.org/html/rfc1035 - (NOERROR => 0, b"NOERROR") + (NOERROR => 0, "NOERROR") /// Format error. /// @@ -704,7 +704,7 @@ int_enum! { /// Defined in [RFC 1035]. /// /// [RFC 1035]: https://tools.ietf.org/html/rfc1035 - (FORMERR => 1, b"FORMERR") + (FORMERR => 1, "FORMERR") /// Server failure. /// @@ -714,7 +714,7 @@ int_enum! { /// Defined in [RFC 1035]. /// /// [RFC 1035]: https://tools.ietf.org/html/rfc1035 - (SERVFAIL => 2, b"SERVFAIL") + (SERVFAIL => 2, "SERVFAIL") /// Name error. /// @@ -723,7 +723,7 @@ int_enum! { /// Defined in [RFC 1035]. /// /// [RFC 1035]: https://tools.ietf.org/html/rfc1035 - (NXDOMAIN => 3, b"NXDOMAIN") + (NXDOMAIN => 3, "NXDOMAIN") /// Not implemented. /// @@ -732,7 +732,7 @@ int_enum! { /// Defined in [RFC 1035]. /// /// [RFC 1035]: https://tools.ietf.org/html/rfc1035 - (NOTIMP => 4, b"NOTIMPL") + (NOTIMP => 4, "NOTIMPL") /// Query refused. /// @@ -742,7 +742,7 @@ int_enum! { /// Defined in [RFC 1035]. /// /// [RFC 1035]: https://tools.ietf.org/html/rfc1035 - (REFUSED => 5, b"REFUSED") + (REFUSED => 5, "REFUSED") /// Name exists when it should not. /// @@ -757,7 +757,7 @@ int_enum! { /// /// [RFC 2136]: https://tools.ietf.org/html/rfc2136 /// [RFC 6672]: https://tools.ietf.org/html/rfc6672 - (YXDOMAIN => 6, b"YXDOMAIN") + (YXDOMAIN => 6, "YXDOMAIN") /// RR set exists when it should not. /// @@ -767,7 +767,7 @@ int_enum! { /// Defined in [RFC 2136]. /// /// [RFC 2136]: https://tools.ietf.org/html/rfc2136 - (YXRRSET => 7, b"YXRRSET") + (YXRRSET => 7, "YXRRSET") /// RR set that should exist does not. /// @@ -777,7 +777,7 @@ int_enum! { /// Defined in [RFC 2136]. /// /// [RFC 2136]: https://tools.ietf.org/html/rfc2136 - (NXRRSET => 8, b"NXRRSET") + (NXRRSET => 8, "NXRRSET") /// Server not authoritative for zone or client not authorized. /// @@ -790,7 +790,7 @@ int_enum! { /// /// [RFC 2136]: https://tools.ietf.org/html/rfc2136 /// [RFC 2845]: https://tools.ietf.org/html/rfc2845 - (NOTAUTH => 9, b"NOTAUTH") + (NOTAUTH => 9, "NOTAUTH") /// Name not contained in zone. /// @@ -800,7 +800,7 @@ int_enum! { /// Defined in [RFC 2136]. /// /// [RFC 2136]: https://tools.ietf.org/html/rfc2136 - (NOTZONE => 10, b"NOTZONE") + (NOTZONE => 10, "NOTZONE") /// TSIG signature failure. /// @@ -809,7 +809,7 @@ int_enum! { /// Defined in [RFC 2845]. /// /// [RFC 2845]: https://tools.ietf.org/html/rfc2845 - (BADSIG => 16, b"BADSIG") + (BADSIG => 16, "BADSIG") /// Key not recognized. /// @@ -819,7 +819,7 @@ int_enum! { /// Defined in [RFC 2845]. /// /// [RFC 2845]: https://tools.ietf.org/html/rfc2845 - (BADKEY => 17, b"BADKEY") + (BADKEY => 17, "BADKEY") /// Signature out of time window. /// @@ -829,7 +829,7 @@ int_enum! { /// Defined in [RFC 2845]. /// /// [RFC 2845]: https://tools.ietf.org/html/rfc2845 - (BADTIME => 18, b"BADTIME") + (BADTIME => 18, "BADTIME") /// Bad TKEY mode. /// @@ -839,7 +839,7 @@ int_enum! { /// Defined in [RFC 2930]. /// /// [RFC 2930]: https://tools.ietf.org/html/rfc2930 - (BADMODE => 19, b"BADMODE") + (BADMODE => 19, "BADMODE") /// Duplicate key name. /// @@ -850,7 +850,7 @@ int_enum! { /// Defined in [RFC 2930]. /// /// [RFC 2930]: https://tools.ietf.org/html/rfc2930 - (BADNAME => 20, b"BADNAME") + (BADNAME => 20, "BADNAME") /// Algorithm not supported. /// @@ -859,7 +859,7 @@ int_enum! { /// record contains a value not supported by the server. /// /// [RFC 2930]: https://tools.ietf.org/html/rfc2930 - (BADALG => 21, b"BADALG") + (BADALG => 21, "BADALG") /// Bad truncation. /// @@ -869,7 +869,7 @@ int_enum! { /// Defined in [RFC 4635]. /// /// [RFC 4635]: https://tools.ietf.org/html/rfc4635 - (BADTRUNC => 22, b"BADTRUNC") + (BADTRUNC => 22, "BADTRUNC") /// Bad or missing server cookie. /// @@ -879,7 +879,7 @@ int_enum! { /// Defined in [RFC 7873]. /// /// [RFC 7873]: https://tools.ietf.org/html/rfc7873 - (BADCOOKIE => 23, b"BADCOOKIE") + (BADCOOKIE => 23, "BADCOOKIE") } //--- From @@ -897,6 +897,7 @@ impl From for TsigRcode { } int_enum_str_with_decimal!(TsigRcode, u16, "unknown TSIG error"); +int_enum_zonefile_fmt_with_decimal!(TsigRcode); //============ Error Types =================================================== diff --git a/src/base/iana/rtype.rs b/src/base/iana/rtype.rs index c38c204b7..a6546476a 100644 --- a/src/base/iana/rtype.rs +++ b/src/base/iana/rtype.rs @@ -24,412 +24,413 @@ int_enum! { Rtype, u16; /// A host address. - (A => 1, b"A") + (A => 1, "A") /// An authoritative name server. - (NS => 2, b"NS") + (NS => 2, "NS") /// A mail destination. /// /// (Obsolete – use MX) - (MD => 3, b"MD") + (MD => 3, "MD") /// A mail forwarder. /// /// (Obsolete – use MX) - (MF => 4, b"MF") + (MF => 4, "MF") /// The canonical name for an alias - (CNAME => 5, b"CNAME") + (CNAME => 5, "CNAME") /// Marks the start of a zone of authority. - (SOA => 6, b"SOA") + (SOA => 6, "SOA") /// A mailbox domain name. /// /// (Experimental.) - (MB => 7, b"MB") + (MB => 7, "MB") /// A mail group member /// /// (Experimental.) - (MG => 8, b"MG") + (MG => 8, "MG") /// A mail rename domain name. /// /// (Experimental.) - (MR => 9, b"MR") + (MR => 9, "MR") /// A null resource record. /// /// (Experimental.) - (NULL => 10, b"NULL") + (NULL => 10, "NULL") /// A well known service description. - (WKS => 11, b"WKS") + (WKS => 11, "WKS") /// A domain name pointer. - (PTR => 12, b"PTR") + (PTR => 12, "PTR") /// Host information. - (HINFO => 13, b"HINFO") + (HINFO => 13, "HINFO") /// Mailbox or mail list information. - (MINFO => 14, b"MINFO") + (MINFO => 14, "MINFO") /// Mail exchange. - (MX => 15, b"MX") + (MX => 15, "MX") /// Text strings. - (TXT => 16, b"TXT") + (TXT => 16, "TXT") /// For Responsible Person. /// /// See RFC 1183 - (RP => 17, b"RP") + (RP => 17, "RP") /// For AFS Data Base location. /// /// See RFC 1183 and RFC 5864. - (AFSDB => 18, b"AFSDB") + (AFSDB => 18, "AFSDB") /// For X.25 PSDN address. /// /// See RFC 1183. - (X25 => 19, b"X25") + (X25 => 19, "X25") /// For ISDN address. /// /// See RFC 1183. - (ISDN => 20, b"ISDN") + (ISDN => 20, "ISDN") /// For Route Through. /// /// See RFC 1183 - (RT => 21, b"RT") + (RT => 21, "RT") /// For SNAP address, NSAP style A record. /// /// See RFC 1706. - (NSAP => 22, b"NSAP") + (NSAP => 22, "NSAP") /// For domain name pointer, NSAP style. /// /// See RFC 1348, RFC 1637, RFC 1706. - (NSAPPTR => 23, b"NSAPPTR") + (NSAPPTR => 23, "NSAPPTR") /// For security signature. - (SIG => 24, b"SIG") + (SIG => 24, "SIG") /// For security key. - (KEY => 25, b"KEY") + (KEY => 25, "KEY") /// X.400 mail mapping information. /// /// See RFC 2163. - (PX => 26, b"PX") + (PX => 26, "PX") /// Geographical position. /// /// See RFC 1712 - (GPOS => 27, b"GPOS") + (GPOS => 27, "GPOS") /// IPv6 address. /// /// See RFC 3596. - (AAAA => 28, b"AAAA") + (AAAA => 28, "AAAA") /// Location information. /// /// See RFC 1876. - (LOC => 29, b"LOC") + (LOC => 29, "LOC") /// Next domain. /// /// (Obsolete.) /// /// See RFC 3755 and RFC 2535. - (NXT => 30, b"NXT") + (NXT => 30, "NXT") /// Endpoint identifier. - (EID => 31, b"EID") + (EID => 31, "EID") /// Nimrod locator. - (NIMLOC => 32, b"NIMLOC") + (NIMLOC => 32, "NIMLOC") /// Server selection. /// /// See RFC 2782. - (SRV => 33, b"SRV") + (SRV => 33, "SRV") /// ATM address. - (ATMA => 34, b"ATMA") + (ATMA => 34, "ATMA") /// Naming authority pointer. /// /// See RFC 2915, RFC 2168, and RFC 3403. - (NAPTR => 35, b"NAPTR") + (NAPTR => 35, "NAPTR") /// Key exchanger. /// /// See RFC 2230. - (KX => 36, b"KX") + (KX => 36, "KX") /// CERT /// /// See RFC 4398. - (CERT => 37, b"CERT") + (CERT => 37, "CERT") /// A6. /// /// (Obsolete – use AAAA.) /// /// See RFC 3226, RFC 2874, and RFC 6563. - (A6 => 38, b"A6") + (A6 => 38, "A6") /// DNAME. /// /// See RFC 6672. - (DNAME => 39, b"DNAME") + (DNAME => 39, "DNAME") /// SINK. - (SINK => 40, b"SINK") + (SINK => 40, "SINK") /// OPT. /// /// See RFC 6891 and RFC 3225. - (OPT => 41, b"OPT") + (OPT => 41, "OPT") /// APL. /// /// See RFC 3123. - (APL => 42, b"APL") + (APL => 42, "APL") /// Delegation signer. /// /// See RFC 4034 and RFC 3658. - (DS => 43, b"DS") + (DS => 43, "DS") /// SSH key fingerprint. /// /// See RFC 4255. - (SSHFP => 44, b"SSHFP") + (SSHFP => 44, "SSHFP") /// IPSECKEY /// /// See RFC 4255. - (IPSECKEY => 45, b"IPSECKEY") + (IPSECKEY => 45, "IPSECKEY") /// RRSIG. /// /// See RFC 4034 and RFC 3755. - (RRSIG => 46, b"RRSIG") + (RRSIG => 46, "RRSIG") /// NSEC. /// /// See RFC 4034 and RFC 3755. - (NSEC => 47, b"NSEC") + (NSEC => 47, "NSEC") /// DNSKEY. /// /// See RFC 4034 and RFC 3755. - (DNSKEY => 48, b"DNSKEY") + (DNSKEY => 48, "DNSKEY") /// DHCID. /// /// See RFC 4701. - (DHCID => 49, b"DHCID") + (DHCID => 49, "DHCID") /// NSEC3 /// /// See RFC 5155. - (NSEC3 => 50, b"NSEC3") + (NSEC3 => 50, "NSEC3") /// NSEC3PARAM. /// /// See RFC 5155. - (NSEC3PARAM => 51, b"NSEC3PARAM") + (NSEC3PARAM => 51, "NSEC3PARAM") /// TLSA. /// /// See RFC 6698. - (TLSA => 52, b"TLSA") + (TLSA => 52, "TLSA") /// S/MIME cert association. /// /// See draft-ietf-dane-smime. - (SMIMEA => 53, b"SMIMEA") + (SMIMEA => 53, "SMIMEA") /// Host Identity Protocol. /// /// See RFC 5205. - (HIP => 55, b"HIP") + (HIP => 55, "HIP") /// NINFO. - (NINFO => 56, b"NINFO") + (NINFO => 56, "NINFO") /// RKEY. - (RKEY => 57, b"RKEY") + (RKEY => 57, "RKEY") /// Trust Anchor Link - (TALINK => 58, b"TALINK") + (TALINK => 58, "TALINK") /// Child DS. /// /// See RFC 7344. - (CDS => 59, b"CDS") + (CDS => 59, "CDS") /// DNSKEY(s) the child wants reflected in DS. /// /// See RFC 7344. - (CDNSKEY => 60, b"CDNSKEY") + (CDNSKEY => 60, "CDNSKEY") /// OpenPGP key. /// /// See draft-ietf-dane-openpgpkey. - (OPENPGPKEY => 61, b"OPENPGPKEY") + (OPENPGPKEY => 61, "OPENPGPKEY") /// Child-to-parent synchronization. /// /// See RFC 7477. - (CSYNC => 62, b"CSYNC") + (CSYNC => 62, "CSYNC") /// Message digest for DNS zone. /// /// See draft-wessels-dns-zone-digest. - (ZONEMD => 63, b"ZONEMD") + (ZONEMD => 63, "ZONEMD") /// General Purpose Service Endpoints. /// /// See draft-ietf-dnsop-svcb-httpssvc - (SVCB => 64, b"SVCB") + (SVCB => 64, "SVCB") /// HTTPS Specific Service Endpoints. /// /// See draft-ietf-dnsop-svcb-httpssvc - (HTTPS => 65, b"HTTPS") + (HTTPS => 65, "HTTPS") /// SPF. /// /// RFC 7208. - (SPF => 99, b"SPF") + (SPF => 99, "SPF") /// UINFO. /// /// IANA-Reserved. - (UINFO => 100, b"UINFO") + (UINFO => 100, "UINFO") /// UID. /// /// IANA-Reserved. - (UID => 101, b"UID") + (UID => 101, "UID") /// GID. /// /// IANA-Reserved. - (GID => 102, b"GID") + (GID => 102, "GID") /// UNSPEC. /// /// IANA-Reserved. - (UNSPEC => 103, b"UNSPEC") + (UNSPEC => 103, "UNSPEC") /// NID. /// /// See RFC 6742. - (NID => 104, b"NID") + (NID => 104, "NID") /// L32. /// /// See RFC 6742. - (L32 => 105, b"L32") + (L32 => 105, "L32") /// L64. /// /// See RFC 6742. - (L64 => 106, b"L64") + (L64 => 106, "L64") /// LP. /// /// See RFC 6742. - (LP => 107, b"LP") + (LP => 107, "LP") /// An EUI-48 address. /// /// See RFC 7043. - (EUI48 => 108, b"EUI48") + (EUI48 => 108, "EUI48") /// An EUI-64 address. /// /// See RFC 7043. - (EUI64 => 109, b"EUI64") + (EUI64 => 109, "EUI64") /// NXNAME. /// /// IANA-Reserved. - (NXNAME => 128, b"NXNAME") + (NXNAME => 128, "NXNAME") /// Transaction key. /// /// See RFC 2930. - (TKEY => 249, b"TKEY") + (TKEY => 249, "TKEY") /// Transaction signature. /// /// See RFC 2845. - (TSIG => 250, b"TSIG") + (TSIG => 250, "TSIG") /// Incremental transfer. /// /// See RFC 1995. - (IXFR => 251, b"IXFR") + (IXFR => 251, "IXFR") /// Transfer of entire zone. /// /// See RFC 1035 and RFC 5936. - (AXFR => 252, b"AXFR") + (AXFR => 252, "AXFR") /// Mailbox-related RRs (MB, MG, or MR). - (MAILB => 253, b"MAILB") + (MAILB => 253, "MAILB") /// Mail agent RRS. /// /// (Obsolete – see MX.) - (MAILA => 254, b"MAILA") + (MAILA => 254, "MAILA") /// A request for all records the server/cache has available. /// /// See RFC 1035 and RFC 6895. - (ANY => 255, b"ANY") + (ANY => 255, "ANY") /// URI. /// /// See RFC 7553. - (URI => 256, b"URI") + (URI => 256, "URI") /// Certification Authority Restriction. /// /// See RFC 6844. - (CAA => 257, b"CAA") + (CAA => 257, "CAA") /// Application visibility and control. - (AVC => 258, b"AVC") + (AVC => 258, "AVC") /// Digital Object Architecture /// /// See draft-durand-doa-over-dns. - (DOA => 259, b"DOA") + (DOA => 259, "DOA") /// DNSSEC trust authorities. - (TA => 32768, b"TA") + (TA => 32768, "TA") /// DNSSEC lookaside validation. /// /// See RFC 4431 - (DLV => 32769, b"DLV") + (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 { /// Returns true if this record type is a type used for Glue records. diff --git a/src/base/iana/secalg.rs b/src/base/iana/secalg.rs index 15b77c337..9e3e17f56 100644 --- a/src/base/iana/secalg.rs +++ b/src/base/iana/secalg.rs @@ -18,7 +18,7 @@ int_enum! { /// This algorithm is used in RFC 8087 to signal to the parent that a /// certain DS record should be deleted. It is _not_ an actual algorithm /// and can neither be used in zone nor transaction signing. - (DELETE => 0, b"DELETE") + (DELETE => 0, "DELETE") /// RSA/MD5 /// @@ -28,92 +28,93 @@ int_enum! { /// /// This algorithm may not be used for zone signing but may be used /// for transaction security. - (RSAMD5 => 1, b"RSAMD5") + (RSAMD5 => 1, "RSAMD5") /// Diffie-Hellman /// /// This algorithm is described in RFC 2539 for storing Diffie-Hellman /// (DH) keys in DNS resource records. It can not be used for zone /// signing but only for transaction security. - (DH => 2, b"DH") + (DH => 2, "DH") /// DSA/SHA1 /// /// This algorithm is described in RFC 2536. It may be used both for /// zone signing and transaction security. - (DSA => 3, b"DSA") + (DSA => 3, "DSA") /// RSA/SHA-1 /// /// This algorithm is described in RFC 3110. It may be used both for /// zone signing and transaction security. It is mandatory for DNSSEC /// implementations. - (RSASHA1 => 5, b"RSASHA1") + (RSASHA1 => 5, "RSASHA1") /// DSA-NSEC3-SHA1 /// /// This value is an alias for `Dsa` for use within NSEC3 records. - (DSA_NSEC3_SHA1 => 6, b"DSA-NSEC3-SHA1") + (DSA_NSEC3_SHA1 => 6, "DSA-NSEC3-SHA1") /// RSASHA1-NSEC3-SHA1 /// /// This value is an alias for `RsaSha1` for use within NSEC3 records. - (RSASHA1_NSEC3_SHA1 => 7, b"RSASHA1-NSEC3-SHA1") + (RSASHA1_NSEC3_SHA1 => 7, "RSASHA1-NSEC3-SHA1") /// RSA/SHA-256 /// /// This algorithm is described in RFC 5702. It may be used for zone /// signing only. - (RSASHA256 => 8, b"RSASHA256") + (RSASHA256 => 8, "RSASHA256") /// RSA/SHA-512 /// /// This algorithm is described in RFC 5702. It may be used for zone /// signing only. - (RSASHA512 => 10, b"RSASHA512") + (RSASHA512 => 10, "RSASHA512") /// GOST R 34.10-2001 /// /// This algorithm is described in RFC 5933. It may be used for zone /// signing only. - (ECC_GOST => 12, b"ECC-GOST") + (ECC_GOST => 12, "ECC-GOST") /// ECDSA Curve P-256 with SHA-256 /// /// This algorithm is described in RFC 6605. It may be used for zone /// signing only. - (ECDSAP256SHA256 => 13, b"ECDSAP256SHA256") + (ECDSAP256SHA256 => 13, "ECDSAP256SHA256") /// ECDSA Curve P-384 with SHA-384 /// /// This algorithm is described in RFC 6605. It may be used for zone /// signing only. - (ECDSAP384SHA384 => 14, b"ECDSAP384SHA384") + (ECDSAP384SHA384 => 14, "ECDSAP384SHA384") /// ED25519 /// /// This algorithm is described in RFC 8080. - (ED25519 => 15, b"ED25519") + (ED25519 => 15, "ED25519") /// ED448 /// /// This algorithm is described in RFC 8080. - (ED448 => 16, b"ED448") + (ED448 => 16, "ED448") /// Reserved for Indirect Keys /// /// This value is reserved by RFC 4034. - (INDIRECT => 252, b"INDIRECT") + (INDIRECT => 252, "INDIRECT") /// A private algorithm identified by a domain name. /// /// This value is defined in RFC 4034. - (PRIVATEDNS => 253, b"PRIVATEDNS") + (PRIVATEDNS => 253, "PRIVATEDNS") /// A private algorithm identified by a ISO OID. /// /// This value is defined in RFC 4034. - (PRIVATEOID => 254, b"PRIVATEOID") + (PRIVATEOID => 254, "PRIVATEOID") } -int_enum_str_with_decimal!(SecAlg, u8, "unknown algorithm"); +int_enum_str_decimal!(SecAlg, u8); +int_enum_zonefile_fmt_decimal!(SecAlg, "algorithm"); diff --git a/src/base/iana/svcb.rs b/src/base/iana/svcb.rs index a8ab621b6..9dd31fbe7 100644 --- a/src/base/iana/svcb.rs +++ b/src/base/iana/svcb.rs @@ -4,19 +4,20 @@ int_enum! { => SvcParamKey, u16; - (MANDATORY => 0, b"mandatory") - (ALPN => 1, b"alpn") - (NO_DEFAULT_ALPN => 2, b"no-default-alpn") - (PORT => 3, b"port") - (IPV4HINT => 4, b"ipv4hint") + (MANDATORY => 0, "mandatory") + (ALPN => 1, "alpn") + (NO_DEFAULT_ALPN => 2, "no-default-alpn") + (PORT => 3, "port") + (IPV4HINT => 4, "ipv4hint") // https://datatracker.ietf.org/doc/draft-ietf-tls-esni/ - (ECH => 5, b"ech") - (IPV6HINT => 6, b"ipv6hint") + (ECH => 5, "ech") + (IPV6HINT => 6, "ipv6hint") // https://datatracker.ietf.org/doc/draft-ietf-add-svcb-dns/ - (DOHPATH => 7, b"dohpath") + (DOHPATH => 7, "dohpath") } int_enum_str_with_prefix!(SvcParamKey, "key", b"key", u16, "unknown key"); +int_enum_zonefile_fmt_with_prefix!(SvcParamKey, "key"); impl SvcParamKey { pub const PRIVATE_RANGE_BEGIN: u16 = 65280; diff --git a/src/base/mod.rs b/src/base/mod.rs index da4edc428..78dbf6352 100644 --- a/src/base/mod.rs +++ b/src/base/mod.rs @@ -121,6 +121,7 @@ pub mod rdata; pub mod record; pub mod scan; pub mod serial; +pub mod zonefile_fmt; //pub mod str; pub mod wire; diff --git a/src/base/name/absolute.rs b/src/base/name/absolute.rs index 4633d2048..394521d05 100644 --- a/src/base/name/absolute.rs +++ b/src/base/name/absolute.rs @@ -394,7 +394,7 @@ impl + ?Sized> Name { /// add a dot after the name, this method can be used to display the name /// always ending in a single dot. pub fn fmt_with_dot(&self) -> impl fmt::Display + '_ { - DisplayWithDot(self.for_slice()) + ToName::fmt_with_dot(self) } } @@ -1127,25 +1127,6 @@ impl<'a, Octs: Octets + ?Sized> Iterator for SuffixIter<'a, Octs> { } } -//------------ DisplayWithDot ------------------------------------------------ - -struct DisplayWithDot<'a>(&'a Name<[u8]>); - -impl<'a> fmt::Display for DisplayWithDot<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.0.is_root() { - f.write_str(".") - } else { - let mut iter = self.0.iter(); - write!(f, "{}", iter.next().unwrap())?; - for label in iter { - write!(f, ".{}", label)? - } - Ok(()) - } - } -} - //============ Error Types =================================================== //------------ NameError ----------------------------------------------------- diff --git a/src/base/name/traits.rs b/src/base/name/traits.rs index 3594584f3..47da86f59 100644 --- a/src/base/name/traits.rs +++ b/src/base/name/traits.rs @@ -8,8 +8,8 @@ use super::label::Label; use super::relative::RelativeName; #[cfg(feature = "bytes")] use bytes::Bytes; -use core::cmp; use core::convert::Infallible; +use core::{cmp, fmt}; use octseq::builder::{ infallible, BuilderAppendError, EmptyBuilder, FreezeBuilder, FromBuilder, OctetsBuilder, ShortBuf, @@ -343,6 +343,35 @@ pub trait ToName: ToLabelIter { labels.count() as u8 } } + + fn fmt_with_dot(&self) -> DisplayWithDot<'_, Self> { + DisplayWithDot(self) + } +} + +pub struct DisplayWithDot<'a, T: ?Sized>(&'a T); + +impl<'a, T> fmt::Display for DisplayWithDot<'a, T> +where + T: ToLabelIter + ?Sized, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut labels = self.0.iter_labels(); + let first = match labels.next() { + Some(first) => first, + None => unreachable!("at least 1 label must be present"), + }; + + if first.is_root() { + f.write_str(".") + } else { + write!(f, "{}", first)?; + for label in labels { + write!(f, ".{}", label)? + } + Ok(()) + } + } } impl<'a, N: ToName + ?Sized + 'a> ToName for &'a N {} diff --git a/src/base/opt/mod.rs b/src/base/opt/mod.rs index c99b2ce1d..a79ab1971 100644 --- a/src/base/opt/mod.rs +++ b/src/base/opt/mod.rs @@ -46,6 +46,7 @@ use super::name::{Name, ToName}; use super::rdata::{ComposeRecordData, ParseRecordData, RecordData}; use super::record::{Record, Ttl}; use super::wire::{Compose, Composer, FormError, ParseError}; +use super::zonefile_fmt::{self, Formatter, ZonefileFmt}; use crate::utils::base16; use core::cmp::Ordering; use core::marker::PhantomData; @@ -378,6 +379,15 @@ impl + ?Sized> fmt::Debug for Opt { } } +//--- ZonefileFmt + +impl + ?Sized> ZonefileFmt for Opt { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + // XXX TODO Print this properly. + p.write_token("OPT ...") + } +} + //------------ OptHeader ----------------------------------------------------- /// The header of an OPT record. diff --git a/src/base/rdata.rs b/src/base/rdata.rs index 8dd5e18cf..d33390113 100644 --- a/src/base/rdata.rs +++ b/src/base/rdata.rs @@ -19,6 +19,7 @@ use super::cmp::CanonicalOrd; use super::iana::Rtype; use super::scan::{Scan, Scanner, ScannerError, Symbol}; use super::wire::{Compose, Composer, ParseError}; +use super::zonefile_fmt::{self, Formatter, ZonefileFmt}; use crate::utils::base16; use core::cmp::Ordering; use core::fmt; @@ -477,6 +478,26 @@ impl> fmt::Debug for UnknownRecordData { } } +//--- ZonefileFmt + +impl> ZonefileFmt for UnknownRecordData { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + struct Data<'a>(&'a [u8]); + + impl fmt::Display for Data<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "\\# {}", self.0.len())?; + for ch in self.0 { + write!(f, " {:02x}", *ch)? + } + Ok(()) + } + } + + p.write_token(Data(self.data.as_ref())) + } +} + //============ Errors ======================================================== //------------ LongRecordData ------------------------------------------------ diff --git a/src/base/record.rs b/src/base/record.rs index 11f495a98..553e2e2e6 100644 --- a/src/base/record.rs +++ b/src/base/record.rs @@ -22,6 +22,7 @@ use super::rdata::{ ComposeRecordData, ParseAnyRecordData, ParseRecordData, RecordData, }; use super::wire::{Compose, Composer, FormError, Parse, ParseError}; +use super::zonefile_fmt::{self, Formatter, ZonefileFmt}; use core::cmp::Ordering; use core::time::Duration; use core::{fmt, hash}; @@ -431,6 +432,22 @@ where } } +//--- ZonefileFmt + +impl ZonefileFmt for Record +where + Name: ToName, + Data: RecordData + ZonefileFmt, +{ + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.write_token(self.owner.fmt_with_dot())?; + p.write_show(self.ttl)?; + p.write_show(self.class)?; + p.write_show(self.data.rtype())?; + p.write_show(&self.data) + } +} + //------------ ComposeRecord ------------------------------------------------- /// A helper trait allowing construction of records on the fly. @@ -1479,6 +1496,63 @@ impl Ttl { .map(Ttl::from_secs) .map_err(Into::into) } + + /// Display the [`Ttl`] in a pretty format with time units + /// + /// This writes the TTL as a duration with weeks, days, hours, minutes + /// and seconds. For example: + /// + /// ```txt + /// 5 weeks 1 day 30 seconds + /// ``` + /// + /// In most cases it will be a single unit, because people tend to pick + /// a nice number as TTL. + pub(crate) fn pretty(&self) -> impl fmt::Display { + struct Inner { + inner: Ttl, + } + + impl fmt::Display for Inner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let days = self.inner.as_days(); + let weeks = days / 7; + let days = days % 7; + let hours = self.inner.as_hours() % 24; + let minutes = self.inner.as_minutes() % 60; + let seconds = self.inner.as_secs() % 60; + + let mut first = true; + for (n, unit) in [ + (weeks, "week"), + (days, "day"), + (hours as u16, "hour"), + (minutes as u16, "minute"), + (seconds as u16, "second"), + ] { + if n == 0 { + continue; + } + if first { + write!(f, " ")?; + } + let s = if n > 1 { "s" } else { "" }; + write!(f, "{n} {unit}{s}")?; + first = false; + } + + Ok(()) + } + } + + Inner { inner: *self } + } +} + +impl ZonefileFmt for Ttl { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.write_token(self.as_secs()) + } } impl core::ops::Add for Ttl { diff --git a/src/base/zonefile_fmt.rs b/src/base/zonefile_fmt.rs new file mode 100644 index 000000000..014fe727d --- /dev/null +++ b/src/base/zonefile_fmt.rs @@ -0,0 +1,337 @@ +use core::fmt; + +#[derive(Clone, Copy, Debug)] +pub struct Error; + +impl From for Error { + fn from(_: fmt::Error) -> Self { + Self + } +} + +pub type Result = core::result::Result<(), Error>; + +pub struct ZoneFileDisplay<'a, T: ?Sized> { + inner: &'a T, + pretty: bool, +} + +impl fmt::Display for ZoneFileDisplay<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.pretty { + self.inner + .fmt(&mut MultiLineWriter::new(f)) + .map_err(|_| fmt::Error) + } else { + self.inner + .fmt(&mut SimpleWriter::new(f)) + .map_err(|_| fmt::Error) + } + } +} + +/// Show a value as zonefile format +pub trait ZonefileFmt { + fn fmt(&self, p: &mut impl Formatter) -> Result; + + fn display_zonefile(&self, pretty: bool) -> ZoneFileDisplay<'_, Self> { + ZoneFileDisplay { + inner: self, + pretty, + } + } +} + +impl ZonefileFmt for &T { + fn fmt(&self, p: &mut impl Formatter) -> Result { + T::fmt(self, p) + } +} + +/// Determines how a zonefile is formatted +pub trait FormatWriter: Sized { + /// Push a token to the zonefile + fn fmt_token(&mut self, args: fmt::Arguments<'_>) -> Result; + + /// Start a block of grouped tokens + /// + /// This might push `'('` to the zonefile, but may be ignored by the + /// `PresentationWriter`. + fn begin_block(&mut self) -> Result; + + /// End a block of grouped tokens + /// + /// This might push `'('` to the zonefile, but may be ignored by the + /// `PresentationWriter`. + fn end_block(&mut self) -> Result; + + /// Write a comment + /// + /// This may be ignored. + fn fmt_comment(&mut self, args: fmt::Arguments<'_>) -> Result; + + /// End the current record and start a new line + fn newline(&mut self) -> Result; +} + +/// The simplest possible zonefile writer +/// +/// This writer does not do any alignment, comments and squeezes each record +/// onto a single line. +struct SimpleWriter { + first: bool, + writer: W, +} + +impl SimpleWriter { + fn new(writer: W) -> Self { + Self { + first: true, + writer, + } + } +} + +impl FormatWriter for SimpleWriter { + fn fmt_token(&mut self, args: fmt::Arguments<'_>) -> Result { + if !self.first { + self.writer.write_char(' ')?; + } + self.first = false; + self.writer.write_fmt(args)?; + Ok(()) + } + + fn begin_block(&mut self) -> Result { + Ok(()) + } + + fn end_block(&mut self) -> Result { + Ok(()) + } + + fn fmt_comment(&mut self, _args: fmt::Arguments<'_>) -> Result { + Ok(()) + } + + fn newline(&mut self) -> Result { + self.writer.write_char('\n')?; + self.first = true; + Ok(()) + } +} + +struct MultiLineWriter { + current_column: usize, + block_indent: Option, + first: bool, + writer: W, +} + +impl MultiLineWriter { + fn new(writer: W) -> Self { + Self { + first: true, + current_column: 0, + block_indent: None, + writer, + } + } +} + +impl FormatWriter for MultiLineWriter { + fn fmt_token(&mut self, args: fmt::Arguments<'_>) -> Result { + use fmt::Write; + if !self.first { + self.write_str(" ")?; + } + self.first = false; + self.write_fmt(args)?; + Ok(()) + } + + fn begin_block(&mut self) -> Result { + self.fmt_token(format_args!("("))?; + self.block_indent = Some(self.current_column + 1); + Ok(()) + } + + fn end_block(&mut self) -> Result { + self.block_indent = None; + self.fmt_token(format_args!(")")) + } + + fn fmt_comment(&mut self, args: fmt::Arguments<'_>) -> Result { + if self.block_indent.is_some() { + write!(self.writer, "\t; {}", args)?; + self.newline() + } else { + // a comment should not have been allowed + // so ignore it + Ok(()) + } + } + + fn newline(&mut self) -> Result { + use fmt::Write; + self.writer.write_char('\n')?; + self.current_column = 0; + if let Some(x) = self.block_indent { + for _ in 0..x { + self.write_str(" ")?; + } + } + self.first = true; + Ok(()) + } +} + +impl fmt::Write for MultiLineWriter { + fn write_str(&mut self, x: &str) -> fmt::Result { + self.current_column += x.len(); + self.writer.write_str(x) + } +} + +/// A more structured wrapper around a [`PresentationWriter`] +pub trait Formatter: FormatWriter { + /// Start a sequence of grouped tokens + /// + /// The block might be surrounded by `(` and `)` in a multiline format. + fn block(&mut self, f: impl Fn(&mut Self) -> Result) -> Result { + self.begin_block()?; + f(self)?; + self.end_block() + } + + /// Push a token + fn write_token(&mut self, token: impl fmt::Display) -> Result { + self.fmt_token(format_args!("{token}")) + } + + /// Call the `show` method on `item` with this `Presenter` + fn write_show(&mut self, item: impl ZonefileFmt) -> Result { + item.fmt(self) + } + + /// Write a comment + /// + /// This may be ignored. + fn write_comment(&mut self, s: impl fmt::Display) -> Result { + self.fmt_comment(format_args!("{s}")) + } +} + +impl Formatter for T {} + +#[cfg(all(test, feature = "std"))] +mod test { + use std::string::ToString as _; + use std::vec::Vec; + + use crate::base::iana::{Class, DigestAlg, SecAlg}; + use crate::base::zonefile_fmt::ZonefileFmt; + use crate::base::{Name, Record, Ttl}; + use crate::rdata::{Cds, Cname, Ds, Mx, Txt, A}; + + fn create_record(data: Data) -> Record<&'static Name<[u8]>, Data> { + let name = Name::from_slice(b"\x07example\x03com\x00").unwrap(); + Record::new(name, Class::IN, Ttl::from_secs(3600), data) + } + + #[test] + fn a_record() { + let record = create_record(A::new("128.140.76.106".parse().unwrap())); + assert_eq!( + "example.com. 3600 IN A 128.140.76.106", + record.display_zonefile(false).to_string() + ); + } + + #[test] + fn cname_record() { + let record = create_record(Cname::new( + Name::from_slice(b"\x07example\x03com\x00").unwrap(), + )); + assert_eq!( + "example.com. 3600 IN CNAME example.com.", + record.display_zonefile(false).to_string() + ); + } + + #[test] + fn ds_key_record() { + let record = create_record( + Ds::new( + 5414, + SecAlg::ED25519, + DigestAlg::SHA256, + &[0xDE, 0xAD, 0xBE, 0xEF], + ) + .unwrap(), + ); + assert_eq!( + "example.com. 3600 IN DS 5414 15 2 DEADBEEF", + record.display_zonefile(false).to_string() + ); + assert_eq!( + [ + "example.com. 3600 IN DS ( 5414\t; key tag", + " 15\t; algorithm: 15(ED25519)", + " 2\t; digest type: 2(SHA-256)", + " DEADBEEF )", + ] + .join("\n"), + record.display_zonefile(true).to_string() + ); + } + + #[test] + fn cds_record() { + let record = create_record( + Cds::new( + 5414, + SecAlg::ED25519, + DigestAlg::SHA256, + &[0xDE, 0xAD, 0xBE, 0xEF], + ) + .unwrap(), + ); + assert_eq!( + "example.com. 3600 IN CDS 5414 15 2 DEADBEEF", + record.display_zonefile(false).to_string() + ); + } + + #[test] + fn mx_record() { + let record = create_record(Mx::new( + 20, + Name::from_slice(b"\x07example\x03com\x00").unwrap(), + )); + assert_eq!( + "example.com. 3600 IN MX 20 example.com.", + record.display_zonefile(false).to_string() + ); + } + + #[test] + fn txt_record() { + let record = create_record(Txt::>::build_from_slice( + b"this is a string that is longer than 255 characters if I just \ + type a little bit more to pad this test out and then write some \ + more like a silly monkey with a typewriter accidentally writing \ + some shakespeare along the way but it feels like I have to type \ + even longer to hit that limit!\ + ").unwrap()); + assert_eq!( + "example.com. 3600 IN TXT \ + \"this is a string that is longer than 255 characters if I just \ + type a little bit more to pad this test out and then write some \ + more like a silly monkey with a typewriter accidentally writing \ + some shakespeare along the way but it feels like I have to type \ + e\" \"ven longer to hit that limit!\"", + record.display_zonefile(false).to_string() + ); + } +} diff --git a/src/rdata/aaaa.rs b/src/rdata/aaaa.rs index 3cd8bc0da..d8636b720 100644 --- a/src/rdata/aaaa.rs +++ b/src/rdata/aaaa.rs @@ -10,6 +10,7 @@ use crate::base::net::Ipv6Addr; use crate::base::rdata::{ComposeRecordData, ParseRecordData, RecordData}; use crate::base::scan::{Scanner, ScannerError}; use crate::base::wire::{Composer, Parse, ParseError}; +use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt}; use core::cmp::Ordering; use core::convert::Infallible; use core::str::FromStr; @@ -156,6 +157,14 @@ impl fmt::Display for Aaaa { } } +//--- ZonefileFmt + +impl ZonefileFmt for Aaaa { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.write_token(self.addr) + } +} + //--- AsRef and AsMut impl AsRef for Aaaa { diff --git a/src/rdata/cds.rs b/src/rdata/cds.rs index 0386538c0..3dd6df2b0 100644 --- a/src/rdata/cds.rs +++ b/src/rdata/cds.rs @@ -4,10 +4,11 @@ use crate::base::cmp::CanonicalOrd; use crate::base::iana::{DigestAlg, Rtype, SecAlg}; use crate::base::rdata::{ - ComposeRecordData, LongRecordData, ParseRecordData, RecordData + ComposeRecordData, LongRecordData, ParseRecordData, RecordData, }; use crate::base::scan::{Scan, Scanner, ScannerError}; use crate::base::wire::{Compose, Composer, Parse, ParseError}; +use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt}; use crate::utils::{base16, base64}; use core::cmp::Ordering; use core::{fmt, hash}; @@ -57,11 +58,15 @@ impl Cdnskey { algorithm: SecAlg, public_key: Octs, ) -> Result - where Octs: AsRef<[u8]> { + where + Octs: AsRef<[u8]>, + { LongRecordData::check_len( usize::from( - u16::COMPOSE_LEN + u8::COMPOSE_LEN + SecAlg::COMPOSE_LEN - ).checked_add(public_key.as_ref().len()).expect("long key") + u16::COMPOSE_LEN + u8::COMPOSE_LEN + SecAlg::COMPOSE_LEN, + ) + .checked_add(public_key.as_ref().len()) + .expect("long key"), )?; Ok(unsafe { Cdnskey::new_unchecked(flags, protocol, algorithm, public_key) @@ -143,13 +148,16 @@ impl Cdnskey { pub fn scan>( scanner: &mut S, ) -> Result - where Octs: AsRef<[u8]> { + where + Octs: AsRef<[u8]>, + { Self::new( u16::scan(scanner)?, u8::scan(scanner)?, SecAlg::scan(scanner)?, scanner.convert_entry(base64::SymbolConverter::new())?, - ).map_err(|err| S::Error::custom(err.as_str())) + ) + .map_err(|err| S::Error::custom(err.as_str())) } } @@ -319,6 +327,19 @@ impl> fmt::Debug for Cdnskey { } } +//--- ZonefileFmt + +impl> ZonefileFmt for Cdnskey { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.block(|p| { + p.write_token(self.flags)?; + p.write_token(self.protocol)?; + p.write_show(self.algorithm)?; + p.write_token(base64::encode_display(&self.public_key)) + }) + } +} + //------------ Cds ----------------------------------------------------------- #[derive(Clone)] @@ -362,11 +383,17 @@ impl Cds { digest_type: DigestAlg, digest: Octs, ) -> Result - where Octs: AsRef<[u8]> { + where + Octs: AsRef<[u8]>, + { LongRecordData::check_len( usize::from( - u16::COMPOSE_LEN + SecAlg::COMPOSE_LEN + DigestAlg::COMPOSE_LEN - ).checked_add(digest.as_ref().len()).expect("long digest") + u16::COMPOSE_LEN + + SecAlg::COMPOSE_LEN + + DigestAlg::COMPOSE_LEN, + ) + .checked_add(digest.as_ref().len()) + .expect("long digest"), )?; Ok(unsafe { Cds::new_unchecked(key_tag, algorithm, digest_type, digest) @@ -452,13 +479,16 @@ impl Cds { pub fn scan>( scanner: &mut S, ) -> Result - where Octs: AsRef<[u8]> { + where + Octs: AsRef<[u8]>, + { Self::new( u16::scan(scanner)?, SecAlg::scan(scanner)?, DigestAlg::scan(scanner)?, scanner.convert_entry(base16::SymbolConverter::new())?, - ).map_err(|err| S::Error::custom(err.as_str())) + ) + .map_err(|err| S::Error::custom(err.as_str())) } } @@ -646,6 +676,20 @@ impl> fmt::Debug for Cds { } } +//--- ZonefileFmt + +impl> ZonefileFmt for Cds { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.block(|p| { + p.write_token(self.key_tag)?; + p.write_comment("key tag")?; + p.write_show(self.algorithm)?; + p.write_show(self.digest_type)?; + p.write_token(base16::encode_display(&self.digest)) + }) + } +} + //------------ parsed -------------------------------------------------------- pub mod parsed { @@ -670,7 +714,7 @@ mod test { let rdata = Cdnskey::new(10, 11, SecAlg::RSASHA1, b"key").unwrap(); test_rdlen(&rdata); test_compose_parse(&rdata, |parser| Cdnskey::parse(parser)); - test_scan(&["10", "11", "RSASHA1", "a2V5"], Cdnskey::scan, &rdata); + test_scan(&["10", "11", "5", "a2V5"], Cdnskey::scan, &rdata); } //--- Cds @@ -678,11 +722,10 @@ mod test { #[test] #[allow(clippy::redundant_closure)] // lifetimes ... fn cds_compose_parse_scan() { - let rdata = Cds::new( - 10, SecAlg::RSASHA1, DigestAlg::SHA256, b"key" - ).unwrap(); + let rdata = + Cds::new(10, SecAlg::RSASHA1, DigestAlg::SHA256, b"key").unwrap(); test_rdlen(&rdata); test_compose_parse(&rdata, |parser| Cds::parse(parser)); - test_scan(&["10", "RSASHA1", "2", "6b6579"], Cds::scan, &rdata); + test_scan(&["10", "5", "2", "6b6579"], Cds::scan, &rdata); } } diff --git a/src/rdata/dnssec.rs b/src/rdata/dnssec.rs index a1eb3959b..fdb79dd52 100644 --- a/src/rdata/dnssec.rs +++ b/src/rdata/dnssec.rs @@ -13,6 +13,7 @@ use crate::base::rdata::{ use crate::base::scan::{Scan, Scanner, ScannerError}; use crate::base::serial::Serial; use crate::base::wire::{Compose, Composer, FormError, Parse, ParseError}; +use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt}; use crate::base::Ttl; use crate::utils::{base16, base64}; use core::cmp::Ordering; @@ -431,6 +432,32 @@ impl> fmt::Debug for Dnskey { } } +//--- ZonefileFmt + +impl> ZonefileFmt for Dnskey { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + let revoked = self.is_revoked(); + let sep = self.is_secure_entry_point(); + let zone_key = self.is_zone_key(); + + p.block(|p| { + p.write_token(self.flags)?; + p.write_comment(format_args!( + "flags:{}{}{}{}", + if revoked { " revoked" } else { "" }, + if sep { " sep" } else { "" }, + if zone_key { " zone_key" } else { "" }, + if self.flags == 0 { " " } else { "" }, + ))?; + p.write_token(self.protocol)?; + p.write_comment("protocol")?; + p.write_show(self.algorithm)?; + p.write_token(base64::encode_display(&self.public_key))?; + p.write_comment(format_args!("key tag: {}", self.key_tag())) + }) + } +} + //------------ ProtoRrsig ---------------------------------------------------- /// The RRSIG RDATA to be included when creating the signature. @@ -757,6 +784,14 @@ impl fmt::Display for Timestamp { } } +//--- ZonefileFmt + +impl ZonefileFmt for Timestamp { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.write_token(self.0) + } +} + //--- PartialOrd and CanonicalOrd impl cmp::PartialOrd for Timestamp { @@ -1354,6 +1389,34 @@ where } } +//--- ZonefileFmt + +impl ZonefileFmt for Rrsig +where + Octs: AsRef<[u8]>, + Name: ToName, +{ + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.block(|p| { + p.write_show(self.type_covered)?; + p.write_show(self.algorithm)?; + p.write_token(self.labels)?; + p.write_comment("labels")?; + p.write_show(self.original_ttl)?; + p.write_comment("original ttl")?; + p.write_show(self.expiration)?; + p.write_comment("expiration")?; + p.write_show(self.inception)?; + p.write_comment("inception")?; + p.write_token(self.key_tag)?; + p.write_comment("key tag")?; + p.write_token(self.signer_name.fmt_with_dot())?; + p.write_comment("signer name")?; + p.write_token(base64::encode_display(&self.signature)) + }) + } +} + //------------ Nsec ---------------------------------------------------------- #[derive(Clone)] @@ -1634,6 +1697,21 @@ where } } +//--- ZonefileFmt + +impl ZonefileFmt for Nsec +where + Octs: AsRef<[u8]>, + Name: ToName, +{ + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.block(|p| { + p.write_token(self.next_name.fmt_with_dot())?; + p.write_show(&self.types) + }) + } +} + //------------ Ds ----------------------------------------------------------- #[derive(Clone)] @@ -1967,6 +2045,20 @@ impl> fmt::Debug for Ds { } } +//--- ZonefileFmt + +impl> ZonefileFmt for Ds { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.block(|p| { + p.write_token(self.key_tag)?; + p.write_comment("key tag")?; + p.write_show(self.algorithm)?; + p.write_show(self.digest_type)?; + p.write_token(base16::encode_display(&self.digest)) + }) + } +} + //------------ RtypeBitmap --------------------------------------------------- #[derive(Clone)] @@ -2169,7 +2261,7 @@ impl> fmt::Display for RtypeBitmap { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut iter = self.iter(); if let Some(rtype) = iter.next() { - rtype.fmt(f)?; + fmt::Display::fmt(&rtype, f)?; } for rtype in iter { write!(f, " {}", rtype)? @@ -2178,6 +2270,17 @@ impl> fmt::Display for RtypeBitmap { } } +//--- ZonefileFmt + +impl> ZonefileFmt for RtypeBitmap { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + for rtype in self { + p.write_token(rtype)?; + } + Ok(()) + } +} + //--- Debug impl> fmt::Debug for RtypeBitmap { @@ -2432,8 +2535,8 @@ where let buf_len = self.buf.as_ref().len(); for src_pos in (0..buf_len).step_by(34) { let chunk_len = (self.buf.as_ref()[src_pos + 1] as usize) + 2; - let buf = self.buf.as_mut(); - buf.copy_within(src_pos..src_pos+chunk_len, dst_pos); + let buf = self.buf.as_mut(); + buf.copy_within(src_pos..src_pos + chunk_len, dst_pos); dst_pos += chunk_len; } self.buf.truncate(dst_pos); @@ -2648,7 +2751,7 @@ mod test { let rdata = Dnskey::new(10, 11, SecAlg::RSASHA1, b"key0").unwrap(); test_rdlen(&rdata); test_compose_parse(&rdata, |parser| Dnskey::parse(parser)); - test_scan(&["10", "11", "RSASHA1", "a2V5MA=="], Dnskey::scan, &rdata); + test_scan(&["10", "11", "5", "a2V5MA=="], Dnskey::scan, &rdata); } //--- Rrsig @@ -2673,7 +2776,7 @@ mod test { test_scan( &[ "A", - "RSASHA1", + "5", "3", "12", "13", @@ -2720,7 +2823,7 @@ mod test { Ds::new(10, SecAlg::RSASHA1, DigestAlg::SHA256, b"key").unwrap(); test_rdlen(&rdata); test_compose_parse(&rdata, |parser| Ds::parse(parser)); - test_scan(&["10", "RSASHA1", "2", "6b6579"], Ds::scan, &rdata); + test_scan(&["10", "5", "2", "6b6579"], Ds::scan, &rdata); } //--- RtypeBitmape diff --git a/src/rdata/macros.rs b/src/rdata/macros.rs index 55c0f4029..5a8a75e5b 100644 --- a/src/rdata/macros.rs +++ b/src/rdata/macros.rs @@ -499,6 +499,28 @@ macro_rules! rdata_types { } } + ///--- ZonefileFmt + + impl $crate::base::zonefile_fmt::ZonefileFmt for ZoneRecordData + where + O: AsRef<[u8]>, + N: ToName, + { + fn fmt( + &self, + p: &mut impl $crate::base::zonefile_fmt::Formatter + ) -> $crate::base::zonefile_fmt::Result { + match *self { + $( $( $( + ZoneRecordData::$mtype(ref inner) => { + inner.fmt(p) + } + )* )* )* + ZoneRecordData::Unknown(ref inner) => inner.fmt(p), + } + } + } + //------------- AllRecordData ---------------------------------------- /// Record data for all record types. @@ -1113,6 +1135,29 @@ macro_rules! rdata_types { } } + //--- ZonefileFmt + + impl $crate::base::zonefile_fmt::ZonefileFmt for AllRecordData + where O: Octets, N: ToName { + fn fmt( + &self, f: &mut impl $crate::base::zonefile_fmt::Formatter + ) -> $crate::base::zonefile_fmt::Result { + match *self { + $( $( $( + AllRecordData::$mtype(ref inner) => { + inner.fmt(f) + } + )* )* )* + $( $( $( + AllRecordData::$ptype(ref inner) => { + inner.fmt(f) + } + )* )* )* + AllRecordData::Opt(ref inner) => inner.fmt(f), + AllRecordData::Unknown(ref inner) => inner.fmt(f), + } + } + } } } @@ -1299,6 +1344,14 @@ macro_rules! name_type_base { write!(f, "{}.", self.$field) } } + + //--- ZonefileFmt + + impl $crate::base::zonefile_fmt::ZonefileFmt for $target { + fn fmt(&self, p: &mut impl $crate::base::zonefile_fmt::Formatter) -> $crate::base::zonefile_fmt::Result { + p.write_token(self.$field.fmt_with_dot()) + } + } } } diff --git a/src/rdata/nsec3.rs b/src/rdata/nsec3.rs index c890c2db8..8d6ac915c 100644 --- a/src/rdata/nsec3.rs +++ b/src/rdata/nsec3.rs @@ -11,6 +11,7 @@ use crate::base::rdata::{ComposeRecordData, ParseRecordData, RecordData}; use crate::base::scan::{ ConvertSymbols, EntrySymbol, Scan, Scanner, ScannerError, }; +use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt}; use crate::base::wire::{Compose, Composer, Parse, ParseError}; use crate::utils::{base16, base32}; #[cfg(feature = "bytes")] @@ -370,6 +371,29 @@ impl> fmt::Debug for Nsec3 { } } +//--- ZonefileFmt + +impl> ZonefileFmt for Nsec3 +where + Octs: AsRef<[u8]>, +{ + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.block(|p| { + p.write_show(self.hash_algorithm)?; + p.write_token(self.flags)?; + p.write_comment(format_args!( + "flags: {}", + if self.opt_out() { "opt-out" } else { "" } + ))?; + p.write_token(self.iterations)?; + p.write_comment("iterations")?; + p.write_show(&self.salt)?; + p.write_token(base32::encode_display_hex(&self.next_owner))?; + p.write_show(&self.types) + }) + } +} + //------------ Nsec3Param ---------------------------------------------------- #[derive(Clone)] @@ -662,6 +686,21 @@ impl> fmt::Debug for Nsec3param { } } +//--- ZonefileFmt + +impl> ZonefileFmt for Nsec3param { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.block(|p| { + p.write_show(self.hash_algorithm)?; + p.write_token(self.flags)?; + p.write_comment("flags")?; + p.write_token(self.iterations)?; + p.write_comment("iterations")?; + p.write_show(&self.salt) + }) + } +} + //------------ Nsec3Salt ----------------------------------------------------- /// The salt value of an NSEC3 record. @@ -979,6 +1018,21 @@ impl + ?Sized> fmt::Debug for Nsec3Salt { } } +//--- ZonefileFmt + +impl + ?Sized> ZonefileFmt for Nsec3Salt { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.block(|p| { + if self.as_slice().is_empty() { + p.write_token("-")?; + } else { + p.write_token(base16::encode_display(self))?; + } + p.write_comment(format_args!("salt (length: {})", self.salt_len())) + }) + } +} + //--- Serialize and Deserialize #[cfg(feature = "serde")] @@ -1513,7 +1567,7 @@ mod test { Nsec3::scan, &rdata, ); - assert_eq!(&format!("{rdata}"), "1 10 11 626172 CPNMU A SRV"); + assert_eq!(&format!("{}", rdata.display_zonefile(false)), "1 10 11 626172 CPNMU A SRV"); } #[test] @@ -1537,7 +1591,7 @@ mod test { Nsec3::scan, &rdata, ); - assert_eq!(&format!("{rdata}"), "1 10 11 - CPNMU A SRV"); + assert_eq!(&format!("{}", rdata.display_zonefile(false)), "1 10 11 - CPNMU A SRV"); } #[test] diff --git a/src/rdata/rfc1035/a.rs b/src/rdata/rfc1035/a.rs index 885555eee..2ed8b7ad5 100644 --- a/src/rdata/rfc1035/a.rs +++ b/src/rdata/rfc1035/a.rs @@ -5,15 +5,14 @@ use crate::base::cmp::CanonicalOrd; use crate::base::iana::Rtype; use crate::base::net::Ipv4Addr; -use crate::base::rdata::{ - ComposeRecordData, ParseRecordData, RecordData, -}; +use crate::base::rdata::{ComposeRecordData, ParseRecordData, RecordData}; use crate::base::scan::{Scanner, ScannerError}; use crate::base::wire::{Composer, Parse, ParseError}; -use core::{fmt, str}; +use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt}; use core::cmp::Ordering; use core::convert::Infallible; use core::str::FromStr; +use core::{fmt, str}; use octseq::octets::OctetsFrom; use octseq::parse::Parser; @@ -26,7 +25,7 @@ use octseq::parse::Parser; /// is the usual dotted notation. /// /// The A record type is defined in [RFC 1035, section 3.4.1][1]. -/// +/// /// [1]: https://tools.ietf.org/html/rfc1035#section-3.4.1 #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -172,6 +171,14 @@ impl fmt::Display for A { } } +//--- ZonefileFmt + +impl ZonefileFmt for A { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.write_token(self.addr) + } +} + //--- AsRef and AsMut impl AsRef for A { @@ -204,4 +211,3 @@ mod test { test_scan(&["1.2.3.4"], A::scan, &rdata); } } - diff --git a/src/rdata/rfc1035/hinfo.rs b/src/rdata/rfc1035/hinfo.rs index 974cc5044..5381cea03 100644 --- a/src/rdata/rfc1035/hinfo.rs +++ b/src/rdata/rfc1035/hinfo.rs @@ -5,13 +5,14 @@ use crate::base::charstr::CharStr; use crate::base::cmp::CanonicalOrd; use crate::base::iana::Rtype; -use crate::base::rdata::{ - ComposeRecordData, ParseRecordData, RecordData, -}; +use crate::base::rdata::{ComposeRecordData, ParseRecordData, RecordData}; use crate::base::scan::Scanner; use crate::base::wire::{Composer, ParseError}; -use core::{fmt, hash}; +use crate::base::zonefile_fmt::{ + self, Formatter, ZonefileFmt, +}; use core::cmp::Ordering; +use core::{fmt, hash}; #[cfg(feature = "serde")] use octseq::builder::{EmptyBuilder, FromBuilder}; use octseq::octets::{Octets, OctetsFrom, OctetsInto}; @@ -236,6 +237,19 @@ impl> fmt::Debug for Hinfo { } } +//--- ZonefileFmt + +impl> ZonefileFmt for Hinfo { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.block(|p| { + p.write_token(&self.cpu)?; + p.write_comment("cpu")?; + p.write_token(&self.os)?; + p.write_comment("os") + }) + } +} + //============ Testing ======================================================= #[cfg(test)] @@ -268,4 +282,3 @@ mod test { assert_eq!(hinfo.os(), hinfo_bytes.os()); } } - diff --git a/src/rdata/rfc1035/minfo.rs b/src/rdata/rfc1035/minfo.rs index 7e24a717c..b91be384c 100644 --- a/src/rdata/rfc1035/minfo.rs +++ b/src/rdata/rfc1035/minfo.rs @@ -5,13 +5,14 @@ use crate::base::cmp::CanonicalOrd; use crate::base::iana::Rtype; use crate::base::name::{FlattenInto, ParsedName, ToName}; -use crate::base::rdata::{ - ComposeRecordData, ParseRecordData, RecordData, -}; +use crate::base::rdata::{ComposeRecordData, ParseRecordData, RecordData}; use crate::base::scan::Scanner; use crate::base::wire::{Composer, ParseError}; -use core::fmt; +use crate::base::zonefile_fmt::{ + self, Formatter, ZonefileFmt, +}; use core::cmp::Ordering; +use core::fmt; use octseq::octets::{Octets, OctetsFrom, OctetsInto}; use octseq::parse::Parser; @@ -26,7 +27,7 @@ use octseq::parse::Parser; /// The Minfo record is experimental. /// /// The Minfo record type is defined in RFC 1035, section 3.3.7. -/// +/// /// [1]: https://tools.ietf.org/html/rfc1035#section-3.3.7 #[derive(Clone, Debug, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -77,7 +78,9 @@ impl Minfo { pub(in crate::rdata) fn flatten( self, ) -> Result, N::AppendError> - where N: FlattenInto { + where + N: FlattenInto, + { Ok(Minfo::new( self.rmailbx.try_flatten_into()?, self.emailbx.try_flatten_into()?, @@ -242,6 +245,19 @@ impl fmt::Display for Minfo { } } +//--- ZonefileFmt + +impl ZonefileFmt for Minfo { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.block(|p| { + p.write_token(self.rmailbx.fmt_with_dot())?; + p.write_comment("responsible mailbox")?; + p.write_token(self.emailbx.fmt_with_dot())?; + p.write_comment("error mailbox") + }) + } +} + //============ Testing ======================================================= #[cfg(test)] @@ -279,4 +295,3 @@ mod test { assert_eq!(minfo.emailbx(), minfo_bytes.emailbx()); } } - diff --git a/src/rdata/rfc1035/mx.rs b/src/rdata/rfc1035/mx.rs index 15d0d9724..f12db75e4 100644 --- a/src/rdata/rfc1035/mx.rs +++ b/src/rdata/rfc1035/mx.rs @@ -9,6 +9,7 @@ use crate::base::rdata::{ ComposeRecordData, ParseRecordData, RecordData, }; use crate::base::scan::{Scan, Scanner}; +use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt}; use crate::base::wire::{Compose, Composer, Parse, ParseError}; use core::fmt; use core::cmp::Ordering; @@ -227,6 +228,18 @@ impl fmt::Display for Mx { } } +//--- ZonefileFmt + +impl ZonefileFmt for Mx { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.block(|p| { + p.write_token(self.preference)?; + p.write_comment("preference")?; + p.write_token(self.exchange.fmt_with_dot()) + }) + } +} + //============ Testing ======================================================= #[cfg(test)] diff --git a/src/rdata/rfc1035/null.rs b/src/rdata/rfc1035/null.rs index eee597cac..0c905b8b7 100644 --- a/src/rdata/rfc1035/null.rs +++ b/src/rdata/rfc1035/null.rs @@ -12,8 +12,11 @@ use crate::base::rdata::{ ComposeRecordData, LongRecordData, ParseRecordData, RecordData, }; use crate::base::wire::{Composer, ParseError}; -use core::{fmt, hash, mem}; +use crate::base::zonefile_fmt::{ + self, Formatter, ZonefileFmt, +}; use core::cmp::Ordering; +use core::{fmt, hash, mem}; use octseq::octets::{Octets, OctetsFrom, OctetsInto}; use octseq::parse::Parser; @@ -25,7 +28,7 @@ use octseq::parse::Parser; /// allowed in zone files. /// /// The Null record type is defined in [RFC 1035, section 3.3.10][1]. -/// +/// /// [1]: https://tools.ietf.org/html/rfc1035#section-3.3.10 #[derive(Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -281,6 +284,26 @@ impl> fmt::Debug for Null { } } +//--- ZonefileFmt + +impl> ZonefileFmt for Null { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + struct Data<'a>(&'a [u8]); + + impl fmt::Display for Data<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "\\# {}", self.0.len())?; + for ch in self.0 { + write!(f, " {:02x}", *ch)? + } + Ok(()) + } + } + + p.write_token(Data(self.data.as_ref())) + } +} + //============ Testing ======================================================= #[cfg(test)] @@ -297,4 +320,3 @@ mod test { test_compose_parse(&rdata, |parser| Null::parse(parser)); } } - diff --git a/src/rdata/rfc1035/soa.rs b/src/rdata/rfc1035/soa.rs index 45fa57b7f..0c48044a7 100644 --- a/src/rdata/rfc1035/soa.rs +++ b/src/rdata/rfc1035/soa.rs @@ -5,15 +5,16 @@ use crate::base::cmp::CanonicalOrd; use crate::base::iana::Rtype; use crate::base::name::{FlattenInto, ParsedName, ToName}; -use crate::base::rdata::{ - ComposeRecordData, ParseRecordData, RecordData, -}; +use crate::base::rdata::{ComposeRecordData, ParseRecordData, RecordData}; use crate::base::record::Ttl; use crate::base::scan::{Scan, Scanner}; use crate::base::serial::Serial; use crate::base::wire::{Compose, Composer, ParseError}; -use core::fmt; +use crate::base::zonefile_fmt::{ + self, Formatter, ZonefileFmt, +}; use core::cmp::Ordering; +use core::fmt; use octseq::octets::{Octets, OctetsFrom, OctetsInto}; use octseq::parse::Parser; @@ -25,7 +26,7 @@ use octseq::parse::Parser; /// name server maintenance operations. /// /// The Soa record type is defined in [RFC 1035, section 3.3.13][1]. -/// +/// /// [1]: https://tools.ietf.org/html/rfc1035#section-3.3.13 #[derive(Clone, Debug, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -118,7 +119,9 @@ impl Soa { pub(in crate::rdata) fn flatten( self, ) -> Result, N::AppendError> - where N: FlattenInto { + where + N: FlattenInto, + { Ok(Soa::new( self.mname.try_flatten_into()?, self.rname.try_flatten_into()?, @@ -401,6 +404,38 @@ impl fmt::Display for Soa { } } +impl ZonefileFmt for Soa { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.block(|p| { + p.write_token(self.mname.fmt_with_dot())?; + p.write_comment("mname")?; + p.write_token(self.rname.fmt_with_dot())?; + p.write_comment("rname")?; + p.write_token(self.serial)?; + p.write_comment("serial")?; + p.write_show(self.refresh)?; + p.write_comment(format_args!( + "refresh ({})", + self.refresh.pretty(), + ))?; + p.write_show(self.retry)?; + p.write_comment( + format_args!("retry ({})", self.retry.pretty(),), + )?; + p.write_show(self.expire)?; + p.write_comment(format_args!( + "expire ({})", + self.expire.pretty(), + ))?; + p.write_show(self.minimum)?; + p.write_comment(format_args!( + "minumum ({})", + self.minimum.pretty(), + )) + }) + } +} + //============ Testing ======================================================= #[cfg(test)] @@ -443,4 +478,3 @@ mod test { ); } } - diff --git a/src/rdata/rfc1035/txt.rs b/src/rdata/rfc1035/txt.rs index e8f44d00f..fb680d24a 100644 --- a/src/rdata/rfc1035/txt.rs +++ b/src/rdata/rfc1035/txt.rs @@ -14,6 +14,7 @@ use crate::base::scan::Scanner; #[cfg(feature = "serde")] use crate::base::scan::Symbol; use crate::base::wire::{Composer, FormError, ParseError}; +use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt}; #[cfg(feature = "bytes")] use bytes::BytesMut; use core::cmp::Ordering; @@ -161,7 +162,7 @@ impl Txt<[u8]> { /// Checks that a slice contains correctly encoded TXT data. fn check_slice(mut slice: &[u8]) -> Result<(), TxtError> { if slice.is_empty() { - return Err(TxtError(TxtErrorInner::Empty)) + return Err(TxtError(TxtErrorInner::Empty)); } LongRecordData::check_len(slice.len())?; while let Some(&len) = slice.first() { @@ -445,6 +446,22 @@ impl> fmt::Debug for Txt { } } +//--- ZonefileFmt + +impl ZonefileFmt for Txt +where + Octs: AsRef<[u8]>, +{ + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.block(|p| { + for slice in self.iter_charstrs() { + p.write_token(slice.display_quoted())?; + } + Ok(()) + }) + } +} + //--- Serialize and Deserialize #[cfg(feature = "serde")] diff --git a/src/rdata/srv.rs b/src/rdata/srv.rs index f0ce9dd8b..04c6e22ce 100644 --- a/src/rdata/srv.rs +++ b/src/rdata/srv.rs @@ -9,6 +9,7 @@ use crate::base::iana::Rtype; use crate::base::name::{FlattenInto, ParsedName, ToName}; use crate::base::rdata::{ComposeRecordData, ParseRecordData, RecordData}; use crate::base::scan::{Scan, Scanner}; +use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt}; use crate::base::wire::{Compose, Composer, Parse, ParseError}; use core::cmp::Ordering; use core::fmt; @@ -282,6 +283,22 @@ impl fmt::Display for Srv { } } +//--- ZonefileFmt + +impl ZonefileFmt for Srv { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.block(|p| { + p.write_token(self.priority)?; + p.write_comment("priority")?; + p.write_token(self.weight)?; + p.write_comment("weight")?; + p.write_token(self.port)?; + p.write_comment("port")?; + p.write_token(self.target.fmt_with_dot()) + }) + } +} + //============ Testing ====================================================== #[cfg(test)] diff --git a/src/rdata/svcb/params.rs b/src/rdata/svcb/params.rs index c0c2a5464..3d9e4c5cb 100644 --- a/src/rdata/svcb/params.rs +++ b/src/rdata/svcb/params.rs @@ -12,12 +12,15 @@ use crate::base::cmp::CanonicalOrd; use crate::base::iana::SvcParamKey; use crate::base::scan::Symbol; use crate::base::wire::{Compose, Parse, ParseError}; +use crate::base::zonefile_fmt::{ + self, Formatter, ZonefileFmt, +}; +use core::cmp::Ordering; +use core::marker::PhantomData; +use core::{cmp, fmt, hash, mem}; use octseq::builder::{EmptyBuilder, FromBuilder, OctetsBuilder, ShortBuf}; use octseq::octets::{Octets, OctetsFrom, OctetsInto}; use octseq::parse::{Parser, ShortInput}; -use core::{cmp, fmt, hash, mem}; -use core::cmp::Ordering; -use core::marker::PhantomData; //------------ SvcParams ----------------------------------------------------- @@ -360,12 +363,35 @@ impl fmt::Display for SvcParams { impl fmt::Debug for SvcParams { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_tuple("SvcParams").field( - &format_args!("{}", self) - ).finish() + f.debug_tuple("SvcParams") + .field(&format_args!("{}", self)) + .finish() } } +//--- ZonefileFmt + +impl ZonefileFmt for SvcParams { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.block(|p| { + let mut parser = Parser::from_ref(self.as_slice()); + while parser.remaining() > 0 { + let key = SvcParamKey::parse(&mut parser) + .expect("invalid SvcbParam"); + let len = usize::from( + u16::parse(&mut parser).expect("invalid SvcParam"), + ); + let mut parser = + parser.parse_parser(len).expect("invalid SvcParam"); + p.write_token(super::value::AllValues::parse_any( + key, + &mut parser, + ))?; + } + Ok(()) + }) + } +} //------------ ValueIter ----------------------------------------------------- @@ -1013,7 +1039,7 @@ mod test { b"\x00\x03\ \x00\x02\ \x00\x35", - [ value::Port::new(53) ] + [value::Port::new(53)] ); } @@ -1023,12 +1049,8 @@ mod test { b"\x02\x9b\ \x00\x05\ \x68\x65\x6c\x6c\x6f", - [ - UnknownSvcParam::new( - 0x029b.into(), - octets512(b"hello") - ).unwrap() - ] + [UnknownSvcParam::new(0x029b.into(), octets512(b"hello")) + .unwrap()] ); } @@ -1038,12 +1060,11 @@ mod test { b"\x02\x9b\ \x00\x09\ \x68\x65\x6c\x6c\x6f\xd2\x71\x6f\x6f", - [ - UnknownSvcParam::new( + [UnknownSvcParam::new( 0x029b.into(), octets512(b"\x68\x65\x6c\x6c\x6f\xd2\x71\x6f\x6f"), - ).unwrap() - ] + ) + .unwrap()] ); } @@ -1059,12 +1080,11 @@ mod test { \x00\x00\x00\x00\x00\x00\x00\x01\ \x20\x01\x0d\xb8\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x53\x00\x01", - [ - value::Ipv6Hint::::from_addrs([ + [value::Ipv6Hint::::from_addrs([ Ipv6Addr::from_str("2001:db8::1").unwrap(), Ipv6Addr::from_str("2001:db8::53:1").unwrap(), - ]).unwrap() - ] + ]) + .unwrap()] ); } @@ -1081,7 +1101,8 @@ mod test { [ value::Ipv6Hint::::from_addrs([ Ipv6Addr::from_str("::ffff:198.51.100.100").unwrap(), - ]).unwrap() + ]) + .unwrap() ] ); } @@ -1096,9 +1117,11 @@ mod test { [ SvcParamKey::ALPN, SvcParamKey::IPV4HINT, - SvcParamKey::PRIVATE_RANGE_BEGIN.into() - ].into_iter() - ).unwrap(); + SvcParamKey::PRIVATE_RANGE_BEGIN.into(), + ] + .into_iter(), + ) + .unwrap(); assert_eq!( "mandatory=alpn,ipv4hint,key65280", format!("{}", mandatory) @@ -1107,41 +1130,37 @@ mod test { let mut alpn_builder = value::AlpnBuilder::::empty(); alpn_builder.push("h2").unwrap(); alpn_builder.push("h3-19").unwrap(); - assert_eq!( - "alpn=h2,h3-19", - format!("{}", alpn_builder.freeze()) - ); + assert_eq!("alpn=h2,h3-19", format!("{}", alpn_builder.freeze())); assert_eq!("nodefaultalpn", format!("{}", value::NoDefaultAlpn)); assert_eq!( "ech", - format!( - "{}", - value::Ech::from_octets(Octets512::new()).unwrap() - ) + format!("{}", value::Ech::from_octets(Octets512::new()).unwrap()) ); assert_eq!( "ipv4hint=192.0.2.1,192.0.2.2", format!( "{}", - value::Ipv4Hint::::from_addrs( - [ - [192, 0, 2, 1].into(), [192, 0, 2, 2].into() - ] - ).unwrap() + value::Ipv4Hint::::from_addrs([ + [192, 0, 2, 1].into(), + [192, 0, 2, 2].into() + ]) + .unwrap() ) ); } - //--- Builder #[test] fn empty_builder() { assert_eq!( - Builder512::empty().freeze::().unwrap().as_slice(), + Builder512::empty() + .freeze::() + .unwrap() + .as_slice(), b"" ); } @@ -1159,15 +1178,15 @@ mod test { #[test] fn three_values_in_order() { let mut builder = Builder512::empty(); - builder.push( - &UnknownSvcParam::new(1.into(), b"223").unwrap() - ).unwrap(); - builder.push( - &UnknownSvcParam::new(2.into(), b"224").unwrap() - ).unwrap(); - builder.push( - &UnknownSvcParam::new(8.into(), b"225").unwrap() - ).unwrap(); + builder + .push(&UnknownSvcParam::new(1.into(), b"223").unwrap()) + .unwrap(); + builder + .push(&UnknownSvcParam::new(2.into(), b"224").unwrap()) + .unwrap(); + builder + .push(&UnknownSvcParam::new(8.into(), b"225").unwrap()) + .unwrap(); assert_eq!( builder.freeze::().unwrap().as_slice(), b"\x00\x01\x00\x03223\ @@ -1179,15 +1198,15 @@ mod test { #[test] fn three_values_out_of_order() { let mut builder = Builder512::empty(); - builder.push( - &UnknownSvcParam::new(1.into(), b"223").unwrap() - ).unwrap(); - builder.push( - &UnknownSvcParam::new(8.into(), b"225").unwrap() - ).unwrap(); - builder.push( - &UnknownSvcParam::new(2.into(), b"224").unwrap() - ).unwrap(); + builder + .push(&UnknownSvcParam::new(1.into(), b"223").unwrap()) + .unwrap(); + builder + .push(&UnknownSvcParam::new(8.into(), b"225").unwrap()) + .unwrap(); + builder + .push(&UnknownSvcParam::new(2.into(), b"224").unwrap()) + .unwrap(); assert_eq!( builder.freeze::().unwrap().as_slice(), b"\x00\x01\x00\x03223\ @@ -1199,17 +1218,14 @@ mod test { #[test] fn three_values_with_collision() { let mut builder = Builder512::empty(); - builder.push( - &UnknownSvcParam::new(1.into(), b"223").unwrap() - ).unwrap(); - builder.push( - &UnknownSvcParam::new(8.into(), b"225").unwrap() - ).unwrap(); - assert!( - builder.push( - &UnknownSvcParam::new(8.into(), b"224").unwrap() - ).is_err() - ); + builder + .push(&UnknownSvcParam::new(1.into(), b"223").unwrap()) + .unwrap(); + builder + .push(&UnknownSvcParam::new(8.into(), b"225").unwrap()) + .unwrap(); + assert!(builder + .push(&UnknownSvcParam::new(8.into(), b"224").unwrap()) + .is_err()); } } - diff --git a/src/rdata/svcb/rdata.rs b/src/rdata/svcb/rdata.rs index 41a422809..57a97d789 100644 --- a/src/rdata/svcb/rdata.rs +++ b/src/rdata/svcb/rdata.rs @@ -10,10 +10,11 @@ use crate::base::rdata::{ ComposeRecordData, LongRecordData, ParseRecordData, RecordData, }; use crate::base::wire::{Compose, Composer, Parse, ParseError}; +use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt}; +use core::marker::PhantomData; +use core::{cmp, fmt, hash}; use octseq::octets::{Octets, OctetsFrom, OctetsInto}; use octseq::parse::Parser; -use core::{cmp, fmt, hash}; -use core::marker::PhantomData; //------------ Svcb and Https ------------------------------------------------ @@ -159,14 +160,12 @@ impl SvcbRdata { impl> SvcbRdata> { /// Parses service bindings record data from its wire format. pub fn parse<'a, Src: Octets = Octs> + ?Sized + 'a>( - parser: &mut Parser<'a, Src> + parser: &mut Parser<'a, Src>, ) -> Result { let priority = u16::parse(parser)?; let target = ParsedName::parse(parser)?; let params = SvcParams::parse(parser)?; - Ok(unsafe { - Self::new_unchecked(priority, target, params) - }) + Ok(unsafe { Self::new_unchecked(priority, target, params) }) } } @@ -260,12 +259,12 @@ impl for SvcbRdata where TOcts: OctetsFrom, - Name: FlattenInto + Name: FlattenInto, { type AppendError = TOcts::Error; fn try_flatten_into( - self + self, ) -> Result, TOcts::Error> { self.flatten() } @@ -446,7 +445,7 @@ where Name: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {} {}", self.priority, self.target, self.params) + write!(f, "{} {}. {}", self.priority, self.target, self.params) } } @@ -464,6 +463,24 @@ where } } +//--- ZonefileFmt + +impl ZonefileFmt for SvcbRdata +where + Octs: Octets, + Name: ToName, +{ + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.block(|p| { + p.write_token(self.priority)?; + p.write_comment("priority")?; + p.write_token(self.target.fmt_with_dot())?; + p.write_comment("target")?; + p.write_show(&self.params) + }) + } +} + //============ Tests ========================================================= #[cfg(test)] diff --git a/src/rdata/tsig.rs b/src/rdata/tsig.rs index e9730fe6f..a3f898435 100644 --- a/src/rdata/tsig.rs +++ b/src/rdata/tsig.rs @@ -22,6 +22,7 @@ use crate::base::name::{FlattenInto, ParsedName, ToName}; use crate::base::rdata::{ ComposeRecordData, LongRecordData, ParseRecordData, RecordData, }; +use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt}; use crate::base::wire::{Compose, Composer, Parse, ParseError}; use crate::utils::base64; @@ -605,6 +606,31 @@ impl, N: fmt::Debug> fmt::Debug for Tsig { } } +//--- ZonefileFmt + +impl, N: ToName> ZonefileFmt for Tsig { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.block(|p| { + p.write_token(self.algorithm.fmt_with_dot())?; + p.write_comment("algorithm")?; + p.write_token(self.time_signed)?; + p.write_comment("time signed")?; + p.write_token(self.fudge)?; + p.write_comment("fudge")?; + p.write_token(base64::encode_display(&self.mac))?; + p.write_comment("mac")?; + p.write_token(self.original_id)?; + p.write_comment("original id")?; + p.write_token(self.error)?; + p.write_comment("error")?; + p.write_token(format_args!( + "\"{}\"", + base64::encode_display(&self.other)) + ) + }) + } +} + //------------ Time48 -------------------------------------------------------- /// A 48-bit Unix timestamp. @@ -615,7 +641,7 @@ pub struct Time48(u64); impl Time48 { /// Returns the timestamp of the current moment. /// - /// The funtion will panic if for whatever reason the current moment is + /// The function will panic if for whatever reason the current moment is /// too far in the future to fit into this type. For a correctly set /// clock, this will happen in December 8,921,556, so should be fine. #[cfg(feature = "std")] diff --git a/src/rdata/zonemd.rs b/src/rdata/zonemd.rs index bfdd9a672..66f41d400 100644 --- a/src/rdata/zonemd.rs +++ b/src/rdata/zonemd.rs @@ -13,6 +13,7 @@ use crate::base::iana::Rtype; use crate::base::rdata::{ComposeRecordData, RecordData}; use crate::base::scan::{Scan, Scanner}; use crate::base::serial::Serial; +use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt}; use crate::base::wire::{Composer, ParseError}; use crate::utils::base16; use core::cmp::Ordering; @@ -227,6 +228,30 @@ impl> fmt::Debug for Zonemd { } } +impl> ZonefileFmt for Zonemd { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.block(|p| { + p.write_token(self.serial)?; + p.write_show(self.scheme)?; + p.write_comment(format_args!("scheme ({})", match self.scheme { + Scheme::Reserved => "reserved", + Scheme::Simple => "simple", + Scheme::Unassigned(_) => "unassigned", + Scheme::Private(_) => "private", + }))?; + p.write_show(self.algo)?; + p.write_comment(format_args!("algorithm ({})", match self.algo { + Algorithm::Reserved => "reserved", + Algorithm::Sha384 => "SHA384", + Algorithm::Sha512 => "SHA512", + Algorithm::Unassigned(_) => "unassigned", + Algorithm::Private(_) => "private", + }))?; + p.write_token(base16::encode_display(&self.digest)) + }) + } +} + impl PartialOrd> for Zonemd where Octs: AsRef<[u8]>, @@ -325,6 +350,12 @@ impl From for Scheme { } } +impl ZonefileFmt for Scheme { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.write_token(u8::from(*self)) + } +} + /// The Hash Algorithm used to construct the digest. /// /// This enumeration wraps an 8-bit unsigned integer that identifies @@ -363,6 +394,12 @@ impl From for Algorithm { } } +impl ZonefileFmt for Algorithm { + fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result { + p.write_token(u8::from(*self)) + } +} + #[cfg(test)] #[cfg(all(feature = "std", feature = "bytes"))] mod test {