use std::net::IpAddr;
use std::sync::Arc;
use hickory_resolver::config::{NameServerConfig, ResolverConfig};
use crate::error::{Result, ShoheError};
pub async fn build_dot_config(addr_str: &str) -> Result<(ResolverConfig, String)> {
let (host, port) = parse_host_port(addr_str, 853)?;
let ip: IpAddr = host
.parse()
.map_err(|_| ShoheError::Parse(format!(
"DoT requires an IP address, not a hostname. Got: '{host}'. \
Try: --dot 1.1.1.1:853"
)))?;
let server_name: Arc<str> = host.clone().into();
let ns = NameServerConfig::tls(ip, server_name);
let config = ResolverConfig::from_parts(None, vec![], vec![ns]);
Ok((config, format!("{host}:{port} (DoT)")))
}
pub(crate) fn parse_host_port(s: &str, default_port: u16) -> Result<(String, u16)> {
if s.starts_with('[') {
let bracket_end = s.find(']').ok_or_else(|| {
ShoheError::Parse(format!("Unclosed '[' in address: {s}"))
})?;
let host = s[1..bracket_end].to_string();
let port = if let Some(rest) = s[bracket_end + 1..].strip_prefix(':') {
rest.parse::<u16>().map_err(|_| {
ShoheError::Parse(format!("Invalid port in address: {s}"))
})?
} else {
default_port
};
return Ok((host, port));
}
if let Some(colon) = s.rfind(':') {
let host_part = &s[..colon];
let port_str = &s[colon + 1..];
if !host_part.contains(':') {
let port = port_str.parse::<u16>().map_err(|_| {
ShoheError::Parse(format!(
"Invalid port in address '{s}': '{port_str}' is not a valid port number (1–65535)"
))
})?;
return Ok((host_part.to_string(), port));
}
}
Ok((s.to_string(), default_port))
}