use crate::hotp::hotp;
pub const DEFAULT_STEP: u64 = 30;
pub const DEFAULT_DIGITS: u32 = 6;
pub fn totp(secret: &[u8], unix_time: u64, step: u64, digits: u32) -> String {
let counter = unix_time / step;
hotp(secret, counter, digits)
}
pub fn verify(
secret: &[u8],
code: &str,
unix_time: u64,
step: u64,
digits: u32,
window: i64,
) -> Option<i64> {
let center = (unix_time / step) as i64;
for offset in -window..=window {
let counter = (center + offset) as u64;
if hotp(secret, counter, digits) == code {
return Some(offset);
}
}
None
}
pub fn seconds_remaining(unix_time: u64, step: u64) -> u64 {
step - (unix_time % step)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rfc6238_appendix_b() {
let secret = b"12345678901234567890";
let cases: &[(u64, &str)] = &[
(59, "94287082"),
(1_111_111_109, "07081804"),
(1_111_111_111, "14050471"),
(1_234_567_890, "89005924"),
(2_000_000_000, "69279037"),
];
for &(t, want) in cases {
assert_eq!(totp(secret, t, 30, 8), want, "t = {}", t);
}
}
#[test]
fn verify_within_window() {
let secret = b"12345678901234567890";
let code_at_59 = totp(secret, 59, 30, 6);
assert_eq!(verify(secret, &code_at_59, 59, 30, 6, 1), Some(0));
assert_eq!(verify(secret, &code_at_59, 89, 30, 6, 1), Some(-1));
assert_eq!(verify(secret, &code_at_59, 130, 30, 6, 1), None);
}
#[test]
fn seconds_remaining_basics() {
assert_eq!(seconds_remaining(0, 30), 30);
assert_eq!(seconds_remaining(29, 30), 1);
assert_eq!(seconds_remaining(30, 30), 30);
assert_eq!(seconds_remaining(31, 30), 29);
}
}