jam-std-common 0.1.16

Common datatypes and utilities for the JAM nodes and tooling
Documentation
mod peer_id;

pub use peer_id::{ParseErr as PeerIdParseErr, PeerId};
use scale::{Decode, Encode, MaxEncodedLen};
use std::{net::SocketAddr, str::FromStr};

/// Address of a peer, including UDP port number. Convertible to/from [`SocketAddr`]. The reason we
/// don't just use [`SocketAddr`] is that [`SocketAddr`] values cannot be directly SCALE
/// encoded/decoded; `PeerAddr` values can be.
#[derive(Clone, Copy, Encode, Decode, MaxEncodedLen, PartialEq, Eq)]
pub struct PeerAddr {
	/// IPv6 address. May be an [IPv4-mapped
	/// address](https://www.rfc-editor.org/rfc/rfc4291#section-2.5.5.2).
	pub ip: [u8; 16],
	/// UDP port.
	pub port: u16,
}

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)
	}
}

/// ID and address of a peer.
#[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)
	}
}

/// Error parsing peer details (ID and address).
#[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()
	}
}

#[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() {
			// IPv4-mapped address
			(rng.random::<u32>() as u128) | (0xffff << 32)
		} else {
			// Global unicast address
			(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() }
	}
}