Skip to main content

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}