use std::fmt;
use std::net::{Ipv4Addr, Ipv6Addr};
use super::dns_opt::{OptParseError, OptValue, parse_opt_value};
use super::enums::{DnsCls, DnsRecordType, DnsRrKey};
use super::rr::DnsRr;
macro_rules! common_accessors {
($struct:ident) => {
pub(super) fn new(rr: &'a DnsRr) -> Self {
Self(rr)
}
pub fn as_dns_rr(self) -> &'a DnsRr {
self.0
}
pub fn name(self) -> &'a str {
self.0.name()
}
pub fn dns_class(self) -> DnsCls {
self.0.dns_class()
}
pub fn ttl(self) -> u32 {
self.0.ttl()
}
};
}
#[derive(Copy, Clone)]
pub struct ARecord<'a>(&'a DnsRr);
impl<'a> ARecord<'a> {
common_accessors!(ARecord);
pub fn addr(self) -> Ipv4Addr {
self.0
.get_addr(DnsRrKey::A_ADDR)
.unwrap_or(Ipv4Addr::UNSPECIFIED)
}
}
impl fmt::Debug for ARecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ARecord")
.field("name", &self.name())
.field("ttl", &self.ttl())
.field("addr", &self.addr())
.finish()
}
}
#[derive(Copy, Clone)]
pub struct AaaaRecord<'a>(&'a DnsRr);
impl<'a> AaaaRecord<'a> {
common_accessors!(AaaaRecord);
pub fn addr(self) -> Ipv6Addr {
self.0
.get_addr6(DnsRrKey::AAAA_ADDR)
.unwrap_or(Ipv6Addr::UNSPECIFIED)
}
}
impl fmt::Debug for AaaaRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AaaaRecord")
.field("name", &self.name())
.field("ttl", &self.ttl())
.field("addr", &self.addr())
.finish()
}
}
#[derive(Copy, Clone)]
pub struct NsRecord<'a>(&'a DnsRr);
impl<'a> NsRecord<'a> {
common_accessors!(NsRecord);
pub fn nsdname(self) -> &'a str {
self.0.get_str(DnsRrKey::NS_NSDNAME).unwrap_or("")
}
}
impl fmt::Debug for NsRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NsRecord")
.field("name", &self.name())
.field("ttl", &self.ttl())
.field("nsdname", &self.nsdname())
.finish()
}
}
#[derive(Copy, Clone)]
pub struct CnameRecord<'a>(&'a DnsRr);
impl<'a> CnameRecord<'a> {
common_accessors!(CnameRecord);
pub fn cname(self) -> &'a str {
self.0.get_str(DnsRrKey::CNAME_CNAME).unwrap_or("")
}
}
impl fmt::Debug for CnameRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CnameRecord")
.field("name", &self.name())
.field("ttl", &self.ttl())
.field("cname", &self.cname())
.finish()
}
}
#[derive(Copy, Clone)]
pub struct SoaRecord<'a>(&'a DnsRr);
impl<'a> SoaRecord<'a> {
common_accessors!(SoaRecord);
pub fn mname(self) -> &'a str {
self.0.get_str(DnsRrKey::SOA_MNAME).unwrap_or("")
}
pub fn rname(self) -> &'a str {
self.0.get_str(DnsRrKey::SOA_RNAME).unwrap_or("")
}
pub fn serial(self) -> u32 {
self.0.get_u32(DnsRrKey::SOA_SERIAL)
}
pub fn refresh(self) -> u32 {
self.0.get_u32(DnsRrKey::SOA_REFRESH)
}
pub fn retry(self) -> u32 {
self.0.get_u32(DnsRrKey::SOA_RETRY)
}
pub fn expire(self) -> u32 {
self.0.get_u32(DnsRrKey::SOA_EXPIRE)
}
pub fn minimum(self) -> u32 {
self.0.get_u32(DnsRrKey::SOA_MINIMUM)
}
}
impl fmt::Debug for SoaRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SoaRecord")
.field("name", &self.name())
.field("ttl", &self.ttl())
.field("mname", &self.mname())
.field("rname", &self.rname())
.field("serial", &self.serial())
.field("refresh", &self.refresh())
.field("retry", &self.retry())
.field("expire", &self.expire())
.field("minimum", &self.minimum())
.finish()
}
}
#[derive(Copy, Clone)]
pub struct PtrRecord<'a>(&'a DnsRr);
impl<'a> PtrRecord<'a> {
common_accessors!(PtrRecord);
pub fn dname(self) -> &'a str {
self.0.get_str(DnsRrKey::PTR_DNAME).unwrap_or("")
}
}
impl fmt::Debug for PtrRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PtrRecord")
.field("name", &self.name())
.field("ttl", &self.ttl())
.field("dname", &self.dname())
.finish()
}
}
#[derive(Copy, Clone)]
pub struct HinfoRecord<'a>(&'a DnsRr);
impl<'a> HinfoRecord<'a> {
common_accessors!(HinfoRecord);
pub fn cpu(self) -> &'a str {
self.0.get_str(DnsRrKey::HINFO_CPU).unwrap_or("")
}
pub fn os(self) -> &'a str {
self.0.get_str(DnsRrKey::HINFO_OS).unwrap_or("")
}
}
impl fmt::Debug for HinfoRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HinfoRecord")
.field("name", &self.name())
.field("ttl", &self.ttl())
.field("cpu", &self.cpu())
.field("os", &self.os())
.finish()
}
}
#[derive(Copy, Clone)]
pub struct MxRecord<'a>(&'a DnsRr);
impl<'a> MxRecord<'a> {
common_accessors!(MxRecord);
pub fn preference(self) -> u16 {
self.0.get_u16(DnsRrKey::MX_PREFERENCE)
}
pub fn exchange(self) -> &'a str {
self.0.get_str(DnsRrKey::MX_EXCHANGE).unwrap_or("")
}
}
impl fmt::Debug for MxRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MxRecord")
.field("name", &self.name())
.field("ttl", &self.ttl())
.field("preference", &self.preference())
.field("exchange", &self.exchange())
.finish()
}
}
#[derive(Copy, Clone)]
pub struct SigRecord<'a>(&'a DnsRr);
impl<'a> SigRecord<'a> {
common_accessors!(SigRecord);
pub fn type_covered(self) -> u16 {
self.0.get_u16(DnsRrKey::SIG_TYPE_COVERED)
}
pub fn algorithm(self) -> u8 {
self.0.get_u8(DnsRrKey::SIG_ALGORITHM)
}
pub fn labels(self) -> u8 {
self.0.get_u8(DnsRrKey::SIG_LABELS)
}
pub fn original_ttl(self) -> u32 {
self.0.get_u32(DnsRrKey::SIG_ORIGINAL_TTL)
}
pub fn expiration(self) -> u32 {
self.0.get_u32(DnsRrKey::SIG_EXPIRATION)
}
pub fn inception(self) -> u32 {
self.0.get_u32(DnsRrKey::SIG_INCEPTION)
}
pub fn key_tag(self) -> u16 {
self.0.get_u16(DnsRrKey::SIG_KEY_TAG)
}
pub fn signers_name(self) -> &'a str {
self.0.get_str(DnsRrKey::SIG_SIGNERS_NAME).unwrap_or("")
}
pub fn signature(self) -> &'a [u8] {
self.0.get_bin(DnsRrKey::SIG_SIGNATURE).unwrap_or(&[])
}
}
impl fmt::Debug for SigRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SigRecord")
.field("name", &self.name())
.field("ttl", &self.ttl())
.field("type_covered", &self.type_covered())
.field("algorithm", &self.algorithm())
.field("labels", &self.labels())
.field("original_ttl", &self.original_ttl())
.field("expiration", &self.expiration())
.field("inception", &self.inception())
.field("key_tag", &self.key_tag())
.field("signers_name", &self.signers_name())
.field("signature_len", &self.signature().len())
.finish()
}
}
#[derive(Copy, Clone)]
pub struct SrvRecord<'a>(&'a DnsRr);
impl<'a> SrvRecord<'a> {
common_accessors!(SrvRecord);
pub fn priority(self) -> u16 {
self.0.get_u16(DnsRrKey::SRV_PRIORITY)
}
pub fn weight(self) -> u16 {
self.0.get_u16(DnsRrKey::SRV_WEIGHT)
}
pub fn port(self) -> u16 {
self.0.get_u16(DnsRrKey::SRV_PORT)
}
pub fn target(self) -> &'a str {
self.0.get_str(DnsRrKey::SRV_TARGET).unwrap_or("")
}
}
impl fmt::Debug for SrvRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SrvRecord")
.field("name", &self.name())
.field("ttl", &self.ttl())
.field("priority", &self.priority())
.field("weight", &self.weight())
.field("port", &self.port())
.field("target", &self.target())
.finish()
}
}
#[derive(Copy, Clone)]
pub struct NaptrRecord<'a>(&'a DnsRr);
impl<'a> NaptrRecord<'a> {
common_accessors!(NaptrRecord);
pub fn order(self) -> u16 {
self.0.get_u16(DnsRrKey::NAPTR_ORDER)
}
pub fn preference(self) -> u16 {
self.0.get_u16(DnsRrKey::NAPTR_PREFERENCE)
}
pub fn flags(self) -> &'a str {
self.0.get_str(DnsRrKey::NAPTR_FLAGS).unwrap_or("")
}
pub fn services(self) -> &'a str {
self.0.get_str(DnsRrKey::NAPTR_SERVICES).unwrap_or("")
}
pub fn regexp(self) -> &'a str {
self.0.get_str(DnsRrKey::NAPTR_REGEXP).unwrap_or("")
}
pub fn replacement(self) -> &'a str {
self.0.get_str(DnsRrKey::NAPTR_REPLACEMENT).unwrap_or("")
}
}
impl fmt::Debug for NaptrRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NaptrRecord")
.field("name", &self.name())
.field("ttl", &self.ttl())
.field("order", &self.order())
.field("preference", &self.preference())
.field("flags", &self.flags())
.field("services", &self.services())
.field("regexp", &self.regexp())
.field("replacement", &self.replacement())
.finish()
}
}
#[derive(Copy, Clone)]
pub struct TlsaRecord<'a>(&'a DnsRr);
impl<'a> TlsaRecord<'a> {
common_accessors!(TlsaRecord);
pub fn cert_usage(self) -> u8 {
self.0.get_u8(DnsRrKey::TLSA_CERT_USAGE)
}
pub fn selector(self) -> u8 {
self.0.get_u8(DnsRrKey::TLSA_SELECTOR)
}
pub fn matching_type(self) -> u8 {
self.0.get_u8(DnsRrKey::TLSA_MATCH)
}
pub fn data(self) -> &'a [u8] {
self.0.get_bin(DnsRrKey::TLSA_DATA).unwrap_or(&[])
}
}
impl fmt::Debug for TlsaRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TlsaRecord")
.field("name", &self.name())
.field("ttl", &self.ttl())
.field("cert_usage", &self.cert_usage())
.field("selector", &self.selector())
.field("matching_type", &self.matching_type())
.field("data_len", &self.data().len())
.finish()
}
}
#[derive(Copy, Clone)]
pub struct UriRecord<'a>(&'a DnsRr);
impl<'a> UriRecord<'a> {
common_accessors!(UriRecord);
pub fn priority(self) -> u16 {
self.0.get_u16(DnsRrKey::URI_PRIORITY)
}
pub fn weight(self) -> u16 {
self.0.get_u16(DnsRrKey::URI_WEIGHT)
}
pub fn target(self) -> &'a str {
self.0.get_str(DnsRrKey::URI_TARGET).unwrap_or("")
}
}
impl fmt::Debug for UriRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UriRecord")
.field("name", &self.name())
.field("ttl", &self.ttl())
.field("priority", &self.priority())
.field("weight", &self.weight())
.field("target", &self.target())
.finish()
}
}
#[derive(Copy, Clone)]
pub struct TxtRecord<'a>(&'a DnsRr);
impl<'a> TxtRecord<'a> {
common_accessors!(TxtRecord);
pub fn entry_count(self) -> usize {
self.0.get_abin_count(DnsRrKey::TXT_DATA)
}
pub fn entries(self) -> impl Iterator<Item = &'a [u8]> {
self.0.abins(DnsRrKey::TXT_DATA)
}
}
impl fmt::Debug for TxtRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TxtRecord")
.field("name", &self.name())
.field("ttl", &self.ttl())
.field("entry_count", &self.entry_count())
.finish()
}
}
#[derive(Copy, Clone)]
pub struct OptRecord<'a>(&'a DnsRr);
impl<'a> OptRecord<'a> {
common_accessors!(OptRecord);
pub fn udp_size(self) -> u16 {
self.0.get_u16(DnsRrKey::OPT_UDP_SIZE)
}
pub fn version(self) -> u8 {
self.0.get_u8(DnsRrKey::OPT_VERSION)
}
pub fn flags(self) -> u16 {
self.0.get_u16(DnsRrKey::OPT_FLAGS)
}
pub fn option_count(self) -> usize {
self.0.get_opt_count(DnsRrKey::OPT_OPTIONS)
}
pub fn options(self) -> impl Iterator<Item = (u16, Result<OptValue, OptParseError>)> + 'a {
self.0
.opts(DnsRrKey::OPT_OPTIONS)
.map(|(k, v)| (k, parse_opt_value(DnsRrKey::OPT_OPTIONS, k, v)))
}
pub fn raw_options(self) -> impl Iterator<Item = (u16, &'a [u8])> {
self.0.opts(DnsRrKey::OPT_OPTIONS)
}
}
impl fmt::Debug for OptRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OptRecord")
.field("udp_size", &self.udp_size())
.field("version", &self.version())
.field("flags", &self.flags())
.field("option_count", &self.option_count())
.finish()
}
}
#[derive(Copy, Clone)]
pub struct SvcbRecord<'a>(&'a DnsRr);
impl<'a> SvcbRecord<'a> {
common_accessors!(SvcbRecord);
pub fn priority(self) -> u16 {
self.0.get_u16(DnsRrKey::SVCB_PRIORITY)
}
pub fn target(self) -> &'a str {
self.0.get_str(DnsRrKey::SVCB_TARGET).unwrap_or("")
}
pub fn param_count(self) -> usize {
self.0.get_opt_count(DnsRrKey::SVCB_PARAMS)
}
pub fn params(self) -> impl Iterator<Item = (u16, Result<OptValue, OptParseError>)> + 'a {
self.0
.opts(DnsRrKey::SVCB_PARAMS)
.map(|(k, v)| (k, parse_opt_value(DnsRrKey::SVCB_PARAMS, k, v)))
}
pub fn raw_params(self) -> impl Iterator<Item = (u16, &'a [u8])> {
self.0.opts(DnsRrKey::SVCB_PARAMS)
}
}
impl fmt::Debug for SvcbRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SvcbRecord")
.field("name", &self.name())
.field("ttl", &self.ttl())
.field("priority", &self.priority())
.field("target", &self.target())
.field("param_count", &self.param_count())
.finish()
}
}
#[derive(Copy, Clone)]
pub struct HttpsRecord<'a>(&'a DnsRr);
impl<'a> HttpsRecord<'a> {
common_accessors!(HttpsRecord);
pub fn priority(self) -> u16 {
self.0.get_u16(DnsRrKey::HTTPS_PRIORITY)
}
pub fn target(self) -> &'a str {
self.0.get_str(DnsRrKey::HTTPS_TARGET).unwrap_or("")
}
pub fn param_count(self) -> usize {
self.0.get_opt_count(DnsRrKey::HTTPS_PARAMS)
}
pub fn params(self) -> impl Iterator<Item = (u16, Result<OptValue, OptParseError>)> + 'a {
self.0
.opts(DnsRrKey::HTTPS_PARAMS)
.map(|(k, v)| (k, parse_opt_value(DnsRrKey::HTTPS_PARAMS, k, v)))
}
pub fn raw_params(self) -> impl Iterator<Item = (u16, &'a [u8])> {
self.0.opts(DnsRrKey::HTTPS_PARAMS)
}
}
impl fmt::Debug for HttpsRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HttpsRecord")
.field("name", &self.name())
.field("ttl", &self.ttl())
.field("priority", &self.priority())
.field("target", &self.target())
.field("param_count", &self.param_count())
.finish()
}
}
#[derive(Copy, Clone)]
pub struct CaaRecord<'a>(&'a DnsRr);
impl<'a> CaaRecord<'a> {
common_accessors!(CaaRecord);
pub fn flags(self) -> u8 {
self.0.get_u8(DnsRrKey::CAA_CRITICAL)
}
pub fn is_critical(self) -> bool {
(self.flags() & 0x80) != 0
}
pub fn tag(self) -> &'a str {
self.0.get_str(DnsRrKey::CAA_TAG).unwrap_or("")
}
pub fn value(self) -> &'a [u8] {
self.0.get_bin(DnsRrKey::CAA_VALUE).unwrap_or(&[])
}
}
impl fmt::Debug for CaaRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CaaRecord")
.field("name", &self.name())
.field("ttl", &self.ttl())
.field("flags", &self.flags())
.field("is_critical", &self.is_critical())
.field("tag", &self.tag())
.field("value_len", &self.value().len())
.finish()
}
}
#[derive(Copy, Clone)]
pub struct RawRrRecord<'a>(&'a DnsRr);
impl<'a> RawRrRecord<'a> {
common_accessors!(RawRrRecord);
pub fn raw_type(self) -> u16 {
self.0.get_u16(DnsRrKey::RAW_RR_TYPE)
}
pub fn data(self) -> &'a [u8] {
self.0.get_bin(DnsRrKey::RAW_RR_DATA).unwrap_or(&[])
}
}
impl fmt::Debug for RawRrRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RawRrRecord")
.field("name", &self.name())
.field("ttl", &self.ttl())
.field("raw_type", &self.raw_type())
.field("data_len", &self.data().len())
.finish()
}
}
#[derive(Copy, Clone, Debug)]
#[non_exhaustive]
pub enum TypedRr<'a> {
A(ARecord<'a>),
Aaaa(AaaaRecord<'a>),
Ns(NsRecord<'a>),
Cname(CnameRecord<'a>),
Soa(SoaRecord<'a>),
Ptr(PtrRecord<'a>),
Hinfo(HinfoRecord<'a>),
Mx(MxRecord<'a>),
Txt(TxtRecord<'a>),
Sig(SigRecord<'a>),
Srv(SrvRecord<'a>),
Naptr(NaptrRecord<'a>),
Opt(OptRecord<'a>),
Tlsa(TlsaRecord<'a>),
Svcb(SvcbRecord<'a>),
Https(HttpsRecord<'a>),
Uri(UriRecord<'a>),
Caa(CaaRecord<'a>),
RawRr(RawRrRecord<'a>),
Any(&'a DnsRr),
Unknown(&'a DnsRr),
}
impl DnsRr {
pub fn as_a(&self) -> Option<ARecord<'_>> {
(self.rr_type() == DnsRecordType::A).then(|| ARecord::new(self))
}
pub fn as_aaaa(&self) -> Option<AaaaRecord<'_>> {
(self.rr_type() == DnsRecordType::AAAA).then(|| AaaaRecord::new(self))
}
pub fn as_ns(&self) -> Option<NsRecord<'_>> {
(self.rr_type() == DnsRecordType::NS).then(|| NsRecord::new(self))
}
pub fn as_cname(&self) -> Option<CnameRecord<'_>> {
(self.rr_type() == DnsRecordType::CNAME).then(|| CnameRecord::new(self))
}
pub fn as_soa(&self) -> Option<SoaRecord<'_>> {
(self.rr_type() == DnsRecordType::SOA).then(|| SoaRecord::new(self))
}
pub fn as_ptr_rr(&self) -> Option<PtrRecord<'_>> {
(self.rr_type() == DnsRecordType::PTR).then(|| PtrRecord::new(self))
}
pub fn as_hinfo(&self) -> Option<HinfoRecord<'_>> {
(self.rr_type() == DnsRecordType::HINFO).then(|| HinfoRecord::new(self))
}
pub fn as_mx(&self) -> Option<MxRecord<'_>> {
(self.rr_type() == DnsRecordType::MX).then(|| MxRecord::new(self))
}
pub fn as_txt(&self) -> Option<TxtRecord<'_>> {
(self.rr_type() == DnsRecordType::TXT).then(|| TxtRecord::new(self))
}
pub fn as_sig(&self) -> Option<SigRecord<'_>> {
(self.rr_type() == DnsRecordType::SIG).then(|| SigRecord::new(self))
}
pub fn as_srv(&self) -> Option<SrvRecord<'_>> {
(self.rr_type() == DnsRecordType::SRV).then(|| SrvRecord::new(self))
}
pub fn as_naptr(&self) -> Option<NaptrRecord<'_>> {
(self.rr_type() == DnsRecordType::NAPTR).then(|| NaptrRecord::new(self))
}
pub fn as_opt(&self) -> Option<OptRecord<'_>> {
(self.rr_type() == DnsRecordType::OPT).then(|| OptRecord::new(self))
}
pub fn as_tlsa(&self) -> Option<TlsaRecord<'_>> {
(self.rr_type() == DnsRecordType::TLSA).then(|| TlsaRecord::new(self))
}
pub fn as_svcb(&self) -> Option<SvcbRecord<'_>> {
(self.rr_type() == DnsRecordType::SVCB).then(|| SvcbRecord::new(self))
}
pub fn as_https(&self) -> Option<HttpsRecord<'_>> {
(self.rr_type() == DnsRecordType::HTTPS).then(|| HttpsRecord::new(self))
}
pub fn as_uri(&self) -> Option<UriRecord<'_>> {
(self.rr_type() == DnsRecordType::URI).then(|| UriRecord::new(self))
}
pub fn as_caa(&self) -> Option<CaaRecord<'_>> {
(self.rr_type() == DnsRecordType::CAA).then(|| CaaRecord::new(self))
}
pub fn as_raw_rr(&self) -> Option<RawRrRecord<'_>> {
(self.rr_type() == DnsRecordType::RAW_RR).then(|| RawRrRecord::new(self))
}
pub fn as_typed(&self) -> TypedRr<'_> {
match self.rr_type() {
DnsRecordType::A => TypedRr::A(ARecord::new(self)),
DnsRecordType::AAAA => TypedRr::Aaaa(AaaaRecord::new(self)),
DnsRecordType::NS => TypedRr::Ns(NsRecord::new(self)),
DnsRecordType::CNAME => TypedRr::Cname(CnameRecord::new(self)),
DnsRecordType::SOA => TypedRr::Soa(SoaRecord::new(self)),
DnsRecordType::PTR => TypedRr::Ptr(PtrRecord::new(self)),
DnsRecordType::HINFO => TypedRr::Hinfo(HinfoRecord::new(self)),
DnsRecordType::MX => TypedRr::Mx(MxRecord::new(self)),
DnsRecordType::TXT => TypedRr::Txt(TxtRecord::new(self)),
DnsRecordType::SIG => TypedRr::Sig(SigRecord::new(self)),
DnsRecordType::SRV => TypedRr::Srv(SrvRecord::new(self)),
DnsRecordType::NAPTR => TypedRr::Naptr(NaptrRecord::new(self)),
DnsRecordType::OPT => TypedRr::Opt(OptRecord::new(self)),
DnsRecordType::TLSA => TypedRr::Tlsa(TlsaRecord::new(self)),
DnsRecordType::SVCB => TypedRr::Svcb(SvcbRecord::new(self)),
DnsRecordType::HTTPS => TypedRr::Https(HttpsRecord::new(self)),
DnsRecordType::URI => TypedRr::Uri(UriRecord::new(self)),
DnsRecordType::CAA => TypedRr::Caa(CaaRecord::new(self)),
DnsRecordType::RAW_RR => TypedRr::RawRr(RawRrRecord::new(self)),
DnsRecordType::ANY => TypedRr::Any(self),
DnsRecordType::UNKNOWN(_) => TypedRr::Unknown(self),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dns::{DnsFlags, DnsOpcode, DnsParseFlags, DnsRcode, DnsRecord, DnsSection};
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
fn assert_copy<T: Copy>() {}
#[test]
fn wrappers_are_send_sync_copy() {
assert_send::<ARecord<'_>>();
assert_sync::<ARecord<'_>>();
assert_copy::<ARecord<'_>>();
assert_send::<TypedRr<'_>>();
assert_sync::<TypedRr<'_>>();
assert_copy::<TypedRr<'_>>();
}
fn make_rec(rtype: DnsRecordType) -> DnsRecord {
let mut rec =
DnsRecord::new(0, DnsFlags::QR, DnsOpcode::Query, DnsRcode::NoError).expect("create");
rec.query_add("example.com", DnsRecordType::A, DnsCls::IN)
.expect("query_add");
rec.rr_add(DnsSection::Answer, "example.com", rtype, DnsCls::IN, 300)
.expect("rr_add");
rec
}
fn first_rr(rec: &DnsRecord) -> &DnsRr {
rec.rr(DnsSection::Answer, 0).expect("rr")
}
#[test]
fn as_a_matches_only_a() {
let rec = make_rec(DnsRecordType::A);
let rr = first_rr(&rec);
assert!(rr.as_a().is_some());
assert!(rr.as_aaaa().is_none());
assert!(rr.as_mx().is_none());
assert!(rr.as_txt().is_none());
}
#[test]
fn as_typed_dispatches_correctly() {
for &rtype in &[
DnsRecordType::A,
DnsRecordType::AAAA,
DnsRecordType::MX,
DnsRecordType::TXT,
DnsRecordType::CAA,
DnsRecordType::SRV,
] {
let rec = make_rec(rtype);
let rr = first_rr(&rec);
match (rtype, rr.as_typed()) {
(DnsRecordType::A, TypedRr::A(_))
| (DnsRecordType::AAAA, TypedRr::Aaaa(_))
| (DnsRecordType::MX, TypedRr::Mx(_))
| (DnsRecordType::TXT, TypedRr::Txt(_))
| (DnsRecordType::CAA, TypedRr::Caa(_))
| (DnsRecordType::SRV, TypedRr::Srv(_)) => {}
(rt, other) => panic!("type {rt:?} dispatched to wrong variant: {other:?}"),
}
}
}
#[test]
fn builder_partial_fields_have_defaults() {
let rec = make_rec(DnsRecordType::A);
let a = first_rr(&rec).as_a().expect("as_a");
let _ = a.addr();
assert_eq!(a.ttl(), 300);
let rec = make_rec(DnsRecordType::MX);
let mx = first_rr(&rec).as_mx().expect("as_mx");
assert_eq!(mx.preference(), 0);
assert_eq!(mx.exchange(), "");
let rec = make_rec(DnsRecordType::SOA);
let soa = first_rr(&rec).as_soa().expect("as_soa");
assert_eq!(soa.mname(), "");
assert_eq!(soa.rname(), "");
assert_eq!(soa.serial(), 0);
assert_eq!(soa.minimum(), 0);
let rec = make_rec(DnsRecordType::CAA);
let caa = first_rr(&rec).as_caa().expect("as_caa");
assert_eq!(caa.flags(), 0);
assert!(!caa.is_critical());
assert_eq!(caa.tag(), "");
assert_eq!(caa.value(), &[] as &[u8]);
let rec = make_rec(DnsRecordType::TXT);
let txt = first_rr(&rec).as_txt().expect("as_txt");
assert_eq!(txt.entry_count(), 0);
assert_eq!(txt.entries().count(), 0);
let rec = make_rec(DnsRecordType::RAW_RR);
let raw = first_rr(&rec).as_raw_rr().expect("as_raw_rr");
assert_eq!(raw.raw_type(), 0);
assert_eq!(raw.data(), &[] as &[u8]);
}
#[test]
fn a_full_roundtrip() {
let mut rec = make_rec(DnsRecordType::A);
let rr = rec.rr_mut(DnsSection::Answer, 0).expect("rr_mut");
rr.set_addr(DnsRrKey::A_ADDR, Ipv4Addr::new(10, 0, 0, 1))
.expect("set");
let a = first_rr(&rec).as_a().expect("as_a");
assert_eq!(a.addr(), Ipv4Addr::new(10, 0, 0, 1));
assert_eq!(a.name(), "example.com");
}
#[test]
fn aaaa_full_roundtrip() {
let mut rec = make_rec(DnsRecordType::AAAA);
let addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
let rr = rec.rr_mut(DnsSection::Answer, 0).expect("rr_mut");
rr.set_addr6(DnsRrKey::AAAA_ADDR, addr).expect("set");
let aaaa = first_rr(&rec).as_aaaa().expect("as_aaaa");
assert_eq!(aaaa.addr(), addr);
}
#[test]
fn mx_full_roundtrip() {
let mut rec = make_rec(DnsRecordType::MX);
let rr = rec.rr_mut(DnsSection::Answer, 0).expect("rr_mut");
rr.set_u16(DnsRrKey::MX_PREFERENCE, 10).expect("pref");
rr.set_str(DnsRrKey::MX_EXCHANGE, "mail.example.com")
.expect("exch");
let mx = first_rr(&rec).as_mx().expect("as_mx");
assert_eq!(mx.preference(), 10);
assert_eq!(mx.exchange(), "mail.example.com");
}
#[test]
fn caa_critical_bit() {
let mut rec = make_rec(DnsRecordType::CAA);
let rr = rec.rr_mut(DnsSection::Answer, 0).expect("rr_mut");
rr.set_u8(DnsRrKey::CAA_CRITICAL, 0x80).expect("crit");
rr.set_str(DnsRrKey::CAA_TAG, "issue").expect("tag");
rr.set_bin(DnsRrKey::CAA_VALUE, b"ca.example.net")
.expect("val");
let caa = first_rr(&rec).as_caa().expect("as_caa");
assert_eq!(caa.flags(), 0x80);
assert!(caa.is_critical());
assert_eq!(caa.tag(), "issue");
assert_eq!(caa.value(), &b"ca.example.net"[..]);
}
#[test]
fn txt_entries_iterator() {
let mut rec = make_rec(DnsRecordType::TXT);
let rr = rec.rr_mut(DnsSection::Answer, 0).expect("rr_mut");
rr.add_abin(DnsRrKey::TXT_DATA, b"first").expect("a1");
rr.add_abin(DnsRrKey::TXT_DATA, b"second").expect("a2");
let txt = first_rr(&rec).as_txt().expect("as_txt");
assert_eq!(txt.entry_count(), 2);
let entries: Vec<&[u8]> = txt.entries().collect();
assert_eq!(entries, vec![&b"first"[..], &b"second"[..]]);
}
fn build_then_parse(rec: &DnsRecord) -> DnsRecord {
let wire = rec.write().expect("write");
DnsRecord::parse(&wire, DnsParseFlags::empty()).expect("parse")
}
#[test]
fn parsed_a_record() {
let mut rec = make_rec(DnsRecordType::A);
rec.rr_mut(DnsSection::Answer, 0)
.unwrap()
.set_addr(DnsRrKey::A_ADDR, Ipv4Addr::new(93, 184, 216, 34))
.unwrap();
let parsed = build_then_parse(&rec);
let a = first_rr(&parsed).as_a().expect("as_a");
assert_eq!(a.addr(), Ipv4Addr::new(93, 184, 216, 34));
}
#[test]
fn parsed_soa_record() {
let mut rec = make_rec(DnsRecordType::SOA);
let rr = rec.rr_mut(DnsSection::Answer, 0).unwrap();
rr.set_str(DnsRrKey::SOA_MNAME, "ns1.example.com").unwrap();
rr.set_str(DnsRrKey::SOA_RNAME, "hostmaster.example.com")
.unwrap();
rr.set_u32(DnsRrKey::SOA_SERIAL, 42).unwrap();
rr.set_u32(DnsRrKey::SOA_REFRESH, 3600).unwrap();
rr.set_u32(DnsRrKey::SOA_RETRY, 900).unwrap();
rr.set_u32(DnsRrKey::SOA_EXPIRE, 604800).unwrap();
rr.set_u32(DnsRrKey::SOA_MINIMUM, 60).unwrap();
let parsed = build_then_parse(&rec);
let soa = first_rr(&parsed).as_soa().expect("as_soa");
assert_eq!(soa.mname(), "ns1.example.com");
assert_eq!(soa.rname(), "hostmaster.example.com");
assert_eq!(soa.serial(), 42);
assert_eq!(soa.expire(), 604800);
}
#[test]
fn parsed_txt_record() {
let mut rec = make_rec(DnsRecordType::TXT);
rec.rr_mut(DnsSection::Answer, 0)
.unwrap()
.add_abin(DnsRrKey::TXT_DATA, b"v=spf1 -all")
.unwrap();
let parsed = build_then_parse(&rec);
let txt = first_rr(&parsed).as_txt().expect("as_txt");
assert_eq!(txt.entry_count(), 1);
assert_eq!(txt.entries().next(), Some(&b"v=spf1 -all"[..]));
}
#[test]
fn parsed_mx_record() {
let mut rec = make_rec(DnsRecordType::MX);
let rr = rec.rr_mut(DnsSection::Answer, 0).unwrap();
rr.set_u16(DnsRrKey::MX_PREFERENCE, 5).unwrap();
rr.set_str(DnsRrKey::MX_EXCHANGE, "mx.example.com").unwrap();
let parsed = build_then_parse(&rec);
let mx = first_rr(&parsed).as_mx().expect("as_mx");
assert_eq!(mx.preference(), 5);
assert_eq!(mx.exchange(), "mx.example.com");
}
fn build<F: FnOnce(&mut DnsRr)>(rtype: DnsRecordType, populate: F) -> DnsRecord {
let mut rec = make_rec(rtype);
populate(rec.rr_mut(DnsSection::Answer, 0).expect("rr_mut"));
rec
}
#[test]
fn ns_full_accessors_and_debug() {
let rec = build(DnsRecordType::NS, |rr| {
rr.set_str(DnsRrKey::NS_NSDNAME, "ns1.example.com").unwrap();
});
let ns = first_rr(&rec).as_ns().expect("as_ns");
assert_eq!(ns.nsdname(), "ns1.example.com");
assert_eq!(ns.name(), "example.com");
assert_eq!(ns.dns_class(), DnsCls::IN);
assert_eq!(ns.ttl(), 300);
let _ = ns.as_dns_rr();
assert!(format!("{ns:?}").contains("NsRecord"));
assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Ns(_)));
}
#[test]
fn cname_full_accessors_and_debug() {
let rec = build(DnsRecordType::CNAME, |rr| {
rr.set_str(DnsRrKey::CNAME_CNAME, "alias.example.com")
.unwrap();
});
let c = first_rr(&rec).as_cname().expect("as_cname");
assert_eq!(c.cname(), "alias.example.com");
assert!(format!("{c:?}").contains("CnameRecord"));
assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Cname(_)));
}
#[test]
fn ptr_full_accessors_and_debug() {
let rec = build(DnsRecordType::PTR, |rr| {
rr.set_str(DnsRrKey::PTR_DNAME, "host.example.com").unwrap();
});
let p = first_rr(&rec).as_ptr_rr().expect("as_ptr_rr");
assert_eq!(p.dname(), "host.example.com");
assert!(format!("{p:?}").contains("PtrRecord"));
assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Ptr(_)));
}
#[test]
fn hinfo_full_accessors_and_debug() {
let rec = build(DnsRecordType::HINFO, |rr| {
rr.set_str(DnsRrKey::HINFO_CPU, "x86_64").unwrap();
rr.set_str(DnsRrKey::HINFO_OS, "linux").unwrap();
});
let h = first_rr(&rec).as_hinfo().expect("as_hinfo");
assert_eq!(h.cpu(), "x86_64");
assert_eq!(h.os(), "linux");
assert!(format!("{h:?}").contains("HinfoRecord"));
assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Hinfo(_)));
}
#[test]
fn soa_full_accessors_and_debug() {
let rec = build(DnsRecordType::SOA, |rr| {
rr.set_str(DnsRrKey::SOA_MNAME, "ns.example.com").unwrap();
rr.set_str(DnsRrKey::SOA_RNAME, "hostmaster.example.com")
.unwrap();
rr.set_u32(DnsRrKey::SOA_SERIAL, 1).unwrap();
rr.set_u32(DnsRrKey::SOA_REFRESH, 2).unwrap();
rr.set_u32(DnsRrKey::SOA_RETRY, 3).unwrap();
rr.set_u32(DnsRrKey::SOA_EXPIRE, 4).unwrap();
rr.set_u32(DnsRrKey::SOA_MINIMUM, 5).unwrap();
});
let s = first_rr(&rec).as_soa().expect("as_soa");
assert_eq!(s.mname(), "ns.example.com");
assert_eq!(s.rname(), "hostmaster.example.com");
assert_eq!(s.serial(), 1);
assert_eq!(s.refresh(), 2);
assert_eq!(s.retry(), 3);
assert_eq!(s.expire(), 4);
assert_eq!(s.minimum(), 5);
assert!(format!("{s:?}").contains("SoaRecord"));
assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Soa(_)));
}
#[test]
fn sig_full_accessors_and_debug() {
let rec = build(DnsRecordType::SIG, |rr| {
rr.set_u16(DnsRrKey::SIG_TYPE_COVERED, 1).unwrap();
rr.set_u8(DnsRrKey::SIG_ALGORITHM, 8).unwrap();
rr.set_u8(DnsRrKey::SIG_LABELS, 2).unwrap();
rr.set_u32(DnsRrKey::SIG_ORIGINAL_TTL, 3600).unwrap();
rr.set_u32(DnsRrKey::SIG_EXPIRATION, 4_000_000_000).unwrap();
rr.set_u32(DnsRrKey::SIG_INCEPTION, 1_000_000_000).unwrap();
rr.set_u16(DnsRrKey::SIG_KEY_TAG, 12345).unwrap();
rr.set_str(DnsRrKey::SIG_SIGNERS_NAME, "example.com")
.unwrap();
rr.set_bin(DnsRrKey::SIG_SIGNATURE, b"\x01\x02\x03\x04")
.unwrap();
});
let s = first_rr(&rec).as_sig().expect("as_sig");
assert_eq!(s.type_covered(), 1);
assert_eq!(s.algorithm(), 8);
assert_eq!(s.labels(), 2);
assert_eq!(s.original_ttl(), 3600);
assert_eq!(s.expiration(), 4_000_000_000);
assert_eq!(s.inception(), 1_000_000_000);
assert_eq!(s.key_tag(), 12345);
assert_eq!(s.signers_name(), "example.com");
assert_eq!(s.signature(), &b"\x01\x02\x03\x04"[..]);
assert!(format!("{s:?}").contains("SigRecord"));
assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Sig(_)));
}
#[test]
fn srv_full_accessors_and_debug() {
let rec = build(DnsRecordType::SRV, |rr| {
rr.set_u16(DnsRrKey::SRV_PRIORITY, 10).unwrap();
rr.set_u16(DnsRrKey::SRV_WEIGHT, 20).unwrap();
rr.set_u16(DnsRrKey::SRV_PORT, 443).unwrap();
rr.set_str(DnsRrKey::SRV_TARGET, "svc.example.com").unwrap();
});
let s = first_rr(&rec).as_srv().expect("as_srv");
assert_eq!(s.priority(), 10);
assert_eq!(s.weight(), 20);
assert_eq!(s.port(), 443);
assert_eq!(s.target(), "svc.example.com");
assert!(format!("{s:?}").contains("SrvRecord"));
}
#[test]
fn naptr_full_accessors_and_debug() {
let rec = build(DnsRecordType::NAPTR, |rr| {
rr.set_u16(DnsRrKey::NAPTR_ORDER, 100).unwrap();
rr.set_u16(DnsRrKey::NAPTR_PREFERENCE, 10).unwrap();
rr.set_str(DnsRrKey::NAPTR_FLAGS, "U").unwrap();
rr.set_str(DnsRrKey::NAPTR_SERVICES, "E2U+sip").unwrap();
rr.set_str(DnsRrKey::NAPTR_REGEXP, "!^.*$!sip:info@example.com!")
.unwrap();
rr.set_str(DnsRrKey::NAPTR_REPLACEMENT, ".").unwrap();
});
let n = first_rr(&rec).as_naptr().expect("as_naptr");
assert_eq!(n.order(), 100);
assert_eq!(n.preference(), 10);
assert_eq!(n.flags(), "U");
assert_eq!(n.services(), "E2U+sip");
assert_eq!(n.regexp(), "!^.*$!sip:info@example.com!");
assert_eq!(n.replacement(), ".");
assert!(format!("{n:?}").contains("NaptrRecord"));
assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Naptr(_)));
}
#[test]
fn tlsa_full_accessors_and_debug() {
let rec = build(DnsRecordType::TLSA, |rr| {
rr.set_u8(DnsRrKey::TLSA_CERT_USAGE, 3).unwrap();
rr.set_u8(DnsRrKey::TLSA_SELECTOR, 1).unwrap();
rr.set_u8(DnsRrKey::TLSA_MATCH, 1).unwrap();
rr.set_bin(DnsRrKey::TLSA_DATA, &[0xab; 32]).unwrap();
});
let t = first_rr(&rec).as_tlsa().expect("as_tlsa");
assert_eq!(t.cert_usage(), 3);
assert_eq!(t.selector(), 1);
assert_eq!(t.matching_type(), 1);
assert_eq!(t.data().len(), 32);
assert!(format!("{t:?}").contains("TlsaRecord"));
assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Tlsa(_)));
}
#[test]
fn uri_full_accessors_and_debug() {
let rec = build(DnsRecordType::URI, |rr| {
rr.set_u16(DnsRrKey::URI_PRIORITY, 1).unwrap();
rr.set_u16(DnsRrKey::URI_WEIGHT, 2).unwrap();
rr.set_str(DnsRrKey::URI_TARGET, "https://example.com/")
.unwrap();
});
let u = first_rr(&rec).as_uri().expect("as_uri");
assert_eq!(u.priority(), 1);
assert_eq!(u.weight(), 2);
assert_eq!(u.target(), "https://example.com/");
assert!(format!("{u:?}").contains("UriRecord"));
assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Uri(_)));
}
#[test]
fn svcb_full_accessors_and_debug() {
let rec = build(DnsRecordType::SVCB, |rr| {
rr.set_u16(DnsRrKey::SVCB_PRIORITY, 1).unwrap();
rr.set_str(DnsRrKey::SVCB_TARGET, "svc.example.com")
.unwrap();
rr.set_opt(DnsRrKey::SVCB_PARAMS, 1, b"\x00\x02h2").unwrap();
});
let s = first_rr(&rec).as_svcb().expect("as_svcb");
assert_eq!(s.priority(), 1);
assert_eq!(s.target(), "svc.example.com");
assert_eq!(s.param_count(), 1);
let raw: Vec<(u16, &[u8])> = s.raw_params().collect();
assert_eq!(raw, vec![(1u16, &b"\x00\x02h2"[..])]);
let parsed: Vec<(u16, OptValue)> =
s.params().map(|(k, v)| (k, v.expect("decode"))).collect();
assert_eq!(parsed.len(), 1);
assert_eq!(parsed[0].0, 1);
assert!(matches!(parsed[0].1, OptValue::StrList(_)));
assert!(format!("{s:?}").contains("SvcbRecord"));
assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Svcb(_)));
}
#[test]
fn https_full_accessors_and_debug() {
let rec = build(DnsRecordType::HTTPS, |rr| {
rr.set_u16(DnsRrKey::HTTPS_PRIORITY, 1).unwrap();
rr.set_str(DnsRrKey::HTTPS_TARGET, "svc.example.com")
.unwrap();
rr.set_opt(DnsRrKey::HTTPS_PARAMS, 1, b"\x00\x02h3")
.unwrap();
});
let h = first_rr(&rec).as_https().expect("as_https");
assert_eq!(h.priority(), 1);
assert_eq!(h.target(), "svc.example.com");
assert_eq!(h.param_count(), 1);
assert_eq!(h.raw_params().count(), 1);
assert_eq!(h.params().count(), 1);
assert!(format!("{h:?}").contains("HttpsRecord"));
assert!(matches!(first_rr(&rec).as_typed(), TypedRr::Https(_)));
}
#[test]
fn opt_full_accessors_and_debug() {
let mut rec =
DnsRecord::new(0, DnsFlags::QR, DnsOpcode::Query, DnsRcode::NoError).expect("create");
rec.query_add("example.com", DnsRecordType::A, DnsCls::IN)
.expect("query_add");
if rec
.rr_add(
DnsSection::Additional,
"",
DnsRecordType::OPT,
DnsCls::IN,
0,
)
.is_err()
{
return;
}
let rr = rec.rr_mut(DnsSection::Additional, 0).expect("rr_mut");
let _ = rr.set_u16(DnsRrKey::OPT_UDP_SIZE, 4096);
let _ = rr.set_u8(DnsRrKey::OPT_VERSION, 0);
let _ = rr.set_u16(DnsRrKey::OPT_FLAGS, 0x8000);
let _ = rr.set_opt(DnsRrKey::OPT_OPTIONS, 10, b"\x01\x02");
let opt_rr = rec
.rr(DnsSection::Additional, 0)
.expect("rr")
.as_opt()
.expect("as_opt");
let _ = opt_rr.udp_size();
let _ = opt_rr.version();
let _ = opt_rr.flags();
let _ = opt_rr.option_count();
let _ = opt_rr.options().count();
let _ = opt_rr.raw_options().count();
let _ = format!("{opt_rr:?}");
assert!(matches!(
rec.rr(DnsSection::Additional, 0).unwrap().as_typed(),
TypedRr::Opt(_)
));
}
#[test]
fn raw_rr_full_accessors_and_debug() {
let rec = build(DnsRecordType::RAW_RR, |rr| {
let _ = rr.set_u16(DnsRrKey::RAW_RR_TYPE, 99);
let _ = rr.set_bin(DnsRrKey::RAW_RR_DATA, b"hello");
});
let r = first_rr(&rec).as_raw_rr().expect("as_raw_rr");
let _ = r.raw_type();
let _ = r.data();
assert!(format!("{r:?}").contains("RawRrRecord"));
assert!(matches!(first_rr(&rec).as_typed(), TypedRr::RawRr(_)));
}
#[test]
fn debug_impls_for_all_simple_wrappers() {
let rec = make_rec(DnsRecordType::A);
assert!(format!("{:?}", first_rr(&rec).as_a().unwrap()).contains("ARecord"));
let rec = make_rec(DnsRecordType::AAAA);
assert!(format!("{:?}", first_rr(&rec).as_aaaa().unwrap()).contains("AaaaRecord"));
let rec = make_rec(DnsRecordType::MX);
assert!(format!("{:?}", first_rr(&rec).as_mx().unwrap()).contains("MxRecord"));
let rec = make_rec(DnsRecordType::TXT);
assert!(format!("{:?}", first_rr(&rec).as_txt().unwrap()).contains("TxtRecord"));
let rec = make_rec(DnsRecordType::CAA);
assert!(format!("{:?}", first_rr(&rec).as_caa().unwrap()).contains("CaaRecord"));
assert!(format!("{:?}", first_rr(&rec).as_typed()).contains("Caa"));
}
#[test]
fn as_discriminators_return_none_on_mismatch() {
let rec = make_rec(DnsRecordType::A);
let rr = first_rr(&rec);
assert!(rr.as_ns().is_none());
assert!(rr.as_cname().is_none());
assert!(rr.as_soa().is_none());
assert!(rr.as_ptr_rr().is_none());
assert!(rr.as_hinfo().is_none());
assert!(rr.as_sig().is_none());
assert!(rr.as_srv().is_none());
assert!(rr.as_naptr().is_none());
assert!(rr.as_opt().is_none());
assert!(rr.as_tlsa().is_none());
assert!(rr.as_svcb().is_none());
assert!(rr.as_https().is_none());
assert!(rr.as_uri().is_none());
assert!(rr.as_caa().is_none());
assert!(rr.as_raw_rr().is_none());
}
}