1use super::hotp;
2use byteorder::{BigEndian, WriteBytesExt};
3use std::time::SystemTime;
4
5const TIME_STEP: u64 = 30;
6const TOTP_DIGITS: u8 = 6;
7
8pub fn totp(secret: &[u8]) -> Result<String, String> {
10 match current_time() {
11 Ok(current_time) => gen_totp(secret, current_time, TOTP_DIGITS),
12 Err(err) => Err(err),
13 }
14}
15
16fn gen_totp(secret: &[u8], time: u64, digits: u8) -> Result<String, String> {
18 let t = time / TIME_STEP;
19
20 let mut byte_t = Vec::new();
21 byte_t.write_u64::<BigEndian>(t).unwrap();
22
23 hotp::hotp(secret, &byte_t, digits)
24}
25
26fn current_time() -> Result<u64, String> {
28 match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
29 Ok(n) => Ok(n.as_secs()),
30 Err(_) => Err(String::from("SystemTime before UNIX EPOCH!")),
31 }
32}
33
34#[cfg(test)]
35mod tests {
36 use super::*;
37
38 #[test]
57 fn rfc_6238_1() {
58 let totp = gen_totp(b"12345678901234567890", 59, 8).unwrap();
59 assert_eq!(totp, "94287082");
60 }
61
62 #[test]
63 fn rfc_6238_2() {
64 let totp = gen_totp(b"12345678901234567890", 1_111_111_109, 8).unwrap();
65 assert_eq!(totp, "07081804");
66 }
67
68 #[test]
69 fn rfc_6238_3() {
70 let totp = gen_totp(b"12345678901234567890", 1_111_111_111, 8).unwrap();
71 assert_eq!(totp, "14050471");
72 }
73
74 #[test]
75 fn rfc_6238_4() {
76 let totp = gen_totp(b"12345678901234567890", 1_234_567_890, 8).unwrap();
77 assert_eq!(totp, "89005924");
78 }
79
80 #[test]
81 fn rfc_6238_5() {
82 let totp = gen_totp(b"12345678901234567890", 2_000_000_000, 8).unwrap();
83 assert_eq!(totp, "69279037");
84 }
85
86 #[test]
87 fn rfc_6238_6() {
88 let totp = gen_totp(b"12345678901234567890", 20_000_000_000, 8).unwrap();
89 assert_eq!(totp, "65353130");
90 }
91}