Merge branch 'main' into new-zonefile

This commit is contained in:
withjannisNLnetLabs
2026-06-17 14:43:23 +02:00
9 changed files with 346 additions and 51 deletions
+5 -5
View File
@@ -43,7 +43,7 @@ jiff = { version = "0.2.1", default-features = false }
arbitrary = { version = "1.4.1", optional = true, features = ["derive"] }
bumpalo = { version = "3.12", optional = true }
constant_time_eq = { version = "0.4.2", optional = true }
octseq = { version = "0.6.0", default-features = false }
octseq = { version = "0.6.1", default-features = false }
rand = { version = "0.10.1", optional = true }
arc-swap = { version = "1.7.0", optional = true }
bytes = { version = "1.2", optional = true, default-features = false }
@@ -60,7 +60,7 @@ proc-macro2 = { version = "1.0.69", optional = true } # Force proc-macro2 to
ring = { version = "0.17.2", optional = true }
rustversion = { version = "1", optional = true }
secrecy = { version = "0.10", optional = true }
serde = { version = "1.0.130", optional = true, features = ["derive"] }
serde = { version = "1.0.130", optional = true, default-features = false, features = ["derive"] }
siphasher = { version = "1", optional = true }
smallvec = { version = "1.3", optional = true }
tokio = { version = "1.33", optional = true, features = ["io-util", "macros", "net", "time", "sync", "rt-multi-thread" ] }
@@ -73,15 +73,15 @@ tracing-subscriber = { version = "0.3.18", optional = true, features = ["env-fil
default = ["std", "rand"]
# Support for libraries
alloc = ["jiff/alloc"]
alloc = ["jiff/alloc", "octseq/alloc", "serde?/alloc"]
bumpalo = ["dep:bumpalo", "std"]
bytes = ["dep:bytes", "octseq/bytes"]
chrono = ["dep:chrono"]
heapless = ["dep:heapless", "octseq/heapless"]
rand = ["dep:rand"]
serde = ["std", "dep:serde", "octseq/serde"]
serde = ["dep:serde", "octseq/serde"]
smallvec = ["dep:smallvec", "octseq/smallvec"]
std = ["alloc", "dep:hashbrown", "bumpalo?/std", "bytes?/std", "octseq/std", "jiff/std"]
std = ["alloc", "dep:hashbrown", "bumpalo?/std", "bytes?/std", "octseq/std", "jiff/std", "serde?/std"]
tracing = ["dep:log", "dep:tracing"]
# Cryptographic backends
+6
View File
@@ -8,10 +8,16 @@ New
Improvements
* Implemented `to_mnemonic_str` for `Rcode` and `OptCode` ([#668] and [#648]
by [@rossmacarthur])
Bug fixes
Other changes
[#648]: https://github.com/NLnetLabs/domain/pull/648
[#668]: https://github.com/NLnetLabs/domain/pull/668
[@rossmacarthur]: https://github.com/rossmacarthur
## 0.12.1
+81 -40
View File
@@ -19,6 +19,7 @@
// bits of the wrapped integer.
use core::fmt;
use core::str;
use core::str::FromStr;
//------------ Rcode ---------------------------------------------------------
@@ -195,18 +196,27 @@ impl Rcode {
/// Returns the mnemonic for this value if there is one.
#[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
#[must_use]
pub const fn to_mnemonic_str(self) -> Option<&'static str> {
match self {
Rcode::NOERROR => Some(b"NOERROR"),
Rcode::FORMERR => Some(b"FORMERR"),
Rcode::SERVFAIL => Some(b"SERVFAIL"),
Rcode::NXDOMAIN => Some(b"NXDOMAIN"),
Rcode::NOTIMP => Some(b"NOTIMP"),
Rcode::REFUSED => Some(b"REFUSED"),
Rcode::YXDOMAIN => Some(b"YXDOMAIN"),
Rcode::YXRRSET => Some(b"YXRRSET"),
Rcode::NXRRSET => Some(b"NXRRSET"),
Rcode::NOTAUTH => Some(b"NOTAUTH"),
Rcode::NOTZONE => Some(b"NOTZONE"),
Rcode::NOERROR => Some("NOERROR"),
Rcode::FORMERR => Some("FORMERR"),
Rcode::SERVFAIL => Some("SERVFAIL"),
Rcode::NXDOMAIN => Some("NXDOMAIN"),
Rcode::NOTIMP => Some("NOTIMP"),
Rcode::REFUSED => Some("REFUSED"),
Rcode::YXDOMAIN => Some("YXDOMAIN"),
Rcode::YXRRSET => Some("YXRRSET"),
Rcode::NXRRSET => Some("NXRRSET"),
Rcode::NOTAUTH => Some("NOTAUTH"),
Rcode::NOTZONE => Some("NOTZONE"),
_ => None,
}
}
@@ -255,10 +265,7 @@ impl From<Rcode> for u8 {
impl fmt::Display for Rcode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self
.to_mnemonic()
.and_then(|bytes| core::str::from_utf8(bytes).ok())
{
match self.to_mnemonic_str() {
Some(mnemonic) => f.write_str(mnemonic),
None => self.0.fmt(f),
}
@@ -267,10 +274,7 @@ impl fmt::Display for Rcode {
impl fmt::Debug for Rcode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self
.to_mnemonic()
.and_then(|bytes| core::str::from_utf8(bytes).ok())
{
match self.to_mnemonic_str() {
Some(mnemonic) => write!(f, "Rcode::{}", mnemonic),
None => f.debug_tuple("Rcode").field(&self.0).finish(),
}
@@ -575,20 +579,29 @@ impl OptRcode {
/// Returns the mnemonic for this value if there is one.
#[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
#[must_use]
pub const fn to_mnemonic_str(self) -> Option<&'static str> {
match self {
OptRcode::NOERROR => Some(b"NOERROR"),
OptRcode::FORMERR => Some(b"FORMERR"),
OptRcode::SERVFAIL => Some(b"SERVFAIL"),
OptRcode::NXDOMAIN => Some(b"NXDOMAIN"),
OptRcode::NOTIMP => Some(b"NOTIMP"),
OptRcode::REFUSED => Some(b"REFUSED"),
OptRcode::YXDOMAIN => Some(b"YXDOMAIN"),
OptRcode::YXRRSET => Some(b"YXRRSET"),
OptRcode::NXRRSET => Some(b"NXRRSET"),
OptRcode::NOTAUTH => Some(b"NOTAUTH"),
OptRcode::NOTZONE => Some(b"NOTZONE"),
OptRcode::BADVERS => Some(b"BADVERS"),
OptRcode::BADCOOKIE => Some(b"BADCOOKIE"),
OptRcode::NOERROR => Some("NOERROR"),
OptRcode::FORMERR => Some("FORMERR"),
OptRcode::SERVFAIL => Some("SERVFAIL"),
OptRcode::NXDOMAIN => Some("NXDOMAIN"),
OptRcode::NOTIMP => Some("NOTIMP"),
OptRcode::REFUSED => Some("REFUSED"),
OptRcode::YXDOMAIN => Some("YXDOMAIN"),
OptRcode::YXRRSET => Some("YXRRSET"),
OptRcode::NXRRSET => Some("NXRRSET"),
OptRcode::NOTAUTH => Some("NOTAUTH"),
OptRcode::NOTZONE => Some("NOTZONE"),
OptRcode::BADVERS => Some("BADVERS"),
OptRcode::BADCOOKIE => Some("BADCOOKIE"),
_ => None,
}
}
@@ -645,10 +658,7 @@ impl From<Rcode> for OptRcode {
impl fmt::Display for OptRcode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self
.to_mnemonic()
.and_then(|bytes| core::str::from_utf8(bytes).ok())
{
match self.to_mnemonic_str() {
Some(mnemonic) => f.write_str(mnemonic),
None => self.0.fmt(f),
}
@@ -657,10 +667,7 @@ impl fmt::Display for OptRcode {
impl fmt::Debug for OptRcode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self
.to_mnemonic()
.and_then(|bytes| core::str::from_utf8(bytes).ok())
{
match self.to_mnemonic_str() {
Some(mnemonic) => write!(f, "Rcode::{}", mnemonic),
None => f.debug_tuple("Rcode").field(&self.0).finish(),
}
@@ -973,6 +980,22 @@ mod test {
assert!("#$%!@".parse::<Rcode>().is_err());
}
#[test]
fn rcode_tomnemonicstr() {
assert_eq!(Rcode::NOERROR.to_mnemonic_str(), Some("NOERROR"));
assert_eq!(Rcode::FORMERR.to_mnemonic_str(), Some("FORMERR"));
assert_eq!(Rcode::SERVFAIL.to_mnemonic_str(), Some("SERVFAIL"));
assert_eq!(Rcode::NXDOMAIN.to_mnemonic_str(), Some("NXDOMAIN"));
assert_eq!(Rcode::NOTIMP.to_mnemonic_str(), Some("NOTIMP"));
assert_eq!(Rcode::REFUSED.to_mnemonic_str(), Some("REFUSED"));
assert_eq!(Rcode::YXDOMAIN.to_mnemonic_str(), Some("YXDOMAIN"));
assert_eq!(Rcode::YXRRSET.to_mnemonic_str(), Some("YXRRSET"));
assert_eq!(Rcode::NXRRSET.to_mnemonic_str(), Some("NXRRSET"));
assert_eq!(Rcode::NOTAUTH.to_mnemonic_str(), Some("NOTAUTH"));
assert_eq!(Rcode::NOTZONE.to_mnemonic_str(), Some("NOTZONE"));
assert_eq!(Rcode(42).to_mnemonic_str(), None);
}
#[test]
fn optrcode_fromstr() {
assert_eq!(Ok(OptRcode::NOERROR), "NOERROR".parse());
@@ -991,6 +1014,24 @@ mod test {
assert!("#$%!@".parse::<Rcode>().is_err());
}
#[test]
fn optrcode_tomnemonicstr() {
assert_eq!(OptRcode::NOERROR.to_mnemonic_str(), Some("NOERROR"));
assert_eq!(OptRcode::FORMERR.to_mnemonic_str(), Some("FORMERR"));
assert_eq!(OptRcode::SERVFAIL.to_mnemonic_str(), Some("SERVFAIL"));
assert_eq!(OptRcode::NXDOMAIN.to_mnemonic_str(), Some("NXDOMAIN"));
assert_eq!(OptRcode::NOTIMP.to_mnemonic_str(), Some("NOTIMP"));
assert_eq!(OptRcode::REFUSED.to_mnemonic_str(), Some("REFUSED"));
assert_eq!(OptRcode::YXDOMAIN.to_mnemonic_str(), Some("YXDOMAIN"));
assert_eq!(OptRcode::YXRRSET.to_mnemonic_str(), Some("YXRRSET"));
assert_eq!(OptRcode::NXRRSET.to_mnemonic_str(), Some("NXRRSET"));
assert_eq!(OptRcode::NOTAUTH.to_mnemonic_str(), Some("NOTAUTH"));
assert_eq!(OptRcode::NOTZONE.to_mnemonic_str(), Some("NOTZONE"));
assert_eq!(OptRcode::BADVERS.to_mnemonic_str(), Some("BADVERS"));
assert_eq!(OptRcode::BADCOOKIE.to_mnemonic_str(), Some("BADCOOKIE"));
assert_eq!(OptRcode(42).to_mnemonic_str(), None);
}
#[test]
fn optrcode_isext() {
assert!(!OptRcode::NOERROR.is_ext());
+9
View File
@@ -80,6 +80,12 @@ use octseq::parse::Parser;
#[cfg_attr(not(feature = "zonefile"), doc = "zonefile")]
/// module for that.
///
/// # Equality, Ordering, and Hashing
///
/// The implementations for the standard equality, ordering, and hashing
/// traits ignore the TTL. That is, two records with the same owner, class,
/// type, and record data are considered identical even if their TTLs differ.
///
/// [`new`]: #method.new
/// [`Message`]: ../message/struct.Message.html
/// [`MessageBuilder`]: ../message_builder/struct.MessageBuilder.html
@@ -300,6 +306,9 @@ where
}
//--- PartialEq and Eq
//
// These impls ignore the TTL. This may have been a poor choice, but we
// keep it for now and reconsider with the re-write of base.
impl<N, NN, D, DD> PartialEq<Record<NN, DD>> for Record<N, D>
where
+40 -1
View File
@@ -26,7 +26,7 @@ use crate::new::zonefile::scanner::{Scan, ScanError, Scanner};
use super::{
CanonicalName, Label, LabelBuf, LabelIter, LabelParseError,
NameCompressor,
NameCompressor, RevNameBuf,
};
//----------- Name -----------------------------------------------------------
@@ -103,6 +103,11 @@ impl Name {
// SAFETY: A 'Name' always contains valid encoded labels.
unsafe { LabelIter::new_unchecked(self.as_bytes()) }
}
/// Covert &[`Name`] into [`RevNameBuf`]
pub fn to_revname(&self) -> RevNameBuf {
NameBuf::copy_from(self).into()
}
}
//--- Canonical operations
@@ -995,10 +1000,25 @@ impl fmt::Display for NameParseError {
}
}
// -- Convert from old Name to new::base::NameBuf ----------------------------
/// Upgrade a [`crate::base::Name`] into a
/// [`crate::new::base::name::NameBuf`].
impl<Octs> From<&crate::base::Name<Octs>> for NameBuf
where
Octs: AsRef<[u8]> + ?Sized,
{
fn from(value: &crate::base::Name<Octs>) -> Self {
NameBuf::parse_bytes(value.as_slice())
.expect("Tried to upgrade invalid name")
}
}
//============ Unit tests ====================================================
#[cfg(test)]
mod test {
use super::*;
#[cfg(feature = "zonefile")]
#[test]
fn scan() {
@@ -1051,4 +1071,23 @@ mod test {
}
}
}
#[test]
fn test_upgrade_name_to_namebuf() {
let old_name =
crate::base::Name::from_slice(b"\x07example\x03com\x00")
.expect("Invalid name");
let new_name: NameBuf = old_name.into();
assert_eq!(old_name.as_slice(), new_name.as_bytes())
}
#[test]
fn test_to_revname() {
let namebuf: NameBuf = "example.com".parse().unwrap();
assert_eq!(
namebuf.to_revname().as_bytes(),
b"\x00\x03com\x07example",
);
}
}
+47
View File
@@ -115,6 +115,11 @@ impl RevName {
// SAFETY: A 'RevName' always contains valid encoded labels.
unsafe { LabelIter::new_unchecked(self.as_bytes()) }
}
/// Covert &[`RevName`] into [`NameBuf`]
pub fn to_name(&self) -> NameBuf {
RevNameBuf::copy_from(self).into()
}
}
//--- Building in DNS messages
@@ -777,10 +782,30 @@ impl<'a> serde::Deserialize<'a> for std::boxed::Box<RevName> {
}
}
// -- Convert from old Name to new::base::RevNameBuf -------------------------
/// Upgrade a [`crate::base::Name`] into a
/// [`crate::new::base::name::reversed::RevNameBuf`].
///
/// # Panics
///
/// The [`crate::base::Name`] slice has to contain a valid domain.
impl<Octs> From<&crate::base::Name<Octs>> for RevNameBuf
where
Octs: AsRef<[u8]> + ?Sized,
{
fn from(value: &crate::base::Name<Octs>) -> Self {
RevNameBuf::parse_bytes(value.as_slice())
.expect("Tried to upgrade invalid name")
}
}
//============ Unit tests ====================================================
#[cfg(test)]
mod test {
use super::*;
#[cfg(feature = "zonefile")]
#[test]
fn scan() {
@@ -830,4 +855,26 @@ mod test {
}
}
}
#[test]
#[cfg(feature = "alloc")]
fn test_upgrade_revnamebuf() {
let old_name =
crate::base::Name::from_slice(b"\x07example\x03com\x00")
.expect("Invalid name");
let new_name: RevNameBuf = old_name.into();
let mut buf = alloc::vec![0; new_name.built_bytes_size()];
new_name.build_bytes(&mut buf).unwrap();
assert_eq!(old_name.as_slice(), buf);
}
#[test]
fn test_to_name() {
let revnamebuf: RevNameBuf = "example.com".parse().unwrap();
assert_eq!(
revnamebuf.to_name().as_bytes(),
b"\x07example\x03com\x00",
);
}
}
+124 -1
View File
@@ -506,6 +506,53 @@ impl Scan<'_> for RType {
Err(ScanError::Custom("unrecognized record type"))
}
}
/// Format an [`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].
///
/// The mnemonics are consolidated by [IANA].
///
/// ```
/// # use domain::new::base::RType;
/// // Known RType with mnemonic
/// assert_eq!("A", format!("{}", RType::A));
/// // Unknown RType
/// 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
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::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),
})
}
}
//----------- RClass ---------------------------------------------------------
@@ -549,6 +596,22 @@ impl RClass {
pub const CH: Self = Self::new(3);
}
//--- Conversion to and from 'u16'
impl From<u16> for RClass {
fn from(value: u16) -> Self {
Self {
code: U16::new(value),
}
}
}
impl From<RClass> for u16 {
fn from(value: RClass) -> Self {
value.code.get()
}
}
//--- Formatting
impl fmt::Debug for RClass {
@@ -594,6 +657,34 @@ impl Scan<'_> for RClass {
}
}
/// Format an [`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].
///
/// The mnemonics are consolidated by [IANA].
///
/// ```
/// # use domain::new::base::RClass;
/// // Known RClass with mnemonic
/// assert_eq!("IN", format!("{}", RClass::IN));
/// // Unknown RClass
/// 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
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),
})
}
}
//----------- TTL ------------------------------------------------------------
/// How long a record can be cached.
@@ -854,7 +945,7 @@ impl Clone for alloc::boxed::Box<UnparsedRecordData> {
#[cfg(test)]
mod test {
use super::{RClass, RType, Record, UnparsedRecordData, TTL};
use super::{RClass, RType, Record, TTL, UnparsedRecordData};
use crate::new::base::{
name::Name,
@@ -958,4 +1049,36 @@ mod test {
);
}
}
#[test]
fn test_rclass_from() {
let rclass: RClass = 1.into();
assert_eq!(rclass, RClass::IN);
let number: u16 = rclass.into();
assert_eq!(number, 1);
}
#[test]
fn test_rtype_from() {
let rtype: RType = 6.into();
assert_eq!(rtype, RType::SOA);
let number: u16 = rtype.into();
assert_eq!(number, 6);
}
#[test]
fn test_rclass_display() {
assert_eq!("IN", format!("{}", RClass::IN));
assert_eq!("CH", format!("{}", RClass::CH));
assert_eq!("CLASS42", format!("{}", RClass::from(42)));
}
#[test]
fn test_rtype_display() {
assert_eq!("A", format!("{}", RType::A));
assert_eq!("MX", format!("{}", RType::MX));
assert_eq!("TYPE265", format!("{}", RType::from(265)));
}
}
+33 -2
View File
@@ -13,7 +13,16 @@ use super::wire::U32;
//----------- Serial ---------------------------------------------------------
/// A serial number.
/// Serial number arithmetic.
///
/// [`Serial`] implements the "Serial number arithmetic" defined in [RFC1982]
/// with a `SERIAL_BITS` value of 32.
///
/// [`Serial`] should not be used interchangably or be confused with the SOA
/// Serial Number, which is a [`Serial`] but not the sole user of "Serial
/// number arithmetic".
///
/// [RFC1982]: https://datatracker.ietf.org/doc/html/rfc1982
#[derive(
Copy,
Clone,
@@ -30,11 +39,23 @@ use super::wire::U32;
UnsizedCopy,
)]
#[repr(transparent)]
pub struct Serial(U32);
pub struct Serial(pub U32);
//--- Construction
impl Serial {
/// Construct a new [`Serial`]
#[must_use]
pub const fn new(value: u32) -> Self {
Serial(U32::new(value))
}
/// Get [`u32`] value of [`Serial`]
#[must_use]
pub const fn get(&self) -> u32 {
self.0.get()
}
/// Measure the current time (in seconds) in serial number space.
#[cfg(feature = "std")]
pub fn unix_time() -> Self {
@@ -56,9 +77,15 @@ impl Serial {
/// instead of a [`u32`] because it is easier to understand and implement
/// a non-negative check versus the upper range check.
///
/// Section 7 in [RFC1982] states, in particular for the SOA Serial
/// Number, but for any Serial number using a "SERIAL_BITS" value of 32:
/// "The maximum defined increment is 2147483647 (2^31 - 1)."
///
/// # Panics
///
/// Panics if the number is negative.
///
/// [RFC1982]: https://datatracker.ietf.org/doc/html/rfc1982#section-7
pub fn inc(self, num: i32) -> Self {
assert!(num >= 0, "Cannot subtract from a `Serial`");
self.0.get().wrapping_add_signed(num).into()
@@ -68,6 +95,10 @@ impl Serial {
//--- Ordering
impl PartialOrd for Serial {
/// The comparison of Serial Number values is defined in Section 3.2
/// [RFC1982].
///
/// [RFC1982]: https://datatracker.ietf.org/doc/html/rfc1982#section-3.2
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
let (lhs, rhs) = (self.0.get(), other.0.get());
+1 -2
View File
@@ -52,8 +52,7 @@ macro_rules! define_int {
}
/// Overwrite this value with an integer.
// TODO: Make 'const' at MSRV 1.83.0.
pub fn set(&mut self, value: $base) {
pub const fn set(&mut self, value: $base) {
*self = Self::new(value)
}
}