1use crate::HOTP;
2use anyhow::Result;
3
4#[derive(Debug, Eq, PartialEq, Clone)]
5pub struct TOTP {
6 hotp: HOTP,
7}
8
9impl TOTP {
10 pub fn new(secret: &str) -> TOTP {
11 TOTP {
12 hotp: HOTP::new(secret),
13 }
14 }
15
16 pub fn from_base32(secret: &str) -> Result<TOTP> {
17 HOTP::from_base32(secret).map(|hotp| TOTP { hotp })
18 }
19
20 pub fn from_bytes(secret: &[u8]) -> TOTP {
21 TOTP {
22 hotp: HOTP::from_bytes(secret),
23 }
24 }
25
26 pub fn generate(&self, period: u64, timestamp: u64) -> Result<u32> {
27 let counter = timestamp / period;
28
29 self.hotp.generate(counter)
30 }
31
32 pub fn verify(&self, code: u32, period: u64, timestamp: u64) -> bool {
33 let code_str = code.to_string();
34 let code_bytes = code_str.as_bytes();
35 if code_bytes.len() > 6 {
36 return false;
37 }
38 let valid_code = self
39 .generate(period, timestamp)
40 .expect("Fail to generate code")
41 .to_string();
42 let valid_bytes = valid_code.as_bytes();
43 if code_bytes.len() != valid_code.len() {
44 return false;
45 }
46 let mut rv = 0;
47 for (a, b) in code_bytes.iter().zip(valid_bytes.iter()) {
48 rv |= a ^ b;
49 }
50 rv == 0
51 }
52
53 pub fn to_uri(&self, label: &str, issuer: &str) -> String {
54 format!(
55 "otpauth://totp/{}?secret={}&issuer={}",
56 label,
57 self.hotp.base32_secret(),
58 issuer
59 )
60 }
61}