otp_std/skew.rs
1//! Time-based One-Time Password (TOTP) skews.
2
3use std::{fmt, iter::once, str::FromStr};
4
5use miette::Diagnostic;
6
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10use thiserror::Error;
11
12use crate::{
13 int::{self, ParseError},
14 macros::errors,
15};
16
17/// The disabled skew value.
18pub const DISABLED: u64 = 0;
19
20/// The default skew value.
21pub const DEFAULT: u64 = 1;
22
23/// Represents errors that can occur when parsing skews.
24#[derive(Debug, Error, Diagnostic)]
25#[error("failed to parse `{string}` to skew")]
26#[diagnostic(code(otp_std::skew), help("see the report for more information"))]
27pub struct Error {
28 /// The source of this error.
29 #[source]
30 #[diagnostic_source]
31 pub source: ParseError,
32 /// The string that could not be parsed.
33 pub string: String,
34}
35
36impl Error {
37 /// Constructs [`Self`].
38 pub const fn new(source: ParseError, string: String) -> Self {
39 Self { source, string }
40 }
41}
42
43/// Represents value skews (see [`apply`] for more information).
44///
45/// [`apply`]: Self::apply
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
47#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
48#[cfg_attr(feature = "serde", serde(from = "u64", into = "u64"))]
49pub struct Skew {
50 value: u64,
51}
52
53errors! {
54 Type = Self::Err,
55 Hack = $,
56 error => new(error, string => to_owned),
57}
58
59impl FromStr for Skew {
60 type Err = Error;
61
62 fn from_str(string: &str) -> Result<Self, Self::Err> {
63 let value = string
64 .parse()
65 .map_err(|error| error!(int::wrap(error), string))?;
66
67 Ok(Self::new(value))
68 }
69}
70
71impl fmt::Display for Skew {
72 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
73 self.get().fmt(formatter)
74 }
75}
76
77impl From<u64> for Skew {
78 fn from(value: u64) -> Self {
79 Self::new(value)
80 }
81}
82
83impl From<Skew> for u64 {
84 fn from(skew: Skew) -> Self {
85 skew.get()
86 }
87}
88
89impl Default for Skew {
90 fn default() -> Self {
91 Self::DEFAULT
92 }
93}
94
95impl Skew {
96 /// Constructs [`Self`].
97 pub const fn new(value: u64) -> Self {
98 Self { value }
99 }
100
101 /// Returns the value wrapped in [`Self`].
102 pub const fn get(self) -> u64 {
103 self.value
104 }
105
106 /// Returns the disabled [`Self`].
107 pub const fn disabled() -> Self {
108 Self::DISABLED
109 }
110
111 /// Applies the skew to the given value.
112 ///
113 /// Given some skew `s` and value `n`, this method returns an iterator that yields
114 ///
115 /// ```text
116 /// n - s, n - s + 1, ..., n - 1, n, n + 1, ..., n + s - 1, n + s
117 /// ```
118 ///
119 /// # Note
120 ///
121 /// In case of overflows, the iterator will skip the values that would cause them.
122 ///
123 /// # Examples
124 ///
125 /// Using zero to only accept the *exact* value:
126 ///
127 /// ```
128 /// use otp_std::Skew;
129 ///
130 /// let skew = Skew::new(0);
131 ///
132 /// let mut values = skew.apply(13);
133 ///
134 /// assert_eq!(values.next(), Some(13));
135 /// assert_eq!(values.next(), None);
136 /// ```
137 ///
138 /// Using one:
139 ///
140 /// ```
141 /// use otp_std::Skew;
142 ///
143 /// let skew = Skew::new(1);
144 ///
145 /// let mut values = skew.apply(13);
146 ///
147 /// assert_eq!(values.next(), Some(12));
148 /// assert_eq!(values.next(), Some(13));
149 /// assert_eq!(values.next(), Some(14));
150 /// assert_eq!(values.next(), None);
151 /// ```
152 ///
153 /// Overflow handling:
154 ///
155 /// ```rust
156 /// use otp_std::Skew;
157 ///
158 /// let skew = Skew::new(1);
159 ///
160 /// let value = u64::MAX;
161 ///
162 /// let mut values = skew.apply(value);
163 ///
164 /// assert_eq!(values.next(), Some(value - 1));
165 /// assert_eq!(values.next(), Some(value));
166 /// assert_eq!(values.next(), None);
167 /// ```
168 pub fn apply(self, value: u64) -> impl Iterator<Item = u64> {
169 let sub = (1..=self.get()).filter_map(move |offset| value.checked_sub(offset));
170
171 let add = (1..=self.get()).filter_map(move |offset| value.checked_add(offset));
172
173 sub.rev().chain(once(value)).chain(add)
174 }
175
176 /// The disabled [`Self`] value.
177 pub const DISABLED: Self = Self::new(DISABLED);
178
179 /// The default [`Self`] value.
180 pub const DEFAULT: Self = Self::new(DEFAULT);
181}