PeerId rework: PeerId is now unique and enforces b32-encoded destination

This commit is contained in:
Philip (a-0) 2024-03-23 18:16:46 +01:00
parent 636aff64b9
commit a68c08292f
5 changed files with 57 additions and 67 deletions

9
Cargo.lock generated
View file

@ -3276,9 +3276,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.10.2" version = "1.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -3288,9 +3288,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.4.3" version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -4357,6 +4357,7 @@ dependencies = [
"i2p", "i2p",
"itertools 0.12.0", "itertools 0.12.0",
"jsonwebtoken", "jsonwebtoken",
"regex",
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",

View file

@ -10,6 +10,7 @@ axum = { version = "0.7.2", features = [ "macros" ] }
chrono = "0.4.31" chrono = "0.4.31"
itertools = "0.12.0" itertools = "0.12.0"
jsonwebtoken = "9.2.0" jsonwebtoken = "9.2.0"
regex = "1.10.4"
reqwest = "0.11.23" reqwest = "0.11.23"
serde = { version = "1.0.166", features = [ "derive" ] } serde = { version = "1.0.166", features = [ "derive" ] }
serde_json = "1.0.99" serde_json = "1.0.99"

View file

@ -1,89 +1,78 @@
use std::hash::Hash; use std::hash::Hash;
use anyhow::{anyhow, bail}; use anyhow::{anyhow, bail, Error};
use i2p::net::{I2pAddr, I2pSocketAddr, ToI2pSocketAddrs}; use i2p::net::{I2pAddr, I2pSocketAddr, ToI2pSocketAddrs};
use regex::Regex;
use serde::{Deserialize, Serialize}; 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 { pub struct PeerId {
i2p_dest: I2pSocketAddr, addr: I2pSocketAddr,
i2p_b32: Option<I2pAddr>,
} }
impl PeerId { impl PeerId {
pub fn addr(&self) -> I2pSocketAddr { pub fn try_from_b32(addr: &str, port: Option<u16>) -> anyhow::Result<Self> {
self.i2p_dest.to_owned() 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."))
} }
pub fn addr_ref(&self) -> &I2pSocketAddr {
&self.i2p_dest
}
pub fn b32_addr(&mut self) -> anyhow::Result<I2pAddr> {
let result = I2pAddr::from_b64(&self.i2p_dest.dest().string());
if let Ok(addr) = &result {
self.i2p_b32 = Some(addr.to_owned());
}
result.map_err(|e| anyhow!(e))
}
pub fn b32_addr_nocache(&self) -> anyhow::Result<I2pAddr> {
I2pAddr::from_b64(&self.i2p_dest.dest().string()).map_err(|e| anyhow!(e))
} }
pub fn try_from_dest(dest: &str, port: Option<u16>) -> anyhow::Result<Self> {
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), pub fn addr(&self) -> I2pSocketAddr {
// and not on whether the b32 address has been computed before self.addr.to_owned()
impl Hash for PeerId { }
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.i2p_dest.hash(state); pub fn addr_ref(&self) -> &I2pSocketAddr {
&self.addr
} }
} }
impl ToString for PeerId { impl ToString for PeerId {
fn to_string(&self) -> String { fn to_string(&self) -> String {
self.i2p_dest.to_string() self.addr.to_string()
}
}
impl TryFrom<&str> for PeerId {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<Self, anyhow::Error> {
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<String> for PeerId {
type Error = anyhow::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::try_from(value.as_str())
}
}
impl From<I2pSocketAddr> for PeerId {
fn from(value: I2pSocketAddr) -> Self {
PeerId { i2p_dest: value, i2p_b32: None }
} }
} }
impl From<PeerId> for I2pSocketAddr { impl From<PeerId> for I2pSocketAddr {
fn from(value: PeerId) -> Self { fn from(value: PeerId) -> Self {
value.i2p_dest value.addr
}
}
impl From<I2pSocketAddr> for PeerId {
fn from(value: I2pSocketAddr) -> Self {
PeerId { addr: value }
} }
} }
impl Default for PeerId { impl Default for PeerId {
fn default() -> Self { fn default() -> Self {
PeerId { PeerId {
i2p_dest: I2pSocketAddr::new(I2pAddr::new(""), 0), addr: I2pSocketAddr::new(I2pAddr::new(""), 0),
i2p_b32: None
} }
} }
} }
#[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())
}
}

View file

@ -11,9 +11,9 @@ use crate::state::CommState;
pub fn handle(state: &CommState, peer: &PeerId, message: Message) { pub fn handle(state: &CommState, peer: &PeerId, message: Message) {
debug!( debug!(
"Received message.\nFrom: {:?} (dest: {:?})\nTo: {:?} (dest: {:?})\nMessage: {message:?}", "Received message.\nFrom: {:?} (dest: {:?})\nTo: {:?} (dest: {:?})\nMessage: {message:?}",
peer.b32_addr_nocache(), peer.addr(),
peer, peer,
state.own_peer_id().unwrap().b32_addr_nocache(), state.own_peer_id().unwrap().addr(),
state.own_peer_id().unwrap() state.own_peer_id().unwrap()
); );
match message.content() { match message.content() {

View file

@ -40,7 +40,6 @@ impl CommHandle {
let listener = listener_builder.build().unwrap(); let listener = listener_builder.build().unwrap();
let mut own_peer_id: PeerId = (&listener).local_addr().map_err(|e| anyhow!(e))?.into(); let mut own_peer_id: PeerId = (&listener).local_addr().map_err(|e| anyhow!(e))?.into();
own_peer_id.b32_addr();
Ok(CommHandle { Ok(CommHandle {
state: Arc::new(state), state: Arc::new(state),
@ -96,7 +95,7 @@ impl CommHandle {
} }
pub async fn send(&self, dest: &I2pSocketAddr, msg: Message) -> anyhow::Result<()> { 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) { match serde_json::to_string(&msg) {
Ok(msg_string) => { Ok(msg_string) => {
self.send_to_addr(dest, msg_string.as_bytes()).await?; self.send_to_addr(dest, msg_string.as_bytes()).await?;
@ -150,8 +149,8 @@ impl CommHandle {
self.peer_id.addr() self.peer_id.addr()
} }
pub fn i2p_b32_address(&self) -> anyhow::Result<I2pAddr> { pub fn i2p_b32_address(&self) -> I2pAddr {
self.peer_id.b32_addr_nocache() self.peer_id.addr().dest()
} }
pub fn own_peer_id(&self) -> anyhow::Result<PeerId> { pub fn own_peer_id(&self) -> anyhow::Result<PeerId> {