Skip to main content

a7n_core/pairing/
code.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3use std::fmt;
4
5use a7n_proto::limits;
6use rand_core::{CryptoRng, RngCore};
7use zeroize::{Zeroize, ZeroizeOnDrop};
8
9/// Human-entered decimal secret. Debug output deliberately hides the code.
10#[derive(Clone, Zeroize, ZeroizeOnDrop)]
11pub struct PairingCode(pub(super) [u8; limits::PAIRING_CODE_LENGTH]);
12
13/// Invalid pairing-code input; never includes the supplied secret.
14#[derive(Debug, thiserror::Error, PartialEq, Eq)]
15#[error("pairing code must contain exactly six ASCII digits")]
16pub struct InvalidPairingCode;
17
18impl PairingCode {
19    /// Generate independent uniform decimal digits, without modulo bias.
20    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    /// Parse exactly the displayed ASCII digits, retaining leading zeroes.
38    ///
39    /// # Errors
40    /// Returns an opaque error for any other input.
41    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}