myna_utils/
lib.rs

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        // Set the version to 4 (random)
163        uuid[6] = (uuid[6] & 0x0F) | 0x40;
164        // Set the variant to DCE 1.1
165        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