iroh_base/endpoint_addr.rs
1//! Addressing for iroh endpoints.
2//!
3//! This module contains some common addressing types for iroh. An endpoint is uniquely
4//! identified by the [`EndpointId`] but that does not make it addressable on the network layer.
5//! For this the addition of a [`RelayUrl`] and/or direct addresses are required.
6//!
7//! The primary way of addressing an endpoint is by using the [`EndpointAddr`].
8
9use std::{collections::BTreeSet, net::SocketAddr};
10
11use serde::{Deserialize, Serialize};
12
13use crate::{EndpointId, PublicKey, RelayUrl};
14
15/// Network-level addressing information for an iroh endpoint.
16///
17/// This combines an endpoint's identifier with network-level addressing information of how to
18/// contact the endpoint.
19///
20/// To establish a network connection to an endpoint both the [`EndpointId`] and one or more network
21/// paths are needed. The network paths can come from various sources, current sources can come from
22///
23/// - A [discovery] service which can provide routing information for a given [`EndpointId`].
24///
25/// - A [`RelayUrl`] of the endpoint's [home relay], this allows establishing the connection via
26/// the Relay server and is very reliable.
27///
28/// - One or more *IP based addresses* on which the endpoint might be reachable. Depending on the
29/// network location of both endpoints it might not be possible to establish a direct
30/// connection without the help of a [Relay server].
31///
32/// This structure will always contain the required [`EndpointId`] and will contain an optional
33/// number of other addressing information. It is a generic addressing type used whenever a connection
34/// to other endpoints needs to be established.
35///
36/// [discovery]: https://docs.rs/iroh/*/iroh/index.html#endpoint-discovery
37/// [home relay]: https://docs.rs/iroh/*/iroh/relay/index.html
38/// [Relay server]: https://docs.rs/iroh/*/iroh/index.html#relay-servers
39#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
40pub struct EndpointAddr {
41 /// The endpoint's identifier.
42 pub id: EndpointId,
43 /// The endpoint's addresses
44 pub addrs: BTreeSet<TransportAddr>,
45}
46
47/// Available address types.
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
49#[non_exhaustive]
50pub enum TransportAddr {
51 /// Relays
52 Relay(RelayUrl),
53 /// IP based addresses
54 Ip(SocketAddr),
55}
56
57impl EndpointAddr {
58 /// Creates a new [`EndpointAddr`] with no network level addresses.
59 ///
60 /// This still is usable with e.g. a discovery service to establish a connection,
61 /// depending on the situation.
62 pub fn new(id: PublicKey) -> Self {
63 EndpointAddr {
64 id,
65 addrs: Default::default(),
66 }
67 }
68
69 /// Creates a new [`EndpointAddr`] from its parts.
70 pub fn from_parts(id: PublicKey, addrs: impl IntoIterator<Item = TransportAddr>) -> Self {
71 Self {
72 id,
73 addrs: addrs.into_iter().collect(),
74 }
75 }
76
77 /// Adds a [`RelayUrl`] address.
78 pub fn with_relay_url(mut self, relay_url: RelayUrl) -> Self {
79 self.addrs.insert(TransportAddr::Relay(relay_url));
80 self
81 }
82
83 /// Adds an IP based address.
84 pub fn with_ip_addr(mut self, addr: SocketAddr) -> Self {
85 self.addrs.insert(TransportAddr::Ip(addr));
86 self
87 }
88
89 /// Adds a list of addresses.
90 pub fn with_addrs(mut self, addrs: impl IntoIterator<Item = TransportAddr>) -> Self {
91 for addr in addrs.into_iter() {
92 self.addrs.insert(addr);
93 }
94 self
95 }
96
97 /// Returns true, if only a [`EndpointId`] is present.
98 pub fn is_empty(&self) -> bool {
99 self.addrs.is_empty()
100 }
101
102 /// Returns a list of IP addresses of this peer.
103 pub fn ip_addrs(&self) -> impl Iterator<Item = &SocketAddr> {
104 self.addrs.iter().filter_map(|addr| match addr {
105 TransportAddr::Ip(addr) => Some(addr),
106 _ => None,
107 })
108 }
109
110 /// Returns a list of relay urls of this peer.
111 ///
112 /// In practice this is expected to be zero or one home relay for all known cases currently.
113 pub fn relay_urls(&self) -> impl Iterator<Item = &RelayUrl> {
114 self.addrs.iter().filter_map(|addr| match addr {
115 TransportAddr::Relay(url) => Some(url),
116 _ => None,
117 })
118 }
119}
120
121impl From<EndpointId> for EndpointAddr {
122 fn from(endpoint_id: EndpointId) -> Self {
123 EndpointAddr::new(endpoint_id)
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
132 #[non_exhaustive]
133 enum NewAddrType {
134 /// Relays
135 Relay(RelayUrl),
136 /// IP based addresses
137 Ip(SocketAddr),
138 /// New addr type for testing
139 Cool(u16),
140 }
141
142 #[test]
143 fn test_roundtrip_new_addr_type() {
144 let old = vec![
145 TransportAddr::Ip("127.0.0.1:9".parse().unwrap()),
146 TransportAddr::Relay("https://example.com".parse().unwrap()),
147 ];
148 let old_ser = postcard::to_stdvec(&old).unwrap();
149 let old_back: Vec<TransportAddr> = postcard::from_bytes(&old_ser).unwrap();
150 assert_eq!(old, old_back);
151
152 let new = vec![
153 NewAddrType::Ip("127.0.0.1:9".parse().unwrap()),
154 NewAddrType::Relay("https://example.com".parse().unwrap()),
155 NewAddrType::Cool(4),
156 ];
157 let new_ser = postcard::to_stdvec(&new).unwrap();
158 let new_back: Vec<NewAddrType> = postcard::from_bytes(&new_ser).unwrap();
159
160 assert_eq!(new, new_back);
161
162 // serialize old into new
163 let old_new_back: Vec<NewAddrType> = postcard::from_bytes(&old_ser).unwrap();
164
165 assert_eq!(
166 old_new_back,
167 vec![
168 NewAddrType::Ip("127.0.0.1:9".parse().unwrap()),
169 NewAddrType::Relay("https://example.com".parse().unwrap()),
170 ]
171 );
172 }
173}