1use crate::{Otp, OtpResult, ToBytes};
2
3use unix_time::Instant;
4
5pub type Totp = Otp<Time>;
10
11impl Totp {
12 pub fn new(key: String, t0: Instant, window: u64, length: u32) -> Self {
17 Totp::new_with_now(key, t0, window, length, Box::new(|| Instant::now()))
18 }
19
20 pub fn new_with_now(
25 key: String,
26 t0: Instant,
27 step: u64,
28 digits: u32,
29 now: Box<dyn Fn() -> Instant>,
30 ) -> Self {
31 Otp {
32 key,
33 generator: Time { t0, step, now },
34 digits,
35 }
36 }
37}
38
39pub struct Time {
42 t0: Instant,
43 step: u64,
44 now: Box<dyn Fn() -> Instant>,
45}
46
47impl ToBytes for Time {
48 fn to_bytes(&mut self) -> OtpResult<[u8; 8]> {
49 let t0 = self.t0;
50 let now = (self.now)();
51 let elapsed = now - t0;
52 let steps = elapsed.as_secs() / self.step;
53 Ok(steps.to_be_bytes().into())
54 }
55}
56
57#[cfg(test)]
58mod test {
59 use test_case::test_case;
60 use unix_time::Instant;
61
62 use crate::Totp;
63
64 #[test_case(59, 94287082)]
67 #[test_case(1111111109, 07081804)]
68 #[test_case(1111111111, 14050471)]
69 #[test_case(1234567890, 89005924)]
70 #[test_case(2000000000, 69279037)]
71 #[test_case(20000000000, 65353130)]
72 fn it_computes_correct_totp(count: u64, expected_code: u32) {
73 let digits = 8;
74 let key = "12345678901234567890".to_string();
75 let step = 30;
76 let t0 = Instant::at(0, 0);
77 let mut otp = Totp::new_with_now(
78 key,
79 t0,
80 step,
81 digits,
82 Box::new(move || Instant::at(count, 0)),
83 );
84 let actual_code = otp.get().unwrap();
85 assert_eq!(actual_code, expected_code);
86 }
87}