jam_std_common/net/
peer_id.rs1use crate::ed25519;
2use codec::{Decode, Encode, MaxEncodedLen};
3
4#[derive(Clone, Copy, Encode, Decode, MaxEncodedLen, Hash, PartialEq, Eq)]
7pub struct PeerId(pub ed25519::Public);
8
9impl PeerId {
10 pub fn to_text(&self) -> Text {
12 self.into()
13 }
14}
15
16impl From<ed25519::Public> for PeerId {
17 fn from(pk: ed25519::Public) -> Self {
18 Self(pk)
19 }
20}
21
22impl core::fmt::Debug for PeerId {
23 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
24 f.pad(self.to_text().as_str())
25 }
26}
27
28impl core::fmt::Display for PeerId {
29 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
30 f.pad(self.to_text().as_str())
31 }
32}
33
34const BITS_TO_CHAR: &[u8; 32] = b"abcdefghijklmnopqrstuvwxyz234567";
37
38const fn gen_char_to_bits() -> [u8; 256] {
39 let mut char_to_bits = [255; 256];
40 let mut i = 0;
41 while i < BITS_TO_CHAR.len() {
42 char_to_bits[BITS_TO_CHAR[i] as usize] = i as u8;
43 i += 1;
44 }
45 char_to_bits
46}
47
48const CHAR_TO_BITS: [u8; 256] = gen_char_to_bits();
49
50const TEXT_SIZE: usize = 1 + (ed25519::PUBLIC_LEN * 8).div_ceil(5);
53pub struct Text([u8; TEXT_SIZE]);
55
56impl Text {
57 pub fn as_str(&self) -> &str {
59 core::str::from_utf8(&self.0).expect("BITS_TO_CHAR only contains ASCII characters")
60 }
61}
62
63impl From<&PeerId> for Text {
64 fn from(peer_id: &PeerId) -> Self {
65 let mut text = [0; TEXT_SIZE];
66 text[0] = b'e';
67 for (char, i) in core::iter::zip(&mut text[1..], (0..).step_by(5)) {
68 let low = peer_id.0.as_bytes()[i / 8];
69 let high = peer_id.0.as_bytes().get((i / 8) + 1).copied().unwrap_or(0);
70 let window = (low as u16) | ((high as u16) << 8);
71 let bits = (window >> (i % 8)) & 0x1f;
72 *char = BITS_TO_CHAR[bits as usize];
73 }
74 Self(text)
75 }
76}
77
78#[derive(Debug, thiserror::Error)]
80pub enum ParseErr {
81 #[error("Bad length {0}; peer IDs are always {TEXT_SIZE} characters long")]
82 BadLength(usize),
83 #[error("Bad prefix; peer IDs always begin with 'e'")]
84 BadPrefix,
85 #[error("Bad character; peer IDs only contain characters from the set {}",
86 core::str::from_utf8(BITS_TO_CHAR).expect("BITS_TO_CHAR only contains ASCII characters"))]
87 BadChar,
88 #[error("Non-zero trailing bits")]
89 NonZeroTrailingBits,
90}
91
92impl TryFrom<&str> for PeerId {
93 type Error = ParseErr;
94
95 fn try_from(text: &str) -> Result<Self, Self::Error> {
96 let text = text.as_bytes();
97 if text.len() != TEXT_SIZE {
98 return Err(ParseErr::BadLength(text.len()));
99 }
100 if text[0] != b'e' {
101 return Err(ParseErr::BadPrefix);
102 }
103 let mut public = [0; ed25519::PUBLIC_LEN];
104 let mut i = 0;
105 let mut acc_bits: u16 = 0;
106 let mut num_acc_bits = 0;
107 for char in &text[1..] {
108 let bits = CHAR_TO_BITS[*char as usize];
109 if bits > 0x1f {
110 return Err(ParseErr::BadChar);
111 }
112 acc_bits |= (bits as u16) << num_acc_bits;
113 num_acc_bits += 5;
114 if num_acc_bits >= 8 {
115 public[i] = acc_bits as u8;
116 i += 1;
117 acc_bits >>= 8;
118 num_acc_bits -= 8;
119 }
120 }
121 assert_eq!(i, ed25519::PUBLIC_LEN);
122 if acc_bits != 0 {
123 return Err(ParseErr::NonZeroTrailingBits);
124 }
125 Ok(Self(ed25519::Public::from(public)))
126 }
127}
128
129impl core::str::FromStr for PeerId {
130 type Err = ParseErr;
131
132 fn from_str(text: &str) -> Result<Self, Self::Err> {
133 text.try_into()
134 }
135}
136
137#[cfg(any(test, feature = "rand"))]
138impl rand::distr::Distribution<PeerId> for rand::distr::StandardUniform {
139 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> PeerId {
140 PeerId(rng.random())
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147 use crate::ed25519::Secret;
148
149 #[test]
150 fn text_round_trip() {
151 let mut rng = rand::rng();
152 for _ in 0..1000 {
153 let peer_id = PeerId(Secret::new(&mut rng).public());
154 assert_eq!(peer_id, peer_id.to_text().as_str().try_into().unwrap());
155 }
156 }
157}