1use core::{
4 fmt::{self, Display},
5 str::{self, FromStr},
6};
7
8use cometbft::node::{self, info::ListenAddress};
9use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};
10use url::Url;
11
12use crate::{error::Error, prelude::*};
13
14pub const TCP_PREFIX: &str = "tcp://";
16
17pub const UNIX_PREFIX: &str = "unix://";
19
20#[derive(Clone, Debug, Eq, Hash, PartialEq)]
28pub enum Address {
29 Tcp {
31 peer_id: Option<node::Id>,
33
34 host: String,
36
37 port: u16,
39 },
40
41 Unix {
43 path: String,
45 },
46}
47
48impl Address {
49 pub fn from_listen_address(address: &ListenAddress) -> Option<Self> {
51 let raw_address = address.as_str();
52 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 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 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::cometbft)?;
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 cometbft::node;
139
140 use super::*;
141
142 const EXAMPLE_TCP_ADDR: &str =
143 "tcp://abd636b766dcefb5322d8ca40011ec2cb35efbc2@35.192.61.41:26656";
144 const EXAMPLE_TCP_ADDR_WITHOUT_ID: &str = "tcp://35.192.61.41:26656";
145 const EXAMPLE_UNIX_ADDR: &str = "unix:///tmp/node.sock";
146 const EXAMPLE_TCP_IPV6_ADDR: &str =
147 "tcp://abd636b766dcefb5322d8ca40011ec2cb35efbc2@[2001:0000:3238:DFE1:0063:0000:0000:FEFB]:26656";
148
149 #[test]
150 fn parse_tcp_addr() {
151 let tcp_addr_without_prefix = &EXAMPLE_TCP_ADDR[TCP_PREFIX.len()..];
152
153 for tcp_addr in &[EXAMPLE_TCP_ADDR, tcp_addr_without_prefix] {
154 match tcp_addr.parse::<Address>().unwrap() {
155 Address::Tcp {
156 peer_id,
157 host,
158 port,
159 } => {
160 assert_eq!(
161 peer_id.unwrap(),
162 "abd636b766dcefb5322d8ca40011ec2cb35efbc2"
163 .parse::<node::Id>()
164 .unwrap()
165 );
166 assert_eq!(host, "35.192.61.41");
167 assert_eq!(port, 26656);
168 },
169 other => panic!("unexpected address type: {other:?}"),
170 }
171 }
172 }
173
174 #[test]
175 fn parse_tcp_addr_without_id() {
176 let addr = EXAMPLE_TCP_ADDR_WITHOUT_ID.parse::<Address>().unwrap();
177 let addr_without_prefix = EXAMPLE_TCP_ADDR_WITHOUT_ID[TCP_PREFIX.len()..]
178 .parse::<Address>()
179 .unwrap();
180 for addr in &[addr, addr_without_prefix] {
181 match addr {
182 Address::Tcp {
183 peer_id,
184 host,
185 port,
186 } => {
187 assert!(peer_id.is_none());
188 assert_eq!(host, "35.192.61.41");
189 assert_eq!(*port, 26656);
190 },
191 other => panic!("unexpected address type: {other:?}"),
192 }
193 }
194 }
195
196 #[test]
197 fn parse_unix_addr() {
198 let addr = EXAMPLE_UNIX_ADDR.parse::<Address>().unwrap();
199 match addr {
200 Address::Unix { path } => {
201 assert_eq!(path, "/tmp/node.sock");
202 },
203 other => panic!("unexpected address type: {other:?}"),
204 }
205 }
206
207 #[test]
208 fn parse_tcp_ipv6_addr() {
209 let addr = EXAMPLE_TCP_IPV6_ADDR.parse::<Address>().unwrap();
210 let addr_without_prefix = EXAMPLE_TCP_IPV6_ADDR[TCP_PREFIX.len()..]
211 .parse::<Address>()
212 .unwrap();
213 for addr in &[addr, addr_without_prefix] {
214 match addr {
215 Address::Tcp {
216 peer_id,
217 host,
218 port,
219 } => {
220 assert_eq!(
221 peer_id.unwrap(),
222 "abd636b766dcefb5322d8ca40011ec2cb35efbc2"
223 .parse::<node::Id>()
224 .unwrap()
225 );
226 assert_eq!(host, "[2001:0:3238:dfe1:63::fefb]");
228 assert_eq!(*port, 26656);
229 },
230 other => panic!("unexpected address type: {other:?}"),
231 }
232 }
233 }
234}