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