use std::fmt;
use a7n_proto::limits;
use rand_core::{CryptoRng, RngCore};
use zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct PairingCode(pub(super) [u8; limits::PAIRING_CODE_LENGTH]);
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
#[error("pairing code must contain exactly six ASCII digits")]
pub struct InvalidPairingCode;
impl PairingCode {
pub fn generate(rng: &mut (impl CryptoRng + RngCore)) -> Self {
const DECIMAL_RADIX: u8 = 10;
const UNBIASED_END: u8 = u8::MAX - u8::MAX % DECIMAL_RADIX;
let mut digits = [0; limits::PAIRING_CODE_LENGTH];
for digit in &mut digits {
loop {
let mut byte = [0];
rng.fill_bytes(&mut byte);
if byte[0] < UNBIASED_END {
*digit = b'0' + byte[0] % DECIMAL_RADIX;
break;
}
}
}
Self(digits)
}
pub fn parse(input: &str) -> Result<Self, InvalidPairingCode> {
let digits = input
.as_bytes()
.try_into()
.map_err(|_| InvalidPairingCode)?;
if !input.bytes().all(|byte| byte.is_ascii_digit()) {
return Err(InvalidPairingCode);
}
Ok(Self(digits))
}
}
impl fmt::Display for PairingCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(std::str::from_utf8(&self.0).expect("PairingCode contains only ASCII digits"))
}
}
impl fmt::Debug for PairingCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("PairingCode([redacted])")
}
}