uni_addr/
unix.rs

1//! Platform-specific code for Unix-like systems
2
3use std::ffi::{CStr, OsStr, OsString};
4use std::hash::{Hash, Hasher};
5use std::os::unix::ffi::OsStrExt;
6use std::path::Path;
7use std::{fmt, fs, io};
8
9wrapper_lite::general_wrapper! {
10    #[wrapper_impl(Deref)]
11    #[derive(Clone)]
12    /// Wrapper over [`std::os::unix::net::SocketAddr`].
13    ///
14    /// See [`SocketAddr::new`] for more details.
15    pub struct SocketAddr(std::os::unix::net::SocketAddr);
16}
17
18impl SocketAddr {
19    /// Creates a new [`SocketAddr`] from its string representation.
20    ///
21    /// # Address Types
22    ///
23    /// - Strings starting with `@` or `\0` are parsed as abstract unix socket
24    ///   addresses (Linux-specific).
25    /// - All other strings are parsed as pathname unix socket addresses.
26    /// - Empty strings create unnamed unix socket addresses.
27    ///
28    /// # Notes
29    ///
30    /// This method accepts an [`OsStr`] and does not guarantee proper null
31    /// termination. While pathname addresses reject interior null bytes,
32    /// abstract addresses accept them silently, potentially causing unexpected
33    /// behavior (e.g., `\0abstract` differs from `\0abstract\0\0\0\0\0...`).
34    /// Use [`SocketAddr::new_strict`] to ensure the abstract names do not
35    /// contain null bytes, too.
36    ///
37    /// # Examples
38    ///
39    /// ```rust
40    /// # use uni_addr::unix::SocketAddr;
41    /// #[cfg(any(target_os = "android", target_os = "linux"))]
42    /// // Abstract address (Linux-specific)
43    /// let abstract_addr = SocketAddr::new("@abstract.example.socket").unwrap();
44    /// // Pathname address
45    /// let pathname_addr = SocketAddr::new("/run/pathname.example.socket").unwrap();
46    /// // Unnamed address
47    /// let unnamed_addr = SocketAddr::new("").unwrap();
48    /// ```
49    ///
50    /// # Errors
51    ///
52    /// Returns an error if the address is invalid or unsupported on the
53    /// current platform.
54    ///
55    /// See [`SocketAddr::from_abstract_name`](std::os::linux::net::SocketAddrExt::from_abstract_name)
56    /// and [`StdSocketAddr::from_pathname`] for more details.
57    pub fn new<S: AsRef<OsStr> + ?Sized>(addr: &S) -> io::Result<Self> {
58        let addr = addr.as_ref();
59
60        match addr.as_bytes() {
61            #[cfg(any(target_os = "android", target_os = "linux"))]
62            [b'@', rest @ ..] | [b'\0', rest @ ..] => Self::new_abstract(rest),
63            #[cfg(not(any(target_os = "android", target_os = "linux")))]
64            [b'@', ..] | [b'\0', ..] => Err(io::Error::new(
65                io::ErrorKind::Unsupported,
66                "abstract unix socket address is not supported",
67            )),
68            _ => Self::new_pathname(addr),
69        }
70    }
71
72    /// See [`SocketAddr::new`].
73    pub fn new_strict<S: AsRef<OsStr> + ?Sized>(addr: &S) -> io::Result<Self> {
74        let addr = addr.as_ref();
75
76        match addr.as_bytes() {
77            #[cfg(any(target_os = "android", target_os = "linux"))]
78            [b'@', rest @ ..] | [b'\0', rest @ ..] => Self::new_abstract_strict(rest),
79            #[cfg(not(any(target_os = "android", target_os = "linux")))]
80            [b'@', ..] | [b'\0', ..] => Err(io::Error::new(
81                io::ErrorKind::Unsupported,
82                "abstract unix socket address is not supported",
83            )),
84            _ => Self::new_pathname(addr),
85        }
86    }
87
88    #[cfg(any(target_os = "android", target_os = "linux"))]
89    /// Creates a Unix socket address in the abstract namespace.
90    ///
91    /// The abstract namespace is a Linux-specific extension that allows Unix
92    /// sockets to be bound without creating an entry in the filesystem.
93    /// Abstract sockets are unaffected by filesystem layout or permissions, and
94    /// no cleanup is necessary when the socket is closed.
95    ///
96    /// An abstract socket address name may contain any bytes, including zero.
97    /// However, we don't recommend using zero bytes, as they may lead to
98    /// unexpected behavior. To avoid this, consider using
99    /// [`new_abstract_strict`](Self::new_abstract_strict).
100    ///
101    /// # Errors
102    ///
103    /// Returns an error if the name is longer than `SUN_LEN - 1`.
104    pub fn new_abstract(bytes: &[u8]) -> io::Result<Self> {
105        use std::os::linux::net::SocketAddrExt;
106
107        std::os::unix::net::SocketAddr::from_abstract_name(bytes).map(Self::const_from)
108    }
109
110    #[cfg(any(target_os = "android", target_os = "linux"))]
111    /// See [`SocketAddr::new_abstract`].
112    pub fn new_abstract_strict(bytes: &[u8]) -> io::Result<Self> {
113        use std::os::linux::net::SocketAddrExt;
114
115        if bytes.is_empty() || bytes.contains(&b'\0') {
116            return Err(io::Error::new(
117                io::ErrorKind::InvalidInput,
118                "parse abstract socket name in strict mode: reject NULL bytes",
119            ));
120        }
121
122        std::os::unix::net::SocketAddr::from_abstract_name(bytes).map(Self::const_from)
123    }
124
125    /// Constructs a [`SocketAddr`] with the family `AF_UNIX` and the provided
126    /// path.
127    ///
128    /// # Errors
129    ///
130    /// Returns an error if the path is longer than `SUN_LEN` or if it contains
131    /// NULL bytes.
132    pub fn new_pathname<P: AsRef<Path>>(pathname: P) -> io::Result<Self> {
133        let _ = fs::remove_file(pathname.as_ref());
134
135        std::os::unix::net::SocketAddr::from_pathname(pathname).map(Self::const_from)
136    }
137
138    #[allow(clippy::missing_panics_doc)]
139    /// Creates an unnamed [`SocketAddr`].
140    pub fn new_unnamed() -> Self {
141        // SAFETY: `from_pathname` will not fail at all.
142        std::os::unix::net::SocketAddr::from_pathname("")
143            .map(Self::const_from)
144            .unwrap()
145    }
146
147    #[inline]
148    /// Creates a new [`SocketAddr`] from bytes.
149    ///
150    /// # Errors
151    ///
152    /// See [`SocketAddr::new`].
153    pub fn from_bytes(bytes: &[u8]) -> io::Result<Self> {
154        Self::new(OsStr::from_bytes(bytes))
155    }
156
157    #[inline]
158    /// Creates a new [`SocketAddr`] from bytes with null termination.
159    ///
160    /// Notes that for abstract addresses, the first byte is also `\0`, which is
161    /// not treated as the termination byte. And the abstract name starts from
162    /// the second byte. cannot be empty or contain interior null bytes, i.e.,
163    /// we will reject bytes like `b"\0\0\0..."`. Although `\0\0...` is a valid
164    /// abstract name, we will reject it to avoid potential confusion. See
165    /// [`SocketAddr::new_abstract_strict`] for more details.
166    ///
167    /// # Errors
168    ///
169    /// See [`SocketAddr::new`].
170    pub fn from_bytes_until_nul(bytes: &[u8]) -> io::Result<Self> {
171        match bytes {
172            [b'\0', rest @ ..] => {
173                let addr = CStr::from_bytes_until_nul(rest)
174                    .map(|rest| rest.to_bytes())
175                    .unwrap_or(rest);
176
177                Self::new_abstract_strict(addr)
178            }
179            _ => {
180                let addr = CStr::from_bytes_until_nul(bytes)
181                    .map(|rest| rest.to_bytes())
182                    .unwrap_or(bytes);
183
184                Self::new_pathname(OsStr::from_bytes(addr))
185            }
186        }
187    }
188
189    /// Serializes the [`SocketAddr`] to an `OsString`.
190    ///
191    /// # Returns
192    ///
193    /// - For abstract ones: returns the name prefixed with **`\0`**
194    /// - For pathname ones: returns the pathname
195    /// - For unnamed ones: returns an empty string.
196    pub fn to_os_string(&self) -> OsString {
197        self.to_os_string_impl("", "\0")
198    }
199
200    /// Likes [`to_os_string`](Self::to_os_string), but returns a `String`
201    /// instead of `OsString`, performing lossy UTF-8 conversion.
202    ///
203    /// # Returns
204    ///
205    /// - For abstract ones: returns the name prefixed with **`@`**
206    /// - For pathname ones: returns the pathname
207    /// - For unnamed ones: returns an empty string.
208    pub fn to_string_lossy(&self) -> String {
209        self.to_os_string_impl("", "@")
210            .to_string_lossy()
211            .into_owned()
212    }
213
214    pub(crate) fn to_os_string_impl(&self, prefix: &str, abstract_identifier: &str) -> OsString {
215        let mut os_string = OsString::from(prefix);
216
217        if let Some(pathname) = self.as_pathname() {
218            // Notice: cannot use `extend` here
219            os_string.push(pathname);
220
221            return os_string;
222        }
223
224        #[cfg(any(target_os = "android", target_os = "linux"))]
225        {
226            use std::os::linux::net::SocketAddrExt;
227
228            if let Some(abstract_name) = self.as_abstract_name() {
229                os_string.push(abstract_identifier);
230                os_string.push(OsStr::from_bytes(abstract_name));
231
232                return os_string;
233            }
234        }
235
236        // An unnamed one...
237        os_string
238    }
239}
240
241impl fmt::Debug for SocketAddr {
242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243        self.as_inner().fmt(f)
244    }
245}
246
247impl PartialEq for SocketAddr {
248    fn eq(&self, other: &Self) -> bool {
249        if let Some((l, r)) = self.as_pathname().zip(other.as_pathname()) {
250            return l == r;
251        }
252
253        #[cfg(any(target_os = "android", target_os = "linux"))]
254        {
255            use std::os::linux::net::SocketAddrExt;
256
257            if let Some((l, r)) = self.as_abstract_name().zip(other.as_abstract_name()) {
258                return l == r;
259            }
260        }
261
262        if self.is_unnamed() && other.is_unnamed() {
263            return true;
264        }
265
266        false
267    }
268}
269
270impl Eq for SocketAddr {}
271
272impl Hash for SocketAddr {
273    fn hash<H: Hasher>(&self, state: &mut H) {
274        if let Some(pathname) = self.as_pathname() {
275            pathname.hash(state);
276
277            return;
278        }
279
280        #[cfg(any(target_os = "android", target_os = "linux"))]
281        {
282            use std::os::linux::net::SocketAddrExt;
283
284            if let Some(abstract_name) = self.as_abstract_name() {
285                b'\0'.hash(state);
286                abstract_name.hash(state);
287
288                return;
289            }
290        }
291
292        debug_assert!(self.is_unnamed(), "SocketAddr is not unnamed one");
293
294        // `Path` cannot contain null bytes, and abstract names are started with
295        // null bytes, this is Ok.
296        b"(unnamed)\0".hash(state);
297    }
298}
299
300#[cfg(feature = "feat-serde")]
301impl serde::Serialize for SocketAddr {
302    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
303    where
304        S: serde::Serializer,
305    {
306        serializer.serialize_str(&self.to_string_lossy())
307    }
308}
309
310#[cfg(feature = "feat-serde")]
311impl<'de> serde::Deserialize<'de> for SocketAddr {
312    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
313    where
314        D: serde::Deserializer<'de>,
315    {
316        Self::new(<&str>::deserialize(deserializer)?).map_err(serde::de::Error::custom)
317    }
318}
319
320#[cfg(test)]
321mod tests {
322    use core::hash::{Hash, Hasher};
323    use std::hash::DefaultHasher;
324
325    use super::*;
326
327    #[test]
328    fn test_unnamed() {
329        const TEST_CASE: &str = "";
330
331        let addr = SocketAddr::new(TEST_CASE).unwrap();
332
333        assert!(addr.as_ref().is_unnamed());
334    }
335
336    #[test]
337    fn test_pathname() {
338        const TEST_CASE: &str = "/tmp/test_pathname.socket";
339
340        let addr = SocketAddr::new(TEST_CASE).unwrap();
341
342        assert_eq!(addr.to_os_string().to_str().unwrap(), TEST_CASE);
343        assert_eq!(addr.to_string_lossy(), TEST_CASE);
344        assert_eq!(addr.as_pathname().unwrap().to_str().unwrap(), TEST_CASE);
345    }
346
347    #[test]
348    #[cfg(any(target_os = "android", target_os = "linux"))]
349    fn test_abstract() {
350        use std::os::linux::net::SocketAddrExt;
351
352        const TEST_CASE_1: &[u8] = b"@abstract.socket";
353        const TEST_CASE_2: &[u8] = b"\0abstract.socket";
354        const TEST_CASE_3: &[u8] = b"@";
355        const TEST_CASE_4: &[u8] = b"\0";
356
357        assert_eq!(
358            SocketAddr::new(OsStr::from_bytes(TEST_CASE_1))
359                .unwrap()
360                .as_abstract_name()
361                .unwrap(),
362            &TEST_CASE_1[1..]
363        );
364
365        assert_eq!(
366            SocketAddr::new(OsStr::from_bytes(TEST_CASE_2))
367                .unwrap()
368                .as_abstract_name()
369                .unwrap(),
370            &TEST_CASE_2[1..]
371        );
372
373        assert_eq!(
374            SocketAddr::new(OsStr::from_bytes(TEST_CASE_3))
375                .unwrap()
376                .as_abstract_name()
377                .unwrap(),
378            &TEST_CASE_3[1..]
379        );
380
381        assert_eq!(
382            SocketAddr::new(OsStr::from_bytes(TEST_CASE_4))
383                .unwrap()
384                .as_abstract_name()
385                .unwrap(),
386            &TEST_CASE_4[1..]
387        );
388    }
389
390    #[test]
391    #[should_panic]
392    fn test_pathname_with_null_byte() {
393        let _addr = SocketAddr::new_pathname("(unamed)\0").unwrap();
394    }
395
396    #[test]
397    fn test_partial_eq_hash() {
398        let addr_pathname_1 = SocketAddr::new("/tmp/test_pathname_1.socket").unwrap();
399        let addr_pathname_2 = SocketAddr::new("/tmp/test_pathname_2.socket").unwrap();
400        let addr_unnamed = SocketAddr::new_unnamed();
401
402        assert_eq!(addr_pathname_1, addr_pathname_1);
403        assert_ne!(addr_pathname_1, addr_pathname_2);
404        assert_ne!(addr_pathname_2, addr_pathname_1);
405
406        assert_eq!(addr_unnamed, addr_unnamed);
407        assert_ne!(addr_pathname_1, addr_unnamed);
408        assert_ne!(addr_unnamed, addr_pathname_1);
409        assert_ne!(addr_pathname_2, addr_unnamed);
410        assert_ne!(addr_unnamed, addr_pathname_2);
411
412        #[cfg(any(target_os = "android", target_os = "linux"))]
413        {
414            let addr_abstract_1 = SocketAddr::new_abstract(b"/tmp/test_pathname_1.socket").unwrap();
415            let addr_abstract_2 = SocketAddr::new_abstract(b"/tmp/test_pathname_2.socket").unwrap();
416            let addr_abstract_empty = SocketAddr::new_abstract(&[]).unwrap();
417            let addr_abstract_unnamed_hash = SocketAddr::new_abstract(b"(unamed)\0").unwrap();
418
419            assert_eq!(addr_abstract_1, addr_abstract_1);
420            assert_ne!(addr_abstract_1, addr_abstract_2);
421            assert_ne!(addr_abstract_2, addr_abstract_1);
422
423            // Empty abstract addresses should be equal to unnamed addresses
424            assert_ne!(addr_unnamed, addr_abstract_empty);
425
426            // Abstract addresses should not be equal to pathname addresses
427            assert_ne!(addr_pathname_1, addr_abstract_1);
428
429            // Abstract unnamed address `@(unamed)\0`' hash should not be equal to unname
430            // ones'
431            let addr_unnamed_hash = {
432                let mut state = DefaultHasher::new();
433                addr_unnamed.hash(&mut state);
434                state.finish()
435            };
436            let addr_abstract_unnamed_hash = {
437                let mut state = DefaultHasher::new();
438                addr_abstract_unnamed_hash.hash(&mut state);
439                state.finish()
440            };
441            assert_ne!(addr_unnamed_hash, addr_abstract_unnamed_hash);
442        }
443    }
444}