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 use std::os::linux::net::SocketAddrExt as _;
294 let name: &[u8] = u.arbitrary()?;
295 Ok(SocketAddr::Unix(
296 unix::SocketAddr::from_abstract_name(name)
297 .map_err(|_| arbitrary::Error::IncorrectFormat)?,
298 ))
299 }
300 }
301 }
302}
303
304#[cfg(test)]
305mod test {
306 #![allow(clippy::bool_assert_comparison)]
308 #![allow(clippy::clone_on_copy)]
309 #![allow(clippy::dbg_macro)]
310 #![allow(clippy::mixed_attributes_style)]
311 #![allow(clippy::print_stderr)]
312 #![allow(clippy::print_stdout)]
313 #![allow(clippy::single_char_pattern)]
314 #![allow(clippy::unwrap_used)]
315 #![allow(clippy::unchecked_duration_subtraction)]
316 #![allow(clippy::useless_vec)]
317 #![allow(clippy::needless_pass_by_value)]
318 use super::AddrParseError;
321 use crate::general;
322 use assert_matches::assert_matches;
323 #[cfg(unix)]
324 use std::os::unix::net as unix;
325 use std::{net, str::FromStr as _};
326
327 fn from_inet(s: &str) -> general::SocketAddr {
331 let a: net::SocketAddr = s.parse().unwrap();
332 a.into()
333 }
334
335 #[test]
336 fn ok_inet() {
337 assert_eq!(
338 from_inet("127.0.0.1:9999"),
339 general::SocketAddr::from_str("127.0.0.1:9999").unwrap()
340 );
341 assert_eq!(
342 from_inet("127.0.0.1:9999"),
343 general::SocketAddr::from_str("inet:127.0.0.1:9999").unwrap()
344 );
345
346 assert_eq!(
347 from_inet("[::1]:9999"),
348 general::SocketAddr::from_str("[::1]:9999").unwrap()
349 );
350 assert_eq!(
351 from_inet("[::1]:9999"),
352 general::SocketAddr::from_str("inet:[::1]:9999").unwrap()
353 );
354
355 assert_ne!(
356 general::SocketAddr::from_str("127.0.0.1:9999").unwrap(),
357 general::SocketAddr::from_str("[::1]:9999").unwrap()
358 );
359
360 let ga1 = from_inet("127.0.0.1:9999");
361 assert_eq!(ga1.display_lossy().to_string(), "inet:127.0.0.1:9999");
362 assert_eq!(ga1.try_to_string().unwrap(), "inet:127.0.0.1:9999");
363
364 let ga2 = from_inet("[::1]:9999");
365 assert_eq!(ga2.display_lossy().to_string(), "inet:[::1]:9999");
366 assert_eq!(ga2.try_to_string().unwrap(), "inet:[::1]:9999");
367 }
368
369 #[cfg(unix)]
373 fn from_pathname(s: impl AsRef<std::path::Path>) -> general::SocketAddr {
374 let a = unix::SocketAddr::from_pathname(s).unwrap();
375 a.into()
376 }
377 #[test]
378 #[cfg(unix)]
379 fn ok_unix() {
380 assert_eq!(
381 from_pathname("/some/path"),
382 general::SocketAddr::from_str("unix:/some/path").unwrap()
383 );
384 assert_eq!(
385 from_pathname("/another/path"),
386 general::SocketAddr::from_str("unix:/another/path").unwrap()
387 );
388 assert_eq!(
389 from_pathname("/path/with spaces"),
390 general::SocketAddr::from_str("unix:/path/with spaces").unwrap()
391 );
392 assert_ne!(
393 general::SocketAddr::from_str("unix:/some/path").unwrap(),
394 general::SocketAddr::from_str("unix:/another/path").unwrap()
395 );
396 assert_eq!(
397 from_pathname(""),
398 general::SocketAddr::from_str("unix:").unwrap()
399 );
400
401 let ga1 = general::SocketAddr::from_str("unix:/some/path").unwrap();
402 assert_eq!(ga1.display_lossy().to_string(), "unix:/some/path");
403 assert_eq!(ga1.try_to_string().unwrap(), "unix:/some/path");
404
405 let ga2 = general::SocketAddr::from_str("unix:/another/path").unwrap();
406 assert_eq!(ga2.display_lossy().to_string(), "unix:/another/path");
407 assert_eq!(ga2.try_to_string().unwrap(), "unix:/another/path");
408 }
409
410 #[test]
411 fn parse_err_inet() {
412 assert_matches!(
413 "1234567890:999".parse::<general::SocketAddr>(),
414 Err(AddrParseError::InvalidInetAddress(_))
415 );
416 assert_matches!(
417 "1z".parse::<general::SocketAddr>(),
418 Err(AddrParseError::InvalidInetAddress(_))
419 );
420 assert_matches!(
421 "[[77".parse::<general::SocketAddr>(),
422 Err(AddrParseError::InvalidInetAddress(_))
423 );
424
425 assert_matches!(
426 "inet:fred:9999".parse::<general::SocketAddr>(),
427 Err(AddrParseError::InvalidInetAddress(_))
428 );
429
430 assert_matches!(
431 "inet:127.0.0.1".parse::<general::SocketAddr>(),
432 Err(AddrParseError::InvalidInetAddress(_))
433 );
434
435 assert_matches!(
436 "inet:[::1]".parse::<general::SocketAddr>(),
437 Err(AddrParseError::InvalidInetAddress(_))
438 );
439 }
440
441 #[test]
442 fn parse_err_schemata() {
443 assert_matches!(
444 "fred".parse::<general::SocketAddr>(),
445 Err(AddrParseError::NoSchema)
446 );
447 assert_matches!(
448 "fred:".parse::<general::SocketAddr>(),
449 Err(AddrParseError::UnrecognizedSchema(f)) if f == "fred"
450 );
451 assert_matches!(
452 "fred:hello".parse::<general::SocketAddr>(),
453 Err(AddrParseError::UnrecognizedSchema(f)) if f == "fred"
454 );
455 }
456
457 #[test]
458 #[cfg(unix)]
459 fn display_unix_weird() {
460 use std::ffi::OsStr;
461 use std::os::unix::ffi::OsStrExt as _;
462
463 let a1 = from_pathname(OsStr::from_bytes(&[255, 255, 255, 255]));
464 assert!(a1.try_to_string().is_none());
465 assert_eq!(a1.display_lossy().to_string(), "unix:���� [lossy]");
466
467 let a2 = from_pathname("");
468 assert_eq!(a2.try_to_string().unwrap(), "unix:");
469 assert_eq!(a2.display_lossy().to_string(), "unix:");
470 }
471
472 #[test]
473 #[cfg(not(unix))]
474 fn parse_err_no_unix() {
475 assert_matches!(
476 "unix:".parse::<general::SocketAddr>(),
477 Err(AddrParseError::InvalidAfUnixAddress(_))
478 );
479 assert_matches!(
480 "unix:/any/path".parse::<general::SocketAddr>(),
481 Err(AddrParseError::InvalidAfUnixAddress(_))
482 );
483 }
484}