diff --git a/src/cfg.rs b/src/cfg.rs new file mode 100644 index 0000000..16acf3d --- /dev/null +++ b/src/cfg.rs @@ -0,0 +1,66 @@ +use std::env; + +use libp2p::{identity, kad}; + +// Configuration +pub trait Cfg { + // ID keys uniqely identifying the node. + fn id_keys(&self) -> identity::Keypair; + // Name of the protocol used to identify the node through libpb Identify. + fn identify_protocol(&self) -> String; + // Server or Client. Defaults to server. + fn kad_mode(&self) -> kad::Mode; + // Multiaddress the node listens on. + fn listen_addr(&self) -> String; + // Peer ID of the node. Derived from the public key in id_keys(). + fn peer_id(&self) -> identity::PeerId; +} + +// Default node configuration. +pub struct DefaultCfg { + id_keys: identity::Keypair, + identify_protocol: String, + listen_addr: String, +} + +impl DefaultCfg { + // Default node configuration. + pub fn new() -> Self { + Self { + id_keys: identity::Keypair::generate_ed25519(), + identify_protocol: "/ww/identify/0.0.1".to_owned(), + listen_addr: "/ip4/0.0.0.0/tcp/0".to_owned(), + } + } + + // Check if the node is a Kademlia client from the command-line arguments. + fn is_kad_client(&self) -> bool { + let args: Vec = env::args().collect(); + return args.iter().any(|arg| arg == "--kad-client"); + } +} + +impl Cfg for DefaultCfg { + fn identify_protocol(&self) -> String { + self.identify_protocol.to_owned() + } + + fn listen_addr(&self) -> String { + self.listen_addr.to_owned() + } + + fn kad_mode(&self) -> kad::Mode { + if self.is_kad_client() { + return kad::Mode::Client; + } + kad::Mode::Server + } + + fn id_keys(&self) -> identity::Keypair { + self.id_keys.clone() + } + + fn peer_id(&self) -> identity::PeerId { + identity::PeerId::from(self.id_keys().public()) + } +} diff --git a/src/main.rs b/src/main.rs index b51cb82..34886dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,18 @@ -use std::{env, error::Error, time::Duration}; +use std::{error::Error, time::Duration}; use anyhow::Result; -use libp2p::{identify, identity, kad, mdns, noise, ping, swarm, tcp, yamux, PeerId}; +use libp2p::{identify, kad, mdns, noise, ping, swarm, tcp, yamux}; use tracing_subscriber::EnvFilter; use ww_net; -fn is_kad_client() -> bool { - let args: Vec = env::args().collect(); - return args.iter().any(|arg| arg == "--kad-client"); -} +pub mod cfg; #[tokio::main] async fn main() -> Result<(), Box> { + // Use the default configuration. + let config: &dyn cfg::Cfg = &cfg::DefaultCfg::new(); + // Start configuring a `fmt` subscriber let subscriber = tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) @@ -26,20 +26,19 @@ async fn main() -> Result<(), Box> { tracing::subscriber::set_global_default(subscriber).unwrap(); // Create a MDNS network behaviour. - let id_keys = identity::Keypair::generate_ed25519(); - let peer_id = PeerId::from(id_keys.public()); - let mdns_behaviour = mdns::tokio::Behaviour::new(mdns::Config::default(), peer_id)?; + let mdns_behaviour = mdns::tokio::Behaviour::new(mdns::Config::default(), config.peer_id())?; // Create Stream behaviour. let ping_behaviour = ping::Behaviour::default(); - // Create Kademlia behaviour. + // Create Kademlia and Identify behaviours. let kad_cfg = kad::Config::default(); - let store = kad::store::MemoryStore::new(id_keys.public().to_peer_id()); - let kad_behaviour = kad::Behaviour::with_config(id_keys.public().to_peer_id(), store, kad_cfg); + let kad_store = kad::store::MemoryStore::new(config.id_keys().public().to_peer_id()); + let kad_behaviour = + kad::Behaviour::with_config(config.id_keys().public().to_peer_id(), kad_store, kad_cfg); let identify_behaviour = identify::Behaviour::new(identify::Config::new( - "/test/1.0.0.".to_owned(), - id_keys.public(), + config.identify_protocol(), + config.id_keys().public(), )); // Combine behaviours. @@ -50,7 +49,7 @@ async fn main() -> Result<(), Box> { identify: identify_behaviour, }; - let raw_swarm = libp2p::SwarmBuilder::with_existing_identity(id_keys) + let raw_swarm = libp2p::SwarmBuilder::with_existing_identity(config.id_keys()) .with_tokio() .with_tcp( tcp::Config::default(), @@ -61,19 +60,14 @@ async fn main() -> Result<(), Box> { .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(u64::MAX))) .build(); + // Wrap the swarm in our custom type to overwrite its behaviour and event management. let mut swarm = ww_net::DefaultSwarm(raw_swarm); - // Configure Kademlia mode, defaults to server. - let kad_mode = if is_kad_client() { - kad::Mode::Client - } else { - kad::Mode::Server - }; - swarm.behaviour_mut().kad.set_mode(Some(kad_mode)); + // Set the Kademlia mode. + swarm.behaviour_mut().kad.set_mode(Some(config.kad_mode())); - // Tell the swarm to listen on all interfaces and a random, OS-assigned - // port. - swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?; + // Tell the swarm to listen on all interfaces and a random, OS-assigned port. + swarm.listen_on(config.listen_addr().parse()?)?; loop { match swarm.select_next_some().await { @@ -82,11 +76,9 @@ async fn main() -> Result<(), Box> { // using our PeerID as the key. tracing::info!("listening on {address:?}") } - swarm::SwarmEvent::Behaviour(ww_net::DefaultBehaviourEvent::Mdns(event)) => { ww_net::net::default_mdns_handler(&mut swarm, event); } - swarm::SwarmEvent::Behaviour(ww_net::DefaultBehaviourEvent::Ping(event)) => { tracing::info!("got PING event: {event:?}"); } diff --git a/ww_net/src/lib.rs b/ww_net/src/lib.rs index be59c49..19f11ef 100644 --- a/ww_net/src/lib.rs +++ b/ww_net/src/lib.rs @@ -8,6 +8,14 @@ use libp2p::{swarm, Swarm}; pub struct DefaultSwarm(pub swarm::Swarm); +impl DefaultSwarm { + // Forward tge call to the inner Swarm. + pub fn select_next_some(&mut self) -> SelectNextSome<'_, Swarm> { + self.0.select_next_some() + } +} + +// Required to use DefaultSwarm as Swarm in our modules. impl Deref for DefaultSwarm { type Target = swarm::Swarm; @@ -16,6 +24,7 @@ impl Deref for DefaultSwarm { } } +// Required to use DefaultSwarm as Swarm in our modules. impl DerefMut for DefaultSwarm { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 @@ -23,17 +32,12 @@ impl DerefMut for DefaultSwarm { } impl net::Dialer for DefaultSwarm { + // Forward the call to the inner Swarm. fn dial(&mut self, opts: swarm::dial_opts::DialOpts) -> Result<(), swarm::DialError> { self.0.dial(opts) } } -impl DefaultSwarm { - pub fn select_next_some(&mut self) -> SelectNextSome<'_, Swarm> { - self.0.select_next_some() - } -} - #[derive(swarm::NetworkBehaviour)] #[behaviour(to_swarm = "DefaultBehaviourEvent")] pub struct DefaultBehaviour { @@ -43,11 +47,10 @@ pub struct DefaultBehaviour { pub identify: libp2p::identify::Behaviour, } +// Events explicitly managed or intercepted by the DefaultBehaviour. #[derive(Debug)] pub enum DefaultBehaviourEvent { - // Events emitted by the MDNS behaviour. Mdns(libp2p::mdns::Event), - // Events emitted by the Ping behaviour. Ping(libp2p::ping::Event), Kad(libp2p::kad::Event), Identify(libp2p::identify::Event), diff --git a/ww_net/src/net.rs b/ww_net/src/net.rs index ebfb2af..dcb27f1 100644 --- a/ww_net/src/net.rs +++ b/ww_net/src/net.rs @@ -7,10 +7,12 @@ use libp2p::{ Multiaddr, PeerId, }; +// Defines the hability to dial another peer. pub trait Dialer { fn dial(&mut self, opts: DialOpts) -> Result<(), swarm::DialError>; } +// Dials each newly discovered peer. pub fn default_mdns_handler(d: &mut dyn Dialer, event: mdns::Event) { match event { mdns::Event::Discovered(peers) => {