Skip to main content

argon2_rust/
encoding.rs

1//! Base64 (standard alphabet, **no** `=` padding) and the PHC hash string.
2//!
3//! Format: `$argon2<T>[$v=<num>]$m=<num>,t=<num>,p=<num>$<b64 salt>$<b64 hash>`.
4//!
5//! Ported line by line from `phc-winner-argon2/src/encoding.c`
6//! (`b64_byte_to_char`, `b64_char_to_byte`, `to_base64`, `from_base64`,
7//! `decode_decimal`, `encode_string`, `decode_string`, `b64len`, `numlen`) plus
8//! `argon2_encodedlen` from `src/argon2.c`.
9//!
10//! Details the C reference gets subtly right, and this port does too:
11//!
12//! * The `$v=` field is **optional**. When absent the version defaults to
13//!   `0x10`, not `0x13`.
14//! * After decoding, `threads = lanes`.
15//! * `"argon2i"` is a prefix of `"argon2id"`. The C matches the type string and
16//!   then relies on the *next* character failing to parse (`$` is expected), so
17//!   `"$argon2id$..."` is rejected when the caller asked for `Argon2i`.
18//!   Reproduced here rather than special-cased.
19//! * Decoding stops at the first non-base64 character, and then **rejects** if
20//!   `acc_len > 4` or any buffered low bits are non-zero.
21//! * `b64_char_to_byte` returns `0xFF` for an invalid character; a computed
22//!   value of 0 is only valid for `'A'`.
23//! * `decode_decimal` rejects an empty run of digits and rejects non-minimal
24//!   encodings (a leading `'0'` followed by more digits).
25//! * [`decode_string`] finishes by running the full `validate_inputs()`, and
26//!   only then requires the string to be fully consumed. The order matters: a
27//!   string with both a zero-length salt *and* trailing junk reports
28//!   [`Error::SaltTooShort`], exactly like the C.
29//!
30//! The character classification is branch-free, as in the C: the `EQ`/`GT`/
31//! `GE`/`LT`/`LE` macros become the [`eq`]/[`gt`]/[`ge`]/[`lt`]/[`le`] helpers
32//! below, which return `0x00` for false and `0xFF` for true without branching.
33//! It matters because these run over salt and tag material.
34//!
35//! # Known divergences from the C reference
36//!
37//! * **Unrepresentable versions.** `validate_inputs()` never looks at
38//!   `ctx->version` (`core.c:388`), so the C accepts `$v=99`. It then applies
39//!   the `0x13` *fill rule* to anything that is not `0x10` (`ref.c:181`) — but
40//!   the raw value is also hashed into H0 (`core.c:561`
41//!   `store32(&value, context->version)`), so the tag is version-specific and
42//!   `$v=99` is a self-consistent, verifiable C record rather than an alias for
43//!   `$v=19`. Measured against `libargon2.a`: `version=99` yields tag
44//!   `2d4d864d…` where `version=19` yields `c1628832…`. [`Version`] is a closed
45//!   enum, so [`decode_string`] reports [`Error::DecodingFail`] instead. This
46//!   is the one divergence reachable from a conforming ASCII PHC string, and
47//!   only from a producer emitting a version other than 16 or 19. The check is
48//!   deliberately the *last* thing it does, so every other error code still
49//!   matches the C.
50//! * **Embedded NULs.** The C stops at the first `'\0'`; a Rust `&str` has no
51//!   terminator, so the whole slice must be consumed. This is strictly
52//!   stricter: it only rejects strings the C would have accepted by truncating.
53//! * **Raw and non-ASCII bytes.** The C decoder takes an arbitrary `char *`
54//!   byte string, while [`decode_string`] takes `&str`, so malformed UTF-8
55//!   cannot reach this API. The C also has a signed-`char` bug in
56//!   `b64_char_to_byte(*src)`: on Apple, x86 Linux and MSVC every byte
57//!   `>= 0x80` sign-extends to a negative `int`, and `EQ`, which its own
58//!   comment says is only valid "over values in the 0..255 range", then reports
59//!   a spurious match against both `'+'` and `'/'`. Every such byte therefore
60//!   decodes as 63 instead of being rejected. Measured on
61//!   `aarch64-apple-darwin` against
62//!   `libargon2.a`: `"$argon2i$v=19$m=65536,t=2,p=1$é9tZXNhbHQ$<tag>"` and
63//!   `"…$//9tZXNhbHQ$<tag>"` both decode, to the same salt `ff ff 6d 65 …`.
64//!   Replacing the first `/` with any single byte `0x80..=0xff`, including
65//!   malformed UTF-8, also verifies the same record on that target.
66//!   Where `char` is unsigned (aarch64 Linux) the same byte is rejected. This
67//!   port always rejects, which matches the unsigned-`char` platforms and the
68//!   evident intent, and is unreachable for any string the encoder produced.
69//!   See `b64_char_to_byte_rejects_everything_else` below.
70
71use alloc::string::String;
72use alloc::vec::Vec;
73
74use crate::base64::Base64Backend;
75use crate::error::Error;
76use crate::params::{Algorithm, Params, Version, validate_inputs};
77
78// ---------------------------------------------------------------------------
79// Constant-time classification (encoding.c lines 74-78)
80// ---------------------------------------------------------------------------
81//
82//   #define EQ(x, y) ((((0U - ((unsigned)(x) ^ (unsigned)(y))) >> 8) & 0xFF) ^ 0xFF)
83//   #define GT(x, y) ((((unsigned)(y) - (unsigned)(x)) >> 8) & 0xFF)
84//   #define GE(x, y) (GT(y, x) ^ 0xFF)
85//   #define LT(x, y) GT(y, x)
86//   #define LE(x, y) GE(y, x)
87//
88// Defined over 0..=255, returning 0x00 for false and 0xFF for true. The C
89// evaluates them in `unsigned` with wrapping arithmetic, hence `wrapping_sub`.
90
91/// `EQ(x, y)`: `0xFF` when `x == y`, else `0x00`.
92#[inline]
93const fn eq(x: u32, y: u32) -> u32 {
94    ((0u32.wrapping_sub(x ^ y) >> 8) & 0xFF) ^ 0xFF
95}
96
97/// `GT(x, y)`: `0xFF` when `x > y`, else `0x00`.
98#[inline]
99const fn gt(x: u32, y: u32) -> u32 {
100    (y.wrapping_sub(x) >> 8) & 0xFF
101}
102
103/// `GE(x, y)`: `0xFF` when `x >= y`, else `0x00`.
104#[inline]
105const fn ge(x: u32, y: u32) -> u32 {
106    gt(y, x) ^ 0xFF
107}
108
109/// `LT(x, y)`: `0xFF` when `x < y`, else `0x00`.
110#[inline]
111const fn lt(x: u32, y: u32) -> u32 {
112    gt(y, x)
113}
114
115/// `LE(x, y)`: `0xFF` when `x <= y`, else `0x00`.
116#[inline]
117const fn le(x: u32, y: u32) -> u32 {
118    ge(y, x)
119}
120
121/// `b64_byte_to_char(x)`: map `0..64` to the standard base64 alphabet.
122///
123/// Branch-free, like the C. `x` is always masked to 6 bits by the caller.
124#[inline]
125const fn b64_byte_to_char(x: u32) -> u8 {
126    // `'a' - 26` and `'0' - 52` are evaluated in the C as `int`; the second one
127    // is negative (-4), which `wrapping_add` reproduces.
128    let a_off = b'A' as u32;
129    let lower_off = (b'a' as u32).wrapping_sub(26);
130    let digit_off = (b'0' as u32).wrapping_sub(52);
131
132    let c = (lt(x, 26) & x.wrapping_add(a_off))
133        | (ge(x, 26) & lt(x, 52) & x.wrapping_add(lower_off))
134        | (ge(x, 52) & lt(x, 62) & x.wrapping_add(digit_off))
135        | (eq(x, 62) & b'+' as u32)
136        | (eq(x, 63) & b'/' as u32);
137    c as u8
138}
139
140/// `b64_char_to_byte(c)`: map a base64 character to its 6-bit value.
141///
142/// Returns `0xFF` for anything that is not a base64 character. Note the final
143/// fixup: a computed value of 0 is only accepted for `'A'`, so every invalid
144/// character (which also computes 0) is turned into `0xFF`.
145///
146/// `c` is always a `u8` widened to `u32`, i.e. `0..=255`, which is the range
147/// the `EQ`/`GE`/`LE` macros are defined over. The C instead passes a `char`,
148/// which sign-extends on most targets and makes bytes `>= 0x80` decode as 63
149/// rather than being rejected — see the module docs.
150#[inline]
151const fn b64_char_to_byte(c: u32) -> u32 {
152    let a_off = b'A' as u32;
153    let lower_off = (b'a' as u32).wrapping_sub(26);
154    let digit_off = (b'0' as u32).wrapping_sub(52);
155
156    let x = (ge(c, b'A' as u32) & le(c, b'Z' as u32) & c.wrapping_sub(a_off))
157        | (ge(c, b'a' as u32) & le(c, b'z' as u32) & c.wrapping_sub(lower_off))
158        | (ge(c, b'0' as u32) & le(c, b'9' as u32) & c.wrapping_sub(digit_off))
159        | (eq(c, b'+' as u32) & 62)
160        | (eq(c, b'/' as u32) & 63);
161
162    x | (eq(x, 0) & (eq(c, b'A' as u32) ^ 0xFF))
163}
164
165// ---------------------------------------------------------------------------
166// Lengths
167// ---------------------------------------------------------------------------
168
169/// `b64len(len)`: length of the unpadded base64 encoding of `len` bytes.
170///
171/// ```text
172/// olen = (len / 3) << 2;
173/// switch (len % 3) { case 2: olen++; /* fall through */ case 1: olen += 2; }
174/// ```
175///
176/// # 32-bit targets
177///
178/// On a target where `usize` is 32 bits, a `len` near `u32::MAX` makes
179/// `(len / 3) << 2` discard its top bits, so the answer wraps. That is not a
180/// defect to fix: `encoding.c:441` computes `((size_t)len / 3) << 2` and wraps
181/// identically where `size_t` is 32 bits, and this function exists to match the
182/// C. It cannot panic (`<<` discards bits rather than trapping, and the sums in
183/// [`encoded_len`] stay inside `usize` even after a wrap), and it is not on the
184/// allocation path — [`encode_string_alloc`] sizes its buffer with
185/// `encoded_len_usize`, which takes real slice lengths and cannot wrap.
186#[must_use]
187pub const fn b64_len(len: u32) -> usize {
188    b64_len_usize(len as usize)
189}
190
191/// [`b64_len`] over a `usize`, for slices.
192///
193/// Cannot overflow **when fed a slice length**, which is every caller: a slice
194/// is at most `isize::MAX` bytes, and `(isize::MAX / 3) * 4 < usize::MAX` on
195/// both 32- and 64-bit targets. The `u32` entry point above has no such bound —
196/// see its note.
197#[inline]
198const fn b64_len_usize(len: usize) -> usize {
199    let mut olen = (len / 3) << 2;
200    // The C's `case 2` falls through into `case 1`, so it adds 1 + 2.
201    match len % 3 {
202        2 => olen += 3,
203        1 => olen += 2,
204        _ => {}
205    }
206    olen
207}
208
209/// `numlen(num)`: number of decimal digits in `num` (1 for 0).
210#[must_use]
211pub const fn num_len(num: u32) -> usize {
212    let mut len = 1usize;
213    let mut n = num;
214    while n >= 10 {
215        len += 1;
216        n /= 10;
217    }
218    len
219}
220
221/// `argon2_encodedlen(...)` from `src/argon2.c`.
222///
223/// ```text
224/// strlen("$$v=$m=,t=,p=$$") + strlen(type) + numlen(t_cost) + numlen(m_cost)
225///   + numlen(parallelism) + b64len(saltlen) + b64len(hashlen)
226///   + numlen(ARGON2_VERSION_NUMBER) + 1
227/// ```
228///
229/// The trailing `+ 1` is the C string's NUL terminator. It is kept so the value
230/// matches the C byte for byte; a Rust [`String`] is one byte shorter. It is
231/// also exactly the buffer size `encode_string` wants, which reserves the
232/// same byte (see there).
233///
234/// Note the C uses `numlen(ARGON2_VERSION_NUMBER)` and not the version actually
235/// being encoded. Both `0x10` (16) and `0x13` (19) are two digits, so it makes
236/// no difference; it is kept verbatim. (In the C it *can* differ, because
237/// `ctx->version` is a raw `uint32_t`: with `version = 0` the string is one
238/// byte shorter than advertised, and a buffer of `argon2_encodedlen() - 1`
239/// suffices. Measured on 1051 of 30000 fuzzed cases, all of them `version = 0`.
240/// [`Version`] is a closed enum of two two-digit values, so the size is always
241/// exact here — see `encode_needs_encoded_len_bytes_exactly`.)
242///
243/// # Argument order
244///
245/// `t_cost` comes before `m_cost` here, which is the reverse of
246/// [`Params::new`]:
247///
248/// ```text
249/// Params::new(m_cost, t_cost, lanes, output_len)
250/// encoded_len(algorithm, t_cost, m_cost, lanes, salt_len, hash_len)
251///                        ^^^^^^^^^^^^^^ reversed against Params::new
252/// ```
253///
254/// The order is the C's, kept so a call can be transcribed position for
255/// position: `argon2_encodedlen(t_cost, m_cost, parallelism, saltlen, hashlen,
256/// type)`, declared at `argon2.h:429` and defined at `argon2.c:447`. One
257/// argument did move. `type` went from last to first and became `algorithm`.
258/// The other five kept their order among themselves; `parallelism` is spelled
259/// `lanes` here, the name [`Params`] uses for it.
260///
261/// `algorithm` comes first here and on the rest of the encode-and-construct
262/// side; the verify family keeps the C's trailing `type` and takes it last.
263///
264/// ```
265/// use argon2_rust::{Algorithm, Params, encoded_len};
266///
267/// // Params::new takes m_cost first.
268/// let params = Params::new(65536, 3, 1, 32).unwrap();
269/// assert_eq!((params.m_cost(), params.t_cost()), (65536, 3));
270///
271/// // encoded_len takes t_cost first: the same two costs, the other way round.
272/// let n = encoded_len(
273///     Algorithm::Argon2id,
274///     params.t_cost(),
275///     params.m_cost(),
276///     params.lanes(),
277///     16, // salt_len
278///     32, // hash_len
279/// );
280/// assert_eq!(n, 98);
281/// ```
282///
283/// # Against a string the crate really produced
284///
285/// The value is a C buffer size, so it counts the NUL terminator described
286/// above and a Rust [`String`] is one byte shorter. That is the whole of the
287/// relationship, and it is exact rather than an upper bound - see
288/// `encode_needs_encoded_len_bytes_exactly`:
289///
290/// ```
291/// use argon2_rust::{Algorithm, Argon2, Params, Version, encoded_len};
292///
293/// let params = Params::new(64, 1, 1, 32)?;
294/// let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
295/// let encoded = argon2.hash_encoded(b"password", b"somesalt")?;
296///
297/// // `salt_len` is the salt's own 8 bytes, not the 11 its base64 occupies.
298/// let n = encoded_len(Algorithm::Argon2id, 1, 64, 1, 8, 32);
299/// assert_eq!(n, 84);
300/// assert_eq!(encoded.len(), n - 1);
301/// assert_eq!(
302///     encoded,
303///     "$argon2id$v=19$m=64,t=1,p=1$c29tZXNhbHQ$cpx6VEQbwTVZvcpxNIxOVUWZ5xnAipUmAe1cg2GMG70",
304/// );
305/// # Ok::<(), argon2_rust::Error>(())
306/// ```
307#[must_use]
308pub fn encoded_len(
309    algorithm: Algorithm,
310    t_cost: u32,
311    m_cost: u32,
312    lanes: u32,
313    salt_len: u32,
314    hash_len: u32,
315) -> usize {
316    "$$v=$m=,t=,p=$$".len()
317        + algorithm.as_str().len()
318        + num_len(t_cost)
319        + num_len(m_cost)
320        + num_len(lanes)
321        + b64_len(salt_len)
322        + b64_len(hash_len)
323        + num_len(Version::DEFAULT.as_u32())
324        + 1
325}
326
327/// [`encoded_len`] computed from slice lengths, without the `u32` casts.
328///
329/// [`encode_string_alloc`] sizes its buffer with this so a salt longer than
330/// `u32::MAX` cannot silently wrap into a too-small allocation.
331fn encoded_len_usize(
332    algorithm: Algorithm,
333    t_cost: u32,
334    m_cost: u32,
335    lanes: u32,
336    salt_len: usize,
337    hash_len: usize,
338) -> usize {
339    "$$v=$m=,t=,p=$$".len()
340        + algorithm.as_str().len()
341        + num_len(t_cost)
342        + num_len(m_cost)
343        + num_len(lanes)
344        + b64_len_usize(salt_len)
345        + b64_len_usize(hash_len)
346        + num_len(Version::DEFAULT.as_u32())
347        + 1
348}
349
350// ---------------------------------------------------------------------------
351// Base64
352// ---------------------------------------------------------------------------
353
354/// `to_base64(dst, dst_len, src, src_len)`.
355///
356/// Writes the unpadded base64 of `src` into `dst` and returns how many bytes
357/// were written. No NUL terminator is written (the C writes one; Rust does not
358/// need it), but the capacity check is kept identical: the C requires
359/// `dst_len > olen`, so this requires `dst.len() > b64_len(src.len())`.
360///
361/// # Errors
362///
363/// [`Error::EncodingFail`] if `dst` is too small.
364pub fn to_base64(dst: &mut [u8], src: &[u8]) -> Result<usize, Error> {
365    if src.len() < crate::base64::MIN_ENCODE_LEN {
366        // Keep backend lookup and the generalized prefix state completely out
367        // of tiny salts. This is the original scalar function's exact shape.
368        return to_base64_scalar(dst, src);
369    }
370    let backend = crate::base64::base64_backend();
371    // SAFETY: runtime detection returns only an executable backend.
372    unsafe { to_base64_with_backend(dst, src, backend) }
373}
374
375/// The original reference-C loop, kept whole so short inputs do not pay for or
376/// inhibit optimization around a SIMD prefix they cannot use.
377#[inline(always)]
378fn to_base64_scalar(dst: &mut [u8], src: &[u8]) -> Result<usize, Error> {
379    let olen = b64_len_usize(src.len());
380    if dst.len() <= olen {
381        return Err(Error::EncodingFail);
382    }
383
384    let mut acc: u32 = 0;
385    let mut acc_len: u32 = 0;
386    let mut written = 0usize;
387    for &byte in src {
388        acc = (acc << 8) | byte as u32;
389        acc_len += 8;
390        while acc_len >= 6 {
391            acc_len -= 6;
392            dst[written] = b64_byte_to_char((acc >> acc_len) & 0x3f);
393            written += 1;
394        }
395    }
396    if acc_len > 0 {
397        dst[written] = b64_byte_to_char((acc << (6 - acc_len)) & 0x3f);
398        written += 1;
399    }
400
401    debug_assert_eq!(written, olen);
402    Ok(written)
403}
404
405/// Encode with an explicitly selected Base64 backend.
406///
407/// This is an unstable test/benchmark hook. Normal callers use [`to_base64`],
408/// which performs safe runtime detection.
409///
410/// # Safety
411///
412/// `backend` must be executable on the current CPU, as reported by
413/// [`Base64Backend::is_available`].
414#[inline]
415pub unsafe fn to_base64_with_backend(
416    dst: &mut [u8],
417    src: &[u8],
418    backend: Base64Backend,
419) -> Result<usize, Error> {
420    if backend == Base64Backend::Scalar {
421        return to_base64_scalar(dst, src);
422    }
423
424    let olen = b64_len_usize(src.len());
425    if dst.len() <= olen {
426        return Err(Error::EncodingFail);
427    }
428
429    // SAFETY: transferred from this function's caller. The capacity check
430    // above proves every complete vector store is within `dst`.
431    let (consumed, mut written) = unsafe { crate::base64::encode_prefix(backend, dst, src) };
432    let mut acc: u32 = 0;
433    let mut acc_len: u32 = 0;
434
435    for &byte in &src[consumed..] {
436        // The C writes `(acc << 8) + *buf++`; the low 8 bits of `acc << 8` are
437        // zero, so `|` is the same value and cannot overflow in debug builds.
438        acc = (acc << 8) | byte as u32;
439        acc_len += 8;
440        while acc_len >= 6 {
441            acc_len -= 6;
442            dst[written] = b64_byte_to_char((acc >> acc_len) & 0x3F);
443            written += 1;
444        }
445    }
446    if acc_len > 0 {
447        dst[written] = b64_byte_to_char((acc << (6 - acc_len)) & 0x3F);
448        written += 1;
449    }
450
451    debug_assert!(written == olen);
452    Ok(written)
453}
454
455/// `from_base64(dst, dst_len, src)`.
456///
457/// Decodes until the first non-base64 byte. Returns
458/// `(bytes_written, bytes_consumed)`, where `bytes_consumed` indexes the first
459/// non-base64 byte in `src` — the equivalent of the pointer the C returns. The
460/// end of the slice acts as the C's terminating NUL, which is itself not a
461/// base64 character.
462///
463/// # Errors
464///
465/// [`Error::DecodingFail`] if `dst` is too small, if `acc_len > 4` at the end,
466/// or if any buffered low bits are non-zero.
467pub fn from_base64(dst: &mut [u8], src: &[u8]) -> Result<(usize, usize), Error> {
468    if src.len() < crate::base64::MIN_DECODE_LEN {
469        // As in the encoder, keep both lookup and generalized prefix state out
470        // of inputs too short for this architecture's smallest vector.
471        return from_base64_scalar(dst, src);
472    }
473    let backend = crate::base64::base64_backend();
474    // SAFETY: as in `to_base64`, detection proves the feature contract.
475    unsafe { from_base64_with_backend(dst, src, backend) }
476}
477
478/// The original reference-C loop, kept whole for the scalar and short-input
479/// paths just like [`to_base64_scalar`].
480#[inline(always)]
481fn from_base64_scalar(dst: &mut [u8], src: &[u8]) -> Result<(usize, usize), Error> {
482    let mut consumed = 0usize;
483    let mut len = 0usize;
484    let mut acc: u32 = 0;
485    let mut acc_len: u32 = 0;
486
487    loop {
488        // Past the end of the slice, feed the NUL the C would have read.
489        let c = match src.get(consumed) {
490            Some(&byte) => byte as u32,
491            None => 0,
492        };
493        let d = b64_char_to_byte(c);
494        if d == 0xFF {
495            break;
496        }
497        consumed += 1;
498        acc = (acc << 6) | d;
499        acc_len += 6;
500        if acc_len >= 8 {
501            acc_len -= 8;
502            if len >= dst.len() {
503                return Err(Error::DecodingFail);
504            }
505            dst[len] = ((acc >> acc_len) & 0xFF) as u8;
506            len += 1;
507        }
508    }
509
510    if acc_len > 4 || (acc & ((1u32 << acc_len) - 1)) != 0 {
511        return Err(Error::DecodingFail);
512    }
513
514    Ok((len, consumed))
515}
516
517/// Decode with an explicitly selected Base64 backend.
518///
519/// This preserves [`from_base64`]'s exact stopping and error behavior and is
520/// exposed only as an unstable differential-test/benchmark hook.
521///
522/// # Safety
523///
524/// `backend` must be executable on the current CPU, as reported by
525/// [`Base64Backend::is_available`].
526#[inline]
527pub unsafe fn from_base64_with_backend(
528    dst: &mut [u8],
529    src: &[u8],
530    backend: Base64Backend,
531) -> Result<(usize, usize), Error> {
532    if backend == Base64Backend::Scalar {
533        return from_base64_scalar(dst, src);
534    }
535
536    // SAFETY: transferred from this function's caller. Each backend checks the
537    // supplied slice lengths before loading or storing a complete block.
538    let (mut consumed, mut len) = unsafe { crate::base64::decode_prefix(backend, dst, src) };
539    let mut acc: u32 = 0;
540    let mut acc_len: u32 = 0;
541
542    loop {
543        // Past the end of the slice, feed the NUL the C would have read.
544        let c = match src.get(consumed) {
545            Some(&byte) => byte as u32,
546            None => 0,
547        };
548        let d = b64_char_to_byte(c);
549        if d == 0xFF {
550            break;
551        }
552        consumed += 1;
553        // As in `to_base64`, `|` matches the C's `+` bit for bit.
554        acc = (acc << 6) | d;
555        acc_len += 6;
556        if acc_len >= 8 {
557            acc_len -= 8;
558            // The C is `if ((len++) >= *dst_len) return NULL;`, i.e. the test
559            // uses the pre-increment value.
560            if len >= dst.len() {
561                return Err(Error::DecodingFail);
562            }
563            dst[len] = ((acc >> acc_len) & 0xFF) as u8;
564            len += 1;
565        }
566    }
567
568    // An input length of 1 modulo 4 leaves 6 unprocessed bits, which is
569    // invalid; otherwise 0, 2 or 4 bits are buffered and they must be zero.
570    if acc_len > 4 || (acc & ((1u32 << acc_len) - 1)) != 0 {
571        return Err(Error::DecodingFail);
572    }
573
574    Ok((len, consumed))
575}
576
577// ---------------------------------------------------------------------------
578// decode_decimal
579// ---------------------------------------------------------------------------
580
581/// `decode_decimal(str, v)`.
582///
583/// Returns `(value, digits_consumed)`, or `None` when there is no digit at all,
584/// when the encoding is not minimal (a leading `'0'` with more digits after
585/// it), or when the value overflows.
586///
587/// The C accumulates in `unsigned long`, which is 64-bit on every target this
588/// crate supports, so `u64` matches it. On a hypothetical 32-bit `unsigned
589/// long` the outcome would still be the same, because every caller is
590/// `DECIMAL_U32`, which rejects anything above `u32::MAX` anyway.
591fn decode_decimal(src: &[u8]) -> Option<(u64, usize)> {
592    let mut acc: u64 = 0;
593    let mut i = 0usize;
594
595    while let Some(&c) = src.get(i) {
596        if !c.is_ascii_digit() {
597            break;
598        }
599        let digit = (c - b'0') as u64;
600        if acc > u64::MAX / 10 {
601            return None;
602        }
603        acc *= 10;
604        if digit > u64::MAX - acc {
605            return None;
606        }
607        acc += digit;
608        i += 1;
609    }
610
611    // `if (str == orig || (*orig == '0' && str != (orig + 1))) return NULL;`
612    if i == 0 {
613        return None;
614    }
615    if src[0] == b'0' && i != 1 {
616        return None;
617    }
618
619    Some((acc, i))
620}
621
622// ---------------------------------------------------------------------------
623// encode_string
624// ---------------------------------------------------------------------------
625
626/// The C's `SS`/`SX`/`SB` macros: a cursor that always keeps one byte spare for
627/// the NUL terminator the C writes, so the capacity requirement is identical.
628struct Writer<'a> {
629    dst: &'a mut [u8],
630    pos: usize,
631}
632
633impl Writer<'_> {
634    /// `SS(str)`: `if (pp_len >= dst_len) return ARGON2_ENCODING_FAIL;`.
635    fn put(&mut self, bytes: &[u8]) -> Result<(), Error> {
636        let remaining = self.dst.len() - self.pos;
637        if bytes.len() >= remaining {
638            return Err(Error::EncodingFail);
639        }
640        let end = self.pos + bytes.len();
641        self.dst[self.pos..end].copy_from_slice(bytes);
642        self.pos = end;
643        Ok(())
644    }
645
646    /// `SX(x)`: the decimal form of `x`, no allocation.
647    fn put_u32(&mut self, value: u32) -> Result<(), Error> {
648        // `u32::MAX` is 4294967295: ten digits.
649        let mut buf = [0u8; 10];
650        let mut i = buf.len();
651        let mut n = value;
652        loop {
653            i -= 1;
654            buf[i] = b'0' + (n % 10) as u8;
655            n /= 10;
656            if n == 0 {
657                break;
658            }
659        }
660        self.put(&buf[i..])
661    }
662
663    /// `SB(buf, len)`: base64, which does its own capacity check.
664    fn put_base64(&mut self, src: &[u8]) -> Result<(), Error> {
665        let written = to_base64(&mut self.dst[self.pos..], src)?;
666        self.pos += written;
667        Ok(())
668    }
669}
670
671/// `validate_inputs(ctx)` as `encode_string` and `decode_string` run it.
672///
673/// `out_len` is the *tag* length, which is what `ctx->outlen` holds in both
674/// call sites, and `pwd_len` is 0 (see the module-level divergence note).
675fn validate_for_string(params: &Params, salt_len: usize, hash_len: usize) -> Result<(), Error> {
676    validate_inputs(
677        hash_len,
678        0,
679        salt_len,
680        0,
681        0,
682        params.m_cost(),
683        params.t_cost(),
684        params.lanes(),
685        params.threads(),
686    )
687}
688
689/// `encode_string(dst, dst_len, ctx, type)`.
690///
691/// Writes the PHC string into `dst` (no NUL terminator) and returns its length.
692/// Always emits `$v=`, as the C does.
693///
694/// `dst` must hold the string **plus one byte**, because the C reserves room
695/// for its NUL terminator and this port keeps the capacity rule identical:
696/// a buffer of exactly [`encoded_len`] bytes is what succeeds, in Rust and in
697/// C alike.
698///
699/// `hash.len()` plays the role of `ctx->outlen` — it is the length that gets
700/// encoded, so it, and not [`Params::output_len`], is what the leading
701/// `validate_inputs()` checks. For a tag produced from these `params` the two
702/// are the same value.
703///
704/// # Errors
705///
706/// [`Error::EncodingFail`] if `dst` is too small, or whatever
707/// [`crate::params::validate_inputs`] returns — `encode_string` runs it first,
708/// exactly like the C.
709pub fn encode_string(
710    dst: &mut [u8],
711    algorithm: Algorithm,
712    version: Version,
713    params: &Params,
714    salt: &[u8],
715    hash: &[u8],
716) -> Result<usize, Error> {
717    validate_for_string(params, salt.len(), hash.len())?;
718
719    let mut w = Writer { dst, pos: 0 };
720
721    w.put(b"$")?;
722    w.put(algorithm.as_str().as_bytes())?;
723
724    w.put(b"$v=")?;
725    w.put_u32(version.as_u32())?;
726
727    w.put(b"$m=")?;
728    w.put_u32(params.m_cost())?;
729    w.put(b",t=")?;
730    w.put_u32(params.t_cost())?;
731    w.put(b",p=")?;
732    w.put_u32(params.lanes())?;
733
734    w.put(b"$")?;
735    w.put_base64(salt)?;
736
737    w.put(b"$")?;
738    w.put_base64(hash)?;
739
740    Ok(w.pos)
741}
742
743/// [`encode_string`] into a freshly allocated [`String`].
744///
745/// # Errors
746///
747/// As [`encode_string`], plus [`Error::MemoryAllocationError`] if the buffer
748/// cannot be allocated (the C returns the same code when its `malloc` fails).
749pub fn encode_string_alloc(
750    algorithm: Algorithm,
751    version: Version,
752    params: &Params,
753    salt: &[u8],
754    hash: &[u8],
755) -> Result<String, Error> {
756    // Validate before allocating, so an over-long salt reports SaltTooLong
757    // rather than failing to allocate a buffer sized from it.
758    validate_for_string(params, salt.len(), hash.len())?;
759
760    let capacity = encoded_len_usize(
761        algorithm,
762        params.t_cost(),
763        params.m_cost(),
764        params.lanes(),
765        salt.len(),
766        hash.len(),
767    );
768
769    let mut buf = alloc_zeroed_vec(capacity)?;
770    let written = encode_string(&mut buf, algorithm, version, params, salt, hash)?;
771    buf.truncate(written);
772
773    // Every byte written comes from the base64 alphabet, the decimal digits or
774    // the ASCII punctuation above, so this is always valid UTF-8.
775    String::from_utf8(buf).map_err(|_| Error::EncodingFail)
776}
777
778/// A zeroed `Vec<u8>` of `len` bytes, without the abort-on-OOM of
779/// `Vec::with_capacity`.
780fn alloc_zeroed_vec(len: usize) -> Result<Vec<u8>, Error> {
781    let mut v = Vec::new();
782    v.try_reserve(len)
783        .map_err(|_| Error::MemoryAllocationError)?;
784    // Cannot reallocate: the capacity was just reserved.
785    v.resize(len, 0);
786    Ok(v)
787}
788
789// ---------------------------------------------------------------------------
790// decode_string
791// ---------------------------------------------------------------------------
792
793/// The fields a PHC string yields.
794#[derive(Debug, Clone, PartialEq, Eq)]
795pub struct Decoded {
796    /// The algorithm named by the string. Always equal to the one requested,
797    /// since a mismatch is a decoding failure.
798    pub algorithm: Algorithm,
799    /// The version. `0x10` when the `$v=` field is absent.
800    pub version: Version,
801    /// `m_cost`, `t_cost`, `lanes`, `threads == lanes`, and
802    /// `output_len == hash.len()`.
803    pub params: Params,
804    /// The decoded salt.
805    pub salt: Vec<u8>,
806    /// The decoded tag.
807    pub hash: Vec<u8>,
808}
809
810/// The remainder of `src` from `pos`, never panicking.
811#[inline]
812fn rest(src: &[u8], pos: usize) -> &[u8] {
813    src.get(pos..).unwrap_or(&[])
814}
815
816/// The `CC(prefix)` macro: consume `prefix` or fail.
817fn expect(src: &[u8], pos: &mut usize, prefix: &[u8]) -> Result<(), Error> {
818    if rest(src, *pos).starts_with(prefix) {
819        *pos += prefix.len();
820        Ok(())
821    } else {
822        Err(Error::DecodingFail)
823    }
824}
825
826/// The `CC_opt(prefix, code)` macro: consume `prefix` if it is there.
827fn expect_opt(src: &[u8], pos: &mut usize, prefix: &[u8]) -> bool {
828    if rest(src, *pos).starts_with(prefix) {
829        *pos += prefix.len();
830        true
831    } else {
832        false
833    }
834}
835
836/// The `DECIMAL_U32(x)` macro.
837fn decimal_u32(src: &[u8], pos: &mut usize) -> Result<u32, Error> {
838    let (value, consumed) = decode_decimal(rest(src, *pos)).ok_or(Error::DecodingFail)?;
839    if value > u32::MAX as u64 {
840        return Err(Error::DecodingFail);
841    }
842    *pos += consumed;
843    Ok(value as u32)
844}
845
846/// The `BIN(buf, max_len, len)` macro.
847///
848/// The C sizes the destination at `strlen(encoded)` (see `argon2_verify`), so
849/// the "output buffer too small" branch of `from_base64` is unreachable there.
850/// The bound used here — three bytes out per four characters in — is likewise
851/// never exceeded, so the two agree.
852fn decode_bin(src: &[u8], pos: &mut usize) -> Result<Vec<u8>, Error> {
853    let tail = rest(src, *pos);
854    // `n` base64 characters decode to `floor(3n / 4)` bytes; `n/4*3 + 3` is an
855    // upper bound for every `n`, and cannot overflow for any real slice.
856    let max_len = tail.len() / 4 * 3 + 3;
857
858    let mut buf = alloc_zeroed_vec(max_len)?;
859    let (written, consumed) = from_base64(&mut buf, tail)?;
860    // `bin_len > UINT32_MAX` is a decoding failure in the C.
861    if written > u32::MAX as usize {
862        return Err(Error::DecodingFail);
863    }
864    buf.truncate(written);
865    *pos += consumed;
866    Ok(buf)
867}
868
869/// `decode_string(ctx, str, type)`.
870///
871/// # Errors
872///
873/// [`Error::DecodingFail`] for a malformed string, or whatever
874/// [`crate::params::validate_inputs`] returns — the C runs the full validation
875/// before accepting the string, so a well-formed string with a zero-length salt
876/// yields [`Error::SaltTooShort`] and not [`Error::DecodingFail`].
877///
878/// See the module documentation for the known divergences from the C
879/// (unrepresentable versions, embedded NULs, and raw/non-ASCII input).
880pub fn decode_string(encoded: &str, algorithm: Algorithm) -> Result<Decoded, Error> {
881    let src = encoded.as_bytes();
882    let mut pos = 0usize;
883
884    // argon2.c:268-271
885    //   encoded_len = strlen(encoded);
886    //   if (encoded_len > UINT32_MAX) return ARGON2_DECODING_FAIL;
887    //
888    // The C puts this in `argon2_verify`, one level up, and computes
889    // `max_field_len` from it. Here it lives in the decoder because all four
890    // verify entry points funnel through this function, so one check covers
891    // them and cannot drift; through the public API the behaviour is identical.
892    // Reachable only where `usize` is wider than `u32`, from a `&str` at least
893    // 4 GiB long.
894    if src.len() > u32::MAX as usize {
895        return Err(Error::DecodingFail);
896    }
897
898    // CC("$"); CC(type_string);
899    //
900    // No `ARGON2_INCORRECT_TYPE` branch: `argon2_type2string` only returns NULL
901    // for a type outside the enum, which `Algorithm` cannot represent.
902    expect(src, &mut pos, b"$")?;
903    expect(src, &mut pos, algorithm.as_str().as_bytes())?;
904
905    // ctx->version = ARGON2_VERSION_10; CC_opt("$v=", DECIMAL_U32(version));
906    let mut version_value = Version::V0x10.as_u32();
907    if expect_opt(src, &mut pos, b"$v=") {
908        version_value = decimal_u32(src, &mut pos)?;
909    }
910
911    expect(src, &mut pos, b"$m=")?;
912    let m_cost = decimal_u32(src, &mut pos)?;
913    expect(src, &mut pos, b",t=")?;
914    let t_cost = decimal_u32(src, &mut pos)?;
915    expect(src, &mut pos, b",p=")?;
916    let lanes = decimal_u32(src, &mut pos)?;
917    // `ctx->threads = ctx->lanes;`
918    let threads = lanes;
919
920    expect(src, &mut pos, b"$")?;
921    let salt = decode_bin(src, &mut pos)?;
922    expect(src, &mut pos, b"$")?;
923    let hash = decode_bin(src, &mut pos)?;
924
925    // "On return, must have valid context": the full validate_inputs(), in the
926    // C's order, before the trailing-character check.
927    validate_inputs(
928        hash.len(),
929        0,
930        salt.len(),
931        0,
932        0,
933        m_cost,
934        t_cost,
935        lanes,
936        threads,
937    )?;
938
939    // "Can't have any additional characters".
940    if pos != src.len() {
941        return Err(Error::DecodingFail);
942    }
943
944    // Last, so that every error code above still matches the C exactly.
945    let version = Version::from_u32(version_value).ok_or(Error::DecodingFail)?;
946
947    // Cannot fail: `validate_inputs` above already accepted these values, and
948    // `Params` re-checks them with a salt length that is valid by construction.
949    let params = Params::new_with_threads(m_cost, t_cost, lanes, threads, hash.len())?;
950
951    Ok(Decoded {
952        algorithm,
953        version,
954        params,
955        salt,
956        hash,
957    })
958}
959
960#[cfg(test)]
961mod tests {
962    use super::*;
963    use alloc::vec;
964
965    // From `phc-winner-argon2/src/test.c`.
966    const V13_ARGON2I: &str = "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ\
967                               $wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA";
968    const V10_ARGON2I: &str = "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ\
969                               $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
970    const V13_ARGON2ID: &str = "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ\
971                                $CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc";
972
973    /// `f6c4db4a...`, the raw tag of the v=0x10 Argon2i vector in test.c.
974    const V10_TAG: [u8; 32] = [
975        0xf6, 0xc4, 0xdb, 0x4a, 0x54, 0xe2, 0xa3, 0x70, 0x62, 0x7a, 0xff, 0x3d, 0xb6, 0x17, 0x6b,
976        0x94, 0xa2, 0xa2, 0x09, 0xa6, 0x2c, 0x8e, 0x36, 0x15, 0x27, 0x11, 0x80, 0x2f, 0x7b, 0x30,
977        0xc6, 0x94,
978    ];
979
980    fn b64(src: &[u8]) -> Vec<u8> {
981        let mut out = vec![0u8; b64_len_usize(src.len()) + 1];
982        let n = to_base64(&mut out, src).expect("buffer sized by b64_len");
983        out.truncate(n);
984        out
985    }
986
987    fn unb64(src: &[u8]) -> Result<Vec<u8>, Error> {
988        let mut out = vec![0u8; src.len()];
989        let (n, consumed) = from_base64(&mut out, src)?;
990        assert_eq!(consumed, src.len(), "test inputs are pure base64");
991        out.truncate(n);
992        Ok(out)
993    }
994
995    fn b64_with_backend(
996        dst: &mut [u8],
997        src: &[u8],
998        backend: Base64Backend,
999    ) -> Result<usize, Error> {
1000        assert!(backend.is_available());
1001        // SAFETY: the assertion establishes this test process can execute the
1002        // requested backend; slice bounds remain checked by the implementation.
1003        unsafe { to_base64_with_backend(dst, src, backend) }
1004    }
1005
1006    fn unb64_with_backend(
1007        dst: &mut [u8],
1008        src: &[u8],
1009        backend: Base64Backend,
1010    ) -> Result<(usize, usize), Error> {
1011        assert!(backend.is_available());
1012        // SAFETY: as in `b64_with_backend`, availability is proved immediately
1013        // above and both pointer/length pairs come from live slices.
1014        unsafe { from_base64_with_backend(dst, src, backend) }
1015    }
1016
1017    // -- lengths ------------------------------------------------------------
1018
1019    #[test]
1020    fn b64_len_matches_c() {
1021        assert_eq!(b64_len(0), 0);
1022        assert_eq!(b64_len(1), 2);
1023        assert_eq!(b64_len(2), 3);
1024        assert_eq!(b64_len(3), 4);
1025        assert_eq!(b64_len(4), 6);
1026        // "somesalt" -> "c29tZXNhbHQ", "…" -> the 43-char tag.
1027        assert_eq!(b64_len(8), 11);
1028        assert_eq!(b64_len(32), 43);
1029        // Cross-check against the encoder for every small length.
1030        for len in 0u32..64 {
1031            let src = vec![0xABu8; len as usize];
1032            assert_eq!(b64(&src).len(), b64_len(len), "len {len}");
1033        }
1034    }
1035
1036    #[test]
1037    fn num_len_matches_c() {
1038        assert_eq!(num_len(0), 1);
1039        assert_eq!(num_len(9), 1);
1040        assert_eq!(num_len(10), 2);
1041        assert_eq!(num_len(19), 2);
1042        assert_eq!(num_len(65536), 5);
1043        assert_eq!(num_len(u32::MAX), 10);
1044    }
1045
1046    #[test]
1047    fn encoded_len_matches_the_c_vector() {
1048        // $argon2id$v=19$m=65536,t=2,p=1$<11>$<43> is 86 chars + NUL.
1049        let n = encoded_len(Algorithm::Argon2id, 2, 65536, 1, 8, 32);
1050        assert_eq!(n, V13_ARGON2ID.len() + 1);
1051        assert_eq!(n, 87);
1052        assert_eq!(
1053            encoded_len(Algorithm::Argon2i, 2, 65536, 1, 8, 32),
1054            V13_ARGON2I.len() + 1
1055        );
1056    }
1057
1058    // Pins the claim in `encoded_len`'s `# Argument order` section: the C's
1059    // `argon2_encodedlen` (`argon2.c:447`) takes `t_cost` before `m_cost`, this
1060    // port keeps that order, and it is the reverse of `Params::new`.
1061    //
1062    // A caller who supplies the two costs the other way round gets no error
1063    // back, and the reason is arithmetic rather than luck. `t_cost` and `m_cost`
1064    // each reach the result through exactly one term, `num_len(t_cost)` and
1065    // `num_len(m_cost)`, and the two terms are added, so the sum does not depend
1066    // on which digit count came from which cost. Nothing else in the body reads
1067    // either value. The swap is therefore *always* harmless, not usually
1068    // harmless: the swapped call returns the same number, and that number is
1069    // also the correct one. Nothing passed to `encoded_len` reaches the emitted
1070    // string either -- `encode_string` writes the `m=` and `t=` fields out of
1071    // the `&Params` it is handed. A swapped call site is a cosmetic
1072    // inconsistency, not a latent bug.
1073    //
1074    // That makes the equality a property of this one formula and not a rule
1075    // about the crate. This test pins it at every digit-count boundary and will
1076    // fail here first if the length ever stops being a plain sum of the two
1077    // terms, at which point the doc gets fixed with it.
1078    #[test]
1079    fn encoded_len_is_symmetric_in_m_and_t() {
1080        // The pair the doc example uses, the C's own test vector, and the
1081        // extreme: `u32::MAX` is ten digits against one, the widest the two
1082        // terms can differ.
1083        assert_eq!(encoded_len(Algorithm::Argon2id, 3, 65536, 1, 16, 32), 98);
1084        assert_eq!(encoded_len(Algorithm::Argon2id, 65536, 3, 1, 16, 32), 98);
1085        assert_eq!(encoded_len(Algorithm::Argon2id, 2, 65536, 1, 8, 32), 87);
1086        assert_eq!(encoded_len(Algorithm::Argon2id, 65536, 2, 1, 8, 32), 87);
1087        assert_eq!(encoded_len(Algorithm::Argon2id, 1, u32::MAX, 1, 16, 32), 103);
1088        assert_eq!(encoded_len(Algorithm::Argon2id, u32::MAX, 1, 1, 16, 32), 103);
1089
1090        // Every place `num_len` changes answer, both sides of each step, over
1091        // all three algorithm strings, so the property is pinned rather than
1092        // sampled at a few lucky points.
1093        const BOUNDARIES: [u32; 22] = [
1094            0,
1095            1,
1096            9,
1097            10,
1098            99,
1099            100,
1100            999,
1101            1_000,
1102            9_999,
1103            10_000,
1104            99_999,
1105            100_000,
1106            999_999,
1107            1_000_000,
1108            9_999_999,
1109            10_000_000,
1110            99_999_999,
1111            100_000_000,
1112            999_999_999,
1113            1_000_000_000,
1114            65536,
1115            u32::MAX,
1116        ];
1117        for algorithm in [Algorithm::Argon2d, Algorithm::Argon2i, Algorithm::Argon2id] {
1118            for t_cost in BOUNDARIES {
1119                for m_cost in BOUNDARIES {
1120                    assert_eq!(
1121                        encoded_len(algorithm, t_cost, m_cost, 1, 16, 32),
1122                        encoded_len(algorithm, m_cost, t_cost, 1, 16, 32),
1123                        "{algorithm:?} t_cost={t_cost} m_cost={m_cost}"
1124                    );
1125                }
1126            }
1127        }
1128    }
1129
1130    // -- base64 -------------------------------------------------------------
1131
1132    #[test]
1133    fn b64_tables_are_the_standard_alphabet() {
1134        const ALPHABET: &[u8; 64] =
1135            b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1136        for (value, &ch) in ALPHABET.iter().enumerate() {
1137            assert_eq!(b64_byte_to_char(value as u32), ch, "value {value}");
1138            assert_eq!(b64_char_to_byte(ch as u32), value as u32, "char {ch}");
1139        }
1140    }
1141
1142    #[test]
1143    fn b64_char_to_byte_rejects_everything_else() {
1144        // The 'A' quirk: a computed 0 is only valid for 'A'.
1145        assert_eq!(b64_char_to_byte(b'A' as u32), 0);
1146        for c in 0u32..256 {
1147            let is_b64 = (c as u8).is_ascii_alphanumeric() || c == b'+' as u32 || c == b'/' as u32;
1148            if !is_b64 {
1149                assert_eq!(b64_char_to_byte(c), 0xFF, "char {c} must be invalid");
1150            }
1151        }
1152        assert_eq!(b64_char_to_byte(b'=' as u32), 0xFF); // no padding
1153        assert_eq!(b64_char_to_byte(0), 0xFF); // NUL terminator
1154        assert_eq!(b64_char_to_byte(b'$' as u32), 0xFF); // field separator
1155
1156        // Bytes >= 0x80 are rejected here. The C's are not, wherever `char` is
1157        // signed — it returns 63 for all 128 of them. See the module docs; this
1158        // loop is the pin for the port's (stricter, portable) choice.
1159        for c in 0x80u32..256 {
1160            assert_eq!(b64_char_to_byte(c), 0xFF, "byte {c:#04x} must be invalid");
1161        }
1162    }
1163
1164    /// `b64_char_to_byte` over `0..=127`, dumped from the C reference.
1165    ///
1166    /// Produced by a harness that `#include`s `encoding.c` (the function is
1167    /// `static`) and prints `b64_char_to_byte(i)` for every `i`, linked against
1168    /// `phc-winner-argon2/libargon2.a`. Only the ASCII half is transcribed: the
1169    /// upper half is the signed-`char` bug documented at the top of this file,
1170    /// where the C returns 63 for all 128 values.
1171    #[rustfmt::skip]
1172    const C_CHAR_TO_BYTE_ASCII: [u32; 128] = [
1173        255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
1174        255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
1175        255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,  62, 255, 255, 255,  63,
1176         52,  53,  54,  55,  56,  57,  58,  59,  60,  61, 255, 255, 255, 255, 255, 255,
1177        255,   0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,
1178         15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25, 255, 255, 255, 255, 255,
1179        255,  26,  27,  28,  29,  30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,
1180         41,  42,  43,  44,  45,  46,  47,  48,  49,  50,  51, 255, 255, 255, 255, 255,
1181    ];
1182
1183    #[test]
1184    fn b64_char_to_byte_matches_the_c_dump() {
1185        for (c, &want) in C_CHAR_TO_BYTE_ASCII.iter().enumerate() {
1186            assert_eq!(b64_char_to_byte(c as u32), want, "char {c}");
1187        }
1188    }
1189
1190    /// `b64_byte_to_char` over `0..64`, dumped from the same C harness.
1191    #[rustfmt::skip]
1192    const C_BYTE_TO_CHAR: [u8; 64] = [
1193        65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
1194        81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102,
1195        103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118,
1196        119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 47,
1197    ];
1198
1199    #[test]
1200    fn b64_byte_to_char_matches_the_c_dump() {
1201        for (x, &want) in C_BYTE_TO_CHAR.iter().enumerate() {
1202            assert_eq!(b64_byte_to_char(x as u32), want, "value {x}");
1203        }
1204    }
1205
1206    #[test]
1207    fn non_ascii_bytes_are_rejected_in_a_field() {
1208        // Measured: the C decodes both of these, identically, to the salt
1209        // ff ff 6d 65 73 61 6c 74, because 0xC3 and 0xA9 each read as '/'.
1210        // This port rejects the first and accepts the second.
1211        let utf8 = "$argon2i$v=19$m=65536,t=2,p=1$é9tZXNhbHQ\
1212                    $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1213        assert_eq!(
1214            decode_string(utf8, Algorithm::Argon2i),
1215            Err(Error::DecodingFail)
1216        );
1217        let slashes = "$argon2i$v=19$m=65536,t=2,p=1$//9tZXNhbHQ\
1218                       $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1219        let d = decode_string(slashes, Algorithm::Argon2i).unwrap();
1220        assert_eq!(d.salt, [0xff, 0xff, 0x6d, 0x65, 0x73, 0x61, 0x6c, 0x74]);
1221    }
1222
1223    #[test]
1224    fn base64_known_vectors() {
1225        assert_eq!(b64(b"somesalt"), b"c29tZXNhbHQ");
1226        assert_eq!(b64(b"diffsalt"), b"ZGlmZnNhbHQ");
1227        assert_eq!(
1228            b64(&V10_TAG),
1229            b"9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
1230        );
1231        assert_eq!(unb64(b"c29tZXNhbHQ").unwrap(), b"somesalt");
1232        assert_eq!(
1233            unb64(b"9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ").unwrap(),
1234            &V10_TAG
1235        );
1236    }
1237
1238    #[test]
1239    fn base64_round_trips_every_short_length() {
1240        for len in 0usize..96 {
1241            let src: Vec<u8> = (0..len)
1242                .map(|i| (i as u8).wrapping_mul(37) ^ 0x5A)
1243                .collect();
1244            let encoded = b64(&src);
1245            assert_eq!(unb64(&encoded).unwrap(), src, "len {len}");
1246        }
1247    }
1248
1249    /// Every executable SIMD backend against the scalar oracle, across the
1250    /// block boundaries and tails each implementation can take. This checks
1251    /// the primitive directly rather than relying on PHC vectors whose usual
1252    /// 16/32-byte fields exercise only two shapes.
1253    #[test]
1254    fn every_base64_backend_matches_scalar_across_lengths() {
1255        for len in 0usize..=512 {
1256            let src: Vec<u8> = (0..len)
1257                .map(|i| (i as u8).wrapping_mul(197) ^ (len as u8).wrapping_mul(11))
1258                .collect();
1259            let capacity = b64_len_usize(len) + 1;
1260            let mut expected = vec![0xa5; capacity];
1261            let expected_len =
1262                b64_with_backend(&mut expected, &src, Base64Backend::Scalar).unwrap();
1263
1264            for &backend in Base64Backend::ALL {
1265                if !backend.is_available()
1266                    || (cfg!(miri) && backend != Base64Backend::Scalar)
1267                {
1268                    continue;
1269                }
1270                let mut actual = vec![0xa5; capacity];
1271                let actual_len = b64_with_backend(&mut actual, &src, backend).unwrap();
1272                assert_eq!(actual_len, expected_len, "{backend} length {len}");
1273                assert_eq!(actual, expected, "{backend} bytes at length {len}");
1274
1275                let mut scalar_decoded = vec![0x5a; len];
1276                let scalar_result = unb64_with_backend(
1277                    &mut scalar_decoded,
1278                    &expected[..expected_len],
1279                    Base64Backend::Scalar,
1280                );
1281                let mut simd_decoded = vec![0x5a; len];
1282                let simd_result = unb64_with_backend(
1283                    &mut simd_decoded,
1284                    &expected[..expected_len],
1285                    backend,
1286                );
1287                assert_eq!(simd_result, scalar_result, "{backend} decode length {len}");
1288                assert_eq!(simd_decoded, scalar_decoded, "{backend} decode bytes {len}");
1289                assert_eq!(simd_decoded, src, "{backend} round trip {len}");
1290            }
1291        }
1292    }
1293
1294    /// An invalid byte in a vector must not make the SIMD prefix lose the C
1295    /// decoder's exact stopping position. The vector is retried by scalar, so
1296    /// both the return value and all bytes written before an error match.
1297    #[test]
1298    fn every_base64_backend_matches_scalar_on_invalid_bytes_and_short_outputs() {
1299        let raw: Vec<u8> = (0..96)
1300            .map(|i| (i as u8).wrapping_mul(37) ^ 0x5a)
1301            .collect();
1302        let encoded = b64(&raw);
1303
1304        for &backend in Base64Backend::ALL {
1305            if !backend.is_available() || (cfg!(miri) && backend != Base64Backend::Scalar) {
1306                continue;
1307            }
1308
1309            for pos in 0..encoded.len() {
1310                for invalid in [0, b'$', b'=', 0x80, 0xff] {
1311                    let mut input = encoded.clone();
1312                    input[pos] = invalid;
1313                    let mut expected = vec![0xa5; raw.len()];
1314                    let expected_result = unb64_with_backend(
1315                        &mut expected,
1316                        &input,
1317                        Base64Backend::Scalar,
1318                    );
1319                    let mut actual = vec![0xa5; raw.len()];
1320                    let actual_result = unb64_with_backend(&mut actual, &input, backend);
1321                    assert_eq!(actual_result, expected_result, "{backend} pos {pos} byte {invalid:#x}");
1322                    assert_eq!(actual, expected, "{backend} output at pos {pos} byte {invalid:#x}");
1323                }
1324            }
1325
1326            for dst_len in 0..raw.len() {
1327                let mut expected = vec![0xa5; dst_len];
1328                let expected_result = unb64_with_backend(
1329                    &mut expected,
1330                    &encoded,
1331                    Base64Backend::Scalar,
1332                );
1333                let mut actual = vec![0xa5; dst_len];
1334                let actual_result = unb64_with_backend(&mut actual, &encoded, backend);
1335                assert_eq!(actual_result, expected_result, "{backend} dst length {dst_len}");
1336                assert_eq!(actual, expected, "{backend} dst bytes at length {dst_len}");
1337            }
1338        }
1339    }
1340
1341    #[test]
1342    fn to_base64_rejects_a_tight_buffer() {
1343        // The C requires dst_len > olen, i.e. room for the NUL as well.
1344        let mut exact = [0u8; 11];
1345        assert_eq!(to_base64(&mut exact, b"somesalt"), Err(Error::EncodingFail));
1346        let mut roomy = [0u8; 12];
1347        assert_eq!(to_base64(&mut roomy, b"somesalt"), Ok(11));
1348        // Even an empty input needs one byte for the terminator.
1349        assert_eq!(to_base64(&mut [], b""), Err(Error::EncodingFail));
1350        assert_eq!(to_base64(&mut [0u8; 1], b""), Ok(0));
1351    }
1352
1353    #[test]
1354    fn from_base64_stops_at_the_first_non_b64_char() {
1355        let mut out = [0u8; 16];
1356        let (written, consumed) = from_base64(&mut out, b"c29tZXNhbHQ$rest").unwrap();
1357        assert_eq!(written, 8);
1358        assert_eq!(consumed, 11);
1359        assert_eq!(&out[..8], b"somesalt");
1360
1361        // An empty run is fine and consumes nothing (this is how the C accepts
1362        // "$argon2i$m=…,p=1$$<tag>" and only later reports SaltTooShort).
1363        let (written, consumed) = from_base64(&mut out, b"$tag").unwrap();
1364        assert_eq!((written, consumed), (0, 0));
1365    }
1366
1367    #[test]
1368    fn from_base64_rejects_leftover_bits() {
1369        let mut out = [0u8; 16];
1370        // 5 chars -> 30 bits -> acc_len == 6 > 4.
1371        assert_eq!(
1372            from_base64(&mut out, b"AAAAA"),
1373            Err(Error::DecodingFail),
1374            "acc_len > 4"
1375        );
1376        // 1 char -> 6 bits -> acc_len == 6 > 4.
1377        assert_eq!(from_base64(&mut out, b"A"), Err(Error::DecodingFail));
1378        // 2 chars, 4 buffered bits, non-zero: 'B' == 1.
1379        assert_eq!(from_base64(&mut out, b"AB"), Err(Error::DecodingFail));
1380        // Same shape but the buffered bits are zero.
1381        assert_eq!(from_base64(&mut out, b"AA"), Ok((1, 2)));
1382        // 3 chars, 2 buffered bits, non-zero: 'B' == 000001.
1383        assert_eq!(from_base64(&mut out, b"AAB"), Err(Error::DecodingFail));
1384        assert_eq!(from_base64(&mut out, b"AAA"), Ok((2, 3)));
1385    }
1386
1387    #[test]
1388    fn from_base64_rejects_a_short_buffer() {
1389        let mut out = [0u8; 4];
1390        assert_eq!(
1391            from_base64(&mut out, b"c29tZXNhbHQ"),
1392            Err(Error::DecodingFail)
1393        );
1394        // The C's `if ((len++) >= *dst_len)` tests the pre-increment value, so
1395        // an exactly-sized buffer is fine and one byte less is not.
1396        let mut exact = [0u8; 8];
1397        assert_eq!(from_base64(&mut exact, b"c29tZXNhbHQ"), Ok((8, 11)));
1398        assert_eq!(&exact, b"somesalt");
1399        let mut tight = [0u8; 7];
1400        assert_eq!(
1401            from_base64(&mut tight, b"c29tZXNhbHQ"),
1402            Err(Error::DecodingFail)
1403        );
1404    }
1405
1406    // -- decode_decimal -----------------------------------------------------
1407
1408    #[test]
1409    fn decode_decimal_matches_c() {
1410        assert_eq!(decode_decimal(b"0"), Some((0, 1)));
1411        assert_eq!(decode_decimal(b"19"), Some((19, 2)));
1412        assert_eq!(decode_decimal(b"65536,t=2"), Some((65536, 5)));
1413        assert_eq!(decode_decimal(b"4294967295"), Some((4294967295, 10)));
1414        // No digits at all.
1415        assert_eq!(decode_decimal(b""), None);
1416        assert_eq!(decode_decimal(b"$"), None);
1417        assert_eq!(decode_decimal(b"x1"), None);
1418        // Non-minimal.
1419        assert_eq!(decode_decimal(b"01"), None);
1420        assert_eq!(decode_decimal(b"00"), None);
1421        assert_eq!(decode_decimal(b"0019"), None);
1422        // Overflow of `unsigned long`.
1423        assert_eq!(
1424            decode_decimal(b"18446744073709551615"),
1425            Some((u64::MAX, 20))
1426        );
1427        assert_eq!(decode_decimal(b"18446744073709551616"), None);
1428        assert_eq!(decode_decimal(b"99999999999999999999999"), None);
1429    }
1430
1431    #[test]
1432    fn decode_decimal_matches_the_c_dump() {
1433        // Every pair below was produced by running the C's `decode_decimal`
1434        // (it is `static`, so via a harness that #includes encoding.c) linked
1435        // against phc-winner-argon2/libargon2.a. `None` is the C's NULL.
1436        /// `(input, decode_decimal(input))`, the C's NULL being `None`.
1437        type Case = (&'static [u8], Option<(u64, usize)>);
1438
1439        let cases: &[Case] = &[
1440            (b"", None),
1441            (b"0", Some((0, 1))),
1442            (b"00", None),
1443            (b"000", None),
1444            (b"01", None),
1445            (b"0019", None),
1446            (b"1", Some((1, 1))),
1447            (b"9", Some((9, 1))),
1448            (b"19", Some((19, 2))),
1449            (b"10", Some((10, 2))),
1450            (b"007", None),
1451            (b"4294967295", Some((4294967295, 10))),
1452            (b"4294967296", Some((4294967296, 10))),
1453            (b"9223372036854775807", Some((9223372036854775807, 19))),
1454            (b"18446744073709551615", Some((18446744073709551615, 20))),
1455            (b"18446744073709551616", None),
1456            (b"18446744073709551620", None),
1457            (b"19999999999999999999", None),
1458            (b"20000000000000000000", None),
1459            (b"99999999999999999999", None),
1460            (b"1000000000000000000000000", None),
1461            (b"65536,t=2", Some((65536, 5))),
1462            (b"1,t=2", Some((1, 1))),
1463            (b"x", None),
1464            (b"1x", Some((1, 1))),
1465            (b" 1", None),
1466            (b"+1", None),
1467            (b"-1", None),
1468            (b"1 ", Some((1, 1))),
1469            // "0" is minimal on its own, so the 'x' just ends the run.
1470            (b"0x10", Some((0, 1))),
1471            (b"12$", Some((12, 2))),
1472            (b"0,", Some((0, 1))),
1473            (b"10$", Some((10, 2))),
1474            (b"$", None),
1475            (b"1234567890", Some((1234567890, 10))),
1476        ];
1477        for (input, want) in cases {
1478            assert_eq!(
1479                decode_decimal(input),
1480                *want,
1481                "input {:?}",
1482                core::str::from_utf8(input).unwrap_or("<non-utf8>")
1483            );
1484        }
1485    }
1486
1487    #[test]
1488    fn decimal_fields_are_u32_bounded() {
1489        // DECIMAL_U32 rejects anything above UINT32_MAX.
1490        let too_big = "$argon2i$v=19$m=4294967296,t=2,p=1$c29tZXNhbHQ\
1491                       $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1492        assert_eq!(
1493            decode_string(too_big, Algorithm::Argon2i),
1494            Err(Error::DecodingFail)
1495        );
1496        let leading_zero = "$argon2i$v=19$m=065536,t=2,p=1$c29tZXNhbHQ\
1497                            $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1498        assert_eq!(
1499            decode_string(leading_zero, Algorithm::Argon2i),
1500            Err(Error::DecodingFail)
1501        );
1502    }
1503
1504    // -- encode -------------------------------------------------------------
1505
1506    #[test]
1507    fn encode_matches_the_official_vectors() {
1508        let params = Params::new(65536, 2, 1, 32).unwrap();
1509        let tag = unb64(b"wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA").unwrap();
1510        let encoded = encode_string_alloc(
1511            Algorithm::Argon2i,
1512            Version::V0x13,
1513            &params,
1514            b"somesalt",
1515            &tag,
1516        )
1517        .unwrap();
1518        assert_eq!(encoded, V13_ARGON2I);
1519
1520        let tag = unb64(b"CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc").unwrap();
1521        let encoded = encode_string_alloc(
1522            Algorithm::Argon2id,
1523            Version::V0x13,
1524            &params,
1525            b"somesalt",
1526            &tag,
1527        )
1528        .unwrap();
1529        assert_eq!(encoded, V13_ARGON2ID);
1530
1531        // The C always emits "$v=", even for 0x10.
1532        let encoded = encode_string_alloc(
1533            Algorithm::Argon2i,
1534            Version::V0x10,
1535            &params,
1536            b"somesalt",
1537            &V10_TAG,
1538        )
1539        .unwrap();
1540        assert_eq!(
1541            encoded,
1542            "$argon2i$v=16$m=65536,t=2,p=1$c29tZXNhbHQ\
1543             $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
1544        );
1545    }
1546
1547    #[test]
1548    fn encode_needs_encoded_len_bytes_exactly() {
1549        let params = Params::new(65536, 2, 1, 32).unwrap();
1550        let want = encoded_len(Algorithm::Argon2i, 2, 65536, 1, 8, 32);
1551        assert_eq!(want, V13_ARGON2I.len() + 1);
1552
1553        let mut buf = vec![0u8; want];
1554        let n = encode_string(
1555            &mut buf,
1556            Algorithm::Argon2i,
1557            Version::V0x13,
1558            &params,
1559            b"somesalt",
1560            &V10_TAG,
1561        )
1562        .unwrap();
1563        assert_eq!(n, want - 1);
1564
1565        // One byte less is a failure, exactly as in the C.
1566        let mut buf = vec![0u8; want - 1];
1567        assert_eq!(
1568            encode_string(
1569                &mut buf,
1570                Algorithm::Argon2i,
1571                Version::V0x13,
1572                &params,
1573                b"somesalt",
1574                &V10_TAG,
1575            ),
1576            Err(Error::EncodingFail)
1577        );
1578    }
1579
1580    #[test]
1581    fn encode_validates_first() {
1582        let params = Params::new(65536, 2, 1, 32).unwrap();
1583        // Short salt: validate_inputs runs before anything is written.
1584        assert_eq!(
1585            encode_string_alloc(
1586                Algorithm::Argon2i,
1587                Version::V0x13,
1588                &params,
1589                b"short",
1590                &V10_TAG
1591            ),
1592            Err(Error::SaltTooShort)
1593        );
1594        // Short tag.
1595        assert_eq!(
1596            encode_string_alloc(
1597                Algorithm::Argon2i,
1598                Version::V0x13,
1599                &params,
1600                b"somesalt",
1601                &[0u8; 3]
1602            ),
1603            Err(Error::OutputTooShort)
1604        );
1605    }
1606
1607    // -- decode -------------------------------------------------------------
1608
1609    #[test]
1610    fn decode_the_v13_vector() {
1611        let d = decode_string(V13_ARGON2I, Algorithm::Argon2i).unwrap();
1612        assert_eq!(d.algorithm, Algorithm::Argon2i);
1613        assert_eq!(d.version, Version::V0x13);
1614        assert_eq!(d.params.m_cost(), 65536);
1615        assert_eq!(d.params.t_cost(), 2);
1616        assert_eq!(d.params.lanes(), 1);
1617        assert_eq!(d.params.output_len(), 32);
1618        assert_eq!(d.salt, b"somesalt");
1619        assert_eq!(
1620            d.hash,
1621            unb64(b"wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA").unwrap()
1622        );
1623    }
1624
1625    #[test]
1626    fn decode_defaults_the_version_to_0x10() {
1627        let d = decode_string(V10_ARGON2I, Algorithm::Argon2i).unwrap();
1628        assert_eq!(d.version, Version::V0x10);
1629        assert_eq!(d.salt, b"somesalt");
1630        assert_eq!(d.hash, &V10_TAG);
1631    }
1632
1633    #[test]
1634    fn decode_sets_threads_to_lanes() {
1635        let s = "$argon2id$v=19$m=65536,t=2,p=4$c29tZXNhbHQ\
1636                 $CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc";
1637        let d = decode_string(s, Algorithm::Argon2id).unwrap();
1638        assert_eq!(d.params.lanes(), 4);
1639        assert_eq!(d.params.threads(), 4);
1640    }
1641
1642    #[test]
1643    fn encode_decode_round_trip() {
1644        let salt = b"0123456789abcdef";
1645        let tag: Vec<u8> = (0u8..48).collect();
1646        for algorithm in Algorithm::ALL {
1647            for version in Version::ALL {
1648                for lanes in [1u32, 2, 255] {
1649                    let params = Params::new(1 << 16, 3, lanes, tag.len()).unwrap();
1650                    let encoded =
1651                        encode_string_alloc(algorithm, version, &params, salt, &tag).unwrap();
1652                    let d = decode_string(&encoded, algorithm).unwrap();
1653                    assert_eq!(d.algorithm, algorithm);
1654                    assert_eq!(d.version, version);
1655                    assert_eq!(d.params, params);
1656                    assert_eq!(d.salt, salt);
1657                    assert_eq!(d.hash, tag);
1658                    // And re-encoding is byte-identical.
1659                    assert_eq!(
1660                        encode_string_alloc(d.algorithm, d.version, &d.params, &d.salt, &d.hash)
1661                            .unwrap(),
1662                        encoded
1663                    );
1664                }
1665            }
1666        }
1667    }
1668
1669    // The four malformed strings from `src/test.c`, both version flavours.
1670
1671    #[test]
1672    fn decode_rejects_a_missing_dollar_before_the_salt() {
1673        // "…,p=1c29tZXNhbHQ$…": the '$' after p=1 is gone.
1674        let v10 = "$argon2i$m=65536,t=2,p=1c29tZXNhbHQ\
1675                   $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1676        assert_eq!(
1677            decode_string(v10, Algorithm::Argon2i),
1678            Err(Error::DecodingFail)
1679        );
1680        let v13 = "$argon2i$v=19$m=65536,t=2,p=1c29tZXNhbHQ\
1681                   $wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA";
1682        assert_eq!(
1683            decode_string(v13, Algorithm::Argon2i),
1684            Err(Error::DecodingFail)
1685        );
1686    }
1687
1688    #[test]
1689    fn decode_rejects_a_missing_dollar_before_the_tag() {
1690        // The salt and tag run together into one 54-character base64 field,
1691        // which decodes cleanly (54 chars leave 4 zero bits); the failure comes
1692        // from the CC("$") that follows.
1693        let v10 = "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ\
1694                   9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1695        assert_eq!(
1696            decode_string(v10, Algorithm::Argon2i),
1697            Err(Error::DecodingFail)
1698        );
1699        let v13 = "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ\
1700                   wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA";
1701        assert_eq!(
1702            decode_string(v13, Algorithm::Argon2i),
1703            Err(Error::DecodingFail)
1704        );
1705    }
1706
1707    #[test]
1708    fn decode_reports_salt_too_short_not_decoding_fail() {
1709        // This is the distinction tests/vectors.rs relies on: the string parses,
1710        // and it is validate_inputs() that rejects it.
1711        let v10 = "$argon2i$m=65536,t=2,p=1$\
1712                   $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1713        assert_eq!(
1714            decode_string(v10, Algorithm::Argon2i),
1715            Err(Error::SaltTooShort)
1716        );
1717        let v13 = "$argon2i$v=19$m=65536,t=2,p=1$\
1718                   $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1719        assert_eq!(
1720            decode_string(v13, Algorithm::Argon2i),
1721            Err(Error::SaltTooShort)
1722        );
1723        // A 7-byte salt is also too short, and still not a DecodingFail.
1724        let short = "$argon2i$v=19$m=65536,t=2,p=1$c2hvcnRz\
1725                     $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1726        assert_eq!(
1727            decode_string(short, Algorithm::Argon2i),
1728            Err(Error::SaltTooShort)
1729        );
1730    }
1731
1732    #[test]
1733    fn decode_argon2i_is_a_prefix_of_argon2id() {
1734        // CC("argon2i") matches the first seven characters of "argon2id"; the
1735        // leftover 'd' then fails the CC("$m=") (or the CC_opt("$v=")).
1736        assert_eq!(
1737            decode_string(V13_ARGON2ID, Algorithm::Argon2i),
1738            Err(Error::DecodingFail)
1739        );
1740        let v10_id = "$argon2id$m=65536,t=2,p=1$c29tZXNhbHQ\
1741                      $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1742        assert_eq!(
1743            decode_string(v10_id, Algorithm::Argon2i),
1744            Err(Error::DecodingFail)
1745        );
1746        // And the other way round: "argon2i" is not "argon2id".
1747        assert_eq!(
1748            decode_string(V13_ARGON2I, Algorithm::Argon2id),
1749            Err(Error::DecodingFail)
1750        );
1751        assert_eq!(
1752            decode_string(V13_ARGON2I, Algorithm::Argon2d),
1753            Err(Error::DecodingFail)
1754        );
1755        // The correct type still works, of course.
1756        assert!(decode_string(V13_ARGON2ID, Algorithm::Argon2id).is_ok());
1757    }
1758
1759    #[test]
1760    fn decode_rejects_structural_damage() {
1761        let cases: &[&str] = &[
1762            "",
1763            "$",
1764            "argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1765            "$argon2i",
1766            "$argon2i$v=19",
1767            "$argon2i$v=19$m=65536,t=2,p=1",
1768            "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ",
1769            // 'x' is not a decimal digit.
1770            "$argon2i$v=19$m=x,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1771            // Fields out of order.
1772            "$argon2i$v=19$t=2,m=65536,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1773            // Trailing junk after the tag.
1774            "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ$",
1775            "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ ",
1776            // '=' padding is not part of the alphabet, so it ends the field.
1777            "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ=$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1778        ];
1779        for case in cases {
1780            assert_eq!(
1781                decode_string(case, Algorithm::Argon2i),
1782                Err(Error::DecodingFail),
1783                "expected DecodingFail for {case:?}"
1784            );
1785        }
1786    }
1787
1788    #[test]
1789    fn decode_surfaces_the_c_validation_codes() {
1790        // A 3-byte tag: outlen is checked first.
1791        assert_eq!(
1792            decode_string(
1793                "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$AAAA",
1794                Algorithm::Argon2i
1795            ),
1796            Err(Error::OutputTooShort)
1797        );
1798        // m_cost < ARGON2_MIN_MEMORY.
1799        assert_eq!(
1800            decode_string(
1801                "$argon2i$v=19$m=1,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1802                Algorithm::Argon2i
1803            ),
1804            Err(Error::MemoryTooLittle)
1805        );
1806        // m_cost < 8 * lanes.
1807        assert_eq!(
1808            decode_string(
1809                "$argon2i$v=19$m=16,t=2,p=4$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1810                Algorithm::Argon2i
1811            ),
1812            Err(Error::MemoryTooLittle)
1813        );
1814        // t_cost < ARGON2_MIN_TIME.
1815        assert_eq!(
1816            decode_string(
1817                "$argon2i$v=19$m=65536,t=0,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1818                Algorithm::Argon2i
1819            ),
1820            Err(Error::TimeTooSmall)
1821        );
1822        // lanes < ARGON2_MIN_LANES.
1823        assert_eq!(
1824            decode_string(
1825                "$argon2i$v=19$m=65536,t=2,p=0$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1826                Algorithm::Argon2i
1827            ),
1828            Err(Error::LanesTooFew)
1829        );
1830        // lanes > ARGON2_MAX_LANES (16777215).
1831        #[cfg(target_pointer_width = "64")]
1832        assert_eq!(
1833            decode_string(
1834                "$argon2i$v=19$m=4294967295,t=2,p=16777216$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1835                Algorithm::Argon2i
1836            ),
1837            Err(Error::LanesTooMany)
1838        );
1839        // On a 32-bit target ARGON2_MAX_MEMORY is 2 MiB (the C's own
1840        // pointer-width rule), so — exactly as the C on 32-bit — the memory
1841        // check fires before the lanes check gets a chance to.
1842        #[cfg(target_pointer_width = "32")]
1843        assert_eq!(
1844            decode_string(
1845                "$argon2i$v=19$m=4294967295,t=2,p=16777216$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1846                Algorithm::Argon2i
1847            ),
1848            Err(Error::MemoryTooMuch)
1849        );
1850    }
1851
1852    #[test]
1853    fn validation_runs_before_the_trailing_character_check() {
1854        // Both wrong: the C returns SALT_TOO_SHORT because validate_inputs()
1855        // comes first.
1856        let s = "$argon2i$v=19$m=65536,t=2,p=1$\
1857                 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ!!!";
1858        assert_eq!(
1859            decode_string(s, Algorithm::Argon2i),
1860            Err(Error::SaltTooShort)
1861        );
1862    }
1863
1864    #[test]
1865    fn decode_rejects_an_unrepresentable_version() {
1866        // Documented divergence: the C accepts this (validate_inputs never
1867        // looks at the version) and treats it as 0x13.
1868        let s = "$argon2i$v=99$m=65536,t=2,p=1$c29tZXNhbHQ\
1869                 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1870        assert_eq!(
1871            decode_string(s, Algorithm::Argon2i),
1872            Err(Error::DecodingFail)
1873        );
1874        // …but an earlier error still wins, so the codes stay C-compatible.
1875        let s = "$argon2i$v=99$m=65536,t=2,p=1$$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1876        assert_eq!(
1877            decode_string(s, Algorithm::Argon2i),
1878            Err(Error::SaltTooShort)
1879        );
1880    }
1881
1882    #[test]
1883    fn decode_accepts_a_long_salt_and_tag() {
1884        let salt: Vec<u8> = (0u8..=255).collect();
1885        let tag: Vec<u8> = (0u8..=200).rev().collect();
1886        let params = Params::new(1 << 16, 1, 1, tag.len()).unwrap();
1887        let encoded =
1888            encode_string_alloc(Algorithm::Argon2d, Version::V0x13, &params, &salt, &tag).unwrap();
1889        let d = decode_string(&encoded, Algorithm::Argon2d).unwrap();
1890        assert_eq!(d.salt, salt);
1891        assert_eq!(d.hash, tag);
1892        assert_eq!(d.params.output_len(), tag.len());
1893    }
1894}