From a68c08292f297e6ab695374b134df73e1f219b43 Mon Sep 17 00:00:00 2001 From: "Philip (a-0)" <@ph:a-0.me> Date: Sat, 23 Mar 2024 18:16:46 +0100 Subject: [PATCH] PeerId rework: PeerId is now unique and enforces b32-encoded destination --- Cargo.lock | 9 ++- ubisync-lib/Cargo.toml | 1 + ubisync-lib/src/types/peer_id.rs | 103 ++++++++++++-------------- ubisync/src/comm/message_processor.rs | 4 +- ubisync/src/comm/mod.rs | 7 +- 5 files changed, 57 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0332b5..62bd36a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3276,9 +3276,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -3288,9 +3288,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -4357,6 +4357,7 @@ dependencies = [ "i2p", "itertools 0.12.0", "jsonwebtoken", + "regex", "reqwest", "serde", "serde_json", diff --git a/ubisync-lib/Cargo.toml b/ubisync-lib/Cargo.toml index 2131140..0594349 100644 --- a/ubisync-lib/Cargo.toml +++ b/ubisync-lib/Cargo.toml @@ -10,6 +10,7 @@ axum = { version = "0.7.2", features = [ "macros" ] } chrono = "0.4.31" itertools = "0.12.0" jsonwebtoken = "9.2.0" +regex = "1.10.4" reqwest = "0.11.23" serde = { version = "1.0.166", features = [ "derive" ] } serde_json = "1.0.99" diff --git a/ubisync-lib/src/types/peer_id.rs b/ubisync-lib/src/types/peer_id.rs index 5cf7e29..71fb01b 100644 --- a/ubisync-lib/src/types/peer_id.rs +++ b/ubisync-lib/src/types/peer_id.rs @@ -1,89 +1,78 @@ use std::hash::Hash; -use anyhow::{anyhow, bail}; +use anyhow::{anyhow, bail, Error}; use i2p::net::{I2pAddr, I2pSocketAddr, ToI2pSocketAddrs}; +use regex::Regex; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +/// Uniquely identifies a peer. The I2pAddr inside `addr` MUST be the peer's b32 address. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct PeerId { - i2p_dest: I2pSocketAddr, - i2p_b32: Option, + addr: I2pSocketAddr, } impl PeerId { - pub fn addr(&self) -> I2pSocketAddr { - self.i2p_dest.to_owned() - } - pub fn addr_ref(&self) -> &I2pSocketAddr { - &self.i2p_dest - } - pub fn b32_addr(&mut self) -> anyhow::Result { - let result = I2pAddr::from_b64(&self.i2p_dest.dest().string()); - if let Ok(addr) = &result { - self.i2p_b32 = Some(addr.to_owned()); + pub fn try_from_b32(addr: &str, port: Option) -> anyhow::Result { + let b32_regex = Regex::new(r"[abcdefghijklmnopqrstuvwxyz234567]{52}.b32.i2p").unwrap(); + if b32_regex.is_match(addr) { + Ok(PeerId { + addr: I2pSocketAddr::new(I2pAddr::new(addr), port.unwrap_or(0)), + }) + } else { + Err(Error::msg("The supplied address was not b32-formatted.")) } - result.map_err(|e| anyhow!(e)) } - pub fn b32_addr_nocache(&self) -> anyhow::Result { - I2pAddr::from_b64(&self.i2p_dest.dest().string()).map_err(|e| anyhow!(e)) + pub fn try_from_dest(dest: &str, port: Option) -> anyhow::Result { + let b32 = I2pAddr::from_b64(dest).map_err(|e| anyhow!(e))?; + Ok(PeerId { + addr: I2pSocketAddr::new(b32, port.unwrap_or(0)), + }) } -} -// The identity of the PeerId only depends on the i2p_dest (which is unique), -// and not on whether the b32 address has been computed before -impl Hash for PeerId { - fn hash(&self, state: &mut H) { - self.i2p_dest.hash(state); + pub fn addr(&self) -> I2pSocketAddr { + self.addr.to_owned() + } + + pub fn addr_ref(&self) -> &I2pSocketAddr { + &self.addr } } impl ToString for PeerId { fn to_string(&self) -> String { - self.i2p_dest.to_string() - } -} - -impl TryFrom<&str> for PeerId { - type Error = anyhow::Error; - - fn try_from(value: &str) -> Result { - match ToI2pSocketAddrs::to_socket_addrs(&value) { - Ok(addr_iter) => { - for addr in addr_iter { - return Ok(PeerId { i2p_dest: addr, i2p_b32: None }); - } - return Err(anyhow::Error::msg("No valid I2P address found")); - } - Err(e) => bail!(e), - } - } -} - -impl TryFrom for PeerId { - type Error = anyhow::Error; - - fn try_from(value: String) -> Result { - Self::try_from(value.as_str()) - } -} - -impl From for PeerId { - fn from(value: I2pSocketAddr) -> Self { - PeerId { i2p_dest: value, i2p_b32: None } + self.addr.to_string() } } impl From for I2pSocketAddr { fn from(value: PeerId) -> Self { - value.i2p_dest + value.addr + } +} + +impl From for PeerId { + fn from(value: I2pSocketAddr) -> Self { + PeerId { addr: value } } } impl Default for PeerId { fn default() -> Self { PeerId { - i2p_dest: I2pSocketAddr::new(I2pAddr::new(""), 0), - i2p_b32: None + addr: I2pSocketAddr::new(I2pAddr::new(""), 0), } } } + +#[cfg(test)] +mod tests { + use super::PeerId; + + #[test] + fn from_b32() { + let addr = "abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrst.b32.i2p"; + let peer_id = PeerId::try_from_b32(addr, None); + + assert!(peer_id.is_ok()) + } +} diff --git a/ubisync/src/comm/message_processor.rs b/ubisync/src/comm/message_processor.rs index f677104..a585c6a 100644 --- a/ubisync/src/comm/message_processor.rs +++ b/ubisync/src/comm/message_processor.rs @@ -11,9 +11,9 @@ use crate::state::CommState; pub fn handle(state: &CommState, peer: &PeerId, message: Message) { debug!( "Received message.\nFrom: {:?} (dest: {:?})\nTo: {:?} (dest: {:?})\nMessage: {message:?}", - peer.b32_addr_nocache(), + peer.addr(), peer, - state.own_peer_id().unwrap().b32_addr_nocache(), + state.own_peer_id().unwrap().addr(), state.own_peer_id().unwrap() ); match message.content() { diff --git a/ubisync/src/comm/mod.rs b/ubisync/src/comm/mod.rs index c23ac29..344d8fb 100644 --- a/ubisync/src/comm/mod.rs +++ b/ubisync/src/comm/mod.rs @@ -40,7 +40,6 @@ impl CommHandle { let listener = listener_builder.build().unwrap(); let mut own_peer_id: PeerId = (&listener).local_addr().map_err(|e| anyhow!(e))?.into(); - own_peer_id.b32_addr(); Ok(CommHandle { state: Arc::new(state), @@ -96,7 +95,7 @@ impl CommHandle { } pub async fn send(&self, dest: &I2pSocketAddr, msg: Message) -> anyhow::Result<()> { - debug!("Sending message...\nFrom '{:?}' (dest: {:?})\nTo '{dest:?}'\n Message: '{msg:?}", self.own_peer_id().unwrap().b32_addr_nocache(), self.own_peer_id().unwrap()); + debug!("Sending message...\nFrom '{:?}' (dest: {:?})\nTo '{dest:?}'\n Message: '{msg:?}", self.own_peer_id().unwrap().addr(), self.own_peer_id().unwrap()); match serde_json::to_string(&msg) { Ok(msg_string) => { self.send_to_addr(dest, msg_string.as_bytes()).await?; @@ -150,8 +149,8 @@ impl CommHandle { self.peer_id.addr() } - pub fn i2p_b32_address(&self) -> anyhow::Result { - self.peer_id.b32_addr_nocache() + pub fn i2p_b32_address(&self) -> I2pAddr { + self.peer_id.addr().dest() } pub fn own_peer_id(&self) -> anyhow::Result {