use std::{net::{IpAddr, Ipv4Addr}, ops, path::{Path, PathBuf}}; use base64::{prelude::BASE64_STANDARD, Engine}; #[cfg(feature="desktop")] use clap::Args; use futures::StreamExt; use libp2p::{identity::{self, DecodingError, Keypair}, noise, ping, swarm::SwarmEvent, tcp, yamux, Swarm}; use serde::{Deserialize, Serialize}; use tokio::{fs::File, io::{AsyncReadExt, AsyncWriteExt}}; use tracing_subscriber::EnvFilter; use crate::{ config::PartialConfig, error::Error, p2p, utils::{emptiable::Emptiable, mergeable::Mergeable} }; static DEFAULT_P2P_LISTEN_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))]; static DEFAULT_P2P_PORT: u16 = 0; fn keypair_to_base64(keypair: &Keypair) -> String { let vec = match keypair.to_protobuf_encoding() { Ok(x) => x, Err(_) => unreachable!(), }; BASE64_STANDARD.encode(vec) } fn base64_to_keypair(base64: &str) -> Result { let vec = BASE64_STANDARD.decode(base64)?; Ok(Keypair::from_protobuf_encoding(&vec)?) } #[derive(Clone, Debug)] pub struct P2pConfig { pub secret: Keypair, pub listen_ips: Vec, pub port: u16, } impl P2pConfig { async fn try_into_swarm (self) -> Result, Error> { let mut swarm = libp2p::SwarmBuilder::with_existing_identity(self.secret) .with_tokio() .with_tcp( tcp::Config::default(), noise::Config::new, yamux::Config::default, )? .with_behaviour(|keypair| p2p::Behaviour::try_from(keypair).unwrap())? .build(); swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?; Ok(swarm) } pub async fn launch_swarm(self) -> Result<(), Error>{ let mut swarm = self.try_into_swarm().await?; loop{ let swarm_event = swarm.select_next_some().await; tokio::spawn(async move{ match swarm_event { SwarmEvent::NewListenAddr { address, .. } => println!("Listening on {address:?}"), SwarmEvent::Behaviour(event) => { println!("{event:?}"); event.run().await; }, _ => {} } }); } } } impl TryFrom for P2pConfig { type Error = Error; fn try_from(raw: PartialP2pConfig) -> Result { Ok(P2pConfig { secret: base64_to_keypair(&raw.secret.ok_or(Error::MissingConfig("secret"))?)?, listen_ips: raw.listen_ips.ok_or(Error::MissingConfig("listen_ips"))?, port: raw.port.ok_or(Error::MissingConfig("port"))? }) } } #[cfg_attr(feature="desktop",derive(Args))] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub struct PartialP2pConfig { #[cfg_attr(feature="desktop",arg(long))] pub secret: Option, #[cfg_attr(feature="desktop",arg(long))] pub listen_ips: Option>, #[cfg_attr(feature="desktop",arg(long))] pub port: Option, } impl PartialP2pConfig { pub fn with_new_secret(mut self) -> Self { self.secret = Some(keypair_to_base64(&Keypair::generate_ed25519())); self } } impl From for PartialP2pConfig { fn from(config: P2pConfig) -> Self { Self { secret: Some(keypair_to_base64(&config.secret)), listen_ips: Some(config.listen_ips), port: Some(config.port) } } } impl Default for PartialP2pConfig { fn default() -> Self { Self { secret: None, listen_ips: Some(Vec::from(DEFAULT_P2P_LISTEN_IPS)), port: Some(DEFAULT_P2P_PORT), } } } impl Emptiable for PartialP2pConfig { fn empty() -> Self { Self{ secret: None, listen_ips: None, port: None } } fn is_empty(&self) -> bool { self.secret.is_none() && self.listen_ips.is_none() && self.port.is_none() } } impl Mergeable for PartialP2pConfig { fn merge(&mut self, mut other: Self) { if let Some(x) = other.secret.take() { let _ = self.secret.insert(x); }; if let Some(x) = other.listen_ips.take() { let _ = self.listen_ips.insert(x); }; if let Some(x) = other.port.take() { let _ = self.port.insert(x); }; } } #[cfg(test)] mod tests { use libp2p::identity; use super::*; use crate::{tests::test_toml_serialize_deserialize}; #[tokio::test] async fn parse_keypair() { let keypair = identity::Keypair::generate_ed25519(); let keypair2 = base64_to_keypair(&keypair_to_base64(&keypair)).unwrap(); assert_eq!(keypair.public(), keypair2.public()); } #[tokio::test] async fn test_p2p_config_serialize_deserialize() { test_toml_serialize_deserialize(PartialP2pConfig::empty()); test_toml_serialize_deserialize(PartialP2pConfig::default()); } }