1mod p2p_connection_outgoing_state;
2pub use p2p_connection_outgoing_state::*;
3
4mod p2p_connection_outgoing_actions;
5pub use p2p_connection_outgoing_actions::*;
6
7mod p2p_connection_outgoing_reducer;
8
9#[cfg(feature = "p2p-libp2p")]
10use std::net::SocketAddr;
11use std::{fmt, net::IpAddr, str::FromStr};
12
13use binprot_derive::{BinProtRead, BinProtWrite};
14use multiaddr::{Multiaddr, Protocol};
15use serde::{Deserialize, Serialize};
16use thiserror::Error;
17
18#[cfg(feature = "p2p-libp2p")]
19use mina_p2p_messages::v2;
20
21use crate::{
22 webrtc::{self, Host, HttpSignalingInfo},
23 PeerId,
24};
25
26#[cfg(feature = "p2p-libp2p")]
27use crate::webrtc::SignalingMethod;
28
29#[derive(
31 BinProtWrite, BinProtRead, derive_more::From, Debug, Ord, PartialOrd, Eq, PartialEq, Clone,
32)]
33pub enum P2pConnectionOutgoingInitOpts {
34 WebRTC {
35 peer_id: PeerId,
36 signaling: webrtc::SignalingMethod,
37 },
38 LibP2P(P2pConnectionOutgoingInitLibp2pOpts),
39}
40
41impl P2pConnectionOutgoingInitOpts {
42 pub fn with_host_resolved(self) -> Option<Self> {
43 if let Self::LibP2P(libp2p_opts) = self {
44 Some(Self::LibP2P(libp2p_opts.with_host_resolved()?))
45 } else {
46 Some(self)
47 }
48 }
49}
50
51#[derive(BinProtWrite, BinProtRead, Eq, PartialEq, Ord, PartialOrd, Debug, Clone)]
52pub struct P2pConnectionOutgoingInitLibp2pOpts {
53 pub peer_id: PeerId,
54 pub host: Host,
55 pub port: u16,
56}
57
58impl P2pConnectionOutgoingInitLibp2pOpts {
59 pub fn update_host_if_needed<'a>(&mut self, mut addrs: impl Iterator<Item = &'a Multiaddr>) {
62 fn is_local(ip: impl Into<IpAddr>) -> bool {
63 match ip.into() {
64 IpAddr::V4(ip) => ip.is_loopback() || ip.is_private(),
65 IpAddr::V6(ip) => ip.is_loopback(),
66 }
67 }
68
69 let update = match &self.host {
71 Host::Domain(_) => false,
72 Host::Ipv4(ip) => is_local(*ip),
73 Host::Ipv6(ip) => is_local(*ip),
74 };
75 if update {
76 let new = addrs.find_map(|x| {
78 x.iter().find_map(|x| match x {
79 Protocol::Dns4(hostname) | Protocol::Dns6(hostname) => {
80 Some(Host::Domain(hostname.into_owned()))
81 }
82 Protocol::Ip4(ip) if !is_local(ip) => Some(Host::Ipv4(ip)),
83 Protocol::Ip6(ip) if !is_local(ip) => Some(Host::Ipv6(ip)),
84 _ => None,
85 })
86 });
87 if let Some(new) = new {
88 self.host = new;
89 }
90 }
91 }
92
93 pub fn with_host_resolved(mut self) -> Option<Self> {
94 self.host = self.host.resolve()?;
95 Some(self)
96 }
97}
98
99pub(crate) mod libp2p_opts {
100 use std::net::{IpAddr, SocketAddr};
101
102 use multiaddr::Multiaddr;
103
104 use crate::{webrtc::Host, PeerId};
105
106 impl super::P2pConnectionOutgoingInitLibp2pOpts {
107 fn to_peer_id_multiaddr(&self) -> (PeerId, Multiaddr) {
108 (
109 self.peer_id,
110 Multiaddr::from_iter([(&self.host).into(), multiaddr::Protocol::Tcp(self.port)]),
111 )
112 }
113 fn into_peer_id_multiaddr(self) -> (PeerId, Multiaddr) {
114 (
115 self.peer_id,
116 Multiaddr::from_iter([(&self.host).into(), multiaddr::Protocol::Tcp(self.port)]),
117 )
118 }
119
120 pub fn matches_socket_addr(&self, addr: SocketAddr) -> bool {
121 self.port == addr.port() && self.matches_socket_ip(addr)
122 }
123
124 pub fn matches_socket_ip(&self, addr: SocketAddr) -> bool {
125 match (&self.host, addr) {
126 (Host::Ipv4(ip), SocketAddr::V4(addr)) => ip == addr.ip(),
127 (Host::Ipv6(ip), SocketAddr::V6(addr)) => ip == addr.ip(),
128 _ => false,
129 }
130 }
131 }
132
133 impl From<&super::P2pConnectionOutgoingInitLibp2pOpts> for (PeerId, Multiaddr) {
134 fn from(value: &super::P2pConnectionOutgoingInitLibp2pOpts) -> Self {
135 value.to_peer_id_multiaddr()
136 }
137 }
138
139 impl From<super::P2pConnectionOutgoingInitLibp2pOpts> for (PeerId, Multiaddr) {
140 fn from(value: super::P2pConnectionOutgoingInitLibp2pOpts) -> Self {
141 value.into_peer_id_multiaddr()
142 }
143 }
144
145 impl From<(PeerId, SocketAddr)> for super::P2pConnectionOutgoingInitLibp2pOpts {
146 fn from((peer_id, addr): (PeerId, SocketAddr)) -> Self {
147 let (host, port) = match addr {
148 SocketAddr::V4(v4) => (Host::Ipv4(*v4.ip()), v4.port()),
149 SocketAddr::V6(v6) => (Host::Ipv6(*v6.ip()), v6.port()),
150 };
151 super::P2pConnectionOutgoingInitLibp2pOpts {
152 peer_id,
153 host,
154 port,
155 }
156 }
157 }
158
159 #[derive(Debug, thiserror::Error)]
160 pub enum P2pConnectionOutgoingInitLibp2pOptsTryToSocketAddrError {
161 #[error("name unresolved: {0}")]
162 Unresolved(String),
163 }
164
165 impl TryFrom<&super::P2pConnectionOutgoingInitLibp2pOpts> for SocketAddr {
166 type Error = P2pConnectionOutgoingInitLibp2pOptsTryToSocketAddrError;
167
168 fn try_from(
169 value: &super::P2pConnectionOutgoingInitLibp2pOpts,
170 ) -> Result<Self, Self::Error> {
171 match &value.host {
172 Host::Domain(name) => Err(
173 P2pConnectionOutgoingInitLibp2pOptsTryToSocketAddrError::Unresolved(
174 name.clone(),
175 ),
176 ),
177 Host::Ipv4(ip) => Ok(SocketAddr::new(IpAddr::V4(*ip), value.port)),
178 Host::Ipv6(ip) => Ok(SocketAddr::new(IpAddr::V6(*ip), value.port)),
179 }
180 }
181 }
182}
183
184impl P2pConnectionOutgoingInitOpts {
185 pub fn is_libp2p(&self) -> bool {
186 matches!(self, Self::LibP2P(_))
187 }
188
189 pub fn peer_id(&self) -> &PeerId {
190 match self {
191 Self::WebRTC { peer_id, .. } => peer_id,
192 Self::LibP2P(v) => &v.peer_id,
193 }
194 }
195
196 pub fn kind(&self) -> &'static str {
197 match self {
198 Self::WebRTC { .. } => "webrtc",
199
200 Self::LibP2P(_) => "libp2p",
201 }
202 }
203
204 pub fn can_connect_directly(&self) -> bool {
205 match self {
206 Self::LibP2P(..) => true,
207 Self::WebRTC { signaling, .. } => signaling.can_connect_directly(),
208 }
209 }
210
211 pub fn webrtc_p2p_relay_peer_id(&self) -> Option<PeerId> {
212 match self {
213 Self::WebRTC { signaling, .. } => signaling.p2p_relay_peer_id(),
214 _ => None,
215 }
216 }
217
218 #[cfg(feature = "p2p-libp2p")]
223 pub fn try_from_mina_rpc(msg: v2::NetworkPeerPeerStableV1) -> Option<Self> {
224 let peer_id_str = String::try_from(&msg.peer_id.0).ok()?;
225 let peer_id = peer_id_str.parse::<libp2p_identity::PeerId>().ok()?;
226 if peer_id.as_ref().code() == 0x12 {
227 return None;
229 }
230
231 let host = String::try_from(&msg.host).ok()?;
232
233 let opts = if host.contains(':') {
234 let mut it = host.split(':');
235 let schema = it.next()?;
236 let host = it.next()?.trim_start_matches('/');
237 let signaling = match schema {
238 "http" => SignalingMethod::Http(HttpSignalingInfo {
239 host: host.parse().ok()?,
240 port: msg.libp2p_port.as_u64() as u16,
241 }),
242 "https" => SignalingMethod::Https(HttpSignalingInfo {
243 host: host.parse().ok()?,
244 port: msg.libp2p_port.as_u64() as u16,
245 }),
246 _ => return None,
247 };
248 Self::WebRTC {
249 peer_id: peer_id.try_into().ok()?,
250 signaling,
251 }
252 } else {
253 let opts = P2pConnectionOutgoingInitLibp2pOpts {
254 peer_id: peer_id.try_into().ok()?,
255 host: host.parse().ok()?,
256 port: msg.libp2p_port.as_u64() as u16,
257 };
258 Self::LibP2P(opts)
259 };
260 Some(opts)
261 }
262
263 #[cfg(feature = "p2p-libp2p")]
267 pub fn try_into_mina_rpc(&self) -> Option<v2::NetworkPeerPeerStableV1> {
268 match self {
269 P2pConnectionOutgoingInitOpts::LibP2P(opts) => Some(v2::NetworkPeerPeerStableV1 {
270 host: opts.host.to_string().as_bytes().into(),
271 libp2p_port: (opts.port as u64).into(),
272 peer_id: v2::NetworkPeerPeerIdStableV1(
273 libp2p_identity::PeerId::try_from(opts.peer_id)
274 .ok()?
275 .to_string()
276 .into_bytes()
277 .into(),
278 ),
279 }),
280 P2pConnectionOutgoingInitOpts::WebRTC { peer_id, signaling } => match signaling {
281 SignalingMethod::Http(info) => Some(v2::NetworkPeerPeerStableV1 {
282 host: format!("http://{}", info.host).as_bytes().into(),
283 libp2p_port: (info.port as u64).into(),
284 peer_id: v2::NetworkPeerPeerIdStableV1(
285 (*peer_id).to_string().into_bytes().into(),
286 ),
287 }),
288 SignalingMethod::Https(info) => Some(v2::NetworkPeerPeerStableV1 {
289 host: format!("https://{}", info.host).as_bytes().into(),
290 libp2p_port: (info.port as u64).into(),
291 peer_id: v2::NetworkPeerPeerIdStableV1(
292 (*peer_id).to_string().into_bytes().into(),
293 ),
294 }),
295 SignalingMethod::HttpsProxy(cluster_id, info) => {
296 Some(v2::NetworkPeerPeerStableV1 {
297 host: format!("https://{}/clusters/{cluster_id}", info.host)
298 .as_bytes()
299 .into(),
300 libp2p_port: (info.port as u64).into(),
301 peer_id: v2::NetworkPeerPeerIdStableV1(
302 (*peer_id).to_string().into_bytes().into(),
303 ),
304 })
305 }
306 SignalingMethod::Proxied(scheme, path_prefix, info) => {
307 Some(v2::NetworkPeerPeerStableV1 {
308 host: format!("{scheme}://{}{}", info.host, path_prefix)
309 .as_bytes()
310 .into(),
311 libp2p_port: (info.port as u64).into(),
312 peer_id: v2::NetworkPeerPeerIdStableV1(
313 (*peer_id).to_string().into_bytes().into(),
314 ),
315 })
316 }
317 SignalingMethod::P2p { .. } => None,
318 },
319 }
320 }
321
322 #[cfg(feature = "p2p-libp2p")]
323 pub fn from_libp2p_socket_addr(peer_id: PeerId, addr: SocketAddr) -> Self {
324 P2pConnectionOutgoingInitOpts::LibP2P((peer_id, addr).into())
325 }
326
327 fn parse_p2p_relay_webrtc_multiaddr(
328 maddr: &multiaddr::Multiaddr,
329 ) -> Result<Self, P2pConnectionOutgoingInitOptsParseError> {
330 let mut iter = maddr.iter();
331
332 let Some(Protocol::P2p(relay_peer_id_hash)) = iter.next() else {
333 return Err(P2pConnectionOutgoingInitOptsParseError::Other(
334 "expected p2p protocol for relay".to_string(),
335 ));
336 };
337 let relay_peer_id = libp2p_identity::PeerId::from_multihash(relay_peer_id_hash.into())
338 .map_err(|_| {
339 P2pConnectionOutgoingInitOptsParseError::Other(
340 "invalid relay peer_id multihash".to_string(),
341 )
342 })?
343 .try_into()
344 .map_err(|_| {
345 P2pConnectionOutgoingInitOptsParseError::Other(
346 "unexpected error converting relay PeerId".to_string(),
347 )
348 })?;
349
350 if iter.next() != Some(Protocol::WebRTC) {
352 return Err(P2pConnectionOutgoingInitOptsParseError::Other(
353 "expected webrtc protocol".to_string(),
354 ));
355 };
356
357 if iter.next() != Some(Protocol::P2pCircuit) {
359 return Err(P2pConnectionOutgoingInitOptsParseError::Other(
360 "expected p2p-circuit protocol".to_string(),
361 ));
362 };
363
364 let peer_id = Self::parse_p2p_peer_id(iter.next(), "target")?;
366
367 Ok(Self::WebRTC {
368 peer_id,
369 signaling: webrtc::SignalingMethod::P2p { relay_peer_id },
370 })
371 }
372
373 fn parse_p2p_peer_id(
374 protocol: Option<multiaddr::Protocol>,
375 label: &'static str,
376 ) -> Result<PeerId, P2pConnectionOutgoingInitOptsParseError> {
377 match protocol {
378 Some(Protocol::P2p(peer_id_hash)) => {
379 libp2p_identity::PeerId::from_multihash(peer_id_hash.into())
380 .map_err(|_| {
381 P2pConnectionOutgoingInitOptsParseError::Other(format!(
382 "invalid {label} peer_id multihash"
383 ))
384 })?
385 .try_into()
386 .map_err(|_| {
387 P2pConnectionOutgoingInitOptsParseError::Other(format!(
388 "unexpected error converting {label} PeerId"
389 ))
390 })
391 }
392 Some(other_protocol) => Err(P2pConnectionOutgoingInitOptsParseError::Other(format!(
393 "expected p2p protocol for {label} peer id, got {other_protocol:?}"
394 ))),
395 None => Err(P2pConnectionOutgoingInitOptsParseError::Other(format!(
396 "missing {label} peer id"
397 ))),
398 }
399 }
400}
401
402impl P2pConnectionOutgoingInitLibp2pOpts {
403 pub fn to_maddr(&self) -> Option<multiaddr::Multiaddr> {
404 self.clone().try_into().ok()
405 }
406}
407
408impl fmt::Display for P2pConnectionOutgoingInitOpts {
409 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
410 let maddr: Multiaddr = self.into();
411 write!(f, "{}", maddr)
412 }
413}
414
415#[derive(Error, Serialize, Deserialize, Debug, Clone)]
416pub enum P2pConnectionOutgoingInitOptsParseError {
417 #[error("not enough args for the signaling method")]
418 NotEnoughArgs,
419 #[error("peer id parse error: {0}")]
420 PeerIdParseError(String),
421 #[error("signaling method parse error: `{0}`")]
422 SignalingMethodParseError(webrtc::SignalingMethodParseError),
423 #[error("other error: {0}")]
424 Other(String),
425}
426
427impl FromStr for P2pConnectionOutgoingInitOpts {
428 type Err = P2pConnectionOutgoingInitOptsParseError;
429
430 fn from_str(s: &str) -> Result<Self, Self::Err> {
431 if s.is_empty() {
432 return Err(P2pConnectionOutgoingInitOptsParseError::NotEnoughArgs);
433 }
434
435 if let Ok(maddr) = Multiaddr::from_str(s) {
437 return Self::try_from(&maddr);
438 }
439
440 let id_end_index = s[1..]
443 .find('/')
444 .map(|i| i + 1)
445 .filter(|i| s.len() > *i)
446 .ok_or(P2pConnectionOutgoingInitOptsParseError::NotEnoughArgs)?;
447
448 let opts = Self::WebRTC {
449 peer_id: s[1..id_end_index].parse::<PeerId>().map_err(|err| {
450 P2pConnectionOutgoingInitOptsParseError::PeerIdParseError(err.to_string())
451 })?,
452 signaling: s[id_end_index..]
453 .parse::<webrtc::SignalingMethod>()
454 .map_err(|err| {
455 P2pConnectionOutgoingInitOptsParseError::SignalingMethodParseError(err)
456 })?,
457 };
458
459 let suggested_maddr: Multiaddr = (&opts).into();
461 tracing::warn!(
462 message = "Deprecated address format detected. Please use multiaddr format instead.",
463 legacy_format = %s,
464 suggested_format = %suggested_maddr,
465 );
466
467 Ok(opts)
468 }
469}
470
471impl Serialize for P2pConnectionOutgoingInitOpts {
472 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
473 where
474 S: serde::Serializer,
475 {
476 serializer.serialize_str(&self.to_string())
477 }
478}
479
480impl<'de> Deserialize<'de> for P2pConnectionOutgoingInitOpts {
481 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
482 where
483 D: serde::Deserializer<'de>,
484 {
485 let s: String = Deserialize::deserialize(deserializer)?;
486 s.parse().map_err(serde::de::Error::custom)
487 }
488}
489
490impl TryFrom<P2pConnectionOutgoingInitLibp2pOpts> for multiaddr::Multiaddr {
491 type Error = libp2p_identity::DecodingError;
492
493 fn try_from(value: P2pConnectionOutgoingInitLibp2pOpts) -> Result<Self, Self::Error> {
494 use multiaddr::Protocol;
495
496 Ok(Self::empty()
497 .with(match &value.host {
498 Host::Domain(v) => Protocol::Dns4(v.into()),
500 Host::Ipv4(v) => Protocol::Ip4(*v),
501 Host::Ipv6(v) => Protocol::Ip6(*v),
502 })
503 .with(Protocol::Tcp(value.port))
504 .with(Protocol::P2p(libp2p_identity::PeerId::try_from(
505 value.peer_id,
506 )?)))
507 }
508}
509
510impl TryFrom<&multiaddr::Multiaddr> for P2pConnectionOutgoingInitOpts {
511 type Error = P2pConnectionOutgoingInitOptsParseError;
512
513 fn try_from(maddr: &multiaddr::Multiaddr) -> Result<Self, Self::Error> {
525 let is_webrtc = maddr.iter().any(|p| p == Protocol::WebRTC);
527
528 if !is_webrtc {
530 cfg_if::cfg_if! {
531 if #[cfg(target_arch = "wasm32")] {
532 return Err(P2pConnectionOutgoingInitOptsParseError::Other(
533 "libp2p not supported in wasm".to_owned(),
534 ))
535 } else {
536 return Ok(Self::LibP2P(maddr.try_into()?))
537 }
538 };
539 }
540
541 let mut iter = maddr.iter();
542
543 match iter.next() {
548 Some(Protocol::P2p(_)) => Self::parse_p2p_relay_webrtc_multiaddr(maddr),
549 other_transport_protocol => {
550 let host = match other_transport_protocol {
552 Some(Protocol::Ip4(v)) => Host::Ipv4(v),
553 Some(Protocol::Ip6(v)) => Host::Ipv6(v),
554 Some(Protocol::Dns(v) | Protocol::Dns4(v) | Protocol::Dns6(v)) => {
555 Host::Domain(v.to_string())
556 }
557 _ => {
558 return Err(P2pConnectionOutgoingInitOptsParseError::Other(
559 "expected host (dns/dns4/dns6/ip4/ip6) in webrtc multiaddr".to_string(),
560 ))
561 }
562 };
563
564 let Some(Protocol::Tcp(port)) = iter.next() else {
566 return Err(P2pConnectionOutgoingInitOptsParseError::Other(
567 "expected tcp port in webrtc multiaddr".to_string(),
568 ));
569 };
570
571 if iter.next() != Some(Protocol::WebRTC) {
573 return Err(P2pConnectionOutgoingInitOptsParseError::Other(
574 "expected webrtc protocol".to_string(),
575 ));
576 };
577
578 let signaling_info = HttpSignalingInfo { host, port };
580 let scheme = match iter.next() {
581 Some(Protocol::Http) => webrtc::ProxyScheme::Http,
582 Some(Protocol::Https) => webrtc::ProxyScheme::Https,
583 _ => {
584 return Err(P2pConnectionOutgoingInitOptsParseError::Other(
585 "expected http or https protocol after webrtc".to_string(),
586 ))
587 }
588 };
589 let (signaling, peer_id) = match iter.next() {
590 Some(Protocol::HttpPath(path)) => {
591 let signaling = webrtc::SignalingMethod::Proxied(
592 scheme,
593 path.into(),
594 signaling_info,
595 );
596 let peer_id = Self::parse_p2p_peer_id(iter.next(), "webrtc")?;
597 (signaling, peer_id)
598 }
599 p2p @ Some(Protocol::P2p(_)) => {
600 let signaling = match scheme {
601 webrtc::ProxyScheme::Http => {
602 webrtc::SignalingMethod::Http(signaling_info)
603 }
604 webrtc::ProxyScheme::Https => {
605 webrtc::SignalingMethod::Https(signaling_info)
606 }
607 };
608 let peer_id = Self::parse_p2p_peer_id(p2p, "webrtc")?;
609 (signaling, peer_id)
610 }
611 Some(other_protocol) => {
612 return Err(P2pConnectionOutgoingInitOptsParseError::Other(
613 format!("expected /p2p/peer_id or /http-path/encoded_path, got {other_protocol:?}"
614 )))
615 },
616 None => {
617 return Err(P2pConnectionOutgoingInitOptsParseError::Other(
618 "expected p2p protocol with peer_id".to_string(),
619 ))
620 }
621 };
622
623 Ok(Self::WebRTC { peer_id, signaling })
624 }
625 }
626 }
627}
628
629impl TryFrom<multiaddr::Multiaddr> for P2pConnectionOutgoingInitOpts {
630 type Error = P2pConnectionOutgoingInitOptsParseError;
631
632 fn try_from(value: multiaddr::Multiaddr) -> Result<Self, Self::Error> {
633 (&value).try_into()
634 }
635}
636
637impl From<&P2pConnectionOutgoingInitOpts> for Multiaddr {
638 fn from(opts: &P2pConnectionOutgoingInitOpts) -> Self {
648 match opts {
649 P2pConnectionOutgoingInitOpts::WebRTC { peer_id, signaling } => {
650 use webrtc::SignalingMethod;
651
652 let peer_id_proto = Protocol::P2p(
660 libp2p_identity::PeerId::try_from(*peer_id).expect("valid peer_id"),
661 );
662
663 match signaling {
664 SignalingMethod::Http(info) => {
665 let host_proto = match &info.host {
666 Host::Domain(v) => Protocol::Dns4(v.into()),
667 Host::Ipv4(v) => Protocol::Ip4(*v),
668 Host::Ipv6(v) => Protocol::Ip6(*v),
669 };
670 Multiaddr::empty()
671 .with(host_proto)
672 .with(Protocol::Tcp(info.port))
673 .with(Protocol::WebRTC)
674 .with(Protocol::Http)
675 .with(peer_id_proto)
676 }
677 SignalingMethod::Https(info) => {
678 let host_proto = match &info.host {
679 Host::Domain(v) => Protocol::Dns4(v.into()),
680 Host::Ipv4(v) => Protocol::Ip4(*v),
681 Host::Ipv6(v) => Protocol::Ip6(*v),
682 };
683 Multiaddr::empty()
684 .with(host_proto)
685 .with(Protocol::Tcp(info.port))
686 .with(Protocol::WebRTC)
687 .with(Protocol::Https)
688 .with(peer_id_proto)
689 }
690 SignalingMethod::HttpsProxy(cluster_id, info) => {
691 let host_proto = match &info.host {
693 Host::Domain(v) => Protocol::Dns4(v.into()),
694 Host::Ipv4(v) => Protocol::Ip4(*v),
695 Host::Ipv6(v) => Protocol::Ip6(*v),
696 };
697 let path = format!("clusters/{}", cluster_id);
698 Multiaddr::empty()
699 .with(host_proto)
700 .with(Protocol::Tcp(info.port))
701 .with(Protocol::WebRTC)
702 .with(Protocol::Https)
703 .with(Protocol::HttpPath(path.into()))
704 .with(peer_id_proto)
705 }
706 SignalingMethod::Proxied(scheme, path, info) => {
707 let host_proto = match &info.host {
708 Host::Domain(v) => Protocol::Dns4(v.into()),
709 Host::Ipv4(v) => Protocol::Ip4(*v),
710 Host::Ipv6(v) => Protocol::Ip6(*v),
711 };
712 let scheme_proto = match scheme {
713 webrtc::ProxyScheme::Http => Protocol::Http,
714 webrtc::ProxyScheme::Https => Protocol::Https,
715 };
716 Multiaddr::empty()
717 .with(host_proto)
718 .with(Protocol::Tcp(info.port))
719 .with(Protocol::WebRTC)
720 .with(scheme_proto)
721 .with(Protocol::HttpPath(path.into()))
722 .with(peer_id_proto)
723 }
724 SignalingMethod::P2p { relay_peer_id } => {
725 let relay_id_proto = Protocol::P2p(
727 libp2p_identity::PeerId::try_from(*relay_peer_id)
728 .expect("valid relay_peer_id"),
729 );
730 Multiaddr::empty()
731 .with(relay_id_proto)
732 .with(Protocol::WebRTC)
733 .with(Protocol::P2pCircuit)
734 .with(peer_id_proto)
735 }
736 }
737 }
738 P2pConnectionOutgoingInitOpts::LibP2P(v) => v.to_maddr().expect("valid libp2p opts"),
740 }
741 }
742}
743
744impl From<P2pConnectionOutgoingInitOpts> for Multiaddr {
745 fn from(opts: P2pConnectionOutgoingInitOpts) -> Self {
746 (&opts).into()
747 }
748}
749
750impl TryFrom<&multiaddr::Multiaddr> for P2pConnectionOutgoingInitLibp2pOpts {
751 type Error = P2pConnectionOutgoingInitOptsParseError;
752
753 fn try_from(maddr: &multiaddr::Multiaddr) -> Result<Self, Self::Error> {
754 use multiaddr::Protocol;
755
756 let mut iter = maddr.iter();
757 Ok(P2pConnectionOutgoingInitLibp2pOpts {
758 host: match iter.next() {
759 Some(Protocol::Ip4(v)) => Host::Ipv4(v),
760 Some(Protocol::Dns(v) | Protocol::Dns4(v) | Protocol::Dns6(v)) => {
761 Host::Domain(v.to_string())
762 }
763 Some(other_host) => {
764 return Err(P2pConnectionOutgoingInitOptsParseError::Other(
765 format!("unexpected transport in multiaddr! expected /dns|dns4|dns6|ip4|ip6/<host>, got {other_host:?}!")
766 ));
767 }
768 None => {
769 return Err(P2pConnectionOutgoingInitOptsParseError::Other(
770 "missing /dns|dns4|dns6|ip4|ip6/host from multiaddr".to_string(),
771 ));
772 }
773 },
774 port: match iter.next() {
775 Some(Protocol::Tcp(port)) => port,
776 Some(other_port) => {
777 return Err(P2pConnectionOutgoingInitOptsParseError::Other(format!(
778 "unexpected part in multiaddr! expected /tcp/<port>, got {other_port:?}"
779 )));
780 }
781 None => {
782 return Err(P2pConnectionOutgoingInitOptsParseError::Other(
783 "missing port part from multiaddr".to_string(),
784 ));
785 }
786 },
787 peer_id: match iter.next() {
788 Some(Protocol::P2p(hash)) => libp2p_identity::PeerId::from_multihash(hash.into())
789 .map_err(|_| {
790 P2pConnectionOutgoingInitOptsParseError::Other(
791 "invalid peer_id multihash".to_string(),
792 )
793 })?
794 .try_into()
795 .map_err(|_| {
796 P2pConnectionOutgoingInitOptsParseError::Other(
797 "unexpected error converting PeerId".to_string(),
798 )
799 })?,
800 Some(_) => {
801 return Err(P2pConnectionOutgoingInitOptsParseError::Other(
802 "unexpected part in multiaddr! expected peer_id".to_string(),
803 ));
804 }
805 None => {
806 return Err(P2pConnectionOutgoingInitOptsParseError::Other(
807 "peer_id not set in multiaddr. Missing `../p2p/<peer_id>`".to_string(),
808 ));
809 }
810 },
811 })
812 }
813}
814
815mod measurement {
816 use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
817
818 use super::P2pConnectionOutgoingInitOpts;
819
820 impl MallocSizeOf for P2pConnectionOutgoingInitOpts {
823 fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
824 0
825 }
826 }
827}
828
829#[cfg(test)]
830mod tests {
831 use super::*;
832 use std::net::Ipv4Addr;
833
834 const TEST_PEER_ID_LIBP2P: &str = "12D3KooWEiGVAFC7curXWXiGZyMWnZK9h8BKr88U8D5PKV3dXciv";
836 const TEST_RELAY_PEER_ID_LIBP2P: &str = "12D3KooWAdgYL6hv18M3iDBdaK1dRygPivSfAfBNDzie6YqydVbs";
837
838 #[test]
839 fn test_parse_webrtc_http_signaling_domain() {
840 let maddr_str = format!(
841 "/dns4/signal.example.com/tcp/8080/webrtc/http/p2p/{}",
842 TEST_PEER_ID_LIBP2P
843 );
844 let opts: P2pConnectionOutgoingInitOpts = maddr_str.parse().unwrap();
845
846 match opts {
847 P2pConnectionOutgoingInitOpts::WebRTC { signaling, .. } => match signaling {
848 webrtc::SignalingMethod::Http(info) => {
849 assert_eq!(info.host, Host::Domain("signal.example.com".to_string()));
850 assert_eq!(info.port, 8080);
851 }
852 x => panic!("Expected Http signaling method, got {x:?}"),
853 },
854 x => panic!("Expected WebRTC variant, got {x:?}"),
855 }
856 }
857
858 #[test]
859 fn test_parse_webrtc_http_signaling_ipv4() {
860 let maddr_str = format!(
861 "/ip4/192.168.1.100/tcp/8080/webrtc/http/p2p/{}",
862 TEST_PEER_ID_LIBP2P
863 );
864 let opts: P2pConnectionOutgoingInitOpts = maddr_str.parse().unwrap();
865
866 match opts {
867 P2pConnectionOutgoingInitOpts::WebRTC { signaling, .. } => match signaling {
868 webrtc::SignalingMethod::Http(info) => {
869 assert_eq!(info.host, Host::Ipv4(Ipv4Addr::new(192, 168, 1, 100)));
870 assert_eq!(info.port, 8080);
871 }
872 x => panic!("Expected Http signaling method, got {x:?}"),
873 },
874 x => panic!("Expected WebRTC variant, got {x:?}"),
875 }
876 }
877
878 #[test]
879 fn test_parse_webrtc_https_signaling() {
880 let maddr_str = format!(
881 "/dns4/signal.example.com/tcp/443/webrtc/https/p2p/{}",
882 TEST_PEER_ID_LIBP2P
883 );
884 let opts: P2pConnectionOutgoingInitOpts = maddr_str.parse().unwrap();
885
886 match opts {
887 P2pConnectionOutgoingInitOpts::WebRTC { signaling, .. } => match signaling {
888 webrtc::SignalingMethod::Https(info) => {
889 assert_eq!(info.host, Host::Domain("signal.example.com".to_string()));
890 assert_eq!(info.port, 443);
891 }
892 x => panic!("Expected Https signaling method, got {x:?}"),
893 },
894 x => panic!("Expected WebRTC variant, got {x:?}"),
895 }
896 }
897
898 #[test]
899 fn test_parse_webrtc_https_proxy_with_http_path() {
900 let maddr_str = format!(
901 "/dns4/proxy.example.com/tcp/443/webrtc/https/http-path/cluster%2F123%2Fmina%2Fwebrtc%2Fsignal/p2p/{}",
902 TEST_PEER_ID_LIBP2P
903 );
904 let opts: P2pConnectionOutgoingInitOpts = maddr_str.parse().unwrap();
905
906 match opts {
907 P2pConnectionOutgoingInitOpts::WebRTC { signaling, .. } => match signaling {
908 webrtc::SignalingMethod::Proxied(scheme, path, info) => {
909 assert_eq!(scheme, webrtc::ProxyScheme::Https);
910 assert_eq!(path.as_ref(), "cluster/123/mina/webrtc/signal");
911 assert_eq!(info.host, Host::Domain("proxy.example.com".to_string()));
912 assert_eq!(info.port, 443);
913 }
914 x => panic!("Expected Proxied signaling method, got {x:?}"),
915 },
916 x => panic!("Expected WebRTC variant, got {x:?}"),
917 }
918 }
919
920 #[test]
921 fn test_parse_webrtc_p2p_relay() {
922 let maddr_str = format!(
923 "/p2p/{}/webrtc/p2p-circuit/p2p/{}",
924 TEST_RELAY_PEER_ID_LIBP2P, TEST_PEER_ID_LIBP2P
925 );
926 let opts: P2pConnectionOutgoingInitOpts = maddr_str.parse().unwrap();
927
928 match opts {
929 P2pConnectionOutgoingInitOpts::WebRTC { signaling, .. } => match signaling {
930 webrtc::SignalingMethod::P2p { .. } => {
931 }
933 x => panic!("Expected P2p signaling method, got {x:?}"),
934 },
935 x => panic!("Expected WebRTC variant, got {x:?}"),
936 }
937 }
938
939 #[test]
940 fn test_parse_libp2p_multiaddr() {
941 let maddr_str = format!(
942 "/dns4/seed-1.devnet.gcp.o1test.net/tcp/10003/p2p/{}",
943 TEST_PEER_ID_LIBP2P
944 );
945 let opts: P2pConnectionOutgoingInitOpts = maddr_str.parse().unwrap();
946
947 match opts {
948 P2pConnectionOutgoingInitOpts::LibP2P(libp2p_opts) => {
949 assert_eq!(
950 libp2p_opts.host,
951 Host::Domain("seed-1.devnet.gcp.o1test.net".to_string())
952 );
953 assert_eq!(libp2p_opts.port, 10003);
954 }
955 x => panic!("Expected LibP2P variant, got {x:?}"),
956 }
957 }
958
959 #[test]
960 fn test_roundtrip_webrtc_http() {
961 let maddr_str = format!(
962 "/dns4/signal.example.com/tcp/8080/webrtc/http/p2p/{}",
963 TEST_PEER_ID_LIBP2P
964 );
965
966 let opts1: P2pConnectionOutgoingInitOpts = maddr_str.parse().unwrap();
968
969 let maddr = Multiaddr::from(&opts1);
971 let encoded_str = maddr.to_string();
972
973 let opts2 = P2pConnectionOutgoingInitOpts::from_str(&encoded_str).unwrap();
975
976 assert_eq!(opts1, opts2);
978 }
979
980 #[test]
981 fn test_roundtrip_webrtc_https() {
982 let maddr_str = format!(
983 "/dns4/signal.example.com/tcp/443/webrtc/https/p2p/{}",
984 TEST_PEER_ID_LIBP2P
985 );
986
987 let opts1: P2pConnectionOutgoingInitOpts = maddr_str.parse().unwrap();
988 let maddr: Multiaddr = (&opts1).into();
989 let opts2: P2pConnectionOutgoingInitOpts = (&maddr).try_into().unwrap();
990
991 assert_eq!(opts1, opts2);
992 }
993
994 #[test]
995 fn test_roundtrip_webrtc_https_proxy() {
996 let maddr_str = format!(
997 "/dns4/proxy.example.com/tcp/443/webrtc/https/http-path/cluster%2F123/p2p/{}",
998 TEST_PEER_ID_LIBP2P
999 );
1000
1001 let opts1: P2pConnectionOutgoingInitOpts = maddr_str.parse().unwrap();
1002 let maddr: Multiaddr = (&opts1).into();
1003 let opts2: P2pConnectionOutgoingInitOpts = (&maddr).try_into().unwrap();
1004
1005 assert_eq!(opts1, opts2);
1006 }
1007
1008 #[test]
1009 fn test_roundtrip_webrtc_p2p_relay() {
1010 let maddr_str = format!(
1011 "/p2p/{}/webrtc/p2p-circuit/p2p/{}",
1012 TEST_RELAY_PEER_ID_LIBP2P, TEST_PEER_ID_LIBP2P
1013 );
1014
1015 let opts1: P2pConnectionOutgoingInitOpts = maddr_str.parse().unwrap();
1016 let maddr: Multiaddr = (&opts1).into();
1017 let opts2: P2pConnectionOutgoingInitOpts = (&maddr).try_into().unwrap();
1018
1019 assert_eq!(opts1, opts2);
1020 }
1021
1022 #[test]
1023 fn test_roundtrip_libp2p() {
1024 let maddr_str = format!("/dns4/example.com/tcp/10003/p2p/{}", TEST_PEER_ID_LIBP2P);
1025
1026 let opts1: P2pConnectionOutgoingInitOpts = maddr_str.parse().unwrap();
1027 let maddr: Multiaddr = (&opts1).into();
1028 let opts2: P2pConnectionOutgoingInitOpts = (&maddr).try_into().unwrap();
1029
1030 assert_eq!(opts1, opts2);
1031 }
1032
1033 #[test]
1034 fn test_from_multiaddr_webrtc_http() {
1035 let maddr_str = format!(
1036 "/dns4/signal.example.com/tcp/8080/webrtc/http/p2p/{}",
1037 TEST_PEER_ID_LIBP2P
1038 );
1039 let maddr: Multiaddr = maddr_str.parse().unwrap();
1040 let opts: P2pConnectionOutgoingInitOpts = (&maddr).try_into().unwrap();
1041
1042 assert!(
1043 matches!(
1044 opts,
1045 P2pConnectionOutgoingInitOpts::WebRTC {
1046 signaling: webrtc::SignalingMethod::Http(_),
1047 ..
1048 }
1049 ),
1050 "expected WebRTC with Http signaling, got {opts:?}"
1051 );
1052 }
1053
1054 #[test]
1055 fn test_to_multiaddr_webrtc_http() {
1056 let libp2p_peer_id: libp2p_identity::PeerId = TEST_PEER_ID_LIBP2P.parse().unwrap();
1058 let peer_id: PeerId = libp2p_peer_id.try_into().unwrap();
1059
1060 let opts = P2pConnectionOutgoingInitOpts::WebRTC {
1061 peer_id,
1062 signaling: webrtc::SignalingMethod::Http(HttpSignalingInfo {
1063 host: Host::Domain("signal.example.com".to_string()),
1064 port: 8080,
1065 }),
1066 };
1067
1068 let maddr: Multiaddr = (&opts).into();
1069 let maddr_str = maddr.to_string();
1070
1071 assert!(maddr_str.contains("/webrtc/http/"));
1072 assert!(maddr_str.contains("/dns4/signal.example.com/"));
1073 assert!(maddr_str.contains("/tcp/8080/"));
1074
1075 let opts2: P2pConnectionOutgoingInitOpts = (&maddr).try_into().unwrap();
1077 assert_eq!(opts, opts2);
1078 }
1079
1080 #[test]
1081 fn test_to_multiaddr_webrtc_p2p_relay() {
1082 let libp2p_peer_id: libp2p_identity::PeerId = TEST_PEER_ID_LIBP2P.parse().unwrap();
1083 let peer_id: PeerId = libp2p_peer_id.try_into().unwrap();
1084
1085 let libp2p_relay_peer_id: libp2p_identity::PeerId =
1086 TEST_RELAY_PEER_ID_LIBP2P.parse().unwrap();
1087 let relay_peer_id: PeerId = libp2p_relay_peer_id.try_into().unwrap();
1088
1089 let opts = P2pConnectionOutgoingInitOpts::WebRTC {
1090 peer_id,
1091 signaling: webrtc::SignalingMethod::P2p { relay_peer_id },
1092 };
1093
1094 let maddr: Multiaddr = (&opts).into();
1095 let maddr_str = maddr.to_string();
1096
1097 assert!(maddr_str.contains("/webrtc/p2p-circuit/"));
1098 assert!(maddr_str.starts_with("/p2p/"));
1099
1100 let opts2: P2pConnectionOutgoingInitOpts = (&maddr).try_into().unwrap();
1102 assert_eq!(opts, opts2);
1103 }
1104
1105 #[test]
1106 fn test_legacy_format_still_works() {
1107 let libp2p_peer_id: libp2p_identity::PeerId = TEST_PEER_ID_LIBP2P.parse().unwrap();
1110 let peer_id: PeerId = libp2p_peer_id.try_into().unwrap();
1111 let legacy_peer_id_str = peer_id.to_string();
1112
1113 let legacy_str = format!("/{}/http/signal.example.com/8080", legacy_peer_id_str);
1115 let opts: P2pConnectionOutgoingInitOpts = legacy_str.parse().unwrap();
1116
1117 match opts {
1118 P2pConnectionOutgoingInitOpts::WebRTC { signaling, .. } => {
1119 assert!(
1120 matches!(signaling, webrtc::SignalingMethod::Http(_)),
1121 "expected Http signaling, got {signaling:?}"
1122 );
1123 }
1124 x => panic!("Expected WebRTC variant, got {x:?}"),
1125 }
1126 }
1127}