use crate::{Otp, OtpResult, ToBytes};
use unix_time::Instant;
pub type Totp = Otp<Time>;
impl Totp {
pub fn new(key: String, t0: Instant, window: u64, length: u32) -> Self {
Totp::new_with_now(key, t0, window, length, Box::new(|| Instant::now()))
}
pub fn new_with_now(
key: String,
t0: Instant,
step: u64,
digits: u32,
now: Box<dyn Fn() -> Instant>,
) -> Self {
Otp {
key,
generator: Time { t0, step, now },
digits,
}
}
}
pub struct Time {
t0: Instant,
step: u64,
now: Box<dyn Fn() -> Instant>,
}
impl ToBytes for Time {
fn to_bytes(&mut self) -> OtpResult<[u8; 8]> {
let t0 = self.t0;
let now = (self.now)();
let elapsed = now - t0;
let steps = elapsed.as_secs() / self.step;
Ok(steps.to_be_bytes().into())
}
}
#[cfg(test)]
mod test {
use test_case::test_case;
use unix_time::Instant;
use crate::Totp;
#[test_case(59, 94287082)]
#[test_case(1111111109, 07081804)]
#[test_case(1111111111, 14050471)]
#[test_case(1234567890, 89005924)]
#[test_case(2000000000, 69279037)]
#[test_case(20000000000, 65353130)]
fn it_computes_correct_totp(count: u64, expected_code: u32) {
let digits = 8;
let key = "12345678901234567890".to_string();
let step = 30;
let t0 = Instant::at(0, 0);
let mut otp = Totp::new_with_now(
key,
t0,
step,
digits,
Box::new(move || Instant::at(count, 0)),
);
let actual_code = otp.get().unwrap();
assert_eq!(actual_code, expected_code);
}
}