use std::cmp::Ordering;
use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub const PEER_ID_BYTE_LEN: usize = 32;
const SHORT_HEX_BYTES: usize = 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PeerId(pub(crate) [u8; PEER_ID_BYTE_LEN]);
impl Serialize for PeerId {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_hex())
}
}
impl<'de> Deserialize<'de> for PeerId {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
PeerId::from_hex(&s).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PeerIdParseError {
InvalidHexEncoding(String),
InvalidLength {
expected: usize,
actual: usize,
},
}
impl fmt::Display for PeerIdParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PeerIdParseError::InvalidHexEncoding(reason) => {
write!(f, "Invalid hex encoding for PeerId: {reason}")
}
PeerIdParseError::InvalidLength { expected, actual } => {
write!(
f,
"Invalid PeerId length: expected {expected} bytes, got {actual}"
)
}
}
}
}
impl std::error::Error for PeerIdParseError {}
impl PeerId {
pub fn to_bytes(&self) -> &[u8; PEER_ID_BYTE_LEN] {
&self.0
}
pub fn as_bytes(&self) -> &[u8; PEER_ID_BYTE_LEN] {
&self.0
}
pub fn xor_distance(&self, other: &PeerId) -> [u8; PEER_ID_BYTE_LEN] {
let mut distance = [0u8; PEER_ID_BYTE_LEN];
for (i, out) in distance.iter_mut().enumerate() {
*out = self.0[i] ^ other.0[i];
}
distance
}
pub fn distance(&self, other: &PeerId) -> [u8; PEER_ID_BYTE_LEN] {
self.xor_distance(other)
}
pub fn from_hex(hex_str: &str) -> Result<Self, PeerIdParseError> {
let bytes = hex::decode(hex_str).map_err(|e| {
PeerIdParseError::InvalidHexEncoding(format!("Invalid hex for PeerId: {e}"))
})?;
if bytes.len() != PEER_ID_BYTE_LEN {
return Err(PeerIdParseError::InvalidLength {
expected: PEER_ID_BYTE_LEN,
actual: bytes.len(),
});
}
let mut id = [0u8; PEER_ID_BYTE_LEN];
id.copy_from_slice(&bytes);
Ok(Self(id))
}
pub fn to_hex(&self) -> String {
hex::encode(self.0)
}
pub fn short_hex(&self) -> String {
hex::encode(&self.0[..SHORT_HEX_BYTES])
}
pub fn from_bytes(bytes: [u8; PEER_ID_BYTE_LEN]) -> Self {
Self(bytes)
}
pub fn from_name(name: &str) -> Self {
let hash = blake3::hash(name.as_bytes());
Self(*hash.as_bytes())
}
pub fn random() -> Self {
Self(rand::random())
}
pub fn new(data: &[u8]) -> Self {
let hash = blake3::hash(data);
Self(*hash.as_bytes())
}
}
impl fmt::Display for PeerId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", hex::encode(self.0))
}
}
impl Ord for PeerId {
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
impl PartialOrd for PeerId {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl From<[u8; PEER_ID_BYTE_LEN]> for PeerId {
fn from(bytes: [u8; PEER_ID_BYTE_LEN]) -> Self {
Self(bytes)
}
}
impl FromStr for PeerId {
type Err = PeerIdParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_hex(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hex_roundtrip() {
let id = PeerId([0xAB; PEER_ID_BYTE_LEN]);
let hex = id.to_hex();
assert_eq!(hex.len(), 64);
assert_eq!(hex, "ab".repeat(32));
let parsed = PeerId::from_hex(&hex).unwrap();
assert_eq!(id, parsed);
}
#[test]
fn test_postcard_roundtrip() {
let id = PeerId([0xAB; PEER_ID_BYTE_LEN]);
let bytes = postcard::to_stdvec(&id).unwrap();
let deserialized: PeerId = postcard::from_bytes(&bytes).unwrap();
assert_eq!(id, deserialized);
}
#[test]
fn test_json_roundtrip() {
let id = PeerId([0xAB; PEER_ID_BYTE_LEN]);
let json = serde_json::to_string(&id).unwrap();
assert_eq!(json, format!("\"{}\"", "ab".repeat(32)));
let deserialized: PeerId = serde_json::from_str(&json).unwrap();
assert_eq!(id, deserialized);
}
#[test]
fn test_xor_distance() {
let id1 = PeerId([0u8; PEER_ID_BYTE_LEN]);
let mut id2_bytes = [0u8; PEER_ID_BYTE_LEN];
id2_bytes[0] = 0xFF;
let id2 = PeerId(id2_bytes);
let distance = id1.xor_distance(&id2);
assert_eq!(distance[0], 0xFF);
for byte in &distance[1..] {
assert_eq!(*byte, 0);
}
}
#[test]
fn test_display_full_hex() {
let id = PeerId([0xAB; PEER_ID_BYTE_LEN]);
let display = format!("{id}");
assert_eq!(display.len(), 64);
assert_eq!(display, "ab".repeat(32));
}
#[test]
fn test_short_hex() {
let id = PeerId([0xAB; PEER_ID_BYTE_LEN]);
let short = id.short_hex();
assert_eq!(short.len(), 16);
assert_eq!(short, "ab".repeat(8));
}
#[test]
fn test_ord() {
let a = PeerId([0x00; PEER_ID_BYTE_LEN]);
let b = PeerId([0xFF; PEER_ID_BYTE_LEN]);
assert!(a < b);
}
#[test]
fn test_from_str() {
let hex = "ab".repeat(32);
let id: PeerId = hex.parse().unwrap();
assert_eq!(id.0, [0xAB; PEER_ID_BYTE_LEN]);
}
#[test]
fn test_from_str_invalid_hex() {
let result = "not-hex".parse::<PeerId>();
assert!(matches!(
result,
Err(PeerIdParseError::InvalidHexEncoding(_))
));
}
#[test]
fn test_from_str_wrong_length() {
let result = "aabb".parse::<PeerId>();
assert!(matches!(
result,
Err(PeerIdParseError::InvalidLength {
expected: 32,
actual: 2,
})
));
}
#[test]
fn test_copy_semantics() {
let a = PeerId([0x42; PEER_ID_BYTE_LEN]);
let b = a; assert_eq!(a, b); }
#[test]
fn test_from_name_deterministic() {
let a = PeerId::from_name("test-peer");
let b = PeerId::from_name("test-peer");
assert_eq!(a, b);
let c = PeerId::from_name("other-peer");
assert_ne!(a, c);
}
#[test]
fn test_from_bytes() {
let bytes = [0x42; PEER_ID_BYTE_LEN];
let id = PeerId::from_bytes(bytes);
assert_eq!(id.0, bytes);
}
#[test]
fn test_from_array() {
let bytes = [0x42; PEER_ID_BYTE_LEN];
let id = PeerId::from(bytes);
assert_eq!(id.0, bytes);
}
#[test]
fn test_new_deterministic() {
let a = PeerId::new(b"some data");
let b = PeerId::new(b"some data");
assert_eq!(a, b);
let c = PeerId::new(b"other data");
assert_ne!(a, c);
}
#[test]
fn test_distance_alias() {
let a = PeerId([0x00; PEER_ID_BYTE_LEN]);
let b = PeerId([0xFF; PEER_ID_BYTE_LEN]);
assert_eq!(a.distance(&b), a.xor_distance(&b));
}
}