1use std::fmt;
4
5use a7n_proto::limits;
6use rand_core::{CryptoRng, RngCore};
7use zeroize::{Zeroize, ZeroizeOnDrop};
8
9#[derive(Clone, Zeroize, ZeroizeOnDrop)]
11pub struct PairingCode(pub(super) [u8; limits::PAIRING_CODE_LENGTH]);
12
13#[derive(Debug, thiserror::Error, PartialEq, Eq)]
15#[error("pairing code must contain exactly six ASCII digits")]
16pub struct InvalidPairingCode;
17
18impl PairingCode {
19 pub fn generate(rng: &mut (impl CryptoRng + RngCore)) -> Self {
21 const DECIMAL_RADIX: u8 = 10;
22 const UNBIASED_END: u8 = u8::MAX - u8::MAX % DECIMAL_RADIX;
23 let mut digits = [0; limits::PAIRING_CODE_LENGTH];
24 for digit in &mut digits {
25 loop {
26 let mut byte = [0];
27 rng.fill_bytes(&mut byte);
28 if byte[0] < UNBIASED_END {
29 *digit = b'0' + byte[0] % DECIMAL_RADIX;
30 break;
31 }
32 }
33 }
34 Self(digits)
35 }
36
37 pub fn parse(input: &str) -> Result<Self, InvalidPairingCode> {
42 let digits = input
43 .as_bytes()
44 .try_into()
45 .map_err(|_| InvalidPairingCode)?;
46 if !input.bytes().all(|byte| byte.is_ascii_digit()) {
47 return Err(InvalidPairingCode);
48 }
49 Ok(Self(digits))
50 }
51}
52
53impl fmt::Display for PairingCode {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 f.write_str(std::str::from_utf8(&self.0).expect("PairingCode contains only ASCII digits"))
56 }
57}
58
59impl fmt::Debug for PairingCode {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 f.write_str("PairingCode([redacted])")
62 }
63}