use core::fmt;
use core::str::FromStr;
#[cfg(feature = "std")]
use super::IpAddr;
use super::{Host, Port};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "arbitrary")]
use arbitrary::Arbitrary;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SocketAddrError {
MissingPortSeparator,
InvalidHost,
InvalidPort,
EmptyInput,
}
impl fmt::Display for SocketAddrError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingPortSeparator => write!(f, "missing port separator ':'"),
Self::InvalidHost => write!(f, "invalid host"),
Self::InvalidPort => write!(f, "invalid port"),
Self::EmptyInput => write!(f, "empty input"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for SocketAddrError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StdConversionError {
NotIpAddress,
PortZero,
}
impl fmt::Display for StdConversionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotIpAddress => write!(
f,
"cannot convert to std::net::SocketAddr: host is not an IP address"
),
Self::PortZero => write!(
f,
"cannot convert from std::net::SocketAddr: port 0 is not allowed"
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for StdConversionError {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SocketAddr {
host: Host,
port: Port,
}
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for SocketAddr {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(Self {
host: Host::arbitrary(u)?,
port: Port::arbitrary(u)?,
})
}
}
impl SocketAddr {
#[must_use]
#[inline]
pub const fn new(host: Host, port: Port) -> Self {
Self { host, port }
}
#[must_use]
#[inline]
pub const fn as_host(&self) -> &Host {
&self.host
}
#[must_use]
#[inline]
pub const fn as_port(&self) -> &Port {
&self.port
}
#[inline]
pub fn set_host(&mut self, host: Host) {
self.host = host;
}
#[inline]
pub const fn set_port(&mut self, port: Port) {
self.port = port;
}
#[must_use]
#[inline]
pub fn into_parts(self) -> (Host, Port) {
(self.host, self.port)
}
pub fn parse_str(s: &str) -> Result<Self, SocketAddrError> {
if s.is_empty() {
return Err(SocketAddrError::EmptyInput);
}
let Some(colon_pos) = s.rfind(':') else {
return Err(SocketAddrError::MissingPortSeparator);
};
let host_str = &s[..colon_pos];
let port_str = &s[colon_pos + 1..];
if host_str.is_empty() || port_str.is_empty() {
return Err(SocketAddrError::MissingPortSeparator);
}
let host_str = if host_str.starts_with('[') && host_str.ends_with(']') {
&host_str[1..host_str.len() - 1]
} else {
host_str
};
let host = Host::parse_str(host_str).map_err(|_| SocketAddrError::InvalidHost)?;
let port = port_str
.parse::<Port>()
.map_err(|_| SocketAddrError::InvalidPort)?;
Ok(Self { host, port })
}
#[must_use]
#[inline]
pub const fn is_ip(&self) -> bool {
self.host.is_ipaddr()
}
#[must_use]
#[inline]
pub const fn is_domain_name(&self) -> bool {
self.host.is_domainname()
}
#[must_use]
#[inline]
pub const fn is_hostname(&self) -> bool {
self.host.is_hostname()
}
}
impl FromStr for SocketAddr {
type Err = SocketAddrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse_str(s)
}
}
impl fmt::Display for SocketAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.host {
Host::IpAddr(ip) if ip.as_inner().is_ipv6() => {
write!(f, "[{}]:{}", self.host, self.port)
}
_ => write!(f, "{}:{}", self.host, self.port),
}
}
}
#[cfg(feature = "std")]
impl TryFrom<std::net::SocketAddr> for SocketAddr {
type Error = StdConversionError;
fn try_from(addr: std::net::SocketAddr) -> Result<Self, Self::Error> {
let port = Port::new(addr.port()).map_err(|_| StdConversionError::PortZero)?;
Ok(Self {
host: Host::from_ipaddr(IpAddr::from(addr.ip())),
port,
})
}
}
#[cfg(feature = "std")]
impl TryFrom<SocketAddr> for std::net::SocketAddr {
type Error = StdConversionError;
fn try_from(addr: SocketAddr) -> Result<Self, Self::Error> {
match addr.host {
Host::IpAddr(ip) => Ok(Self::new(ip.into(), addr.port.as_u16())),
Host::DomainName(_) | Host::Hostname(_) => Err(StdConversionError::NotIpAddress),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let host = Host::parse_str("192.168.1.1").unwrap();
let port = Port::new(8080).unwrap();
let addr = SocketAddr::new(host, port);
assert!(addr.as_host().is_ipaddr());
assert_eq!(addr.as_port().as_u16(), 8080);
}
#[test]
fn test_parse_ipv4() {
let addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
assert!(addr.as_host().is_ipaddr());
assert_eq!(addr.as_port().as_u16(), 8080);
assert_eq!(format!("{addr}"), "192.168.1.1:8080");
}
#[test]
fn test_parse_ipv6() {
let addr: SocketAddr = "[::1]:8080".parse().unwrap();
assert!(addr.as_host().is_ipaddr());
assert_eq!(addr.as_port().as_u16(), 8080);
}
#[test]
fn test_parse_domainname() {
let addr: SocketAddr = "example.com:443".parse().unwrap();
assert!(addr.as_host().is_domainname());
assert_eq!(addr.as_port().as_u16(), 443);
assert_eq!(format!("{addr}"), "example.com:443");
}
#[test]
fn test_parse_domainname_with_digits() {
let addr: SocketAddr = "123.example.com:443".parse().unwrap();
assert!(addr.as_host().is_domainname());
assert_eq!(addr.as_port().as_u16(), 443);
}
#[test]
fn test_parse_str_empty() {
assert!(SocketAddr::parse_str("").is_err());
}
#[test]
fn test_parse_str_missing_separator() {
assert!(SocketAddr::parse_str("192.168.1.1").is_err());
}
#[test]
fn test_parse_str_empty_host() {
assert!(SocketAddr::parse_str(":8080").is_err());
}
#[test]
fn test_parse_str_empty_port() {
assert!(SocketAddr::parse_str("192.168.1.1:").is_err());
}
#[test]
fn test_parse_str_invalid_host() {
assert!(SocketAddr::parse_str("-invalid:8080").is_err());
}
#[test]
fn test_parse_str_invalid_port() {
assert!(SocketAddr::parse_str("192.168.1.1:invalid").is_err());
}
#[test]
fn test_parse_str_port_zero() {
assert!(SocketAddr::parse_str("192.168.1.1:0").is_err());
}
#[test]
fn test_parse_str_port_out_of_range() {
assert!(SocketAddr::parse_str("192.168.1.1:99999").is_err());
}
#[test]
fn test_set_host() {
let mut addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
let new_host = Host::parse_str("10.0.0.1").unwrap();
addr.set_host(new_host);
assert_eq!(format!("{addr}"), "10.0.0.1:8080");
}
#[test]
fn test_set_port() {
let mut addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
let new_port = Port::new(9090).unwrap();
addr.set_port(new_port);
assert_eq!(format!("{addr}"), "192.168.1.1:9090");
}
#[test]
fn test_into_parts() {
let host = Host::parse_str("192.168.1.1").unwrap();
let port = Port::new(8080).unwrap();
let addr = SocketAddr::new(host, port);
let (h, p) = addr.into_parts();
assert!(h.is_ipaddr());
assert_eq!(p.as_u16(), 8080);
}
#[test]
fn test_as_host_and_as_port() {
let addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
assert!(addr.as_host().is_ipaddr());
assert_eq!(addr.as_port().as_u16(), 8080);
}
#[test]
fn test_is_ip() {
let addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
assert!(addr.is_ip());
assert!(!addr.is_domain_name());
assert!(!addr.is_hostname());
}
#[test]
fn test_is_domain_name() {
let addr: SocketAddr = "example.com:443".parse().unwrap();
assert!(!addr.is_ip());
assert!(addr.is_domain_name());
assert!(!addr.is_hostname());
}
#[test]
fn test_host_is_domainname() {
let addr: SocketAddr = "example.com:443".parse().unwrap();
assert!(addr.as_host().is_domainname());
}
#[test]
fn test_socket_addr_is_domain_name() {
let addr: SocketAddr = "example.com:443".parse().unwrap();
assert!(addr.is_domain_name());
}
#[test]
fn test_is_hostname() {
let hostname = crate::net::Hostname::new("localhost").unwrap();
let host = Host::from_hostname(hostname);
let port = Port::new(3000).unwrap();
let addr = SocketAddr::new(host, port);
assert!(!addr.is_ip());
assert!(!addr.is_domain_name());
assert!(addr.is_hostname());
}
#[test]
fn test_equality() {
let addr1: SocketAddr = "192.168.1.1:8080".parse().unwrap();
let addr2: SocketAddr = "192.168.1.1:8080".parse().unwrap();
let addr3: SocketAddr = "192.168.1.1:9090".parse().unwrap();
let addr4: SocketAddr = "10.0.0.1:8080".parse().unwrap();
assert_eq!(addr1, addr2);
assert_ne!(addr1, addr3);
assert_ne!(addr1, addr4);
}
#[test]
fn test_clone() {
let addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
let addr2 = addr.clone();
assert_eq!(addr, addr2);
}
#[test]
fn test_display() {
let addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
assert_eq!(format!("{addr}"), "192.168.1.1:8080");
let addr: SocketAddr = "example.com:443".parse().unwrap();
assert_eq!(format!("{addr}"), "example.com:443");
let addr: SocketAddr = "[::1]:8080".parse().unwrap();
assert_eq!(format!("{addr}"), "[::1]:8080");
let addr: SocketAddr = "[2001:db8::1]:443".parse().unwrap();
assert_eq!(format!("{addr}"), "[2001:db8::1]:443");
}
#[test]
fn test_debug() {
let addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
let debug = format!("{:?}", addr);
assert!(debug.contains("SocketAddr"));
}
#[test]
fn test_hash() {
use core::hash::Hash;
use core::hash::Hasher;
#[derive(Default)]
struct SimpleHasher(u64);
impl Hasher for SimpleHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
for byte in bytes {
self.0 = self.0.wrapping_mul(31).wrapping_add(*byte as u64);
}
}
}
let addr1: SocketAddr = "192.168.1.1:8080".parse().unwrap();
let addr2: SocketAddr = "192.168.1.1:8080".parse().unwrap();
let addr3: SocketAddr = "10.0.0.1:8080".parse().unwrap();
let mut hasher1 = SimpleHasher::default();
let mut hasher2 = SimpleHasher::default();
let mut hasher3 = SimpleHasher::default();
addr1.hash(&mut hasher1);
addr2.hash(&mut hasher2);
addr3.hash(&mut hasher3);
assert_eq!(hasher1.finish(), hasher2.finish());
assert_ne!(hasher1.finish(), hasher3.finish());
}
#[test]
fn test_parse_ipv6_with_brackets() {
let addr: SocketAddr = "[2001:db8::1]:8080".parse().unwrap();
assert!(addr.as_host().is_ipaddr());
assert_eq!(addr.as_port().as_u16(), 8080);
}
#[test]
fn test_parse_localhost() {
let addr: SocketAddr = "localhost:3000".parse().unwrap();
assert!(addr.as_host().is_domainname());
assert_eq!(addr.as_port().as_u16(), 3000);
}
#[test]
fn test_parse_system_port() {
let addr: SocketAddr = "192.168.1.1:80".parse().unwrap();
assert_eq!(addr.as_port().as_u16(), 80);
assert!(addr.as_port().is_system_port());
}
#[test]
fn test_parse_registered_port() {
let addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
assert_eq!(addr.as_port().as_u16(), 8080);
assert!(addr.as_port().is_registered_port());
}
#[test]
fn test_parse_dynamic_port() {
let addr: SocketAddr = "192.168.1.1:50000".parse().unwrap();
assert_eq!(addr.as_port().as_u16(), 50000);
assert!(addr.as_port().is_dynamic_port());
}
#[test]
fn test_error_display() {
let err = SocketAddrError::MissingPortSeparator;
assert_eq!(format!("{err}"), "missing port separator ':'");
let err = SocketAddrError::InvalidHost;
assert_eq!(format!("{err}"), "invalid host");
let err = SocketAddrError::InvalidPort;
assert_eq!(format!("{err}"), "invalid port");
let err = SocketAddrError::EmptyInput;
assert_eq!(format!("{err}"), "empty input");
}
#[test]
fn test_parse_str_method() {
let addr = SocketAddr::parse_str("192.168.1.1:8080").unwrap();
assert!(addr.as_host().is_ipaddr());
assert_eq!(addr.as_port().as_u16(), 8080);
}
#[test]
fn test_case_insensitive() {
let addr1: SocketAddr = "EXAMPLE.COM:443".parse().unwrap();
let addr2: SocketAddr = "example.com:443".parse().unwrap();
assert_eq!(addr1, addr2);
}
#[test]
fn test_ipv4_loopback() {
let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
assert!(addr.as_host().is_ipaddr());
}
#[test]
fn test_ipv4_private() {
let addr: SocketAddr = "10.0.0.1:8080".parse().unwrap();
assert!(addr.as_host().is_ipaddr());
}
#[test]
fn test_ipv6_loopback() {
let addr: SocketAddr = "[::1]:8080".parse().unwrap();
assert!(addr.as_host().is_ipaddr());
}
#[test]
fn test_numeric_domainname() {
let addr: SocketAddr = "123:8080".parse().unwrap();
assert!(addr.as_host().is_domainname());
}
#[test]
fn test_multi_label_domainname() {
let addr: SocketAddr = "api.v1.example.com:443".parse().unwrap();
assert!(addr.as_host().is_domainname());
}
#[test]
fn test_max_port() {
let addr: SocketAddr = "192.168.1.1:65535".parse().unwrap();
assert_eq!(addr.as_port().as_u16(), 65535);
}
#[test]
fn test_min_port() {
let addr: SocketAddr = "192.168.1.1:1".parse().unwrap();
assert_eq!(addr.as_port().as_u16(), 1);
}
#[cfg(feature = "std")]
#[test]
fn test_try_from_std_socket_addr() {
let std_addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));
let addr = SocketAddr::try_from(std_addr).unwrap();
assert!(addr.as_host().is_ipaddr());
assert_eq!(addr.as_port().as_u16(), 8080);
}
#[cfg(feature = "std")]
#[test]
fn test_try_from_std_socket_addr_ipv6() {
let std_addr = std::net::SocketAddr::from(([0, 0, 0, 0, 0, 0, 0, 1], 8080));
let addr = SocketAddr::try_from(std_addr).unwrap();
assert!(addr.as_host().is_ipaddr());
assert_eq!(addr.as_port().as_u16(), 8080);
}
#[cfg(feature = "std")]
#[test]
fn test_try_from_std_socket_addr_port_zero_fails() {
let std_addr = std::net::SocketAddr::from(([127, 0, 0, 1], 0));
let result = SocketAddr::try_from(std_addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), StdConversionError::PortZero);
}
#[cfg(feature = "std")]
#[test]
fn test_try_to_std_socket_addr_ip() {
let addr: SocketAddr = "192.168.1.1:8080".parse().unwrap();
let std_addr = std::net::SocketAddr::try_from(addr).unwrap();
assert_eq!(std_addr.ip(), std::net::IpAddr::from([192, 168, 1, 1]));
assert_eq!(std_addr.port(), 8080);
}
#[cfg(feature = "std")]
#[test]
fn test_try_to_std_socket_addr_domainname_fails() {
let addr: SocketAddr = "example.com:443".parse().unwrap();
let result = std::net::SocketAddr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), StdConversionError::NotIpAddress);
}
#[cfg(feature = "std")]
#[test]
fn test_try_to_std_socket_addr_hostname_fails() {
let hostname = crate::net::Hostname::new("localhost").unwrap();
let host = Host::from_hostname(hostname);
let port = Port::new(3000).unwrap();
let addr = SocketAddr::new(host, port);
let result = std::net::SocketAddr::try_from(addr);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), StdConversionError::NotIpAddress);
}
#[cfg(feature = "std")]
#[test]
fn test_std_conversion_error_display() {
let err = StdConversionError::NotIpAddress;
assert_eq!(
format!("{err}"),
"cannot convert to std::net::SocketAddr: host is not an IP address"
);
let err = StdConversionError::PortZero;
assert_eq!(
format!("{err}"),
"cannot convert from std::net::SocketAddr: port 0 is not allowed"
);
}
#[cfg(feature = "std")]
#[test]
fn test_roundtrip_std_socket_addr() {
let std_addr1 = std::net::SocketAddr::from(([192, 168, 1, 1], 8080));
let addr = SocketAddr::try_from(std_addr1).unwrap();
let std_addr2 = std::net::SocketAddr::try_from(addr).unwrap();
assert_eq!(std_addr1, std_addr2);
}
}