kumo_address/
socket.rs

1use crate::host::{AddressParseError, HostAddress};
2use serde::{Deserialize, Serialize};
3use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
4use std::os::unix::net::SocketAddr as UnixSocketAddr;
5use std::path::Path;
6use std::str::FromStr;
7
8#[derive(Clone, Serialize, Deserialize)]
9#[serde(try_from = "String", into = "String")]
10pub enum SocketAddress {
11    UnixDomain(Box<UnixSocketAddr>),
12    V4(std::net::SocketAddrV4),
13    V6(std::net::SocketAddrV6),
14}
15
16impl std::fmt::Debug for SocketAddress {
17    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
18        <Self as std::fmt::Display>::fmt(self, fmt)
19    }
20}
21
22impl std::fmt::Display for SocketAddress {
23    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
24        match self {
25            Self::UnixDomain(unix) => match unix.as_pathname() {
26                Some(path) => path.display().fmt(fmt),
27                None => write!(fmt, "<unbound unix domain>"),
28            },
29            Self::V4(a) => a.fmt(fmt),
30            Self::V6(a) => a.fmt(fmt),
31        }
32    }
33}
34
35impl From<SocketAddress> for String {
36    fn from(a: SocketAddress) -> String {
37        format!("{a}")
38    }
39}
40
41impl TryFrom<String> for SocketAddress {
42    type Error = AddressParseError;
43    fn try_from(s: String) -> Result<SocketAddress, Self::Error> {
44        SocketAddress::from_str(&s)
45    }
46}
47
48impl SocketAddress {
49    /// Returns the "host" portion of the address
50    pub fn host(&self) -> HostAddress {
51        match self {
52            Self::UnixDomain(p) => HostAddress::UnixDomain(p.clone()),
53            Self::V4(a) => HostAddress::V4(*a.ip()),
54            Self::V6(a) => HostAddress::V6(*a.ip()),
55        }
56    }
57
58    /// Returns the unix domain socket representation of the address
59    pub fn unix(&self) -> Option<UnixSocketAddr> {
60        match self {
61            Self::V4(_) | Self::V6(_) => None,
62            Self::UnixDomain(unix) => Some((**unix).clone()),
63        }
64    }
65
66    /// Returns the ip representation of the address
67    pub fn ip(&self) -> Option<SocketAddr> {
68        match self {
69            Self::V4(a) => Some((*a).into()),
70            Self::V6(a) => Some((*a).into()),
71            Self::UnixDomain(_) => None,
72        }
73    }
74}
75
76impl FromStr for SocketAddress {
77    type Err = AddressParseError;
78    fn from_str(s: &str) -> Result<SocketAddress, Self::Err> {
79        // At the time of writing, Rust's IPv6 SockAddr parsing
80        // interally only accepts `[address]:port` while its IPv4
81        // SockAddr parsing only accepts `address:port`.
82        // In the email world, `[]` is used to indicate a literal
83        // IP address so we desire the ability to uniformly use
84        // the `[]` syntax in both cases, so we check for that
85        // first and parse the internal address out.
86
87        if s.starts_with('[') {
88            if let Some(host_end) = s.find(']') {
89                let (host, remainder) = s.split_at(host_end);
90                let host = &host[1..];
91
92                if let Some(port) = remainder.strip_prefix("]:") {
93                    if let Ok(port) = port.parse::<u16>() {
94                        match HostAddress::from_str(host) {
95                            Ok(HostAddress::V4(a)) => {
96                                return Ok(SocketAddress::V4(SocketAddrV4::new(a, port)))
97                            }
98                            Ok(HostAddress::V6(a)) => {
99                                return Ok(SocketAddress::V6(SocketAddrV6::new(a, port, 0, 0)))
100                            }
101
102                            _ => {}
103                        }
104                    }
105                }
106            }
107        }
108
109        match SocketAddr::from_str(s) {
110            Ok(a) => Ok(a.into()),
111            Err(net_err) => {
112                let path: &Path = s.as_ref();
113                if path.is_relative() {
114                    Err(AddressParseError {
115                        candidate: s.to_string(),
116                        net_err,
117                        unix_err: std::io::Error::other("unix domain path must be absolute"),
118                    })
119                } else {
120                    match UnixSocketAddr::from_pathname(path) {
121                        Ok(unix) => Ok(SocketAddress::UnixDomain(unix.into())),
122                        Err(unix_err) => Err(AddressParseError {
123                            candidate: s.to_string(),
124                            net_err,
125                            unix_err,
126                        }),
127                    }
128                }
129            }
130        }
131    }
132}
133
134impl PartialEq for SocketAddress {
135    fn eq(&self, other: &Self) -> bool {
136        match (self, other) {
137            (Self::UnixDomain(a), Self::UnixDomain(b)) => {
138                match (a.as_pathname(), b.as_pathname()) {
139                    (Some(a), Some(b)) => a.eq(b),
140                    (None, None) => true,
141                    _ => false,
142                }
143            }
144            (Self::V4(a), Self::V4(b)) => a.eq(b),
145            (Self::V6(a), Self::V6(b)) => a.eq(b),
146            _ => false,
147        }
148    }
149}
150
151impl Eq for SocketAddress {}
152
153impl From<UnixSocketAddr> for SocketAddress {
154    fn from(unix: UnixSocketAddr) -> SocketAddress {
155        SocketAddress::UnixDomain(unix.into())
156    }
157}
158
159impl From<SocketAddr> for SocketAddress {
160    fn from(ip: SocketAddr) -> SocketAddress {
161        match ip {
162            SocketAddr::V4(a) => SocketAddress::V4(a),
163            SocketAddr::V6(a) => SocketAddress::V6(a),
164        }
165    }
166}
167
168impl From<tokio::net::unix::SocketAddr> for SocketAddress {
169    fn from(unix: tokio::net::unix::SocketAddr) -> SocketAddress {
170        let unix: UnixSocketAddr = unix.into();
171        unix.into()
172    }
173}
174
175#[cfg(test)]
176mod test {
177    use super::*;
178    use std::net::{Ipv4Addr, Ipv6Addr};
179
180    #[test]
181    fn parse() {
182        assert_eq!(
183            "10.0.0.1:25".parse::<SocketAddress>(),
184            Ok(SocketAddress::V4(SocketAddrV4::new(
185                Ipv4Addr::new(10, 0, 0, 1),
186                25
187            )))
188        );
189        assert_eq!(
190            "[10.0.0.1]:25".parse::<SocketAddress>(),
191            Ok(SocketAddress::V4(SocketAddrV4::new(
192                Ipv4Addr::new(10, 0, 0, 1),
193                25
194            )))
195        );
196        assert_eq!(
197            "[::1]:100".parse::<SocketAddress>(),
198            Ok(SocketAddress::V6(SocketAddrV6::new(
199                Ipv6Addr::LOCALHOST,
200                100,
201                0,
202                0
203            )))
204        );
205        assert_eq!(
206            "/some/path".parse::<SocketAddress>(),
207            Ok(SocketAddress::UnixDomain(
208                UnixSocketAddr::from_pathname("/some/path").unwrap().into()
209            ))
210        );
211        assert_eq!(
212            format!("{:#}", "hello there".parse::<SocketAddress>().unwrap_err()),
213            "Failed to parse 'hello there' as an address. \
214            Got 'invalid socket address syntax' when considering it as \
215            an IP address and 'unix domain path must be absolute' \
216            when considering it as a unix domain socket path."
217        );
218        assert_eq!(
219            format!("{:#}", "[10.0.0.1]".parse::<SocketAddress>().unwrap_err()),
220            "Failed to parse '[10.0.0.1]' as an address. \
221            Got 'invalid socket address syntax' when considering it as \
222            an IP address and 'unix domain path must be absolute' \
223            when considering it as a unix domain socket path."
224        );
225    }
226}