tendermint_config/
net.rs

1//! Remote addresses (`tcp://` or `unix://`)
2
3use core::{
4    fmt::{self, Display},
5    str::{self, FromStr},
6};
7
8use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};
9use tendermint::node::{self, info::ListenAddress};
10use url::Url;
11
12use crate::{error::Error, prelude::*};
13
14/// URI prefix for TCP connections
15pub const TCP_PREFIX: &str = "tcp://";
16
17/// URI prefix for Unix socket connections
18pub const UNIX_PREFIX: &str = "unix://";
19
20/// Remote address (TCP or UNIX socket)
21///
22/// For TCP-based addresses, this supports both IPv4 and IPv6 addresses and
23/// hostnames.
24///
25/// If the scheme is not supplied (i.e. `tcp://` or `unix://`) when parsing
26/// from a string, it is assumed to be a TCP address.
27#[derive(Clone, Debug, Eq, Hash, PartialEq)]
28pub enum Address {
29    /// TCP connections
30    Tcp {
31        /// Remote peer ID
32        peer_id: Option<node::Id>,
33
34        /// Hostname or IP address
35        host: String,
36
37        /// Port
38        port: u16,
39    },
40
41    /// UNIX domain sockets
42    Unix {
43        /// Path to a UNIX domain socket path
44        path: String,
45    },
46}
47
48impl Address {
49    /// Convert `ListenAddress` to a `net::Address`
50    pub fn from_listen_address(address: &ListenAddress) -> Option<Self> {
51        let raw_address = address.as_str();
52        // TODO(tarcieri): validate these and handle them better at parse time
53        if raw_address.starts_with("tcp://") {
54            raw_address.parse().ok()
55        } else {
56            format!("tcp://{raw_address}").parse().ok()
57        }
58    }
59}
60
61impl<'de> Deserialize<'de> for Address {
62    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
63        Self::from_str(&String::deserialize(deserializer)?)
64            .map_err(|e| D::Error::custom(format!("{e}")))
65    }
66}
67
68impl Display for Address {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        match self {
71            Address::Tcp {
72                peer_id: None,
73                host,
74                port,
75            } => write!(f, "{TCP_PREFIX}{host}:{port}"),
76            Address::Tcp {
77                peer_id: Some(peer_id),
78                host,
79                port,
80            } => write!(f, "{TCP_PREFIX}{peer_id}@{host}:{port}"),
81            Address::Unix { path } => write!(f, "{UNIX_PREFIX}{path}"),
82        }
83    }
84}
85
86impl FromStr for Address {
87    type Err = Error;
88
89    fn from_str(addr: &str) -> Result<Self, Error> {
90        // unix abstract socket address, `man 7 unix` for system descriptions
91        if addr.starts_with("unix://@") {
92            return Ok(Self::Unix {
93                path: addr.strip_prefix("unix://").unwrap().to_owned(),
94            });
95        }
96
97        let prefixed_addr = if addr.contains("://") {
98            addr.to_owned()
99        } else {
100            // If the address has no scheme, assume it's TCP
101            format!("{TCP_PREFIX}{addr}")
102        };
103        let url = Url::parse(&prefixed_addr).map_err(Error::parse_url)?;
104        match url.scheme() {
105            "tcp" => Ok(Self::Tcp {
106                peer_id: if !url.username().is_empty() {
107                    let username = url.username().parse().map_err(Error::tendermint)?;
108                    Some(username)
109                } else {
110                    None
111                },
112                host: url
113                    .host_str()
114                    .ok_or_else(|| {
115                        Error::parse(format!("invalid TCP address (missing host): {addr}"))
116                    })?
117                    .to_owned(),
118                port: url.port().ok_or_else(|| {
119                    Error::parse(format!("invalid TCP address (missing port): {addr}"))
120                })?,
121            }),
122            "unix" => Ok(Self::Unix {
123                path: url.path().to_string(),
124            }),
125            _ => Err(Error::parse(format!("invalid address scheme: {addr:?}"))),
126        }
127    }
128}
129
130impl Serialize for Address {
131    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
132        self.to_string().serialize(serializer)
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    const EXAMPLE_TCP_ADDR: &str =
141        "tcp://abd636b766dcefb5322d8ca40011ec2cb35efbc2@35.192.61.41:26656";
142    const EXAMPLE_TCP_ADDR_WITHOUT_ID: &str = "tcp://35.192.61.41:26656";
143    const EXAMPLE_UNIX_ADDR: &str = "unix:///tmp/node.sock";
144    const EXAMPLE_TCP_IPV6_ADDR: &str =
145        "tcp://abd636b766dcefb5322d8ca40011ec2cb35efbc2@[2001:0000:3238:DFE1:0063:0000:0000:FEFB]:26656";
146
147    #[test]
148    fn parse_tcp_addr() {
149        let tcp_addr_without_prefix = &EXAMPLE_TCP_ADDR[TCP_PREFIX.len()..];
150
151        for tcp_addr in &[EXAMPLE_TCP_ADDR, tcp_addr_without_prefix] {
152            match tcp_addr.parse::<Address>().unwrap() {
153                Address::Tcp {
154                    peer_id,
155                    host,
156                    port,
157                } => {
158                    assert_eq!(
159                        peer_id.unwrap(),
160                        "abd636b766dcefb5322d8ca40011ec2cb35efbc2"
161                            .parse::<node::Id>()
162                            .unwrap()
163                    );
164                    assert_eq!(host, "35.192.61.41");
165                    assert_eq!(port, 26656);
166                },
167                other => panic!("unexpected address type: {other:?}"),
168            }
169        }
170    }
171
172    #[test]
173    fn parse_tcp_addr_without_id() {
174        let addr = EXAMPLE_TCP_ADDR_WITHOUT_ID.parse::<Address>().unwrap();
175        let addr_without_prefix = EXAMPLE_TCP_ADDR_WITHOUT_ID[TCP_PREFIX.len()..]
176            .parse::<Address>()
177            .unwrap();
178        for addr in &[addr, addr_without_prefix] {
179            match addr {
180                Address::Tcp {
181                    peer_id,
182                    host,
183                    port,
184                } => {
185                    assert!(peer_id.is_none());
186                    assert_eq!(host, "35.192.61.41");
187                    assert_eq!(*port, 26656);
188                },
189                other => panic!("unexpected address type: {other:?}"),
190            }
191        }
192    }
193
194    #[test]
195    fn parse_unix_addr() {
196        let addr = EXAMPLE_UNIX_ADDR.parse::<Address>().unwrap();
197        match addr {
198            Address::Unix { path } => {
199                assert_eq!(path, "/tmp/node.sock");
200            },
201            other => panic!("unexpected address type: {other:?}"),
202        }
203    }
204
205    #[test]
206    fn parse_tcp_ipv6_addr() {
207        let addr = EXAMPLE_TCP_IPV6_ADDR.parse::<Address>().unwrap();
208        let addr_without_prefix = EXAMPLE_TCP_IPV6_ADDR[TCP_PREFIX.len()..]
209            .parse::<Address>()
210            .unwrap();
211        for addr in &[addr, addr_without_prefix] {
212            match addr {
213                Address::Tcp {
214                    peer_id,
215                    host,
216                    port,
217                } => {
218                    assert_eq!(
219                        peer_id.unwrap(),
220                        "abd636b766dcefb5322d8ca40011ec2cb35efbc2"
221                            .parse::<node::Id>()
222                            .unwrap()
223                    );
224                    // The parser URL strips the leading zeroes and converts to lowercase hex
225                    assert_eq!(host, "[2001:0:3238:dfe1:63::fefb]");
226                    assert_eq!(*port, 26656);
227                },
228                other => panic!("unexpected address type: {other:?}"),
229            }
230        }
231    }
232}