Skip to main content

totp_rfc/
error.rs

1use core::fmt;
2
3/// An error in an HOTP or TOTP system parameter.
4#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5#[non_exhaustive]
6pub enum Error {
7    /// The shared secret is shorter than RFC 4226 permits.
8    SecretTooShort {
9        /// Length supplied by the caller, in bytes.
10        actual: usize,
11        /// Minimum accepted length, in bytes.
12        minimum: usize,
13    },
14    /// The requested decimal code width is outside the RFC-defined range.
15    InvalidDigits {
16        /// Width supplied by the caller.
17        actual: u8,
18    },
19    /// A zero-second TOTP period was requested.
20    ZeroPeriod,
21    /// The supplied Unix timestamp precedes the configured TOTP epoch.
22    TimestampBeforeEpoch {
23        /// Timestamp supplied by the caller.
24        timestamp: u64,
25        /// Configured initial timestamp (`T0`).
26        epoch: u64,
27    },
28}
29
30impl fmt::Display for Error {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        match self {
33            Self::SecretTooShort { actual, minimum } => write!(
34                f,
35                "shared secret is {actual} bytes; RFC 4226 requires at least {minimum}"
36            ),
37            Self::InvalidDigits { actual } => {
38                write!(f, "invalid OTP width {actual}; expected 6, 7, or 8 digits")
39            }
40            Self::ZeroPeriod => f.write_str("TOTP period must be non-zero"),
41            Self::TimestampBeforeEpoch { timestamp, epoch } => write!(
42                f,
43                "Unix timestamp {timestamp} precedes the configured TOTP epoch {epoch}"
44            ),
45        }
46    }
47}
48
49#[cfg(feature = "std")]
50impl std::error::Error for Error {}
51
52/// A syntax error in a user-supplied OTP code.
53#[derive(Clone, Copy, Debug, Eq, PartialEq)]
54#[non_exhaustive]
55pub enum CodeError {
56    /// The code does not have the configured number of bytes.
57    InvalidLength {
58        /// Actual byte length of the supplied value.
59        actual: usize,
60        /// Required byte length.
61        expected: u8,
62    },
63    /// The code contains a byte outside ASCII `0` through `9`.
64    NonDecimal {
65        /// Zero-based byte position of the first invalid byte.
66        index: usize,
67    },
68}
69
70impl fmt::Display for CodeError {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        match self {
73            Self::InvalidLength { actual, expected } => {
74                write!(f, "OTP is {actual} bytes; expected exactly {expected}")
75            }
76            Self::NonDecimal { index } => {
77                write!(f, "OTP contains a non-decimal byte at index {index}")
78            }
79        }
80    }
81}
82
83#[cfg(feature = "std")]
84impl std::error::Error for CodeError {}