1use std::path::Path;
15use std::sync::Arc;
16
17use crate::unix;
18use std::{io::Error as IoError, net};
19
20#[cfg(target_os = "android")]
21use std::os::android::net::SocketAddrExt as _;
22#[cfg(target_os = "linux")]
23use std::os::linux::net::SocketAddrExt as _;
24
25#[derive(Clone, Debug, derive_more::From, derive_more::TryInto)]
101#[non_exhaustive]
102pub enum SocketAddr {
103 Inet(net::SocketAddr),
105 Unix(unix::SocketAddr),
109}
110
111impl SocketAddr {
112 pub fn display_lossy(&self) -> DisplayLossy<'_> {
120 DisplayLossy(self)
121 }
122
123 pub fn try_to_string(&self) -> Option<String> {
127 use SocketAddr::*;
128 match self {
129 Inet(sa) => Some(format!("inet:{}", sa)),
130 Unix(sa) => {
131 if sa.is_unnamed() {
132 Some("unix:".to_string())
133 } else {
134 sa.as_pathname()
135 .and_then(Path::to_str)
136 .map(|p| format!("unix:{}", p))
137 }
138 }
139 }
140 }
141
142 pub fn as_pathname(&self) -> Option<&Path> {
145 match self {
146 SocketAddr::Inet(_) => None,
147 SocketAddr::Unix(socket_addr) => socket_addr.as_pathname(),
148 }
149 }
150}
151
152pub struct DisplayLossy<'a>(&'a SocketAddr);
154
155impl<'a> std::fmt::Display for DisplayLossy<'a> {
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 use SocketAddr::*;
158 match self.0 {
159 Inet(sa) => write!(f, "inet:{}", sa),
160 Unix(sa) => {
161 if let Some(path) = sa.as_pathname() {
162 if let Some(path_str) = path.to_str() {
163 write!(f, "unix:{}", path_str)
164 } else {
165 write!(f, "unix:{} [lossy]", path.to_string_lossy())
166 }
167 } else if sa.is_unnamed() {
168 write!(f, "unix:")
169 } else {
170 write!(f, "unix:{:?} [lossy]", sa)
171 }
172 }
173 }
174 }
175}
176
177impl std::str::FromStr for SocketAddr {
178 type Err = AddrParseError;
179
180 fn from_str(s: &str) -> Result<Self, Self::Err> {
181 if s.starts_with(|c: char| c.is_ascii_digit() || c == '[') {
182 Ok(s.parse::<net::SocketAddr>()?.into())
184 } else if let Some((schema, remainder)) = s.split_once(':') {
185 match schema {
186 "unix" => Ok(unix::SocketAddr::from_pathname(remainder)?.into()),
187 "inet" => Ok(remainder.parse::<net::SocketAddr>()?.into()),
188 _ => Err(AddrParseError::UnrecognizedSchema(schema.to_string())),
189 }
190 } else {
191 Err(AddrParseError::NoSchema)
192 }
193 }
194}
195
196#[derive(Clone, Debug, thiserror::Error)]
198#[non_exhaustive]
199pub enum AddrParseError {
200 #[error("Address schema {0:?} unrecognized")]
202 UnrecognizedSchema(String),
203 #[error("Address did not look like internet, but had no address schema.")]
205 NoSchema,
206 #[error("Invalid AF_UNIX address")]
208 InvalidAfUnixAddress(#[source] Arc<IoError>),
209 #[error("Invalid internet address")]
211 InvalidInetAddress(#[from] std::net::AddrParseError),
212}
213
214impl From<IoError> for AddrParseError {
215 fn from(e: IoError) -> Self {
216 Self::InvalidAfUnixAddress(Arc::new(e))
217 }
218}
219
220impl PartialEq for SocketAddr {
221 fn eq(&self, other: &Self) -> bool {
232 match (self, other) {
233 (Self::Inet(l0), Self::Inet(r0)) => l0 == r0,
234 #[cfg(unix)]
235 (Self::Unix(l0), Self::Unix(r0)) => {
236 if l0.is_unnamed() && r0.is_unnamed() {
240 return true;
241 }
242 if let (Some(a), Some(b)) = (l0.as_pathname(), r0.as_pathname()) {
243 return a == b;
244 }
245 #[cfg(any(target_os = "android", target_os = "linux"))]
246 if let (Some(a), Some(b)) = (l0.as_abstract_name(), r0.as_abstract_name()) {
247 return a == b;
248 }
249 false
250 }
251 _ => false,
252 }
253 }
254}
255
256#[cfg(feature = "arbitrary")]
257impl<'a> arbitrary::Arbitrary<'a> for SocketAddr {
258 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
259 #[allow(clippy::missing_docs_in_private_items)]
261 #[derive(arbitrary::Arbitrary)]
262 enum Kind {
263 V4,
264 V6,
265 #[cfg(unix)]
266 Unix,
267 #[cfg(any(target_os = "android", target_os = "linux"))]
268 UnixAbstract,
269 }
270 match u.arbitrary()? {
271 Kind::V4 => Ok(SocketAddr::Inet(
272 net::SocketAddrV4::new(u.arbitrary()?, u.arbitrary()?).into(),
273 )),
274 Kind::V6 => Ok(SocketAddr::Inet(
275 net::SocketAddrV6::new(
276 u.arbitrary()?,
277 u.arbitrary()?,
278 u.arbitrary()?,
279 u.arbitrary()?,
280 )
281 .into(),
282 )),
283 #[cfg(unix)]
284 Kind::Unix => {
285 let pathname: std::ffi::OsString = u.arbitrary()?;
286 Ok(SocketAddr::Unix(
287 unix::SocketAddr::from_pathname(pathname)
288 .map_err(|_| arbitrary::Error::IncorrectFormat)?,
289 ))
290 }
291 #[cfg(any(target_os = "android", target_os = "linux"))]
292 Kind::UnixAbstract => {
293 #[cfg(target_os = "android")]
294 use std::os::android::net::SocketAddrExt as _;
295 #[cfg(target_os = "linux")]
296 use std::os::linux::net::SocketAddrExt as _;
297 let name: &[u8] = u.arbitrary()?;
298 Ok(SocketAddr::Unix(
299 unix::SocketAddr::from_abstract_name(name)
300 .map_err(|_| arbitrary::Error::IncorrectFormat)?,
301 ))
302 }
303 }
304 }
305}
306
307#[cfg(test)]
308mod test {
309 #![allow(clippy::bool_assert_comparison)]
311 #![allow(clippy::clone_on_copy)]
312 #![allow(clippy::dbg_macro)]
313 #![allow(clippy::mixed_attributes_style)]
314 #![allow(clippy::print_stderr)]
315 #![allow(clippy::print_stdout)]
316 #![allow(clippy::single_char_pattern)]
317 #![allow(clippy::unwrap_used)]
318 #![allow(clippy::unchecked_time_subtraction)]
319 #![allow(clippy::useless_vec)]
320 #![allow(clippy::needless_pass_by_value)]
321 #![allow(clippy::string_slice)] use super::AddrParseError;
325 use crate::general;
326 use assert_matches::assert_matches;
327 #[cfg(unix)]
328 use std::os::unix::net as unix;
329 use std::{net, str::FromStr as _};
330
331 fn from_inet(s: &str) -> general::SocketAddr {
335 let a: net::SocketAddr = s.parse().unwrap();
336 a.into()
337 }
338
339 #[test]
340 fn ok_inet() {
341 assert_eq!(
342 from_inet("127.0.0.1:9999"),
343 general::SocketAddr::from_str("127.0.0.1:9999").unwrap()
344 );
345 assert_eq!(
346 from_inet("127.0.0.1:9999"),
347 general::SocketAddr::from_str("inet:127.0.0.1:9999").unwrap()
348 );
349
350 assert_eq!(
351 from_inet("[::1]:9999"),
352 general::SocketAddr::from_str("[::1]:9999").unwrap()
353 );
354 assert_eq!(
355 from_inet("[::1]:9999"),
356 general::SocketAddr::from_str("inet:[::1]:9999").unwrap()
357 );
358
359 assert_ne!(
360 general::SocketAddr::from_str("127.0.0.1:9999").unwrap(),
361 general::SocketAddr::from_str("[::1]:9999").unwrap()
362 );
363
364 let ga1 = from_inet("127.0.0.1:9999");
365 assert_eq!(ga1.display_lossy().to_string(), "inet:127.0.0.1:9999");
366 assert_eq!(ga1.try_to_string().unwrap(), "inet:127.0.0.1:9999");
367
368 let ga2 = from_inet("[::1]:9999");
369 assert_eq!(ga2.display_lossy().to_string(), "inet:[::1]:9999");
370 assert_eq!(ga2.try_to_string().unwrap(), "inet:[::1]:9999");
371 }
372
373 #[cfg(unix)]
377 fn from_pathname(s: impl AsRef<std::path::Path>) -> general::SocketAddr {
378 let a = unix::SocketAddr::from_pathname(s).unwrap();
379 a.into()
380 }
381 #[test]
382 #[cfg(unix)]
383 fn ok_unix() {
384 assert_eq!(
385 from_pathname("/some/path"),
386 general::SocketAddr::from_str("unix:/some/path").unwrap()
387 );
388 assert_eq!(
389 from_pathname("/another/path"),
390 general::SocketAddr::from_str("unix:/another/path").unwrap()
391 );
392 assert_eq!(
393 from_pathname("/path/with spaces"),
394 general::SocketAddr::from_str("unix:/path/with spaces").unwrap()
395 );
396 assert_ne!(
397 general::SocketAddr::from_str("unix:/some/path").unwrap(),
398 general::SocketAddr::from_str("unix:/another/path").unwrap()
399 );
400 assert_eq!(
401 from_pathname(""),
402 general::SocketAddr::from_str("unix:").unwrap()
403 );
404
405 let ga1 = general::SocketAddr::from_str("unix:/some/path").unwrap();
406 assert_eq!(ga1.display_lossy().to_string(), "unix:/some/path");
407 assert_eq!(ga1.try_to_string().unwrap(), "unix:/some/path");
408
409 let ga2 = general::SocketAddr::from_str("unix:/another/path").unwrap();
410 assert_eq!(ga2.display_lossy().to_string(), "unix:/another/path");
411 assert_eq!(ga2.try_to_string().unwrap(), "unix:/another/path");
412 }
413
414 #[test]
415 fn parse_err_inet() {
416 assert_matches!(
417 "1234567890:999".parse::<general::SocketAddr>(),
418 Err(AddrParseError::InvalidInetAddress(_))
419 );
420 assert_matches!(
421 "1z".parse::<general::SocketAddr>(),
422 Err(AddrParseError::InvalidInetAddress(_))
423 );
424 assert_matches!(
425 "[[77".parse::<general::SocketAddr>(),
426 Err(AddrParseError::InvalidInetAddress(_))
427 );
428
429 assert_matches!(
430 "inet:fred:9999".parse::<general::SocketAddr>(),
431 Err(AddrParseError::InvalidInetAddress(_))
432 );
433
434 assert_matches!(
435 "inet:127.0.0.1".parse::<general::SocketAddr>(),
436 Err(AddrParseError::InvalidInetAddress(_))
437 );
438
439 assert_matches!(
440 "inet:[::1]".parse::<general::SocketAddr>(),
441 Err(AddrParseError::InvalidInetAddress(_))
442 );
443 }
444
445 #[test]
446 fn parse_err_schemata() {
447 assert_matches!(
448 "fred".parse::<general::SocketAddr>(),
449 Err(AddrParseError::NoSchema)
450 );
451 assert_matches!(
452 "fred:".parse::<general::SocketAddr>(),
453 Err(AddrParseError::UnrecognizedSchema(f)) if f == "fred"
454 );
455 assert_matches!(
456 "fred:hello".parse::<general::SocketAddr>(),
457 Err(AddrParseError::UnrecognizedSchema(f)) if f == "fred"
458 );
459 }
460
461 #[test]
462 #[cfg(unix)]
463 fn display_unix_weird() {
464 use std::ffi::OsStr;
465 use std::os::unix::ffi::OsStrExt as _;
466
467 let a1 = from_pathname(OsStr::from_bytes(&[255, 255, 255, 255]));
468 assert!(a1.try_to_string().is_none());
469 assert_eq!(a1.display_lossy().to_string(), "unix:���� [lossy]");
470
471 let a2 = from_pathname("");
472 assert_eq!(a2.try_to_string().unwrap(), "unix:");
473 assert_eq!(a2.display_lossy().to_string(), "unix:");
474 }
475
476 #[test]
477 #[cfg(not(unix))]
478 fn parse_err_no_unix() {
479 assert_matches!(
480 "unix:".parse::<general::SocketAddr>(),
481 Err(AddrParseError::InvalidAfUnixAddress(_))
482 );
483 assert_matches!(
484 "unix:/any/path".parse::<general::SocketAddr>(),
485 Err(AddrParseError::InvalidAfUnixAddress(_))
486 );
487 }
488}