mod peer_id;
use codec::{ConstEncodedLen, Decode, Encode, MaxEncodedLen};
pub use peer_id::{P256PeerId, ParseErr as PeerIdParseErr, PeerId};
use std::{net::SocketAddr, str::FromStr};
#[derive(Clone, Copy, Encode, Decode, MaxEncodedLen, PartialEq, Eq)]
pub struct PeerAddr {
pub ip: [u8; 16],
pub port: u16,
}
impl ConstEncodedLen for PeerAddr {}
impl core::fmt::Debug for PeerAddr {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
SocketAddr::from(*self).fmt(f)
}
}
impl core::fmt::Display for PeerAddr {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
SocketAddr::from(*self).fmt(f)
}
}
impl TryFrom<&str> for PeerAddr {
type Error = std::net::AddrParseError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
Ok(SocketAddr::from_str(s)?.into())
}
}
impl FromStr for PeerAddr {
type Err = std::net::AddrParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.try_into()
}
}
impl From<SocketAddr> for PeerAddr {
fn from(addr: SocketAddr) -> Self {
let ip = match addr.ip() {
std::net::IpAddr::V4(ip) => ip.to_ipv6_mapped(),
std::net::IpAddr::V6(ip) => ip,
};
Self { ip: ip.octets(), port: addr.port() }
}
}
impl From<PeerAddr> for SocketAddr {
fn from(addr: PeerAddr) -> Self {
Self::new(std::net::Ipv6Addr::from(addr.ip).to_canonical(), addr.port)
}
}
#[derive(Clone, Encode, Decode, MaxEncodedLen, PartialEq, Eq)]
pub struct PeerDetails {
pub id: PeerId,
pub addr: PeerAddr,
}
impl core::fmt::Debug for PeerDetails {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
write!(f, "{:?}@{:?}", self.id, self.addr)
}
}
impl core::fmt::Display for PeerDetails {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
write!(f, "{}@{}", self.id, self.addr)
}
}
#[derive(Debug, thiserror::Error)]
pub enum PeerDetailsParseErr {
#[error("No @ character; expected between peer ID and address")]
NoSeparator,
#[error("Bad peer ID: {0}")]
Id(#[from] PeerIdParseErr),
#[error("Bad address: {0}")]
Addr(#[from] std::net::AddrParseError),
}
impl TryFrom<&str> for PeerDetails {
type Error = PeerDetailsParseErr;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let Some((id, addr)) = s.split_once('@') else {
return Err(PeerDetailsParseErr::NoSeparator)
};
Ok(Self { id: id.try_into()?, addr: addr.try_into()? })
}
}
impl FromStr for PeerDetails {
type Err = PeerDetailsParseErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.try_into()
}
}
impl serde::Serialize for PeerDetails {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
impl<'de> serde::Deserialize<'de> for PeerDetails {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let text = <&str>::deserialize(deserializer)?;
text.parse().map_err(serde::de::Error::custom)
}
}
#[cfg(any(test, feature = "rand"))]
impl rand::distr::Distribution<PeerAddr> for rand::distr::StandardUniform {
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> PeerAddr {
let ip = if rng.random() {
(rng.random::<u32>() as u128) | (0xffff << 32)
} else {
(rng.random::<u128>() & !(0x7 << 125)) | (1 << 125)
};
PeerAddr { ip: ip.to_be_bytes(), port: rng.random() }
}
}
#[cfg(any(test, feature = "rand"))]
impl rand::distr::Distribution<PeerDetails> for rand::distr::StandardUniform {
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> PeerDetails {
PeerDetails { id: rng.random(), addr: rng.random() }
}
}
#[derive(Clone, Debug)]
pub struct UnresolvedAddr {
pub host: String,
pub port: u16,
}
impl UnresolvedAddr {
pub fn as_ref(&self) -> (&str, u16) {
(&self.host, self.port)
}
}
impl std::fmt::Display for UnresolvedAddr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.host, self.port)
}
}
impl std::str::FromStr for UnresolvedAddr {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (host, port) = s.rsplit_once(':').ok_or("Expected ':' between host and port")?;
let port = port.parse().map_err(|_| "Bad port")?;
Ok(Self { host: host.to_string(), port })
}
}
#[cfg(test)]
mod test {
#![allow(clippy::unwrap_used)]
use super::*;
use crate::SecretKeyset;
#[test]
fn peer_details_round_trip_as_a_json_string() {
let details = PeerDetails {
id: PeerId(SecretKeyset::trivial(0).ed25519.public()),
addr: "127.0.0.1:40000".parse::<SocketAddr>().unwrap().into(),
};
let json = serde_json::to_string(&details).unwrap();
assert_eq!(json, format!("\"{details}\""));
assert_eq!(serde_json::from_str::<PeerDetails>(&json).unwrap(), details);
}
}