use std::fmt;
use std::net::{Ipv4Addr, Ipv6Addr};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RData {
A(Ipv4Addr),
AAAA(Ipv6Addr),
CNAME(String),
MX {
preference: u16,
exchange: String,
},
NS(String),
PTR(String),
TXT(Vec<String>),
SOA {
mname: String,
rname: String,
serial: u32,
refresh: u32,
retry: u32,
expire: u32,
minimum: u32,
},
SRV {
priority: u16,
weight: u16,
port: u16,
target: String,
},
CAA {
flags: u8,
tag: String,
value: String,
},
SVCB {
priority: u16,
target: String,
params: Vec<u8>,
},
HTTPS {
priority: u16,
target: String,
params: Vec<u8>,
},
DS {
key_tag: u16,
algorithm: u8,
digest_type: u8,
digest: Vec<u8>,
},
RRSIG {
type_covered: u16,
algorithm: u8,
labels: u8,
original_ttl: u32,
expiration: u32,
inception: u32,
key_tag: u16,
signer_name: String,
signature: Vec<u8>,
},
NSEC {
next_domain: String,
type_bitmaps: Vec<u8>,
},
DNSKEY {
flags: u16,
protocol: u8,
algorithm: u8,
public_key: Vec<u8>,
},
NSEC3 {
hash_algorithm: u8,
flags: u8,
iterations: u16,
salt: Vec<u8>,
next_hashed: Vec<u8>,
type_bitmaps: Vec<u8>,
},
NSEC3PARAM {
hash_algorithm: u8,
flags: u8,
iterations: u16,
salt: Vec<u8>,
},
OPT {
extended_rcode: u8,
version: u8,
flags: u16,
options: Vec<u8>,
},
Unknown(Vec<u8>),
}
impl RData {
pub fn a(addr: Ipv4Addr) -> Self {
RData::A(addr)
}
pub fn aaaa(addr: Ipv6Addr) -> Self {
RData::AAAA(addr)
}
pub fn cname(name: String) -> Self {
RData::CNAME(name)
}
pub fn mx(preference: u16, exchange: String) -> Self {
RData::MX {
preference,
exchange,
}
}
pub fn ns(name: String) -> Self {
RData::NS(name)
}
pub fn ptr(name: String) -> Self {
RData::PTR(name)
}
pub fn txt(texts: Vec<String>) -> Self {
RData::TXT(texts)
}
pub fn soa(
mname: String,
rname: String,
serial: u32,
refresh: u32,
retry: u32,
expire: u32,
minimum: u32,
) -> Self {
RData::SOA {
mname,
rname,
serial,
refresh,
retry,
expire,
minimum,
}
}
pub fn srv(priority: u16, weight: u16, port: u16, target: String) -> Self {
RData::SRV {
priority,
weight,
port,
target,
}
}
pub fn caa(flags: u8, tag: String, value: String) -> Self {
RData::CAA { flags, tag, value }
}
pub fn svcb(priority: u16, target: String, params: Vec<u8>) -> Self {
RData::SVCB {
priority,
target,
params,
}
}
pub fn https(priority: u16, target: String, params: Vec<u8>) -> Self {
RData::HTTPS {
priority,
target,
params,
}
}
}
impl fmt::Display for RData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RData::A(addr) => write!(f, "{}", addr),
RData::AAAA(addr) => write!(f, "{}", addr),
RData::CNAME(name) => write!(f, "{}", name),
RData::MX {
preference,
exchange,
} => write!(f, "{} {}", preference, exchange),
RData::NS(name) => write!(f, "{}", name),
RData::PTR(name) => write!(f, "{}", name),
RData::TXT(texts) => {
let joined = texts
.iter()
.map(|s| format!("\"{}\"", s))
.collect::<Vec<_>>()
.join(" ");
write!(f, "{}", joined)
}
RData::SOA {
mname,
rname,
serial,
refresh,
retry,
expire,
minimum,
} => write!(
f,
"{} {} {} {} {} {} {}",
mname, rname, serial, refresh, retry, expire, minimum
),
RData::SRV {
priority,
weight,
port,
target,
} => write!(f, "{} {} {} {}", priority, weight, port, target),
RData::CAA { flags, tag, value } => write!(f, "{} {} \"{}\"", flags, tag, value),
RData::SVCB {
priority,
target,
params,
} => write!(
f,
"{} {} <params: {} bytes>",
priority,
target,
params.len()
),
RData::HTTPS {
priority,
target,
params,
} => write!(
f,
"{} {} <params: {} bytes>",
priority,
target,
params.len()
),
RData::DS {
key_tag,
algorithm,
digest_type,
digest,
} => write!(
f,
"{} {} {} <digest: {} bytes>",
key_tag,
algorithm,
digest_type,
digest.len()
),
RData::RRSIG {
type_covered,
algorithm,
signer_name,
..
} => write!(f, "{} {} {} ...", type_covered, algorithm, signer_name),
RData::NSEC {
next_domain,
type_bitmaps,
} => write!(f, "{} <{} types>", next_domain, type_bitmaps.len()),
RData::DNSKEY {
flags,
protocol,
algorithm,
public_key,
} => write!(
f,
"{} {} {} <key: {} bytes>",
flags,
protocol,
algorithm,
public_key.len()
),
RData::NSEC3 {
hash_algorithm,
iterations,
..
} => write!(f, "{} {} ...", hash_algorithm, iterations),
RData::NSEC3PARAM {
hash_algorithm,
iterations,
..
} => write!(f, "{} {} ...", hash_algorithm, iterations),
RData::OPT {
version,
flags,
options,
..
} => write!(
f,
"EDNS v{} flags:{:#x} <{} bytes>",
version,
flags,
options.len()
),
RData::Unknown(data) => write!(f, "<{} bytes>", data.len()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn test_a_record() {
let ip = Ipv4Addr::from_str("192.0.2.1").unwrap();
let rdata = RData::a(ip);
assert_eq!(rdata, RData::A(ip));
assert_eq!(format!("{}", rdata), "192.0.2.1");
}
#[test]
fn test_aaaa_record() {
let ip = Ipv6Addr::from_str("2001:db8::1").unwrap();
let rdata = RData::aaaa(ip);
assert_eq!(rdata, RData::AAAA(ip));
assert_eq!(format!("{}", rdata), "2001:db8::1");
}
#[test]
fn test_cname_record() {
let rdata = RData::cname("example.com".to_string());
assert_eq!(rdata, RData::CNAME("example.com".to_string()));
assert_eq!(format!("{}", rdata), "example.com");
}
#[test]
fn test_mx_record() {
let rdata = RData::mx(10, "mail.example.com".to_string());
if let RData::MX {
preference,
exchange,
} = &rdata
{
assert_eq!(*preference, 10);
assert_eq!(exchange, "mail.example.com");
} else {
panic!("Expected MX record");
}
assert_eq!(format!("{}", rdata), "10 mail.example.com");
}
#[test]
fn test_txt_record() {
let rdata = RData::txt(vec![
"v=spf1".to_string(),
"include:example.com".to_string(),
]);
let display = format!("{}", rdata);
assert!(display.contains("v=spf1"));
assert!(display.contains("include:example.com"));
}
#[test]
fn test_srv_record() {
let rdata = RData::srv(10, 60, 5060, "sipserver.example.com".to_string());
if let RData::SRV {
priority,
weight,
port,
target,
} = &rdata
{
assert_eq!(*priority, 10);
assert_eq!(*weight, 60);
assert_eq!(*port, 5060);
assert_eq!(target, "sipserver.example.com");
} else {
panic!("Expected SRV record");
}
}
#[test]
fn test_ns_record() {
let rdata = RData::ns("ns1.example.com".to_string());
assert_eq!(format!("{}", rdata), "ns1.example.com");
}
#[test]
fn test_ptr_record() {
let rdata = RData::ptr("example.com".to_string());
assert_eq!(format!("{}", rdata), "example.com");
}
#[test]
fn test_svcb_record() {
let rdata = RData::svcb(1, "example.com".to_string(), vec![1, 2, 3]);
if let RData::SVCB {
priority,
target,
params,
} = &rdata
{
assert_eq!(*priority, 1);
assert_eq!(target, "example.com");
assert_eq!(params, &vec![1, 2, 3]);
} else {
panic!("Expected SVCB record");
}
assert!(format!("{}", rdata).contains("example.com"));
assert!(format!("{}", rdata).contains("3 bytes"));
}
#[test]
fn test_https_record() {
let rdata = RData::https(1, "example.com".to_string(), vec![4, 5, 6]);
if let RData::HTTPS {
priority,
target,
params,
} = &rdata
{
assert_eq!(*priority, 1);
assert_eq!(target, "example.com");
assert_eq!(params, &vec![4, 5, 6]);
} else {
panic!("Expected HTTPS record");
}
assert!(format!("{}", rdata).contains("example.com"));
assert!(format!("{}", rdata).contains("3 bytes"));
}
}