use core::fmt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub const MAX_LABEL_LEN: usize = 63;
pub const MAX_DOMAIN_LEN: usize = 255;
pub const MAX_TTL: u32 = 2_147_483_647;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Environment {
#[default]
Production,
Sandbox,
}
impl Environment {
pub fn is_production(&self) -> bool {
matches!(self, Environment::Production)
}
pub fn is_sandbox(&self) -> bool {
matches!(self, Environment::Sandbox)
}
}
impl fmt::Display for Environment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Environment::Production => write!(f, "production"),
Environment::Sandbox => write!(f, "sandbox"),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[repr(C)]
pub struct Label {
len: u8,
data: [u8; MAX_LABEL_LEN],
}
impl Label {
#[inline]
pub const fn new(bytes: &[u8]) -> Option<Self> {
if bytes.is_empty() || bytes.len() > MAX_LABEL_LEN {
return None;
}
let mut data = [0u8; MAX_LABEL_LEN];
let mut i = 0;
while i < bytes.len() {
data[i] = bytes[i];
i += 1;
}
Some(Self {
len: bytes.len() as u8,
data,
})
}
#[inline]
pub const fn from_str(s: &str) -> Option<Self> {
Self::new(s.as_bytes())
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
&self.data[..self.len as usize]
}
#[inline]
pub const fn len(&self) -> usize {
self.len as usize
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub fn as_str(&self) -> Option<&str> {
std::str::from_utf8(self.as_bytes()).ok()
}
}
impl fmt::Debug for Label {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.as_str() {
Some(s) => write!(f, "Label({:?})", s),
None => write!(f, "Label({:?})", self.as_bytes()),
}
}
}
impl fmt::Display for Label {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.as_str() {
Some(s) => write!(f, "{}", s),
None => write!(f, "{:?}", self.as_bytes()),
}
}
}
impl Default for Label {
fn default() -> Self {
Self {
len: 0,
data: [0u8; MAX_LABEL_LEN],
}
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
#[repr(C)]
pub struct DomainName {
len: u8,
data: [u8; MAX_DOMAIN_LEN],
}
impl DomainName {
pub fn from_dotted(s: &str) -> Option<Self> {
if s.is_empty() {
return Some(Self {
len: 1,
data: [0u8; MAX_DOMAIN_LEN],
});
}
let mut data = [0u8; MAX_DOMAIN_LEN];
let mut pos = 0usize;
for label in s.trim_end_matches('.').split('.') {
let label_bytes = label.as_bytes();
if label_bytes.is_empty() || label_bytes.len() > MAX_LABEL_LEN {
return None;
}
if pos + 1 + label_bytes.len() >= MAX_DOMAIN_LEN {
return None;
}
data[pos] = label_bytes.len() as u8;
pos += 1;
data[pos..pos + label_bytes.len()].copy_from_slice(label_bytes);
pos += label_bytes.len();
}
data[pos] = 0;
pos += 1;
Some(Self {
len: pos as u8,
data,
})
}
pub fn to_dotted(&self) -> String {
let mut result = String::with_capacity(self.len as usize);
let mut pos = 0usize;
while pos < self.len as usize {
let label_len = self.data[pos] as usize;
if label_len == 0 {
break;
}
if !result.is_empty() {
result.push('.');
}
pos += 1;
if let Ok(s) = std::str::from_utf8(&self.data[pos..pos + label_len]) {
result.push_str(s);
}
pos += label_len;
}
result
}
#[inline]
pub const fn wire_len(&self) -> usize {
self.len as usize
}
#[inline]
pub fn as_wire_bytes(&self) -> &[u8] {
&self.data[..self.len as usize]
}
#[inline]
pub const fn is_root(&self) -> bool {
self.len == 1 && self.data[0] == 0
}
}
impl fmt::Debug for DomainName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "DomainName({:?})", self.to_dotted())
}
}
impl fmt::Display for DomainName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_dotted())
}
}
impl Default for DomainName {
fn default() -> Self {
Self {
len: 1,
data: [0u8; MAX_DOMAIN_LEN], }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(transparent)]
pub struct Ttl(u32);
impl Ttl {
pub const ZERO: Ttl = Ttl(0);
pub const ONE_HOUR: Ttl = Ttl(3600);
pub const ONE_DAY: Ttl = Ttl(86400);
pub const ONE_WEEK: Ttl = Ttl(604800);
pub const MAX: Ttl = Ttl(MAX_TTL);
#[inline]
pub const fn new(seconds: u32) -> Self {
if seconds > MAX_TTL {
Self(MAX_TTL)
} else {
Self(seconds)
}
}
#[inline]
pub const fn try_new(seconds: u32) -> Option<Self> {
if seconds > MAX_TTL {
None
} else {
Some(Self(seconds))
}
}
#[inline]
pub const fn as_secs(&self) -> u32 {
self.0
}
#[inline]
pub const fn is_zero(&self) -> bool {
self.0 == 0
}
}
impl From<u32> for Ttl {
#[inline]
fn from(secs: u32) -> Self {
Self::new(secs)
}
}
impl From<Ttl> for u32 {
#[inline]
fn from(ttl: Ttl) -> Self {
ttl.0
}
}
impl fmt::Display for Ttl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u16)]
pub enum RecordType {
A = 1,
NS = 2,
CNAME = 5,
SOA = 6,
PTR = 12,
HINFO = 13,
MX = 15,
TXT = 16,
AAAA = 28,
SRV = 33,
DS = 43,
DNSKEY = 48,
CAA = 257,
}
impl RecordType {
pub const fn from_u16(value: u16) -> Option<Self> {
match value {
1 => Some(Self::A),
2 => Some(Self::NS),
5 => Some(Self::CNAME),
6 => Some(Self::SOA),
12 => Some(Self::PTR),
13 => Some(Self::HINFO),
15 => Some(Self::MX),
16 => Some(Self::TXT),
28 => Some(Self::AAAA),
33 => Some(Self::SRV),
43 => Some(Self::DS),
48 => Some(Self::DNSKEY),
257 => Some(Self::CAA),
_ => None,
}
}
pub fn parse(s: &str) -> Option<Self> {
match s.to_uppercase().as_str() {
"A" => Some(Self::A),
"NS" => Some(Self::NS),
"CNAME" => Some(Self::CNAME),
"SOA" => Some(Self::SOA),
"PTR" => Some(Self::PTR),
"HINFO" => Some(Self::HINFO),
"MX" => Some(Self::MX),
"TXT" => Some(Self::TXT),
"AAAA" => Some(Self::AAAA),
"SRV" => Some(Self::SRV),
"DS" => Some(Self::DS),
"DNSKEY" => Some(Self::DNSKEY),
"CAA" => Some(Self::CAA),
_ => None,
}
}
#[inline]
pub const fn as_u16(&self) -> u16 {
*self as u16
}
pub const fn as_str(&self) -> &'static 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::AAAA => "AAAA",
Self::SRV => "SRV",
Self::DS => "DS",
Self::DNSKEY => "DNSKEY",
Self::CAA => "CAA",
}
}
}
impl fmt::Display for RecordType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u16)]
pub enum RecordClass {
#[default]
IN = 1,
CS = 2,
CH = 3,
HS = 4,
}
impl RecordClass {
pub const fn from_u16(value: u16) -> Option<Self> {
match value {
1 => Some(Self::IN),
2 => Some(Self::CS),
3 => Some(Self::CH),
4 => Some(Self::HS),
_ => None,
}
}
#[inline]
pub const fn as_u16(&self) -> u16 {
*self as u16
}
}
impl fmt::Display for RecordClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::IN => write!(f, "IN"),
Self::CS => write!(f, "CS"),
Self::CH => write!(f, "CH"),
Self::HS => write!(f, "HS"),
}
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct MxData {
pub priority: u16,
pub exchange: DomainName,
}
impl MxData {
pub fn new(priority: u16, exchange: DomainName) -> Self {
Self { priority, exchange }
}
}
impl fmt::Debug for MxData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MxData")
.field("priority", &self.priority)
.field("exchange", &self.exchange.to_dotted())
.finish()
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct SrvData {
pub priority: u16,
pub weight: u16,
pub port: u16,
pub target: DomainName,
}
impl SrvData {
pub fn new(priority: u16, weight: u16, port: u16, target: DomainName) -> Self {
Self {
priority,
weight,
port,
target,
}
}
}
impl fmt::Debug for SrvData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SrvData")
.field("priority", &self.priority)
.field("weight", &self.weight)
.field("port", &self.port)
.field("target", &self.target.to_dotted())
.finish()
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct SoaData {
pub mname: DomainName,
pub rname: DomainName,
pub serial: u32,
pub refresh: u32,
pub retry: u32,
pub expire: u32,
pub minimum: u32,
}
impl fmt::Debug for SoaData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SoaData")
.field("mname", &self.mname.to_dotted())
.field("rname", &self.rname.to_dotted())
.field("serial", &self.serial)
.field("refresh", &self.refresh)
.field("retry", &self.retry)
.field("expire", &self.expire)
.field("minimum", &self.minimum)
.finish()
}
}