network_libp2p/native/
mod.rs

1use chat_core::error::ChatResult;
2use chat_core::network::{Multiaddr, NetworkEvent};
3use futures::StreamExt;
4use libp2p::{identity, noise, PeerId};
5
6use crate::behaviour::build_chat_behaviour;
7use crate::bootstrap::bootstrap_swarm;
8use crate::common::{init_libp2p_network, Libp2pNetwork};
9use crate::NodeOptions;
10
11/// Build a native `Libp2pNetwork` with TCP + QUIC + WebSocket + WebRTC + DNS + relay.
12pub async fn build_node(options: NodeOptions) -> ChatResult<Libp2pNetwork> {
13    let keypair = identity::Keypair::generate_ed25519();
14    build_node_with_keypair(options, keypair).await
15}
16
17/// Build a native `Libp2pNetwork` with an existing keypair.
18pub async fn build_node_with_keypair(
19    options: NodeOptions,
20    keypair: identity::Keypair,
21) -> ChatResult<Libp2pNetwork> {
22    let local_peer_id = PeerId::from(keypair.public());
23
24    // Generate an ephemeral WebRTC certificate.
25    let webrtc_cert = libp2p_webrtc::tokio::Certificate::generate(&mut rand::thread_rng())
26        .map_err(|e| e.to_string())?;
27
28    // Build the swarm with TCP + QUIC + WebRTC + DNS + WebSocket + relay.
29    // Note: libp2p 0.57 builder type-state requires this exact ordering.
30    let builder = libp2p::SwarmBuilder::with_existing_identity(keypair.clone())
31        .with_tokio()
32        .with_tcp(
33            libp2p::tcp::Config::default(),
34            noise::Config::new,
35            libp2p::yamux::Config::default,
36        )
37        .map_err(|e| e.to_string())?
38        .with_quic();
39
40    let builder = builder
41        .with_other_transport(|keypair| {
42            let webrtc = libp2p_webrtc::tokio::Transport::new(
43                keypair.clone(),
44                webrtc_cert,
45            );
46            Ok(webrtc)
47        })
48        .map_err(|e| e.to_string())?;
49
50    let builder = builder
51        .with_dns()
52        .map_err(|e| e.to_string())?
53        .with_websocket(noise::Config::new, libp2p::yamux::Config::default)
54        .await
55        .map_err(|e| e.to_string())?;
56
57    let builder = builder
58        .with_relay_client(noise::Config::new, libp2p::yamux::Config::default)
59        .map_err(|e| e.to_string())?;
60
61    let mut swarm = builder
62        .with_behaviour(|keypair, relay_client| {
63            build_chat_behaviour(keypair, local_peer_id, relay_client, &options)
64        })
65        .map_err(|e| e.to_string())?
66        .with_swarm_config(|cfg| {
67            cfg.with_idle_connection_timeout(std::time::Duration::from_secs(60))
68        })
69        .build();
70
71    // Listen on default TCP, QUIC and WebRTC addresses.
72    swarm
73        .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap())
74        .map_err(|e| e.to_string())?;
75    swarm
76        .listen_on("/ip4/0.0.0.0/udp/0/quic-v1".parse().unwrap())
77        .map_err(|e| e.to_string())?;
78    swarm
79        .listen_on("/ip4/0.0.0.0/udp/0/webrtc-direct".parse().unwrap())
80        .map_err(|e| e.to_string())?;
81
82    // Also listen on any explicitly configured addresses.
83    for addr in &options.listen_addresses {
84        let multiaddr = addr.parse().map_err(|e: libp2p::multiaddr::Error| e.to_string())?;
85        swarm.listen_on(multiaddr).map_err(|e| e.to_string())?;
86    }
87
88    let bootstrap_peer_ids = bootstrap_swarm(&mut swarm, &options);
89
90    Ok(init_libp2p_network(swarm, local_peer_id, &options, bootstrap_peer_ids))
91}
92
93// Platform-specific hooks for `NetworkNode` delegation in `common.rs`.
94
95impl Libp2pNetwork {
96    pub(crate) fn platform_dial_addrs(&self) -> Vec<Multiaddr> {
97        let mut addrs: Vec<Multiaddr> = self.swarm.listeners().map(|a| Multiaddr::from(a.to_string())).collect();
98        addrs.extend(
99            self.swarm
100                .external_addresses()
101                .filter(|a| {
102                    let s = a.to_string();
103                    s.contains("p2p-circuit") || s.contains("webrtc")
104                })
105                .map(|a| Multiaddr::from(a.to_string())),
106        );
107        addrs
108    }
109
110    pub(crate) async fn platform_next_event(&mut self) -> Option<NetworkEvent> {
111        use libp2p::swarm::SwarmEvent;
112        loop {
113            let event = self.swarm.select_next_some().await;
114            if let Some(ev) = match event {
115                SwarmEvent::Behaviour(crate::behaviour::ChatBehaviourEvent::Dcutr(ev)) => {
116                    match ev.result {
117                        Ok(_) => {
118                            tracing::info!("DCUtR succeeded with {}", ev.remote_peer_id);
119                        }
120                        Err(ref e) => {
121                            tracing::warn!(
122                                "DCUtR failed with {}: {}",
123                                ev.remote_peer_id,
124                                e
125                            );
126                        }
127                    }
128                    None
129                }
130                SwarmEvent::Behaviour(crate::behaviour::ChatBehaviourEvent::Autonat(ev)) => {
131                    match ev {
132                        libp2p::autonat::Event::StatusChanged { old, new } => {
133                            tracing::info!("AutoNAT status changed: {:?} -> {:?}", old, new);
134                        }
135                        other => {
136                            tracing::debug!("AutoNAT event: {:?}", other);
137                        }
138                    }
139                    None
140                }
141                other => self.handle_swarm_event(other),
142            } {
143                return Some(ev);
144            }
145        }
146    }
147}