use crate::netmap::{MAX_DNS_DOMAINS, MAX_DNS_NAME, MAX_DNS_RESOLVERS};
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct DnsConfig {
domains: heapless::Vec<heapless::String<MAX_DNS_NAME>, MAX_DNS_DOMAINS>,
resolvers: heapless::Vec<heapless::String<MAX_DNS_NAME>, MAX_DNS_RESOLVERS>,
pub proxied: bool,
pub truncated: bool,
}
impl DnsConfig {
pub const fn new() -> Self {
Self {
domains: heapless::Vec::new(),
resolvers: heapless::Vec::new(),
proxied: false,
truncated: false,
}
}
pub fn domains(&self) -> impl Iterator<Item = &str> {
self.domains.iter().map(heapless::String::as_str)
}
pub fn resolvers(&self) -> impl Iterator<Item = &str> {
self.resolvers.iter().map(heapless::String::as_str)
}
pub fn resolver_addresses(&self) -> impl Iterator<Item = core::net::IpAddr> + '_ {
self.resolvers.iter().filter_map(|addr| addr.parse().ok())
}
pub fn is_empty(&self) -> bool {
self.domains.is_empty() && self.resolvers.is_empty() && !self.proxied
}
pub fn clear(&mut self) {
self.domains.clear();
self.resolvers.clear();
self.proxied = false;
self.truncated = false;
}
pub fn push_domain(&mut self, domain: &str) {
self.truncated |= match heapless::String::try_from(domain) {
Ok(domain) => self.domains.push(domain).is_err(),
Err(_) => true,
};
}
pub fn push_resolver(&mut self, address: &str) {
self.truncated |= match heapless::String::try_from(address) {
Ok(address) => self.resolvers.push(address).is_err(),
Err(_) => true,
};
}
pub(crate) fn copy_from(&mut self, source: &Self) {
self.domains.clear();
for domain in &source.domains {
self.domains
.push(domain.clone())
.expect("equal DNS domain capacities");
}
self.resolvers.clear();
for resolver in &source.resolvers {
self.resolvers
.push(resolver.clone())
.expect("equal DNS resolver capacities");
}
self.proxied = source.proxied;
self.truncated = source.truncated;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn overflow_is_recorded_rather_than_dropped_silently() {
let mut config = DnsConfig::new();
for index in 0..MAX_DNS_DOMAINS {
config.push_domain(&alloc_name(index));
}
assert!(!config.truncated);
assert_eq!(config.domains().count(), MAX_DNS_DOMAINS);
config.push_domain("one.too.many");
assert!(config.truncated);
assert_eq!(config.domains().count(), MAX_DNS_DOMAINS);
}
#[test]
fn a_name_longer_than_the_bound_is_not_stored_truncated() {
let mut config = DnsConfig::new();
let long = [b'a'; MAX_DNS_NAME + 1];
config.push_domain(core::str::from_utf8(&long).unwrap());
assert!(config.truncated);
assert_eq!(config.domains().count(), 0);
}
#[test]
fn only_resolvers_that_parse_become_addresses() {
let mut config = DnsConfig::new();
config.push_resolver("1.1.1.1");
config.push_resolver("https://dns.example/dns-query");
assert_eq!(config.resolvers().count(), 2);
let addresses: heapless::Vec<_, MAX_DNS_RESOLVERS> = config.resolver_addresses().collect();
assert_eq!(addresses.len(), 1);
assert_eq!(
addresses[0],
"1.1.1.1".parse::<core::net::IpAddr>().unwrap()
);
}
fn alloc_name(index: usize) -> heapless::String<MAX_DNS_NAME> {
let mut name = heapless::String::new();
use core::fmt::Write as _;
write!(name, "domain{index}.internal").unwrap();
name
}
}