1use std::fmt;
42use std::str::FromStr;
43use thiserror::Error;
44
45const MAX_NICKNAME_LENGTH: usize = 19;
46const FINGERPRINT_LENGTH: usize = 40;
47const ED25519_PUBLIC_KEY_LENGTH: usize = 32;
48
49#[derive(Debug, Error)]
51pub enum FingerprintError {
52 #[error("fingerprint must be exactly 40 hexadecimal characters")]
54 InvalidLength,
55 #[error("fingerprint contains invalid characters")]
57 InvalidCharacters,
58}
59
60#[derive(Debug, Error)]
62pub enum NicknameError {
63 #[error("nickname must be 1-19 characters long")]
65 InvalidLength,
66 #[error("nickname must contain only alphanumeric characters")]
68 InvalidCharacters,
69}
70
71#[derive(Debug, Error)]
73pub enum Ed25519PublicKeyError {
74 #[error("invalid base64 encoding")]
76 InvalidBase64,
77 #[error("invalid key length: expected 32 bytes, got {0}")]
79 InvalidLength(usize),
80}
81
82#[derive(Debug, Error)]
84pub enum Ed25519IdentityError {
85 #[error("invalid base64 encoding")]
87 InvalidBase64,
88 #[error("invalid identity length: expected 32 bytes, got {0}")]
90 InvalidLength(usize),
91}
92
93#[derive(Clone, PartialEq, Eq, Hash)]
120pub struct Fingerprint(String);
121
122impl Fingerprint {
123 pub fn new(s: impl Into<String>) -> Result<Self, FingerprintError> {
131 let s = s.into();
132 if s.len() != FINGERPRINT_LENGTH {
133 return Err(FingerprintError::InvalidLength);
134 }
135 if !s.chars().all(|c| c.is_ascii_hexdigit()) {
136 return Err(FingerprintError::InvalidCharacters);
137 }
138 Ok(Self(s.to_uppercase()))
139 }
140
141 pub fn as_str(&self) -> &str {
143 &self.0
144 }
145
146 pub fn to_lowercase(&self) -> String {
148 self.0.to_lowercase()
149 }
150}
151
152impl fmt::Display for Fingerprint {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 write!(f, "{}", self.0)
155 }
156}
157
158impl fmt::Debug for Fingerprint {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 write!(f, "Fingerprint({})", self.0)
161 }
162}
163
164impl FromStr for Fingerprint {
165 type Err = FingerprintError;
166
167 fn from_str(s: &str) -> Result<Self, Self::Err> {
168 Self::new(s)
169 }
170}
171
172#[derive(Clone, PartialEq, Eq, Hash)]
196pub struct Nickname(String);
197
198impl Nickname {
199 pub fn new(s: impl Into<String>) -> Result<Self, NicknameError> {
205 let s = s.into();
206 let len = s.len();
207 if !(1..=MAX_NICKNAME_LENGTH).contains(&len) {
208 return Err(NicknameError::InvalidLength);
209 }
210 if !s.chars().all(|c| c.is_ascii_alphanumeric()) {
211 return Err(NicknameError::InvalidCharacters);
212 }
213 Ok(Self(s))
214 }
215
216 pub fn as_str(&self) -> &str {
218 &self.0
219 }
220}
221
222impl fmt::Display for Nickname {
223 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224 write!(f, "{}", self.0)
225 }
226}
227
228impl fmt::Debug for Nickname {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 write!(f, "Nickname({})", self.0)
231 }
232}
233
234impl FromStr for Nickname {
235 type Err = NicknameError;
236
237 fn from_str(s: &str) -> Result<Self, Self::Err> {
238 Self::new(s)
239 }
240}
241
242#[derive(Clone, PartialEq, Eq)]
260pub struct Ed25519PublicKey([u8; ED25519_PUBLIC_KEY_LENGTH]);
261
262impl Ed25519PublicKey {
263 pub fn new(bytes: [u8; ED25519_PUBLIC_KEY_LENGTH]) -> Self {
265 Self(bytes)
266 }
267
268 pub fn from_base64(s: &str) -> Result<Self, Ed25519PublicKeyError> {
274 use base64::Engine;
275 let engine = base64::engine::general_purpose::STANDARD;
276 let bytes = engine
277 .decode(s)
278 .map_err(|_| Ed25519PublicKeyError::InvalidBase64)?;
279
280 if bytes.len() != ED25519_PUBLIC_KEY_LENGTH {
281 return Err(Ed25519PublicKeyError::InvalidLength(bytes.len()));
282 }
283
284 let mut array = [0u8; ED25519_PUBLIC_KEY_LENGTH];
285 array.copy_from_slice(&bytes);
286 Ok(Self(array))
287 }
288
289 pub fn as_bytes(&self) -> &[u8; ED25519_PUBLIC_KEY_LENGTH] {
291 &self.0
292 }
293
294 pub fn to_base64(&self) -> String {
296 use base64::Engine;
297 let engine = base64::engine::general_purpose::STANDARD;
298 engine.encode(self.0)
299 }
300}
301
302impl fmt::Display for Ed25519PublicKey {
303 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304 write!(f, "{}", self.to_base64())
305 }
306}
307
308impl fmt::Debug for Ed25519PublicKey {
309 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310 write!(f, "Ed25519PublicKey({})", self.to_base64())
311 }
312}
313
314impl FromStr for Ed25519PublicKey {
315 type Err = Ed25519PublicKeyError;
316
317 fn from_str(s: &str) -> Result<Self, Self::Err> {
318 Self::from_base64(s)
319 }
320}
321
322#[derive(Clone, PartialEq, Eq)]
340pub struct Ed25519Identity([u8; ED25519_PUBLIC_KEY_LENGTH]);
341
342impl Ed25519Identity {
343 pub fn new(bytes: [u8; ED25519_PUBLIC_KEY_LENGTH]) -> Self {
345 Self(bytes)
346 }
347
348 pub fn from_base64(s: &str) -> Result<Self, Ed25519IdentityError> {
354 use base64::Engine;
355 let engine = base64::engine::general_purpose::STANDARD;
356 let bytes = engine
357 .decode(s)
358 .map_err(|_| Ed25519IdentityError::InvalidBase64)?;
359
360 if bytes.len() != ED25519_PUBLIC_KEY_LENGTH {
361 return Err(Ed25519IdentityError::InvalidLength(bytes.len()));
362 }
363
364 let mut array = [0u8; ED25519_PUBLIC_KEY_LENGTH];
365 array.copy_from_slice(&bytes);
366 Ok(Self(array))
367 }
368
369 pub fn as_bytes(&self) -> &[u8; ED25519_PUBLIC_KEY_LENGTH] {
371 &self.0
372 }
373
374 pub fn to_base64(&self) -> String {
376 use base64::Engine;
377 let engine = base64::engine::general_purpose::STANDARD;
378 engine.encode(self.0)
379 }
380}
381
382impl fmt::Display for Ed25519Identity {
383 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384 write!(f, "{}", self.to_base64())
385 }
386}
387
388impl fmt::Debug for Ed25519Identity {
389 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390 write!(f, "Ed25519Identity({})", self.to_base64())
391 }
392}
393
394impl FromStr for Ed25519Identity {
395 type Err = Ed25519IdentityError;
396
397 fn from_str(s: &str) -> Result<Self, Self::Err> {
398 Self::from_base64(s)
399 }
400}
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405
406 #[test]
407 fn test_fingerprint_valid() {
408 let fp = Fingerprint::from_str("9695DFC35FFEB861329B9F1AB04C46397020CE31").unwrap();
409 assert_eq!(fp.as_str(), "9695DFC35FFEB861329B9F1AB04C46397020CE31");
410 }
411
412 #[test]
413 fn test_fingerprint_lowercase() {
414 let fp = Fingerprint::from_str("9695dfc35ffeb861329b9f1ab04c46397020ce31").unwrap();
415 assert_eq!(fp.as_str(), "9695DFC35FFEB861329B9F1AB04C46397020CE31");
416 }
417
418 #[test]
419 fn test_fingerprint_invalid_length() {
420 let result = Fingerprint::from_str("ABCD");
421 assert!(matches!(result, Err(FingerprintError::InvalidLength)));
422 }
423
424 #[test]
425 fn test_fingerprint_invalid_chars() {
426 let result = Fingerprint::from_str("ZZZZDFC35FFEB861329B9F1AB04C46397020CE31");
427 assert!(matches!(result, Err(FingerprintError::InvalidCharacters)));
428 }
429
430 #[test]
431 fn test_nickname_valid() {
432 let nick = Nickname::from_str("MyRelay").unwrap();
433 assert_eq!(nick.as_str(), "MyRelay");
434 }
435
436 #[test]
437 fn test_nickname_single_char() {
438 let nick = Nickname::from_str("A").unwrap();
439 assert_eq!(nick.as_str(), "A");
440 }
441
442 #[test]
443 fn test_nickname_max_length() {
444 let nick = Nickname::from_str("1234567890123456789").unwrap();
445 assert_eq!(nick.as_str(), "1234567890123456789");
446 }
447
448 #[test]
449 fn test_nickname_too_long() {
450 let result = Nickname::from_str("12345678901234567890");
451 assert!(matches!(result, Err(NicknameError::InvalidLength)));
452 }
453
454 #[test]
455 fn test_nickname_empty() {
456 let result = Nickname::from_str("");
457 assert!(matches!(result, Err(NicknameError::InvalidLength)));
458 }
459
460 #[test]
461 fn test_nickname_invalid_chars() {
462 let result = Nickname::from_str("my-relay");
463 assert!(matches!(result, Err(NicknameError::InvalidCharacters)));
464 }
465
466 #[test]
467 fn test_ed25519_public_key_roundtrip() {
468 let bytes = [42u8; 32];
469 let key = Ed25519PublicKey::new(bytes);
470 let base64 = key.to_base64();
471 let decoded = Ed25519PublicKey::from_base64(&base64).unwrap();
472 assert_eq!(key, decoded);
473 }
474
475 #[test]
476 fn test_ed25519_identity_roundtrip() {
477 let bytes = [99u8; 32];
478 let identity = Ed25519Identity::new(bytes);
479 let base64 = identity.to_base64();
480 let decoded = Ed25519Identity::from_base64(&base64).unwrap();
481 assert_eq!(identity, decoded);
482 }
483}