1#![doc = include_str!("../README.md")]
2#![allow(clippy::must_use_candidate)]
3#![deprecated(
4 since = "0.0.0",
5 note = "This crate is deprecated, use `uaddr` instead: https://crates.io/crates/uaddr"
6)]
7
8use std::borrow::Cow;
9use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
10use std::str::FromStr;
11use std::sync::Arc;
12use std::{fmt, io};
13
14#[cfg(unix)]
15pub mod unix;
16
17pub const UNIX_URI_PREFIX: &str = "unix://";
22
23wrapper_lite::wrapper!(
24 #[wrapper_impl(Debug)]
25 #[wrapper_impl(Display)]
26 #[wrapper_impl(AsRef)]
27 #[wrapper_impl(Deref)]
28 #[repr(align(cache))]
29 #[derive(Clone, PartialEq, Eq, Hash)]
30 pub struct UniAddr(UniAddrInner);
47);
48
49impl From<SocketAddr> for UniAddr {
50 fn from(addr: SocketAddr) -> Self {
51 UniAddr::from_inner(UniAddrInner::Inet(addr))
52 }
53}
54
55#[cfg(unix)]
56impl From<std::os::unix::net::SocketAddr> for UniAddr {
57 fn from(addr: std::os::unix::net::SocketAddr) -> Self {
58 UniAddr::from_inner(UniAddrInner::Unix(addr.into()))
59 }
60}
61
62#[cfg(all(unix, feature = "feat-tokio"))]
63impl From<tokio::net::unix::SocketAddr> for UniAddr {
64 fn from(addr: tokio::net::unix::SocketAddr) -> Self {
65 UniAddr::from_inner(UniAddrInner::Unix(unix::SocketAddr::from(addr.into())))
66 }
67}
68
69#[cfg(feature = "feat-socket2")]
70impl TryFrom<socket2::SockAddr> for UniAddr {
71 type Error = io::Error;
72
73 fn try_from(addr: socket2::SockAddr) -> Result<Self, Self::Error> {
74 UniAddr::try_from(&addr)
75 }
76}
77
78#[cfg(feature = "feat-socket2")]
79impl TryFrom<&socket2::SockAddr> for UniAddr {
80 type Error = io::Error;
81
82 fn try_from(addr: &socket2::SockAddr) -> Result<Self, Self::Error> {
83 if let Some(addr) = addr.as_socket() {
84 return Ok(Self::from(addr));
85 }
86
87 #[cfg(unix)]
88 if let Some(addr) = addr.as_unix() {
89 return Ok(Self::from(addr));
90 }
91
92 #[cfg(unix)]
93 if addr.is_unnamed() {
94 return Ok(Self::from(crate::unix::SocketAddr::new_unnamed()));
95 }
96
97 #[cfg(any(target_os = "android", target_os = "linux", target_os = "cygwin"))]
98 if let Some(addr) = addr.as_abstract_namespace() {
99 return crate::unix::SocketAddr::new_abstract(addr).map(Self::from);
100 }
101
102 Err(io::Error::new(
103 io::ErrorKind::Other,
104 "unsupported address type",
105 ))
106 }
107}
108
109#[cfg(feature = "feat-socket2")]
110impl TryFrom<UniAddr> for socket2::SockAddr {
111 type Error = io::Error;
112
113 fn try_from(addr: UniAddr) -> Result<Self, Self::Error> {
114 socket2::SockAddr::try_from(&addr)
115 }
116}
117
118#[cfg(feature = "feat-socket2")]
119impl TryFrom<&UniAddr> for socket2::SockAddr {
120 type Error = io::Error;
121
122 fn try_from(addr: &UniAddr) -> Result<Self, Self::Error> {
123 match &addr.inner {
124 UniAddrInner::Inet(addr) => Ok(socket2::SockAddr::from(*addr)),
125 #[cfg(unix)]
126 UniAddrInner::Unix(addr) => socket2::SockAddr::unix(addr.to_os_string()),
127 UniAddrInner::Host(_) => Err(io::Error::new(
128 io::ErrorKind::Other,
129 "The host name address must be resolved before converting to SockAddr",
130 )),
131 }
132 }
133}
134
135#[cfg(unix)]
136impl From<crate::unix::SocketAddr> for UniAddr {
137 fn from(addr: crate::unix::SocketAddr) -> Self {
138 UniAddr::from_inner(UniAddrInner::Unix(addr))
139 }
140}
141
142impl FromStr for UniAddr {
143 type Err = ParseError;
144
145 fn from_str(addr: &str) -> Result<Self, Self::Err> {
146 Self::new(addr)
147 }
148}
149
150#[cfg(feature = "feat-serde")]
151impl serde::Serialize for UniAddr {
152 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
153 where
154 S: serde::Serializer,
155 {
156 serializer.serialize_str(&self.to_str())
157 }
158}
159
160#[cfg(feature = "feat-serde")]
161impl<'de> serde::Deserialize<'de> for UniAddr {
162 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
163 where
164 D: serde::Deserializer<'de>,
165 {
166 Self::new(&String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
167 }
168}
169
170impl UniAddr {
171 #[inline]
172 pub fn new(addr: &str) -> Result<Self, ParseError> {
178 if addr.is_empty() {
179 return Err(ParseError::Empty);
180 }
181
182 #[cfg(unix)]
183 if let Some(addr) = addr.strip_prefix(UNIX_URI_PREFIX) {
184 return unix::SocketAddr::new(addr)
185 .map(UniAddrInner::Unix)
186 .map(Self::from_inner)
187 .map_err(ParseError::InvalidUDSAddress);
188 }
189
190 #[cfg(not(unix))]
191 if let Some(_addr) = addr.strip_prefix(UNIX_URI_PREFIX) {
192 return Err(ParseError::Unsupported);
193 }
194
195 let Some((host, port)) = addr.rsplit_once(':') else {
196 return Err(ParseError::InvalidPort);
197 };
198
199 let Ok(port) = port.parse::<u16>() else {
200 return Err(ParseError::InvalidPort);
201 };
202
203 if host.chars().next().is_some_and(|c| c.is_ascii_digit()) {
205 return Ipv4Addr::from_str(host)
206 .map(|ip| SocketAddr::V4(SocketAddrV4::new(ip, port)))
207 .map(UniAddrInner::Inet)
208 .map(Self::from_inner)
209 .map_err(|_| ParseError::InvalidHost)
210 .or_else(|_| {
211 Self::new_host(addr, Some((host, port)))
213 });
214 }
215
216 if let Some(ipv6_addr) = host.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
219 return Ipv6Addr::from_str(ipv6_addr)
220 .map(|ip| SocketAddr::V6(SocketAddrV6::new(ip, port, 0, 0)))
221 .map(UniAddrInner::Inet)
222 .map(Self::from_inner)
223 .map_err(|_| ParseError::InvalidHost);
224 }
225
226 Self::new_host(addr, Some((host, port)))
228 }
229
230 pub fn new_host(addr: &str, parsed: Option<(&str, u16)>) -> Result<Self, ParseError> {
238 let (hostname, _port) = match parsed {
239 Some((hostname, port)) => (hostname, port),
240 None => addr
241 .rsplit_once(':')
242 .ok_or(ParseError::InvalidPort)
243 .and_then(|(hostname, port)| {
244 let Ok(port) = port.parse::<u16>() else {
245 return Err(ParseError::InvalidPort);
246 };
247
248 Ok((hostname, port))
249 })?,
250 };
251
252 Self::validate_host_name(hostname.as_bytes()).map_err(|()| ParseError::InvalidHost)?;
253
254 Ok(Self::from_inner(UniAddrInner::Host(Arc::from(addr))))
255 }
256
257 const fn validate_host_name(input: &[u8]) -> Result<(), ()> {
259 enum State {
260 Start,
261 Next,
262 NumericOnly { len: usize },
263 NextAfterNumericOnly,
264 Subsequent { len: usize },
265 Hyphen { len: usize },
266 }
267
268 use State::{Hyphen, Next, NextAfterNumericOnly, NumericOnly, Start, Subsequent};
269
270 const MAX_LABEL_LENGTH: usize = 63;
272
273 const MAX_NAME_LENGTH: usize = 253;
275
276 let mut state = Start;
277
278 if input.len() > MAX_NAME_LENGTH {
279 return Err(());
280 }
281
282 let mut idx = 0;
283 while idx < input.len() {
284 let ch = input[idx];
285 state = match (state, ch) {
286 (Start | Next | NextAfterNumericOnly | Hyphen { .. }, b'.') => {
287 return Err(());
288 }
289 (Subsequent { .. }, b'.') => Next,
290 (NumericOnly { .. }, b'.') => NextAfterNumericOnly,
291 (Subsequent { len } | NumericOnly { len } | Hyphen { len }, _)
292 if len >= MAX_LABEL_LENGTH =>
293 {
294 return Err(());
295 }
296 (Start | Next | NextAfterNumericOnly, b'0'..=b'9') => NumericOnly { len: 1 },
297 (NumericOnly { len }, b'0'..=b'9') => NumericOnly { len: len + 1 },
298 (Start | Next | NextAfterNumericOnly, b'a'..=b'z' | b'A'..=b'Z' | b'_') => {
299 Subsequent { len: 1 }
300 }
301 (Subsequent { len } | NumericOnly { len } | Hyphen { len }, b'-') => {
302 Hyphen { len: len + 1 }
303 }
304 (
305 Subsequent { len } | NumericOnly { len } | Hyphen { len },
306 b'a'..=b'z' | b'A'..=b'Z' | b'_' | b'0'..=b'9',
307 ) => Subsequent { len: len + 1 },
308 _ => return Err(()),
309 };
310 idx += 1;
311 }
312
313 if matches!(
314 state,
315 Start | Hyphen { .. } | NumericOnly { .. } | NextAfterNumericOnly | Next
316 ) {
317 return Err(());
318 }
319
320 Ok(())
321 }
322
323 pub fn blocking_resolve_socket_addrs(&mut self) -> io::Result<()> {
334 self.blocking_resolve_socket_addrs_with(ToSocketAddrs::to_socket_addrs)
335 }
336
337 pub fn blocking_resolve_socket_addrs_with<F, A>(&mut self, f: F) -> io::Result<()>
344 where
345 F: FnOnce(&str) -> io::Result<A>,
346 A: Iterator<Item = SocketAddr>,
347 {
348 if let UniAddrInner::Host(addr) = self.as_inner() {
349 let resolved = f(addr)?.next().ok_or_else(|| {
350 io::Error::new(
351 io::ErrorKind::Other,
352 "Host resolution failed, no available address",
353 )
354 })?;
355
356 *self = Self::from_inner(UniAddrInner::Inet(resolved));
357 }
358
359 Ok(())
360 }
361
362 #[cfg(feature = "feat-tokio")]
363 pub async fn resolve_socket_addrs(&mut self) -> io::Result<()> {
373 if let UniAddrInner::Host(addr) = self.as_inner() {
374 let addr = addr.clone();
375 let resolved = tokio::task::spawn_blocking(move || addr.to_socket_addrs())
376 .await??
377 .next()
378 .ok_or_else(|| {
379 io::Error::new(
380 io::ErrorKind::Other,
381 "Host resolution failed, no available address",
382 )
383 })?;
384
385 *self = Self::from_inner(UniAddrInner::Inet(resolved));
386 }
387
388 Ok(())
389 }
390
391 #[inline]
392 pub fn to_str(&self) -> Cow<'_, str> {
394 self.as_inner().to_str()
395 }
396}
397
398#[non_exhaustive]
399#[derive(Debug, Clone, PartialEq, Eq, Hash)]
400pub enum UniAddrInner {
406 Inet(SocketAddr),
408
409 #[cfg(unix)]
410 Unix(crate::unix::SocketAddr),
412
413 Host(Arc<str>),
419}
420
421impl fmt::Display for UniAddrInner {
422 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423 self.to_str().fmt(f)
424 }
425}
426
427impl UniAddrInner {
428 #[inline]
429 pub fn to_str(&self) -> Cow<'_, str> {
431 match self {
432 Self::Inet(addr) => addr.to_string().into(),
433 #[cfg(unix)]
434 Self::Unix(addr) => addr
435 .to_os_string_impl(UNIX_URI_PREFIX, "@")
436 .to_string_lossy()
437 .to_string()
438 .into(),
439 Self::Host(host) => Cow::Borrowed(host),
440 }
441 }
442}
443
444#[derive(Debug)]
445pub enum ParseError {
447 Empty,
449
450 InvalidHost,
452
453 InvalidPort,
455
456 InvalidUDSAddress(io::Error),
458
459 Unsupported,
461}
462
463impl fmt::Display for ParseError {
464 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
465 match self {
466 Self::Empty => write!(f, "empty address string"),
467 Self::InvalidHost => write!(f, "invalid or missing host address"),
468 Self::InvalidPort => write!(f, "invalid or missing port"),
469 Self::InvalidUDSAddress(err) => write!(f, "invalid UDS address: {err}"),
470 Self::Unsupported => write!(f, "unsupported address type on this platform"),
471 }
472 }
473}
474
475impl std::error::Error for ParseError {
476 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
477 match self {
478 Self::InvalidUDSAddress(err) => Some(err),
479 _ => None,
480 }
481 }
482}
483
484impl From<ParseError> for io::Error {
485 fn from(value: ParseError) -> Self {
486 io::Error::new(io::ErrorKind::Other, value)
487 }
488}
489
490#[cfg(test)]
491mod tests {
492 #![allow(non_snake_case)]
493
494 use rstest::rstest;
495
496 use super::*;
497
498 #[rstest]
499 #[case("0.0.0.0:0")]
500 #[case("0.0.0.0:8080")]
501 #[case("127.0.0.1:0")]
502 #[case("127.0.0.1:8080")]
503 #[case("[::]:0")]
504 #[case("[::]:8080")]
505 #[case("[::1]:0")]
506 #[case("[::1]:8080")]
507 #[case("example.com:8080")]
508 #[case("1example.com:8080")]
509 #[cfg_attr(unix, case("unix://"))]
510 #[cfg_attr(
511 any(target_os = "android", target_os = "linux", target_os = "cygwin"),
512 case("unix://@")
513 )]
514 #[cfg_attr(unix, case("unix:///tmp/test_UniAddr_new_Display.socket"))]
515 #[cfg_attr(
516 any(target_os = "android", target_os = "linux", target_os = "cygwin"),
517 case("unix://@test_UniAddr_new_Display.socket")
518 )]
519 fn test_UniAddr_new_Display(#[case] addr: &str) {
520 let addr_displayed = UniAddr::new(addr).unwrap().to_string();
521
522 assert_eq!(
523 addr_displayed, addr,
524 "addr_displayed {addr_displayed:?} != {addr:?}"
525 );
526 }
527
528 #[rstest]
529 #[case("example.com:8080")]
530 #[case("1example.com:8080")]
531 #[should_panic]
532 #[case::panic("1example.com")]
533 #[should_panic]
534 #[case::panic("1example.com.")]
535 #[should_panic]
536 #[case::panic("1example.com.:14514")]
537 #[should_panic]
538 #[case::panic("1example.com:1919810")]
539 #[should_panic]
540 #[case::panic("this-is-a-long-host-name-this-is-a-long-host-name-this-is-a-long-host-name-this-is-a-long-host-name-this-is-a-long-host-name-this-is-a-long-host-name-this-is-a-long-host-name-this-is-a-long-host-name-this-is-a-long-host-name-this-is-a-long-host-name-this-is-a-long-host-name:19810")]
541 fn test_UniAddr_new_host(#[case] addr: &str) {
542 let addr_displayed = UniAddr::new_host(addr, None).unwrap().to_string();
543
544 assert_eq!(
545 addr_displayed, addr,
546 "addr_displayed {addr_displayed:?} != {addr:?}"
547 );
548 }
549
550 #[rstest]
551 #[should_panic]
552 #[case::panic("")]
553 #[should_panic]
554 #[case::panic("not-an-address")]
555 #[should_panic]
556 #[case::panic("127.0.0.1")]
557 #[should_panic]
558 #[case::panic("127.0.0.1:99999")]
559 #[should_panic]
560 #[case::panic("127.0.0.256:99999")]
561 #[should_panic]
562 #[case::panic("::1")]
563 #[should_panic]
564 #[case::panic("[::1]")]
565 #[should_panic]
566 #[case::panic("[::1]:99999")]
567 #[should_panic]
568 #[case::panic("[::gg]:99999")]
569 #[should_panic]
570 #[case::panic("example.com")]
571 #[should_panic]
572 #[case::panic("example.com:99999")]
573 #[should_panic]
574 #[case::panic("examp😀le.com:99999")]
575 fn test_UniAddr_new_invalid(#[case] addr: &str) {
576 let _ = UniAddr::new(addr).unwrap();
577 }
578
579 #[cfg(not(unix))]
580 #[test]
581 fn test_UniAddr_new_unsupported() {
582 let result = UniAddr::new("unix:///tmp/test.sock");
584
585 assert!(matches!(result.unwrap_err(), ParseError::Unsupported));
586 }
587
588 #[rstest]
589 #[case("0.0.0.0:0")]
590 #[case("0.0.0.0:8080")]
591 #[case("127.0.0.1:0")]
592 #[case("127.0.0.1:8080")]
593 #[case("[::]:0")]
594 #[case("[::]:8080")]
595 #[case("[::1]:0")]
596 #[case("[::1]:8080")]
597 #[cfg_attr(unix, case("unix:///tmp/test_socket2_sock_addr_conversion.socket"))]
598 #[cfg_attr(unix, case("unix://"))]
599 #[cfg_attr(
600 any(target_os = "android", target_os = "linux", target_os = "cygwin"),
601 case("unix://@test_socket2_sock_addr_conversion.socket")
602 )]
603 fn test_socket2_SockAddr_conversion(#[case] addr: &str) {
604 let uni_addr = UniAddr::new(addr).unwrap();
605 let sock_addr = socket2::SockAddr::try_from(&uni_addr).unwrap();
606 let uni_addr_converted = UniAddr::try_from(sock_addr).unwrap();
607
608 assert_eq!(
609 uni_addr, uni_addr_converted,
610 "{uni_addr} != {uni_addr_converted}"
611 );
612 }
613}