2025-06-19 07:24:44 +09:00
|
|
|
use std::{net::{IpAddr, Ipv4Addr}, ops, path::{Path, PathBuf}};
|
2025-05-30 09:26:47 +09:00
|
|
|
|
2025-06-01 15:18:17 +09:00
|
|
|
use base64::{prelude::BASE64_STANDARD, Engine};
|
2025-06-18 08:36:01 +09:00
|
|
|
#[cfg(feature="desktop")]
|
|
|
|
use clap::Args;
|
2025-06-02 12:02:04 +09:00
|
|
|
use libp2p::{identity::{self, DecodingError, Keypair}, noise, ping, tcp, yamux, Swarm};
|
2025-05-24 06:11:00 +09:00
|
|
|
use serde::{Deserialize, Serialize};
|
2025-06-01 16:29:35 +09:00
|
|
|
use tokio::{fs::File, io::{AsyncReadExt, AsyncWriteExt}};
|
2025-06-02 12:02:04 +09:00
|
|
|
use tracing_subscriber::EnvFilter;
|
2025-05-24 06:11:00 +09:00
|
|
|
|
2025-06-01 15:18:17 +09:00
|
|
|
|
2025-06-18 08:36:01 +09:00
|
|
|
use crate::{
|
|
|
|
config::PartialConfig,
|
|
|
|
error::Error, p2p
|
|
|
|
};
|
2025-06-01 15:18:17 +09:00
|
|
|
|
2025-06-19 07:24:44 +09:00
|
|
|
static DEFAULT_P2P_LISTEN_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))];
|
|
|
|
static DEFAULT_P2P_PORT: u16 = 0;
|
|
|
|
|
2025-06-18 08:36:01 +09:00
|
|
|
fn keypair_to_base64(keypair: &Keypair) -> String {
|
|
|
|
let vec = match keypair.to_protobuf_encoding() {
|
|
|
|
Ok(x) => x,
|
|
|
|
Err(_) => unreachable!(),
|
|
|
|
};
|
|
|
|
BASE64_STANDARD.encode(vec)
|
2025-06-01 15:18:17 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
fn base64_to_keypair(base64: &str) -> Result<Keypair, Error> {
|
|
|
|
let vec = BASE64_STANDARD.decode(base64)?;
|
|
|
|
Ok(Keypair::from_protobuf_encoding(&vec)?)
|
|
|
|
}
|
2025-05-30 09:26:47 +09:00
|
|
|
|
2025-06-05 09:23:24 +09:00
|
|
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
2025-06-19 07:24:44 +09:00
|
|
|
pub struct P2pConfig {
|
2025-06-01 15:18:17 +09:00
|
|
|
#[serde(with = "keypair_parser")]
|
2025-06-02 12:02:04 +09:00
|
|
|
pub secret: Keypair,
|
|
|
|
pub listen_ips: Vec<IpAddr>,
|
|
|
|
pub port: u16,
|
2025-05-30 09:26:47 +09:00
|
|
|
}
|
|
|
|
|
2025-06-19 07:24:44 +09:00
|
|
|
impl P2pConfig {
|
2025-06-04 07:13:37 +09:00
|
|
|
pub async fn try_into_swarm (self) -> Result<Swarm<p2p::Behaviour>, Error> {
|
2025-06-02 12:02:04 +09:00
|
|
|
let mut swarm = libp2p::SwarmBuilder::with_existing_identity(self.secret)
|
|
|
|
.with_tokio()
|
|
|
|
.with_tcp(
|
|
|
|
tcp::Config::default(),
|
|
|
|
noise::Config::new,
|
|
|
|
yamux::Config::default,
|
|
|
|
)?
|
2025-06-04 07:13:37 +09:00
|
|
|
.with_behaviour(|keypair| p2p::Behaviour::try_from(keypair).unwrap())?
|
2025-06-02 12:02:04 +09:00
|
|
|
.build();
|
|
|
|
swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
|
|
|
|
Ok(swarm)
|
2025-05-30 09:26:47 +09:00
|
|
|
}
|
2025-06-01 15:18:17 +09:00
|
|
|
}
|
|
|
|
|
2025-06-19 07:24:44 +09:00
|
|
|
impl TryFrom<PartialP2pConfig> for P2pConfig {
|
2025-06-01 15:18:17 +09:00
|
|
|
type Error = Error;
|
2025-06-19 07:24:44 +09:00
|
|
|
fn try_from(raw: PartialP2pConfig) -> Result<P2pConfig, Self::Error> {
|
|
|
|
Ok(P2pConfig {
|
2025-06-01 15:18:17 +09:00
|
|
|
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"))?
|
|
|
|
})
|
2025-05-30 09:26:47 +09:00
|
|
|
}
|
2025-05-24 06:11:00 +09:00
|
|
|
}
|
|
|
|
|
2025-06-01 15:18:17 +09:00
|
|
|
mod keypair_parser {
|
2025-05-24 06:11:00 +09:00
|
|
|
use libp2p::identity::Keypair;
|
|
|
|
use serde::{Deserialize, Deserializer, Serializer};
|
|
|
|
|
|
|
|
pub fn serialize<S>(keypair: &Keypair, serializer: S) -> Result<S::Ok, S::Error>
|
|
|
|
where S: Serializer
|
|
|
|
{
|
2025-06-18 08:36:01 +09:00
|
|
|
serializer.serialize_str(&super::keypair_to_base64(keypair))
|
2025-05-24 06:11:00 +09:00
|
|
|
}
|
|
|
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Keypair, D::Error>
|
|
|
|
where D: Deserializer<'de>
|
|
|
|
{
|
2025-06-01 15:18:17 +09:00
|
|
|
match super::base64_to_keypair(&String::deserialize(deserializer)?) {
|
|
|
|
Ok(x) => Ok(x),
|
|
|
|
Err(crate::error::Error::Base64Decode(_)) => Err(serde::de::Error::custom("Decoding base64 error")),
|
|
|
|
Err(_) => unreachable!()
|
|
|
|
}
|
2025-05-24 06:11:00 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-06-18 08:36:01 +09:00
|
|
|
#[cfg_attr(feature="desktop",derive(Args))]
|
2025-06-19 07:24:44 +09:00
|
|
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
|
|
|
pub struct PartialP2pConfig {
|
2025-06-18 08:36:01 +09:00
|
|
|
#[cfg_attr(feature="desktop",arg(long))]
|
2025-06-02 12:02:04 +09:00
|
|
|
pub secret: Option<String>,
|
2025-06-18 08:36:01 +09:00
|
|
|
#[cfg_attr(feature="desktop",arg(long))]
|
2025-06-02 12:02:04 +09:00
|
|
|
pub listen_ips: Option<Vec<IpAddr>>,
|
2025-06-18 08:36:01 +09:00
|
|
|
#[cfg_attr(feature="desktop",arg(long))]
|
2025-06-02 12:02:04 +09:00
|
|
|
pub port: Option<u16>,
|
2025-06-01 15:18:17 +09:00
|
|
|
}
|
2025-06-19 07:24:44 +09:00
|
|
|
impl PartialP2pConfig {
|
2025-06-01 16:29:35 +09:00
|
|
|
|
|
|
|
pub fn with_new_secret(mut self) -> Self {
|
2025-06-18 08:36:01 +09:00
|
|
|
self.secret = Some(keypair_to_base64(&Keypair::generate_ed25519()));
|
2025-06-01 16:29:35 +09:00
|
|
|
self
|
|
|
|
}
|
|
|
|
pub async fn read_or_create<T>(path: T) -> Result<Self, Error>
|
|
|
|
where
|
|
|
|
T: AsRef<Path>
|
|
|
|
{
|
|
|
|
if !path.as_ref().exists() {
|
2025-06-18 08:36:01 +09:00
|
|
|
Self::empty().write_to(&path).await?;
|
2025-06-01 16:29:35 +09:00
|
|
|
}
|
|
|
|
Self::read_from(&path).await
|
|
|
|
}
|
|
|
|
pub async fn read_from<T>(path:T) -> Result<Self, Error>
|
|
|
|
where
|
|
|
|
T: AsRef<Path>
|
|
|
|
{
|
|
|
|
let mut file = File::open(path.as_ref()).await?;
|
|
|
|
let mut content = String::new();
|
|
|
|
file.read_to_string(&mut content).await?;
|
2025-06-18 08:36:01 +09:00
|
|
|
let config: Self = toml::from_str(&content)?;
|
2025-06-01 16:29:35 +09:00
|
|
|
Ok(config)
|
|
|
|
}
|
|
|
|
pub async fn write_to<T>(&self, path:T) -> Result<(), Error>
|
|
|
|
where
|
|
|
|
T: AsRef<Path>
|
|
|
|
{
|
|
|
|
if !path.as_ref().exists() {
|
|
|
|
if let Some(x) = path.as_ref().parent() {
|
|
|
|
std::fs::create_dir_all(x)?;
|
|
|
|
};
|
|
|
|
let _ = File::create(&path).await?;
|
|
|
|
}
|
|
|
|
let mut file = File::create(&path).await?;
|
|
|
|
file.write_all(toml::to_string(self)?.as_bytes()).await?;
|
|
|
|
Ok(())
|
|
|
|
}
|
2025-06-18 08:36:01 +09:00
|
|
|
}
|
2025-06-02 12:02:04 +09:00
|
|
|
|
2025-06-19 07:24:44 +09:00
|
|
|
impl From<P2pConfig> for PartialP2pConfig {
|
|
|
|
fn from(config: P2pConfig) -> Self {
|
2025-06-18 08:36:01 +09:00
|
|
|
Self {
|
|
|
|
secret: Some(keypair_to_base64(&config.secret)),
|
|
|
|
listen_ips: Some(config.listen_ips),
|
|
|
|
port: Some(config.port)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-06-19 07:24:44 +09:00
|
|
|
|
|
|
|
impl PartialConfig for PartialP2pConfig {
|
2025-06-18 08:36:01 +09:00
|
|
|
fn empty() -> Self {
|
|
|
|
Self {
|
|
|
|
secret: None,
|
|
|
|
listen_ips: None,
|
|
|
|
port: None,
|
|
|
|
}
|
|
|
|
}
|
2025-06-19 07:24:44 +09:00
|
|
|
fn is_empty(&self) -> bool {
|
|
|
|
self.secret.is_none() && self.listen_ips.is_none() && self.port.is_none()
|
|
|
|
}
|
2025-06-18 08:36:01 +09:00
|
|
|
fn merge(&mut self, another: Self) {
|
2025-06-02 12:02:04 +09:00
|
|
|
if let Some(x) = another.secret {
|
|
|
|
self.secret = Some(x);
|
|
|
|
};
|
|
|
|
if let Some(x) = another.listen_ips {
|
|
|
|
self.listen_ips = Some(x);
|
|
|
|
};
|
|
|
|
if let Some(x) = another.port {
|
|
|
|
self.port = Some(x);
|
|
|
|
};
|
|
|
|
}
|
2025-06-18 08:36:01 +09:00
|
|
|
|
|
|
|
fn default() -> Self {
|
2025-06-19 07:24:44 +09:00
|
|
|
Self {
|
|
|
|
secret: None,
|
|
|
|
listen_ips: Some(Vec::from(DEFAULT_P2P_LISTEN_IPS)),
|
|
|
|
port: Some(DEFAULT_P2P_PORT),
|
|
|
|
}
|
2025-06-02 12:02:04 +09:00
|
|
|
}
|
2025-06-01 16:29:35 +09:00
|
|
|
}
|
2025-06-01 15:18:17 +09:00
|
|
|
|
2025-06-19 07:24:44 +09:00
|
|
|
|
|
|
|
|
|
|
|
|
2025-05-24 06:11:00 +09:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use libp2p::identity;
|
|
|
|
use super::*;
|
2025-06-19 07:24:44 +09:00
|
|
|
use crate::{config::PartialConfig, tests::test_toml_serialize_deserialize};
|
2025-05-24 06:11:00 +09:00
|
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
2025-06-01 15:18:17 +09:00
|
|
|
async fn parse_keypair() {
|
2025-05-24 06:11:00 +09:00
|
|
|
let keypair = identity::Keypair::generate_ed25519();
|
2025-06-18 08:36:01 +09:00
|
|
|
let keypair2 = base64_to_keypair(&keypair_to_base64(&keypair)).unwrap();
|
2025-06-01 15:18:17 +09:00
|
|
|
|
|
|
|
assert_eq!(keypair.public(), keypair2.public());
|
2025-05-24 06:11:00 +09:00
|
|
|
}
|
2025-06-19 07:24:44 +09:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_p2p_config_serialize_deserialize() {
|
|
|
|
test_toml_serialize_deserialize(PartialP2pConfig::empty());
|
|
|
|
test_toml_serialize_deserialize(PartialP2pConfig::default());
|
|
|
|
}
|
2025-05-24 06:11:00 +09:00
|
|
|
}
|