1use crate::constants::{DEFAULT_ALGORITHM, DEFAULT_DIGITS, DEFAULT_PERIOD};
2use crate::hotp::{CheckOption, Hotp, MakeOption};
3use hmacsha::ShaTypes;
4use std::time::SystemTime;
5
6fn create_counter(period: u64) -> u64 {
7 SystemTime::now()
8 .duration_since(SystemTime::UNIX_EPOCH)
9 .unwrap()
10 .as_secs()
11 / period
12}
13
14pub struct Totp<'a> {
18 pub hotp: Hotp<'a>,
19 pub digits: u32,
20 pub period: u64,
21 pub algorithm: &'a ShaTypes,
22}
23#[derive(Clone, Copy)]
25pub enum CreateOption<'a> {
26 Default,
28 Digits(u32),
30 Period(u64),
32 Full {
34 digits: u32,
35 period: u64,
36 algorithm: &'a ShaTypes,
37 },
38 Algorithm(&'a ShaTypes),
40}
41
42impl<'a> Totp<'a> {
43 const fn new(hotp: Hotp<'a>, digits: u32, period: u64, algorithm: &'a ShaTypes) -> Self {
45 Self {
46 hotp,
47 digits,
48 period,
49 algorithm,
50 }
51 }
52
53 pub const fn secret(secret: &'a str, option: CreateOption<'a>) -> Totp<'a> {
55 let hotp = Hotp::new(secret);
56 let (digits, period, algorithm) = match option {
57 CreateOption::Default => (DEFAULT_DIGITS, DEFAULT_PERIOD, DEFAULT_ALGORITHM),
58 CreateOption::Digits(digits) => (digits, DEFAULT_PERIOD, DEFAULT_ALGORITHM),
59 CreateOption::Period(period) => (DEFAULT_DIGITS, period, DEFAULT_ALGORITHM),
60 CreateOption::Full {
61 digits,
62 period,
63 algorithm,
64 } => (digits, period, algorithm),
65 CreateOption::Algorithm(algorithm) => (DEFAULT_DIGITS, DEFAULT_PERIOD, algorithm),
66 };
67 Totp::new(hotp, digits, period, algorithm)
68 }
69 pub fn make(&self) -> String {
90 self.hotp.make(MakeOption::Full {
91 counter: create_counter(self.period),
92 digits: self.digits,
93 algorithm: self.algorithm,
94 })
95 }
96
97 pub fn make_time(&self, time: u64) -> String {
117 self.hotp.make(MakeOption::Full {
118 counter: time / self.period,
119 digits: self.digits,
120 algorithm: self.algorithm,
121 })
122 }
123 pub fn check(&self, otp: &str, breadth: Option<u64>) -> bool {
155 self.hotp.check(
156 otp,
157 CheckOption::Full {
158 counter: create_counter(self.period),
159 breadth: breadth.unwrap_or(DEFAULT_PERIOD),
160 algorithm: self.algorithm,
161 },
162 )
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::{CreateOption, Totp};
169 use crate::constants::{self, DEFAULT_DIGITS};
170
171 #[test]
172 fn it_works() {
173 let secret = "A strong shared secret";
174 let totp = Totp::secret(secret, CreateOption::Default);
175 let code = totp.make();
176 assert_eq!(code.len(), DEFAULT_DIGITS as usize);
177 }
178
179 #[test]
181 fn make_test_correcteness() {
182 let secret = "12345678901234567890";
183 let totp = Totp::secret(secret, CreateOption::Digits(8));
184 let code = totp.make_time(59);
185 assert_eq!(code, "94287082");
186 let code = totp.make_time(1_111_111_109);
187 assert_eq!(code, "07081804");
188 let code = totp.make_time(1_111_111_111);
189 assert_eq!(code, "14050471");
190 let code = totp.make_time(1_234_567_890);
191 assert_eq!(code, "89005924");
192 let code = totp.make_time(2_000_000_000);
193 assert_eq!(code, "69279037");
194 let code = totp.make_time(20_000_000_000);
195 assert_eq!(code, "65353130");
196 }
197
198 #[test]
201 fn make_test_correcteness_sha256() {
202 let secret = "12345678901234567890123456789012";
203 let totp = Totp::secret(
204 secret,
205 CreateOption::Full {
206 digits: 8,
207 period: constants::DEFAULT_PERIOD,
208 algorithm: &hmacsha::ShaTypes::Sha2_256,
209 },
210 );
211 let code = totp.make_time(59);
212 assert_eq!(code, "46119246");
213 let code = totp.make_time(1_111_111_109);
214 assert_eq!(code, "68084774");
215 let code = totp.make_time(1_111_111_111);
216 assert_eq!(code, "67062674");
217 let code = totp.make_time(1_234_567_890);
218 assert_eq!(code, "91819424");
219 let code = totp.make_time(2_000_000_000);
220 assert_eq!(code, "90698825");
221 let code = totp.make_time(20_000_000_000);
222 assert_eq!(code, "77737706");
223 }
224
225 #[test]
226 fn check_test() {
227 let secret = "A strong shared secret";
228 let totp = Totp::secret(secret, CreateOption::Default);
229 let code = totp.make();
230 assert!(totp.check(code.as_str(), None))
231 }
232
233 #[test]
234 fn rapid_make_test() {
235 let secret = "A strong shared secret";
236 let totp = Totp::secret(secret, CreateOption::Default);
237 let code1 = totp.make();
238 let code2 = totp.make();
239 assert!(totp.check(code1.as_str(), None));
240 assert!(totp.check(code2.as_str(), None));
241 assert_eq!(code1, code2);
242 }
243}