argon2_rust/error.rs
1//! Error codes, mirroring `argon2_error_codes` from `phc-winner-argon2/include/argon2.h`.
2//!
3//! Every variant carries the *exact* numeric value the C reference uses, so
4//! differential tests can compare `Error::as_c_code()` against the `int`
5//! returned by `argon2_ctx` / `argon2_hash` / `argon2_verify`.
6//!
7//! `ARGON2_OK` (0) is deliberately absent: success is `Ok(())` in Rust.
8
9/// An Argon2 error.
10///
11/// The discriminants are the C error codes, so `error as i32` is the C value
12/// (see [`Error::as_c_code`]).
13///
14/// Some variants are unreachable through this crate's API but are kept so the
15/// mapping from C codes is total (see [`Error::from_c_code`]):
16///
17/// * [`Error::OutputPtrNull`], [`Error::PwdPtrMismatch`],
18/// [`Error::SaltPtrMismatch`], [`Error::SecretPtrMismatch`],
19/// [`Error::AdPtrMismatch`] — Rust uses slices, there are no null pointers
20/// with a non-zero length.
21/// * [`Error::FreeMemoryCbkNull`], [`Error::AllocateMemoryCbkNull`],
22/// [`Error::MissingArgs`] — this crate has no allocator callbacks.
23/// * [`Error::IncorrectType`] — [`crate::Algorithm`] is a closed enum.
24///
25/// # Ordering, and why this is `#[non_exhaustive]`
26///
27/// The derived [`Ord`] compares discriminants, so the C codes sort `-35 ..= -1`
28/// and any crate-specific variant sorts below all of them. That is incidental,
29/// not a guarantee — do not read meaning into the ordering.
30///
31/// [`Error::OsRandom`] is the first variant that is *not* a C code, and there
32/// may be more later. `#[non_exhaustive]` is what keeps adding one from being a
33/// breaking change for a downstream `match`; write a `_` arm.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
35#[repr(i32)]
36#[non_exhaustive]
37pub enum Error {
38 /// `ARGON2_OUTPUT_PTR_NULL` (-1). Unreachable in Rust.
39 OutputPtrNull = -1,
40 /// `ARGON2_OUTPUT_TOO_SHORT` (-2). Output shorter than [`crate::params::MIN_OUTLEN`].
41 OutputTooShort = -2,
42 /// `ARGON2_OUTPUT_TOO_LONG` (-3). Output longer than [`crate::params::MAX_OUTLEN`].
43 OutputTooLong = -3,
44 /// `ARGON2_PWD_TOO_SHORT` (-4). Unreachable: `MIN_PWD_LENGTH` is 0.
45 PwdTooShort = -4,
46 /// `ARGON2_PWD_TOO_LONG` (-5).
47 PwdTooLong = -5,
48 /// `ARGON2_SALT_TOO_SHORT` (-6). Salt shorter than [`crate::params::MIN_SALT_LENGTH`].
49 SaltTooShort = -6,
50 /// `ARGON2_SALT_TOO_LONG` (-7).
51 SaltTooLong = -7,
52 /// `ARGON2_AD_TOO_SHORT` (-8). Unreachable: `MIN_AD_LENGTH` is 0.
53 AdTooShort = -8,
54 /// `ARGON2_AD_TOO_LONG` (-9).
55 AdTooLong = -9,
56 /// `ARGON2_SECRET_TOO_SHORT` (-10). Unreachable: `MIN_SECRET` is 0.
57 SecretTooShort = -10,
58 /// `ARGON2_SECRET_TOO_LONG` (-11).
59 SecretTooLong = -11,
60 /// `ARGON2_TIME_TOO_SMALL` (-12).
61 TimeTooSmall = -12,
62 /// `ARGON2_TIME_TOO_LARGE` (-13).
63 TimeTooLarge = -13,
64 /// `ARGON2_MEMORY_TOO_LITTLE` (-14).
65 MemoryTooLittle = -14,
66 /// `ARGON2_MEMORY_TOO_MUCH` (-15).
67 MemoryTooMuch = -15,
68 /// `ARGON2_LANES_TOO_FEW` (-16).
69 LanesTooFew = -16,
70 /// `ARGON2_LANES_TOO_MANY` (-17).
71 LanesTooMany = -17,
72 /// `ARGON2_PWD_PTR_MISMATCH` (-18). Unreachable in Rust.
73 PwdPtrMismatch = -18,
74 /// `ARGON2_SALT_PTR_MISMATCH` (-19). Unreachable in Rust.
75 SaltPtrMismatch = -19,
76 /// `ARGON2_SECRET_PTR_MISMATCH` (-20). Unreachable in Rust.
77 SecretPtrMismatch = -20,
78 /// `ARGON2_AD_PTR_MISMATCH` (-21). Unreachable in Rust.
79 AdPtrMismatch = -21,
80 /// `ARGON2_MEMORY_ALLOCATION_ERROR` (-22). The block arena could not be allocated.
81 MemoryAllocationError = -22,
82 /// `ARGON2_FREE_MEMORY_CBK_NULL` (-23). Unreachable: no allocator callbacks.
83 FreeMemoryCbkNull = -23,
84 /// `ARGON2_ALLOCATE_MEMORY_CBK_NULL` (-24). Unreachable: no allocator callbacks.
85 AllocateMemoryCbkNull = -24,
86 /// `ARGON2_INCORRECT_PARAMETER` (-25).
87 IncorrectParameter = -25,
88 /// `ARGON2_INCORRECT_TYPE` (-26). Unreachable: [`crate::Algorithm`] is a closed enum.
89 IncorrectType = -26,
90 /// `ARGON2_OUT_PTR_MISMATCH` (-27). The output slice length disagrees with
91 /// [`crate::Params::tag_len_bytes`]. The C never returns this code — its `out`
92 /// and `outlen` travel together — so the crate reuses it for this
93 /// Rust-only condition.
94 OutPtrMismatch = -27,
95 /// `ARGON2_THREADS_TOO_FEW` (-28).
96 ThreadsTooFew = -28,
97 /// `ARGON2_THREADS_TOO_MANY` (-29).
98 ThreadsTooMany = -29,
99 /// `ARGON2_MISSING_ARGS` (-30). Unreachable in Rust.
100 MissingArgs = -30,
101 /// `ARGON2_ENCODING_FAIL` (-31). The PHC string did not fit the output buffer.
102 EncodingFail = -31,
103 /// `ARGON2_DECODING_FAIL` (-32). The PHC string is malformed.
104 DecodingFail = -32,
105 /// `ARGON2_THREAD_FAIL` (-33). A worker thread could not be started or panicked.
106 ThreadFail = -33,
107 /// `ARGON2_DECODING_LENGTH_FAIL` (-34).
108 DecodingLengthFail = -34,
109 /// `ARGON2_VERIFY_MISMATCH` (-35). The password does not match the hash.
110 VerifyMismatch = -35,
111 /// Not a C code: every OS entropy source failed. Crate-specific (-100),
112 /// the only variant that does not come from `argon2.h`; it exists
113 /// because the C never generates randomness and so has no code for it.
114 OsRandom = -100,
115}
116
117impl Error {
118 /// The lowest C error code (`ARGON2_VERIFY_MISMATCH`).
119 ///
120 /// This bounds the codes that come from `argon2.h`, **not** the
121 /// discriminants of this enum: crate-specific variants such as
122 /// [`Error::OsRandom`] deliberately sit below it so they can never collide
123 /// with a present or future C code.
124 pub const MIN_C_CODE: i32 = -35;
125 /// The highest non-OK C error code (`ARGON2_OUTPUT_PTR_NULL`).
126 pub const MAX_C_CODE: i32 = -1;
127
128 /// The numeric code the C reference returns for this condition.
129 ///
130 /// For the crate-specific variants (see [`Error::MIN_C_CODE`]) there is no
131 /// such C code, and this returns the crate's own value instead.
132 #[inline]
133 #[must_use]
134 pub const fn as_c_code(&self) -> i32 {
135 *self as i32
136 }
137
138 /// Inverse of [`Error::as_c_code`].
139 ///
140 /// Returns `None` for `ARGON2_OK` (0) and for any code that is not a
141 /// discriminant of this enum, so differential tests can turn a C return
142 /// value into a `Result<(), Error>`.
143 ///
144 /// Every C code in `-35..=-1` maps, plus the crate-specific codes below
145 /// [`Error::MIN_C_CODE`] — currently only `-100` ([`Error::OsRandom`]),
146 /// which the C never returns. A differential test that wants *strictly*
147 /// the C's range should bound itself with
148 /// [`MIN_C_CODE`](Error::MIN_C_CODE)`..=`[`MAX_C_CODE`](Error::MAX_C_CODE)
149 /// rather than assume this function rejects everything else.
150 ///
151 /// ```
152 /// use argon2_rust::Error;
153 ///
154 /// // The discriminant *is* the C code, so the pair round-trips.
155 /// assert_eq!(Error::from_c_code(-35), Some(Error::VerifyMismatch));
156 /// assert_eq!(Error::VerifyMismatch.as_c_code(), -35);
157 ///
158 /// // `ARGON2_OK` is `Ok(())` here, so 0 is not a variant...
159 /// assert_eq!(Error::from_c_code(0), None);
160 /// // ...and neither is -36, the code just below the end of the C's range.
161 /// // Being below that end is not on its own enough to predict `None`,
162 /// // as the `-100` case further down shows.
163 /// assert_eq!(Error::from_c_code(Error::MIN_C_CODE - 1), None);
164 ///
165 /// // The crate's own codes sit below that range and still map, which is
166 /// // the caveat above: `None` does not mean "outside the C's codes".
167 /// assert_eq!(Error::from_c_code(-100), Some(Error::OsRandom));
168 /// assert!(Error::OsRandom.as_c_code() < Error::MIN_C_CODE);
169 /// ```
170 #[must_use]
171 pub const fn from_c_code(code: i32) -> Option<Error> {
172 Some(match code {
173 -1 => Error::OutputPtrNull,
174 -2 => Error::OutputTooShort,
175 -3 => Error::OutputTooLong,
176 -4 => Error::PwdTooShort,
177 -5 => Error::PwdTooLong,
178 -6 => Error::SaltTooShort,
179 -7 => Error::SaltTooLong,
180 -8 => Error::AdTooShort,
181 -9 => Error::AdTooLong,
182 -10 => Error::SecretTooShort,
183 -11 => Error::SecretTooLong,
184 -12 => Error::TimeTooSmall,
185 -13 => Error::TimeTooLarge,
186 -14 => Error::MemoryTooLittle,
187 -15 => Error::MemoryTooMuch,
188 -16 => Error::LanesTooFew,
189 -17 => Error::LanesTooMany,
190 -18 => Error::PwdPtrMismatch,
191 -19 => Error::SaltPtrMismatch,
192 -20 => Error::SecretPtrMismatch,
193 -21 => Error::AdPtrMismatch,
194 -22 => Error::MemoryAllocationError,
195 -23 => Error::FreeMemoryCbkNull,
196 -24 => Error::AllocateMemoryCbkNull,
197 -25 => Error::IncorrectParameter,
198 -26 => Error::IncorrectType,
199 -27 => Error::OutPtrMismatch,
200 -28 => Error::ThreadsTooFew,
201 -29 => Error::ThreadsTooMany,
202 -30 => Error::MissingArgs,
203 -31 => Error::EncodingFail,
204 -32 => Error::DecodingFail,
205 -33 => Error::ThreadFail,
206 -34 => Error::DecodingLengthFail,
207 -35 => Error::VerifyMismatch,
208 // Crate-specific (not a C code); mapped back for totality.
209 -100 => Error::OsRandom,
210 _ => return None,
211 })
212 }
213
214 /// The message `argon2_error_message()` returns for this code, verbatim.
215 ///
216 /// Kept byte-identical to `src/argon2.c` so differential tests can compare
217 /// the strings too.
218 #[must_use]
219 pub const fn message(&self) -> &'static str {
220 match self {
221 Error::OutputPtrNull => "Output pointer is NULL",
222 Error::OutputTooShort => "Output is too short",
223 Error::OutputTooLong => "Output is too long",
224 Error::PwdTooShort => "Password is too short",
225 Error::PwdTooLong => "Password is too long",
226 Error::SaltTooShort => "Salt is too short",
227 Error::SaltTooLong => "Salt is too long",
228 Error::AdTooShort => "Associated data is too short",
229 Error::AdTooLong => "Associated data is too long",
230 Error::SecretTooShort => "Secret is too short",
231 Error::SecretTooLong => "Secret is too long",
232 Error::TimeTooSmall => "Time cost is too small",
233 Error::TimeTooLarge => "Time cost is too large",
234 Error::MemoryTooLittle => "Memory cost is too small",
235 Error::MemoryTooMuch => "Memory cost is too large",
236 Error::LanesTooFew => "Too few lanes",
237 Error::LanesTooMany => "Too many lanes",
238 Error::PwdPtrMismatch => "Password pointer is NULL, but password length is not 0",
239 Error::SaltPtrMismatch => "Salt pointer is NULL, but salt length is not 0",
240 Error::SecretPtrMismatch => "Secret pointer is NULL, but secret length is not 0",
241 Error::AdPtrMismatch => "Associated data pointer is NULL, but ad length is not 0",
242 Error::MemoryAllocationError => "Memory allocation error",
243 Error::FreeMemoryCbkNull => "The free memory callback is NULL",
244 Error::AllocateMemoryCbkNull => "The allocate memory callback is NULL",
245 Error::IncorrectParameter => "Argon2_Context context is NULL",
246 Error::IncorrectType => "There is no such version of Argon2",
247 Error::OutPtrMismatch => "Output pointer mismatch",
248 Error::ThreadsTooFew => "Not enough threads",
249 Error::ThreadsTooMany => "Too many threads",
250 Error::MissingArgs => "Missing arguments",
251 Error::EncodingFail => "Encoding failed",
252 Error::DecodingFail => "Decoding failed",
253 Error::ThreadFail => "Threading failure",
254 Error::DecodingLengthFail => "Some of encoded parameters are too long or too short",
255 Error::VerifyMismatch => "The password does not match the supplied hash",
256 // Crate-specific: not a C message.
257 Error::OsRandom => "OS entropy source failed",
258 }
259 }
260}
261
262impl core::fmt::Display for Error {
263 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
264 f.write_str(self.message())
265 }
266}
267
268impl core::error::Error for Error {}
269
270#[cfg(test)]
271mod tests {
272 use super::Error;
273
274 #[test]
275 fn discriminants_match_c() {
276 assert_eq!(Error::OutputPtrNull.as_c_code(), -1);
277 assert_eq!(Error::VerifyMismatch.as_c_code(), -35);
278 assert_eq!(Error::MemoryTooLittle.as_c_code(), -14);
279 }
280
281 #[test]
282 fn round_trip_every_code() {
283 for code in Error::MIN_C_CODE..=Error::MAX_C_CODE {
284 let e = Error::from_c_code(code).expect("every code in range maps");
285 assert_eq!(e.as_c_code(), code);
286 }
287 assert!(Error::from_c_code(0).is_none());
288 assert!(Error::from_c_code(-36).is_none());
289 assert!(Error::from_c_code(1).is_none());
290 }
291
292 /// Every variant whose discriminant is *not* an `argon2.h` code.
293 ///
294 /// Add to this when adding such a variant — that is what makes the test
295 /// below cover it. One entry today; it is a slice rather than a single
296 /// value so growing it stays a one-line change.
297 const CRATE_SPECIFIC: &[Error] = &[Error::OsRandom];
298
299 /// The crate-specific codes are outside `MIN_C_CODE..=MAX_C_CODE`, so the
300 /// loop above cannot reach them. They still have to round-trip, and they
301 /// still have to stay clear of the C's range.
302 #[test]
303 fn crate_specific_codes_round_trip_and_avoid_the_c_range() {
304 for &e in CRATE_SPECIFIC {
305 let code = e.as_c_code();
306 assert_eq!(
307 Error::from_c_code(code),
308 Some(e),
309 "{e:?} does not round-trip"
310 );
311 assert!(
312 code < Error::MIN_C_CODE,
313 "{e:?} ({code}) must sit below the C range so it cannot collide \
314 with a present or future argon2.h code"
315 );
316 assert!(!e.message().is_empty());
317 }
318 }
319}