1use core::fmt;
2
3use subtle::{Choice, ConstantTimeEq};
4
5use crate::{CodeError, Error};
6
7#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
12pub struct Digits(u8);
13
14impl Digits {
15 pub const SIX: Self = Self(6);
17 pub const SEVEN: Self = Self(7);
19 pub const EIGHT: Self = Self(8);
21
22 pub const fn new(value: u8) -> Result<Self, Error> {
28 if value >= 6 && value <= 8 {
29 Ok(Self(value))
30 } else {
31 Err(Error::InvalidDigits { actual: value })
32 }
33 }
34
35 #[must_use]
37 pub const fn get(self) -> u8 {
38 self.0
39 }
40
41 pub(crate) const fn modulus(self) -> u32 {
42 match self.0 {
43 6 => 1_000_000,
44 7 => 10_000_000,
45 8 => 100_000_000,
46 _ => unreachable!(),
47 }
48 }
49}
50
51impl TryFrom<u8> for Digits {
52 type Error = Error;
53
54 fn try_from(value: u8) -> Result<Self, Self::Error> {
55 Self::new(value)
56 }
57}
58
59impl From<Digits> for u8 {
60 fn from(value: Digits) -> Self {
61 value.get()
62 }
63}
64
65#[derive(Clone, Copy, Debug)]
69pub struct Code {
70 value: u32,
71 digits: Digits,
72}
73
74impl Code {
75 pub(crate) const fn generated(value: u32, digits: Digits) -> Self {
76 Self { value, digits }
77 }
78
79 pub fn parse(input: &str, digits: Digits) -> Result<Self, CodeError> {
89 let bytes = input.as_bytes();
90 if bytes.len() != usize::from(digits.get()) {
91 return Err(CodeError::InvalidLength {
92 actual: bytes.len(),
93 expected: digits.get(),
94 });
95 }
96
97 let mut value = 0_u32;
98 for (index, byte) in bytes.iter().copied().enumerate() {
99 if !byte.is_ascii_digit() {
100 return Err(CodeError::NonDecimal { index });
101 }
102 value = value * 10 + u32::from(byte - b'0');
103 }
104
105 Ok(Self { value, digits })
106 }
107
108 #[must_use]
111 pub const fn value(self) -> u32 {
112 self.value
113 }
114
115 #[must_use]
117 pub const fn digits(self) -> Digits {
118 self.digits
119 }
120
121 pub(crate) fn ct_eq_choice(self, other: Self) -> Choice {
122 let values_equal = self.value.to_be_bytes().ct_eq(&other.value.to_be_bytes());
123 let widths_equal = self.digits.get().ct_eq(&other.digits.get());
124 values_equal & widths_equal
125 }
126
127 pub(crate) fn ct_eq(self, other: Self) -> bool {
128 bool::from(self.ct_eq_choice(other))
129 }
130}
131
132impl PartialEq for Code {
133 fn eq(&self, other: &Self) -> bool {
134 (*self).ct_eq(*other)
135 }
136}
137
138impl Eq for Code {}
139
140impl fmt::Display for Code {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 write!(
143 f,
144 "{:0width$}",
145 self.value,
146 width = usize::from(self.digits.get())
147 )
148 }
149}