+/// [`SocketAddress`] error variants
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+pub enum SocketAddressParseError {
+ /// Socket address (IPv4/IPv6) parsing error
+ SocketAddrParse,
+ /// Invalid input format
+ InvalidInput,
+ /// Invalid port
+ InvalidPort,
+ /// Invalid onion v3 address
+ InvalidOnionV3,
+}
+
+impl fmt::Display for SocketAddressParseError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ SocketAddressParseError::SocketAddrParse => write!(f, "Socket address (IPv4/IPv6) parsing error"),
+ SocketAddressParseError::InvalidInput => write!(f, "Invalid input format. \
+ Expected: \"<ipv4>:<port>\", \"[<ipv6>]:<port>\", \"<onion address>.onion:<port>\" or \"<hostname>:<port>\""),
+ SocketAddressParseError::InvalidPort => write!(f, "Invalid port"),
+ SocketAddressParseError::InvalidOnionV3 => write!(f, "Invalid onion v3 address"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<std::net::SocketAddrV4> for SocketAddress {
+ fn from(addr: std::net::SocketAddrV4) -> Self {
+ SocketAddress::TcpIpV4 { addr: addr.ip().octets(), port: addr.port() }
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<std::net::SocketAddrV6> for SocketAddress {
+ fn from(addr: std::net::SocketAddrV6) -> Self {
+ SocketAddress::TcpIpV6 { addr: addr.ip().octets(), port: addr.port() }
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<std::net::SocketAddr> for SocketAddress {
+ fn from(addr: std::net::SocketAddr) -> Self {
+ match addr {
+ std::net::SocketAddr::V4(addr) => addr.into(),
+ std::net::SocketAddr::V6(addr) => addr.into(),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::net::ToSocketAddrs for SocketAddress {
+ type Iter = std::vec::IntoIter<std::net::SocketAddr>;
+
+ fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
+ match self {
+ SocketAddress::TcpIpV4 { addr, port } => {
+ let ip_addr = std::net::Ipv4Addr::from(*addr);
+ let socket_addr = SocketAddr::new(ip_addr.into(), *port);
+ Ok(vec![socket_addr].into_iter())
+ }
+ SocketAddress::TcpIpV6 { addr, port } => {
+ let ip_addr = std::net::Ipv6Addr::from(*addr);
+ let socket_addr = SocketAddr::new(ip_addr.into(), *port);
+ Ok(vec![socket_addr].into_iter())
+ }
+ SocketAddress::Hostname { ref hostname, port } => {
+ (hostname.as_str(), *port).to_socket_addrs()
+ }
+ SocketAddress::OnionV2(..) => {
+ Err(std::io::Error::new(std::io::ErrorKind::Other, "Resolution of OnionV2 \
+ addresses is currently unsupported."))
+ }
+ SocketAddress::OnionV3 { .. } => {
+ Err(std::io::Error::new(std::io::ErrorKind::Other, "Resolution of OnionV3 \
+ addresses is currently unsupported."))
+ }
+ }
+ }
+}
+
+/// Parses an OnionV3 host and port into a [`SocketAddress::OnionV3`].
+///
+/// The host part must end with ".onion".
+pub fn parse_onion_address(host: &str, port: u16) -> Result<SocketAddress, SocketAddressParseError> {
+ if host.ends_with(".onion") {
+ let domain = &host[..host.len() - ".onion".len()];
+ if domain.len() != 56 {
+ return Err(SocketAddressParseError::InvalidOnionV3);
+ }
+ let onion = base32::Alphabet::RFC4648 { padding: false }.decode(&domain).map_err(|_| SocketAddressParseError::InvalidOnionV3)?;
+ if onion.len() != 35 {
+ return Err(SocketAddressParseError::InvalidOnionV3);
+ }
+ let version = onion[0];
+ let first_checksum_flag = onion[1];
+ let second_checksum_flag = onion[2];
+ let mut ed25519_pubkey = [0; 32];
+ ed25519_pubkey.copy_from_slice(&onion[3..35]);
+ let checksum = u16::from_be_bytes([first_checksum_flag, second_checksum_flag]);
+ return Ok(SocketAddress::OnionV3 { ed25519_pubkey, checksum, version, port });
+
+ } else {
+ return Err(SocketAddressParseError::InvalidInput);
+ }
+}
+
+impl Display for SocketAddress {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ SocketAddress::TcpIpV4{addr, port} => write!(
+ f, "{}.{}.{}.{}:{}", addr[0], addr[1], addr[2], addr[3], port)?,
+ SocketAddress::TcpIpV6{addr, port} => write!(
+ f,
+ "[{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}]:{}",
+ addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8], addr[9], addr[10], addr[11], addr[12], addr[13], addr[14], addr[15], port
+ )?,
+ SocketAddress::OnionV2(bytes) => write!(f, "OnionV2({:?})", bytes)?,
+ SocketAddress::OnionV3 {
+ ed25519_pubkey,
+ checksum,
+ version,
+ port,
+ } => {
+ let [first_checksum_flag, second_checksum_flag] = checksum.to_be_bytes();
+ let mut addr = vec![*version, first_checksum_flag, second_checksum_flag];
+ addr.extend_from_slice(ed25519_pubkey);
+ let onion = base32::Alphabet::RFC4648 { padding: false }.encode(&addr);
+ write!(f, "{}.onion:{}", onion, port)?
+ },
+ SocketAddress::Hostname { hostname, port } => write!(f, "{}:{}", hostname, port)?,
+ }
+ Ok(())
+ }
+}
+
+#[cfg(feature = "std")]
+impl FromStr for SocketAddress {
+ type Err = SocketAddressParseError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match std::net::SocketAddr::from_str(s) {
+ Ok(addr) => Ok(addr.into()),
+ Err(_) => {
+ let trimmed_input = match s.rfind(":") {
+ Some(pos) => pos,
+ None => return Err(SocketAddressParseError::InvalidInput),
+ };
+ let host = &s[..trimmed_input];
+ let port: u16 = s[trimmed_input + 1..].parse().map_err(|_| SocketAddressParseError::InvalidPort)?;
+ if host.ends_with(".onion") {
+ return parse_onion_address(host, port);
+ };
+ if let Ok(hostname) = Hostname::try_from(s[..trimmed_input].to_string()) {
+ return Ok(SocketAddress::Hostname { hostname, port });
+ };
+ return Err(SocketAddressParseError::SocketAddrParse)
+ },
+ }
+ }
+}
+