1#[cfg(any(feature = "native", test))]
9use crate::AddressType;
10use crate::MultiAddr;
11use serde::{Deserialize, Serialize};
12
13pub const MAX_TRANSPORT_ADDRESS_PAYLOAD: usize = 2 * 1024;
15
16pub const MAX_TRANSPORT_ADDRESS_RECORDS: usize = 16;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum KnownTransport {
26 Quic,
28 WebRtcDirect,
30}
31
32impl KnownTransport {
33 pub const QUIC_ID: u16 = 1;
35 pub const WEBRTC_DIRECT_ID: u16 = 2;
37
38 #[must_use]
40 pub const fn id(self) -> u16 {
41 match self {
42 Self::Quic => Self::QUIC_ID,
43 Self::WebRtcDirect => Self::WEBRTC_DIRECT_ID,
44 }
45 }
46
47 #[must_use]
49 pub const fn from_id(id: u16) -> Option<Self> {
50 match id {
51 Self::QUIC_ID => Some(Self::Quic),
52 Self::WEBRTC_DIRECT_ID => Some(Self::WebRtcDirect),
53 _ => None,
54 }
55 }
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum KnownReachability {
64 Relay,
65 Direct,
66 Unverified,
67 Lan,
68}
69
70impl KnownReachability {
71 pub const RELAY_ID: u16 = 1;
72 pub const DIRECT_ID: u16 = 2;
73 pub const UNVERIFIED_ID: u16 = 3;
74 pub const LAN_ID: u16 = 4;
75
76 #[must_use]
77 pub const fn id(self) -> u16 {
78 match self {
79 Self::Relay => Self::RELAY_ID,
80 Self::Direct => Self::DIRECT_ID,
81 Self::Unverified => Self::UNVERIFIED_ID,
82 Self::Lan => Self::LAN_ID,
83 }
84 }
85
86 #[must_use]
87 pub const fn from_id(id: u16) -> Option<Self> {
88 match id {
89 Self::RELAY_ID => Some(Self::Relay),
90 Self::DIRECT_ID => Some(Self::Direct),
91 Self::UNVERIFIED_ID => Some(Self::Unverified),
92 Self::LAN_ID => Some(Self::Lan),
93 _ => None,
94 }
95 }
96
97 #[must_use]
98 #[cfg(any(feature = "native", test))]
99 pub(crate) const fn from_legacy(value: AddressType) -> Self {
100 match value {
101 AddressType::Relay => Self::Relay,
102 AddressType::Direct => Self::Direct,
103 AddressType::Unverified => Self::Unverified,
104 AddressType::Lan => Self::Lan,
105 }
106 }
107
108 #[must_use]
109 #[cfg(any(feature = "native", test))]
110 pub(crate) const fn into_legacy(self) -> AddressType {
111 match self {
112 Self::Relay => AddressType::Relay,
113 Self::Direct => AddressType::Direct,
114 Self::Unverified => AddressType::Unverified,
115 Self::Lan => AddressType::Lan,
116 }
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
126pub struct TransportAddressRecord {
127 pub transport: u16,
128 pub reachability: u16,
129 #[serde(deserialize_with = "bounded_address")]
130 pub address: Vec<u8>,
131}
132
133impl TransportAddressRecord {
134 pub fn from_multiaddr(
140 address: &MultiAddr,
141 reachability: KnownReachability,
142 ) -> Result<Option<Self>, postcard::Error> {
143 let transport = if address.is_quic() {
144 KnownTransport::Quic
145 } else if address.is_webrtc_direct() {
146 KnownTransport::WebRtcDirect
147 } else {
148 return Ok(None);
149 };
150 let reachability = match transport {
151 KnownTransport::Quic => reachability,
152 KnownTransport::WebRtcDirect => KnownReachability::Unverified,
153 };
154
155 Ok(Some(Self {
156 transport: transport.id(),
157 reachability: reachability.id(),
158 address: postcard::to_stdvec(address)?,
159 }))
160 }
161
162 pub fn decode_known(&self) -> Result<Option<MultiAddr>, postcard::Error> {
166 let Some(transport) = KnownTransport::from_id(self.transport) else {
167 return Ok(None);
168 };
169 let address: MultiAddr = postcard::from_bytes(&self.address)?;
170 let matches_transport = match transport {
171 KnownTransport::Quic => address.is_quic(),
172 KnownTransport::WebRtcDirect => address.is_webrtc_direct(),
173 };
174 Ok(matches_transport.then_some(address))
175 }
176
177 #[must_use]
178 #[cfg(any(feature = "native", test))]
179 pub(crate) fn legacy_reachability(&self) -> Option<AddressType> {
180 KnownReachability::from_id(self.reachability).map(KnownReachability::into_legacy)
181 }
182
183 #[must_use]
184 #[cfg(any(feature = "native", test))]
185 pub(crate) fn is_within_wire_bounds(&self) -> bool {
186 !self.address.is_empty() && self.address.len() <= MAX_TRANSPORT_ADDRESS_PAYLOAD
187 }
188}
189
190fn bounded_address<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
191 crate::signed_address::bounded_vec::<_, _, MAX_TRANSPORT_ADDRESS_PAYLOAD>(d)
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use crate::{PeerId, WebRtcCertificateHash, WebRtcDirectAddr};
198
199 #[test]
200 fn known_names_have_stable_open_wire_ids() {
201 assert_eq!(KnownTransport::Quic.id(), 1);
202 assert_eq!(KnownTransport::WebRtcDirect.id(), 2);
203 assert_eq!(KnownReachability::Relay.id(), 1);
204 assert_eq!(KnownReachability::Direct.id(), 2);
205 assert_eq!(KnownReachability::Unverified.id(), 3);
206 assert_eq!(KnownReachability::Lan.id(), 4);
207 }
208
209 #[test]
210 fn unknown_identifiers_round_trip_without_enum_decode_failure() {
211 let record = TransportAddressRecord {
212 transport: 900,
213 reachability: 901,
214 address: vec![1, 2, 3, 4],
215 };
216 let bytes = postcard::to_stdvec(&record).unwrap();
217 let decoded: TransportAddressRecord = postcard::from_bytes(&bytes).unwrap();
218
219 assert_eq!(decoded, record);
220 assert_eq!(decoded.decode_known().unwrap(), None);
221 assert_eq!(decoded.legacy_reachability(), None);
222 }
223
224 #[test]
225 fn quic_record_round_trips() {
226 let address: MultiAddr = "/ip4/203.0.113.9/udp/12000/quic".parse().unwrap();
227 let record = TransportAddressRecord::from_multiaddr(&address, KnownReachability::Direct)
228 .unwrap()
229 .unwrap();
230
231 assert_eq!(record.transport, KnownTransport::Quic.id());
232 assert_eq!(record.legacy_reachability(), Some(AddressType::Direct));
233 assert_eq!(record.decode_known().unwrap(), Some(address));
234 }
235
236 #[test]
237 fn webrtc_direct_record_is_always_unverified() {
238 let peer_id = PeerId::from_bytes([0x22; 32]);
239 let address = MultiAddr::webrtc_direct(
240 WebRtcDirectAddr::new(
241 "203.0.113.9:42768".parse().unwrap(),
242 WebRtcCertificateHash::new([0x33; 32]),
243 )
244 .unwrap(),
245 )
246 .with_peer_id(peer_id);
247 let record = TransportAddressRecord::from_multiaddr(&address, KnownReachability::Direct)
248 .unwrap()
249 .unwrap();
250
251 assert_eq!(record.transport, KnownTransport::WebRtcDirect.id());
252 assert_eq!(record.legacy_reachability(), Some(AddressType::Unverified));
253 assert_eq!(record.decode_known().unwrap(), Some(address));
254 }
255
256 #[test]
257 fn declared_transport_must_match_the_encoded_multiaddress() {
258 let address: MultiAddr = "/ip4/203.0.113.9/udp/12000/quic".parse().unwrap();
259 let mut record =
260 TransportAddressRecord::from_multiaddr(&address, KnownReachability::Direct)
261 .unwrap()
262 .unwrap();
263 record.transport = KnownTransport::WebRtcDirect.id();
264
265 assert_eq!(record.decode_known().unwrap(), None);
266 }
267}