Skip to main content

jam_std_common/net/
mod.rs

1mod peer_id;
2
3use codec::{ConstEncodedLen, Decode, Encode, MaxEncodedLen};
4pub use peer_id::{P256PeerId, ParseErr as PeerIdParseErr, PeerId};
5use std::{net::SocketAddr, str::FromStr};
6
7/// Address of a peer, including UDP port number. Convertible to/from [`SocketAddr`]. The reason we
8/// don't just use [`SocketAddr`] is that [`SocketAddr`] values cannot be directly SCALE
9/// encoded/decoded; `PeerAddr` values can be.
10#[derive(Clone, Copy, Encode, Decode, MaxEncodedLen, PartialEq, Eq)]
11pub struct PeerAddr {
12	/// IPv6 address. May be an [IPv4-mapped
13	/// address](https://www.rfc-editor.org/rfc/rfc4291#section-2.5.5.2).
14	pub ip: [u8; 16],
15	/// UDP port.
16	pub port: u16,
17}
18
19impl ConstEncodedLen for PeerAddr {}
20
21impl core::fmt::Debug for PeerAddr {
22	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
23		SocketAddr::from(*self).fmt(f)
24	}
25}
26
27impl core::fmt::Display for PeerAddr {
28	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
29		SocketAddr::from(*self).fmt(f)
30	}
31}
32
33impl TryFrom<&str> for PeerAddr {
34	type Error = std::net::AddrParseError;
35
36	fn try_from(s: &str) -> Result<Self, Self::Error> {
37		Ok(SocketAddr::from_str(s)?.into())
38	}
39}
40
41impl FromStr for PeerAddr {
42	type Err = std::net::AddrParseError;
43
44	fn from_str(s: &str) -> Result<Self, Self::Err> {
45		s.try_into()
46	}
47}
48
49impl From<SocketAddr> for PeerAddr {
50	fn from(addr: SocketAddr) -> Self {
51		let ip = match addr.ip() {
52			std::net::IpAddr::V4(ip) => ip.to_ipv6_mapped(),
53			std::net::IpAddr::V6(ip) => ip,
54		};
55		Self { ip: ip.octets(), port: addr.port() }
56	}
57}
58
59impl From<PeerAddr> for SocketAddr {
60	fn from(addr: PeerAddr) -> Self {
61		Self::new(std::net::Ipv6Addr::from(addr.ip).to_canonical(), addr.port)
62	}
63}
64
65/// ID and address of a peer.
66#[derive(Clone, Encode, Decode, MaxEncodedLen, PartialEq, Eq)]
67pub struct PeerDetails {
68	pub id: PeerId,
69	pub addr: PeerAddr,
70}
71
72impl core::fmt::Debug for PeerDetails {
73	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
74		write!(f, "{:?}@{:?}", self.id, self.addr)
75	}
76}
77
78impl core::fmt::Display for PeerDetails {
79	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
80		write!(f, "{}@{}", self.id, self.addr)
81	}
82}
83
84/// Error parsing peer details (ID and address).
85#[derive(Debug, thiserror::Error)]
86pub enum PeerDetailsParseErr {
87	#[error("No @ character; expected between peer ID and address")]
88	NoSeparator,
89	#[error("Bad peer ID: {0}")]
90	Id(#[from] PeerIdParseErr),
91	#[error("Bad address: {0}")]
92	Addr(#[from] std::net::AddrParseError),
93}
94
95impl TryFrom<&str> for PeerDetails {
96	type Error = PeerDetailsParseErr;
97
98	fn try_from(s: &str) -> Result<Self, Self::Error> {
99		let Some((id, addr)) = s.split_once('@') else {
100			return Err(PeerDetailsParseErr::NoSeparator)
101		};
102		Ok(Self { id: id.try_into()?, addr: addr.try_into()? })
103	}
104}
105
106impl FromStr for PeerDetails {
107	type Err = PeerDetailsParseErr;
108
109	fn from_str(s: &str) -> Result<Self, Self::Err> {
110		s.try_into()
111	}
112}
113
114impl serde::Serialize for PeerDetails {
115	fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
116		serializer.collect_str(self)
117	}
118}
119
120impl<'de> serde::Deserialize<'de> for PeerDetails {
121	fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
122		let text = <&str>::deserialize(deserializer)?;
123		text.parse().map_err(serde::de::Error::custom)
124	}
125}
126
127#[cfg(any(test, feature = "rand"))]
128impl rand::distr::Distribution<PeerAddr> for rand::distr::StandardUniform {
129	fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> PeerAddr {
130		let ip = if rng.random() {
131			// IPv4-mapped address
132			(rng.random::<u32>() as u128) | (0xffff << 32)
133		} else {
134			// Global unicast address
135			(rng.random::<u128>() & !(0x7 << 125)) | (1 << 125)
136		};
137		PeerAddr { ip: ip.to_be_bytes(), port: rng.random() }
138	}
139}
140
141#[cfg(any(test, feature = "rand"))]
142impl rand::distr::Distribution<PeerDetails> for rand::distr::StandardUniform {
143	fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> PeerDetails {
144		PeerDetails { id: rng.random(), addr: rng.random() }
145	}
146}
147
148/// A (host, port) pair, with the host part being a plain `String` (ie unresolved).
149#[derive(Clone, Debug)]
150pub struct UnresolvedAddr {
151	pub host: String,
152	pub port: u16,
153}
154
155impl UnresolvedAddr {
156	/// Returns the pair as `(&str, u16)`. Note that `ToSocketAddrs` is implemented for this type.
157	pub fn as_ref(&self) -> (&str, u16) {
158		(&self.host, self.port)
159	}
160}
161
162impl std::fmt::Display for UnresolvedAddr {
163	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164		write!(f, "{}:{}", self.host, self.port)
165	}
166}
167
168impl std::str::FromStr for UnresolvedAddr {
169	type Err = &'static str;
170
171	fn from_str(s: &str) -> Result<Self, Self::Err> {
172		let (host, port) = s.rsplit_once(':').ok_or("Expected ':' between host and port")?;
173		let port = port.parse().map_err(|_| "Bad port")?;
174		Ok(Self { host: host.to_string(), port })
175	}
176}
177
178#[cfg(test)]
179mod test {
180	#![allow(clippy::unwrap_used)]
181	use super::*;
182	use crate::SecretKeyset;
183
184	#[test]
185	fn peer_details_round_trip_as_a_json_string() {
186		let details = PeerDetails {
187			id: PeerId(SecretKeyset::trivial(0).ed25519.public()),
188			addr: "127.0.0.1:40000".parse::<SocketAddr>().unwrap().into(),
189		};
190
191		let json = serde_json::to_string(&details).unwrap();
192		assert_eq!(json, format!("\"{details}\""));
193		assert_eq!(serde_json::from_str::<PeerDetails>(&json).unwrap(), details);
194	}
195}