use std::path::Path;
use std::sync::Arc;
use crate::unix;
use std::{io::Error as IoError, net};
#[cfg(target_os = "android")]
use std::os::android::net::SocketAddrExt as _;
#[cfg(target_os = "linux")]
use std::os::linux::net::SocketAddrExt as _;
#[derive(Clone, Debug, derive_more::From, derive_more::TryInto)]
#[non_exhaustive]
pub enum SocketAddr {
Inet(net::SocketAddr),
Unix(unix::SocketAddr),
}
impl SocketAddr {
pub fn display_lossy(&self) -> DisplayLossy<'_> {
DisplayLossy(self)
}
pub fn try_to_string(&self) -> Option<String> {
use SocketAddr::*;
match self {
Inet(sa) => Some(format!("inet:{}", sa)),
Unix(sa) => {
if sa.is_unnamed() {
Some("unix:".to_string())
} else {
sa.as_pathname()
.and_then(Path::to_str)
.map(|p| format!("unix:{}", p))
}
}
}
}
pub fn as_pathname(&self) -> Option<&Path> {
match self {
SocketAddr::Inet(_) => None,
SocketAddr::Unix(socket_addr) => socket_addr.as_pathname(),
}
}
}
pub struct DisplayLossy<'a>(&'a SocketAddr);
impl<'a> std::fmt::Display for DisplayLossy<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use SocketAddr::*;
match self.0 {
Inet(sa) => write!(f, "inet:{}", sa),
Unix(sa) => {
if let Some(path) = sa.as_pathname() {
if let Some(path_str) = path.to_str() {
write!(f, "unix:{}", path_str)
} else {
write!(f, "unix:{} [lossy]", path.to_string_lossy())
}
} else if sa.is_unnamed() {
write!(f, "unix:")
} else {
write!(f, "unix:{:?} [lossy]", sa)
}
}
}
}
}
impl std::str::FromStr for SocketAddr {
type Err = AddrParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with(|c: char| c.is_ascii_digit() || c == '[') {
Ok(s.parse::<net::SocketAddr>()?.into())
} else if let Some((schema, remainder)) = s.split_once(':') {
match schema {
"unix" => Ok(unix::SocketAddr::from_pathname(remainder)?.into()),
"inet" => Ok(remainder.parse::<net::SocketAddr>()?.into()),
_ => Err(AddrParseError::UnrecognizedSchema(schema.to_string())),
}
} else {
Err(AddrParseError::NoSchema)
}
}
}
#[derive(Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum AddrParseError {
#[error("Address schema {0:?} unrecognized")]
UnrecognizedSchema(String),
#[error("Address did not look like internet, but had no address schema.")]
NoSchema,
#[error("Invalid AF_UNIX address")]
InvalidAfUnixAddress(#[source] Arc<IoError>),
#[error("Invalid internet address")]
InvalidInetAddress(#[from] std::net::AddrParseError),
}
impl From<IoError> for AddrParseError {
fn from(e: IoError) -> Self {
Self::InvalidAfUnixAddress(Arc::new(e))
}
}
impl PartialEq for SocketAddr {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Inet(l0), Self::Inet(r0)) => l0 == r0,
#[cfg(unix)]
(Self::Unix(l0), Self::Unix(r0)) => {
if l0.is_unnamed() && r0.is_unnamed() {
return true;
}
if let (Some(a), Some(b)) = (l0.as_pathname(), r0.as_pathname()) {
return a == b;
}
#[cfg(any(target_os = "android", target_os = "linux"))]
if let (Some(a), Some(b)) = (l0.as_abstract_name(), r0.as_abstract_name()) {
return a == b;
}
false
}
_ => false,
}
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for SocketAddr {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
#[allow(clippy::missing_docs_in_private_items)]
#[derive(arbitrary::Arbitrary)]
enum Kind {
V4,
V6,
#[cfg(unix)]
Unix,
#[cfg(any(target_os = "android", target_os = "linux"))]
UnixAbstract,
}
match u.arbitrary()? {
Kind::V4 => Ok(SocketAddr::Inet(
net::SocketAddrV4::new(u.arbitrary()?, u.arbitrary()?).into(),
)),
Kind::V6 => Ok(SocketAddr::Inet(
net::SocketAddrV6::new(
u.arbitrary()?,
u.arbitrary()?,
u.arbitrary()?,
u.arbitrary()?,
)
.into(),
)),
#[cfg(unix)]
Kind::Unix => {
let pathname: std::ffi::OsString = u.arbitrary()?;
Ok(SocketAddr::Unix(
unix::SocketAddr::from_pathname(pathname)
.map_err(|_| arbitrary::Error::IncorrectFormat)?,
))
}
#[cfg(any(target_os = "android", target_os = "linux"))]
Kind::UnixAbstract => {
#[cfg(target_os = "android")]
use std::os::android::net::SocketAddrExt as _;
#[cfg(target_os = "linux")]
use std::os::linux::net::SocketAddrExt as _;
let name: &[u8] = u.arbitrary()?;
Ok(SocketAddr::Unix(
unix::SocketAddr::from_abstract_name(name)
.map_err(|_| arbitrary::Error::IncorrectFormat)?,
))
}
}
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::AddrParseError;
use crate::general;
use assert_matches::assert_matches;
#[cfg(unix)]
use std::os::unix::net as unix;
use std::{net, str::FromStr as _};
fn from_inet(s: &str) -> general::SocketAddr {
let a: net::SocketAddr = s.parse().unwrap();
a.into()
}
#[test]
fn ok_inet() {
assert_eq!(
from_inet("127.0.0.1:9999"),
general::SocketAddr::from_str("127.0.0.1:9999").unwrap()
);
assert_eq!(
from_inet("127.0.0.1:9999"),
general::SocketAddr::from_str("inet:127.0.0.1:9999").unwrap()
);
assert_eq!(
from_inet("[::1]:9999"),
general::SocketAddr::from_str("[::1]:9999").unwrap()
);
assert_eq!(
from_inet("[::1]:9999"),
general::SocketAddr::from_str("inet:[::1]:9999").unwrap()
);
assert_ne!(
general::SocketAddr::from_str("127.0.0.1:9999").unwrap(),
general::SocketAddr::from_str("[::1]:9999").unwrap()
);
let ga1 = from_inet("127.0.0.1:9999");
assert_eq!(ga1.display_lossy().to_string(), "inet:127.0.0.1:9999");
assert_eq!(ga1.try_to_string().unwrap(), "inet:127.0.0.1:9999");
let ga2 = from_inet("[::1]:9999");
assert_eq!(ga2.display_lossy().to_string(), "inet:[::1]:9999");
assert_eq!(ga2.try_to_string().unwrap(), "inet:[::1]:9999");
}
#[cfg(unix)]
fn from_pathname(s: impl AsRef<std::path::Path>) -> general::SocketAddr {
let a = unix::SocketAddr::from_pathname(s).unwrap();
a.into()
}
#[test]
#[cfg(unix)]
fn ok_unix() {
assert_eq!(
from_pathname("/some/path"),
general::SocketAddr::from_str("unix:/some/path").unwrap()
);
assert_eq!(
from_pathname("/another/path"),
general::SocketAddr::from_str("unix:/another/path").unwrap()
);
assert_eq!(
from_pathname("/path/with spaces"),
general::SocketAddr::from_str("unix:/path/with spaces").unwrap()
);
assert_ne!(
general::SocketAddr::from_str("unix:/some/path").unwrap(),
general::SocketAddr::from_str("unix:/another/path").unwrap()
);
assert_eq!(
from_pathname(""),
general::SocketAddr::from_str("unix:").unwrap()
);
let ga1 = general::SocketAddr::from_str("unix:/some/path").unwrap();
assert_eq!(ga1.display_lossy().to_string(), "unix:/some/path");
assert_eq!(ga1.try_to_string().unwrap(), "unix:/some/path");
let ga2 = general::SocketAddr::from_str("unix:/another/path").unwrap();
assert_eq!(ga2.display_lossy().to_string(), "unix:/another/path");
assert_eq!(ga2.try_to_string().unwrap(), "unix:/another/path");
}
#[test]
fn parse_err_inet() {
assert_matches!(
"1234567890:999".parse::<general::SocketAddr>(),
Err(AddrParseError::InvalidInetAddress(_))
);
assert_matches!(
"1z".parse::<general::SocketAddr>(),
Err(AddrParseError::InvalidInetAddress(_))
);
assert_matches!(
"[[77".parse::<general::SocketAddr>(),
Err(AddrParseError::InvalidInetAddress(_))
);
assert_matches!(
"inet:fred:9999".parse::<general::SocketAddr>(),
Err(AddrParseError::InvalidInetAddress(_))
);
assert_matches!(
"inet:127.0.0.1".parse::<general::SocketAddr>(),
Err(AddrParseError::InvalidInetAddress(_))
);
assert_matches!(
"inet:[::1]".parse::<general::SocketAddr>(),
Err(AddrParseError::InvalidInetAddress(_))
);
}
#[test]
fn parse_err_schemata() {
assert_matches!(
"fred".parse::<general::SocketAddr>(),
Err(AddrParseError::NoSchema)
);
assert_matches!(
"fred:".parse::<general::SocketAddr>(),
Err(AddrParseError::UnrecognizedSchema(f)) if f == "fred"
);
assert_matches!(
"fred:hello".parse::<general::SocketAddr>(),
Err(AddrParseError::UnrecognizedSchema(f)) if f == "fred"
);
}
#[test]
#[cfg(unix)]
fn display_unix_weird() {
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt as _;
let a1 = from_pathname(OsStr::from_bytes(&[255, 255, 255, 255]));
assert!(a1.try_to_string().is_none());
assert_eq!(a1.display_lossy().to_string(), "unix:���� [lossy]");
let a2 = from_pathname("");
assert_eq!(a2.try_to_string().unwrap(), "unix:");
assert_eq!(a2.display_lossy().to_string(), "unix:");
}
#[test]
#[cfg(not(unix))]
fn parse_err_no_unix() {
assert_matches!(
"unix:".parse::<general::SocketAddr>(),
Err(AddrParseError::InvalidAfUnixAddress(_))
);
assert_matches!(
"unix:/any/path".parse::<general::SocketAddr>(),
Err(AddrParseError::InvalidAfUnixAddress(_))
);
}
}