use std::hash::{Hash, Hasher};
use crate::dns_parser;
use crate::errors::{DnsError, DnsResult};
#[derive(Debug, Clone, Eq)]
pub struct DNSKey {
pub name: String,
pub qtype: u16,
pub qclass: u16,
pub dnssec: bool,
}
impl DNSKey {
pub fn new(name: String, qtype: u16, qclass: u16, dnssec: bool) -> DnsResult<Self> {
let normalized_name = Self::normalize_name(&name)?;
Ok(DNSKey {
name: normalized_name,
qtype,
qclass,
dnssec,
})
}
pub fn from_packet(packet: &[u8]) -> DnsResult<Self> {
dns_parser::validate_dns_packet(packet)?;
let qname_bytes = dns_parser::qname(packet)?;
let qname = match std::str::from_utf8(&qname_bytes) {
Ok(s) => s.to_string(),
Err(_) => {
return Err(DnsError::InvalidDomainName(
"Invalid UTF-8 in query name".to_string(),
));
}
};
let (qtype, qclass) = dns_parser::query_type_class(packet)?;
let dnssec = dns_parser::is_dnssec_requested(packet)?;
DNSKey::new(qname, qtype, qclass, dnssec)
}
pub fn normalize_name(name: &str) -> DnsResult<String> {
if name.len() > crate::dns_parser::DNS_MAX_HOSTNAME_SIZE {
return Err(DnsError::InvalidDomainName(format!(
"Name too long: {} bytes (max {})",
name.len(),
crate::dns_parser::DNS_MAX_HOSTNAME_SIZE
)));
}
if name.is_empty() || name == "." {
return Ok(".".to_string());
}
if name.starts_with('.') {
return Err(DnsError::InvalidDomainName(
"Name cannot start with a dot".to_string(),
));
}
if name.contains("..") {
return Err(DnsError::InvalidDomainName(
"Name contains consecutive dots".to_string(),
));
}
let name = name.trim_end_matches('.');
let normalized = name.to_lowercase();
Ok(normalized)
}
}
impl PartialEq for DNSKey {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
&& self.qtype == other.qtype
&& self.qclass == other.qclass
&& self.dnssec == other.dnssec
}
}
impl Hash for DNSKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.qtype.hash(state);
self.qclass.hash(state);
self.dnssec.hash(state);
}
}