1use std::{fmt::Display, str::FromStr};
2
3use sha2::Sha256;
4use hmac::{Hmac, Mac};
5
6#[derive(Debug, PartialEq, Eq)]
7pub enum MynaError {
8 ParseError(String),
9 InvalidInput(String),
10}
11
12impl Display for MynaError {
13 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14 match self {
15 MynaError::ParseError(s) => write!(f, "Parse error: {}", s),
16 MynaError::InvalidInput(s) => write!(f, "Invalid input: {}", s),
17 }
18 }
19}
20
21impl std::error::Error for MynaError {}
22
23#[derive(Debug, PartialEq, Eq, Clone, Copy)]
24pub struct Myna {
25 digits: [u8; 11],
26}
27
28const Q_N: [u8; 11] = [6, 5, 4, 3, 2, 7, 6, 5, 4, 3, 2];
29
30impl Myna {
31 pub const ZERO: Myna = Myna { digits: [0; 11] };
32
33 fn check_digit(&self) -> u8 {
34 let mut sum = 0u16;
35 for i in 0..11 {
36 sum += self.digits[i] as u16 * Q_N[i] as u16;
37 }
38 let remainder = (sum % 11u16) as u8;
39 if remainder == 0 || remainder == 1 {
40 0
41 } else {
42 11 - remainder
43 }
44 }
45
46 pub fn parse(input: &str) -> Result<Myna, MynaError> {
47 let input = input.trim().as_bytes();
48 if input.len() != 12 {
49 return Err(MynaError::InvalidInput("Input must be 12 characters long".to_string()));
50 }
51
52 let mut bytes = [0; 12];
53 for i in 0..12 {
54 bytes[i] = match input[i] {
55 b'0'..=b'9' => input[i] - b'0',
56 _ => return Err(MynaError::ParseError("Invalid character".to_string())),
57 };
58 }
59
60 let myna = Myna {
61 digits: [bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10]],
62 };
63
64 if myna.check_digit() != bytes[11] {
65 return Err(MynaError::ParseError("Invalid check digit".to_string()));
66 }
67
68 Ok(myna)
69 }
70
71 pub fn increment(&mut self) {
72 let mut i = 10;
73 loop {
74 if self.digits[i] == 9 {
75 self.digits[i] = 0;
76 if i == 0 {
77 break;
78 }
79 i -= 1;
80 } else {
81 self.digits[i] += 1;
82 break;
83 }
84 }
85 }
86}
87
88impl FromStr for Myna {
89 type Err = MynaError;
90
91 fn from_str(s: &str) -> Result<Self, Self::Err> {
92 Myna::parse(s)
93 }
94}
95
96impl Display for Myna {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 let zero = b'0';
99 for i in 0..11 {
100 write!(f, "{}", (self.digits[i] + zero) as char)?;
101 }
102 write!(f, "{}", self.check_digit())?;
103 Ok(())
104 }
105}
106
107impl Default for Myna {
108 fn default() -> Self {
109 Myna {
110 digits: [0; 11],
111 }
112 }
113}
114
115pub struct MynaIter {
116 myna: Myna,
117}
118
119impl MynaIter {
120 pub fn new() -> MynaIter {
121 MynaIter { myna: Myna::default() }
122 }
123}
124
125impl Iterator for MynaIter {
126 type Item = Myna;
127
128 fn next(&mut self) -> Option<Self::Item> {
129 let myna = self.myna;
130 self.myna.increment();
131 if self.myna == Myna::ZERO {
132 return None;
133 }
134 Some(myna)
135 }
136}
137
138type HmacSha256 = Hmac<Sha256>;
139
140pub struct MynaDb {
141 secret: Vec<u8>,
142}
143
144impl MynaDb {
145 pub fn new(secret: &str) -> MynaDb {
146 MynaDb {
147 secret: secret.as_bytes().to_vec(),
148 }
149 }
150
151 fn hash(&self, myna: &Myna) -> Vec<u8> {
152 let mut mac = HmacSha256::new_from_slice(&self.secret).unwrap();
153 mac.update(&myna.to_string().as_bytes());
154 mac.finalize().into_bytes().to_vec()
155 }
156
157 fn uuid(&self, myna: &Myna) -> String {
158 let hash = self.hash(myna);
159 let mut uuid = [0u8; 16];
160 uuid.copy_from_slice(&hash[..16]);
161
162 uuid[6] = (uuid[6] & 0x0F) | 0x40;
164 uuid[8] = (uuid[8] & 0x3F) | 0x80;
166
167 let uuid = uuid.iter().map(|b| format!("{:02x}", b)).collect::<Vec<_>>().join("");
168 format!(
169 "{}-{}-{}-{}-{}",
170 &uuid[0..8],
171 &uuid[8..12],
172 &uuid[12..16],
173 &uuid[16..20],
174 &uuid[20..32]
175 )
176 }
177
178 pub fn get_line(&self, myna: &Myna) -> String {
179 format!("{} {}\n", myna, self.uuid(myna))
180 }
181}
182