use crate::transport::{TransportAddr, TransportError};
use std::net::SocketAddr;
#[derive(Clone, Debug)]
pub enum TorAddr {
Onion(String, u16),
Clearnet(SocketAddr),
ClearnetHostname(String, u16),
}
pub(super) fn parse_tor_addr(addr: &TransportAddr) -> Result<TorAddr, TransportError> {
let s = addr.as_str().ok_or_else(|| {
TransportError::InvalidAddress("Tor address must be a valid UTF-8 string".into())
})?;
if s.contains(".onion:") {
let (host, port_str) = s.rsplit_once(':').ok_or_else(|| {
TransportError::InvalidAddress(format!("invalid onion address: {}", s))
})?;
let port: u16 = port_str.parse().map_err(|_| {
TransportError::InvalidAddress(format!("invalid port in onion address: {}", s))
})?;
Ok(TorAddr::Onion(host.to_string(), port))
} else if let Ok(socket_addr) = s.parse::<SocketAddr>() {
Ok(TorAddr::Clearnet(socket_addr))
} else {
let (host, port_str) = s.rsplit_once(':').ok_or_else(|| {
TransportError::InvalidAddress(format!("invalid address (expected host:port): {}", s))
})?;
let port: u16 = port_str
.parse()
.map_err(|_| TransportError::InvalidAddress(format!("invalid port: {}", s)))?;
if !host.contains('.') {
return Err(TransportError::InvalidAddress(format!(
"hostname must be fully qualified (contain a dot): {}",
host
)));
}
Ok(TorAddr::ClearnetHostname(host.to_string(), port))
}
}
pub(super) fn validate_host_port(addr: &str, field_name: &str) -> Result<(), TransportError> {
if addr.parse::<SocketAddr>().is_ok() {
return Ok(());
}
let parts: Vec<&str> = addr.rsplitn(2, ':').collect();
if parts.len() != 2 || parts[0].parse::<u16>().is_err() || parts[1].is_empty() {
return Err(TransportError::StartFailed(format!(
"invalid {} '{}': expected host:port or IP:port",
field_name, addr
)));
}
Ok(())
}