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    to_base64_raw(dst.as_mut_ptr(), dst.len(), src)
398}
399
400fn to_base64_raw(dst: *mut u8, dst_len: usize, src: &[u8]) -> Result<usize, Error> {
401    if src.len() < crate::base64::MIN_ENCODE_LEN {
402        // Keep backend lookup and the generalized prefix state completely out
403        // of tiny salts. This is the original scalar function's exact shape.
404        return to_base64_scalar(dst, dst_len, src);
405    }
406    let backend = crate::base64::base64_backend();
407    // SAFETY: runtime detection returns only an executable backend.
408    unsafe { to_base64_with_backend_raw(dst, dst_len, src, backend) }
409}
410
411/// The original reference-C loop, kept whole so short inputs do not pay for or
412/// inhibit optimization around a SIMD prefix they cannot use.
413///
414/// Writes only; `dst` may be uninitialized spare capacity.
415#[inline(always)]
416fn to_base64_scalar(dst: *mut u8, dst_len: usize, src: &[u8]) -> Result<usize, Error> {
417    let olen = b64_len_usize(src.len());
418    if dst_len <= olen {
419        return Err(Error::EncodingFail);
420    }
421
422    let mut acc: u32 = 0;
423    let mut acc_len: u32 = 0;
424    let mut written = 0usize;
425    for &byte in src {
426        acc = (acc << 8) | byte as u32;
427        acc_len += 8;
428        while acc_len >= 6 {
429            acc_len -= 6;
430            // SAFETY: `written < olen < dst_len`.
431            unsafe {
432                dst.add(written)
433                    .write(b64_byte_to_char((acc >> acc_len) & 0x3f));
434            }
435            written += 1;
436        }
437    }
438    if acc_len > 0 {
439        // SAFETY: the last leftover character is still inside `olen`.
440        unsafe {
441            dst.add(written)
442                .write(b64_byte_to_char((acc << (6 - acc_len)) & 0x3f));
443        }
444        written += 1;
445    }
446
447    debug_assert_eq!(written, olen);
448    Ok(written)
449}
450
451/// Encode with an explicitly selected Base64 backend.
452///
453/// This is an unstable test/benchmark hook. Normal callers use [`to_base64`],
454/// which performs safe runtime detection.
455///
456/// # Safety
457///
458/// `backend` must be executable on the current CPU, as reported by
459/// [`Base64Backend::is_available`].
460#[inline]
461pub unsafe fn to_base64_with_backend(
462    dst: &mut [u8],
463    src: &[u8],
464    backend: Base64Backend,
465) -> Result<usize, Error> {
466    // SAFETY: `dst` is a live initialized slice; the backend contract is the
467    // caller's, same as before this raw-pointer split.
468    unsafe { to_base64_with_backend_raw(dst.as_mut_ptr(), dst.len(), src, backend) }
469}
470
471unsafe fn to_base64_with_backend_raw(
472    dst: *mut u8,
473    dst_len: usize,
474    src: &[u8],
475    backend: Base64Backend,
476) -> Result<usize, Error> {
477    if backend == Base64Backend::Scalar {
478        return to_base64_scalar(dst, dst_len, src);
479    }
480
481    let olen = b64_len_usize(src.len());
482    if dst_len <= olen {
483        return Err(Error::EncodingFail);
484    }
485
486    // SAFETY: transferred from this function's caller. The capacity check
487    // above proves every complete vector store is within `dst_len`.
488    let (consumed, mut written) = unsafe { crate::base64::encode_prefix(backend, dst, src) };
489    let mut acc: u32 = 0;
490    let mut acc_len: u32 = 0;
491
492    for &byte in &src[consumed..] {
493        // The C writes `(acc << 8) + *buf++`; the low 8 bits of `acc << 8` are
494        // zero, so `|` is the same value and cannot overflow in debug builds.
495        acc = (acc << 8) | byte as u32;
496        acc_len += 8;
497        while acc_len >= 6 {
498            acc_len -= 6;
499            // SAFETY: `written` stays inside `olen < dst_len`.
500            unsafe {
501                dst.add(written)
502                    .write(b64_byte_to_char((acc >> acc_len) & 0x3F));
503            }
504            written += 1;
505        }
506    }
507    if acc_len > 0 {
508        // SAFETY: leftover character is the last of `olen`.
509        unsafe {
510            dst.add(written)
511                .write(b64_byte_to_char((acc << (6 - acc_len)) & 0x3F));
512        }
513        written += 1;
514    }
515
516    debug_assert!(written == olen);
517    Ok(written)
518}
519
520/// `from_base64(dst, dst_len, src)`.
521///
522/// Decodes until the first non-base64 byte. Returns
523/// `(bytes_written, bytes_consumed)`, where `bytes_consumed` indexes the first
524/// non-base64 byte in `src` — the equivalent of the pointer the C returns. The
525/// end of the slice acts as the C's terminating NUL, which is itself not a
526/// base64 character.
527///
528/// # Errors
529///
530/// [`Error::DecodingFail`] if `dst` is too small, if `acc_len > 4` at the end,
531/// or if any buffered low bits are non-zero.
532pub fn from_base64(dst: &mut [u8], src: &[u8]) -> Result<(usize, usize), Error> {
533    from_base64_raw(dst.as_mut_ptr(), dst.len(), src)
534}
535
536fn from_base64_raw(dst: *mut u8, dst_len: usize, src: &[u8]) -> Result<(usize, usize), Error> {
537    if src.len() < crate::base64::MIN_DECODE_LEN {
538        // As in the encoder, keep both lookup and generalized prefix state out
539        // of inputs too short for this architecture's smallest vector.
540        return from_base64_scalar(dst, dst_len, src);
541    }
542    let backend = crate::base64::base64_backend();
543    // SAFETY: as in `to_base64`, detection proves the feature contract.
544    unsafe { from_base64_with_backend_raw(dst, dst_len, src, backend) }
545}
546
547/// The original reference-C loop, kept whole for the scalar and short-input
548/// paths just like [`to_base64_scalar`].
549///
550/// Writes only; `dst` may be uninitialized spare capacity.
551#[inline(always)]
552fn from_base64_scalar(dst: *mut u8, dst_len: usize, src: &[u8]) -> Result<(usize, usize), Error> {
553    let mut consumed = 0usize;
554    let mut len = 0usize;
555    let mut acc: u32 = 0;
556    let mut acc_len: u32 = 0;
557
558    loop {
559        // Past the end of the slice, feed the NUL the C would have read.
560        let c = match src.get(consumed) {
561            Some(&byte) => byte as u32,
562            None => 0,
563        };
564        let d = b64_char_to_byte(c);
565        if d == 0xFF {
566            break;
567        }
568        consumed += 1;
569        acc = (acc << 6) | d;
570        acc_len += 6;
571        if acc_len >= 8 {
572            acc_len -= 8;
573            if len >= dst_len {
574                return Err(Error::DecodingFail);
575            }
576            // SAFETY: `len < dst_len`.
577            unsafe {
578                dst.add(len).write(((acc >> acc_len) & 0xFF) as u8);
579            }
580            len += 1;
581        }
582    }
583
584    if acc_len > 4 || (acc & ((1u32 << acc_len) - 1)) != 0 {
585        return Err(Error::DecodingFail);
586    }
587
588    Ok((len, consumed))
589}
590
591/// Decode with an explicitly selected Base64 backend.
592///
593/// This preserves [`from_base64`]'s exact stopping and error behavior and is
594/// exposed only as an unstable differential-test/benchmark hook.
595///
596/// # Safety
597///
598/// `backend` must be executable on the current CPU, as reported by
599/// [`Base64Backend::is_available`].
600#[inline]
601pub unsafe fn from_base64_with_backend(
602    dst: &mut [u8],
603    src: &[u8],
604    backend: Base64Backend,
605) -> Result<(usize, usize), Error> {
606    // SAFETY: `dst` is a live slice; the backend contract is the caller's.
607    unsafe { from_base64_with_backend_raw(dst.as_mut_ptr(), dst.len(), src, backend) }
608}
609
610unsafe fn from_base64_with_backend_raw(
611    dst: *mut u8,
612    dst_len: usize,
613    src: &[u8],
614    backend: Base64Backend,
615) -> Result<(usize, usize), Error> {
616    if backend == Base64Backend::Scalar {
617        return from_base64_scalar(dst, dst_len, src);
618    }
619
620    // SAFETY: transferred from this function's caller. Each backend checks the
621    // supplied slice lengths before loading or storing a complete block.
622    let (mut consumed, mut len) =
623        unsafe { crate::base64::decode_prefix(backend, dst, dst_len, src) };
624    let mut acc: u32 = 0;
625    let mut acc_len: u32 = 0;
626
627    loop {
628        // Past the end of the slice, feed the NUL the C would have read.
629        let c = match src.get(consumed) {
630            Some(&byte) => byte as u32,
631            None => 0,
632        };
633        let d = b64_char_to_byte(c);
634        if d == 0xFF {
635            break;
636        }
637        consumed += 1;
638        // As in `to_base64`, `|` matches the C's `+` bit for bit.
639        acc = (acc << 6) | d;
640        acc_len += 6;
641        if acc_len >= 8 {
642            acc_len -= 8;
643            // The C is `if ((len++) >= *dst_len) return NULL;`, i.e. the test
644            // uses the pre-increment value.
645            if len >= dst_len {
646                return Err(Error::DecodingFail);
647            }
648            // SAFETY: `len < dst_len`.
649            unsafe {
650                dst.add(len).write(((acc >> acc_len) & 0xFF) as u8);
651            }
652            len += 1;
653        }
654    }
655
656    // An input length of 1 modulo 4 leaves 6 unprocessed bits, which is
657    // invalid; otherwise 0, 2 or 4 bits are buffered and they must be zero.
658    if acc_len > 4 || (acc & ((1u32 << acc_len) - 1)) != 0 {
659        return Err(Error::DecodingFail);
660    }
661
662    Ok((len, consumed))
663}
664
665/// Encode `src` as unpadded standard Base64, the alphabet PHC strings use.
666///
667/// Same dispatch as [`to_base64`]: SIMD when `src` is at least one vector
668/// long (16 bytes on x86, 24 on aarch64), scalar below that.
669///
670/// ```
671/// use argon2_rust::encode_base64;
672///
673/// assert_eq!(encode_base64(b"somesalt")?, "c29tZXNhbHQ");
674/// # Ok::<(), argon2_rust::Error>(())
675/// ```
676///
677/// # Errors
678///
679/// [`Error::MemoryAllocationError`] if the output buffer cannot be allocated.
680pub fn encode_base64(src: &[u8]) -> Result<String, Error> {
681    // `to_base64` requires `dst_len > olen` (the C writes a NUL).
682    let cap = b64_len_usize(src.len()) + 1;
683    let mut buf = reserve_vec(cap)?;
684    let written = to_base64_raw(buf.as_mut_ptr(), cap, src)?;
685    // SAFETY: `to_base64_raw` wrote `written` bytes of the Base64 alphabet.
686    unsafe {
687        buf.set_len(written);
688    }
689    Ok(ascii_to_string(buf))
690}
691
692/// Decode unpadded standard Base64, the alphabet PHC strings use.
693///
694/// Same dispatch as [`from_base64`]. The whole of `src` must be alphabet
695/// characters; leftover junk is [`Error::DecodingFail`], unlike the C's
696/// `from_base64`, which stops at the first non-alphabet byte and leaves the
697/// tail for the caller. PHC fields have no tail.
698///
699/// ```
700/// use argon2_rust::decode_base64;
701///
702/// assert_eq!(decode_base64(b"c29tZXNhbHQ")?, b"somesalt");
703/// # Ok::<(), argon2_rust::Error>(())
704/// ```
705///
706/// # Errors
707///
708/// [`Error::DecodingFail`] if `src` is not entirely valid unpadded Base64, or
709/// [`Error::MemoryAllocationError`] if the output buffer cannot be allocated.
710pub fn decode_base64(src: &[u8]) -> Result<Vec<u8>, Error> {
711    // Isolated PHC fields are all alphabet; `n * 3 / 4` is exact for every
712    // valid unpadded length and is the most a valid decode can write.
713    let cap = src.len() * 3 / 4;
714    let mut buf = reserve_vec(cap)?;
715    let (written, consumed) = from_base64_raw(buf.as_mut_ptr(), cap, src)?;
716    if consumed != src.len() {
717        return Err(Error::DecodingFail);
718    }
719    // SAFETY: `from_base64_raw` wrote `written` bytes and `written <= cap`.
720    unsafe {
721        buf.set_len(written);
722    }
723    Ok(buf)
724}
725
726// ---------------------------------------------------------------------------
727// decode_decimal
728// ---------------------------------------------------------------------------
729
730/// `decode_decimal(str, v)`.
731///
732/// Returns `(value, digits_consumed)`, or `None` when there is no digit at all,
733/// when the encoding is not minimal (a leading `'0'` with more digits after
734/// it), or when the value overflows.
735///
736/// The C accumulates in `unsigned long`, which is 64-bit on every target this
737/// crate supports, so `u64` matches it. On a hypothetical 32-bit `unsigned
738/// long` the outcome would still be the same, because every caller is
739/// `DECIMAL_U32`, which rejects anything above `u32::MAX` anyway.
740fn decode_decimal(src: &[u8]) -> Option<(u64, usize)> {
741    let mut acc: u64 = 0;
742    let mut i = 0usize;
743
744    while let Some(&c) = src.get(i) {
745        if !c.is_ascii_digit() {
746            break;
747        }
748        let digit = (c - b'0') as u64;
749        if acc > u64::MAX / 10 {
750            return None;
751        }
752        acc *= 10;
753        if digit > u64::MAX - acc {
754            return None;
755        }
756        acc += digit;
757        i += 1;
758    }
759
760    // `if (str == orig || (*orig == '0' && str != (orig + 1))) return NULL;`
761    if i == 0 {
762        return None;
763    }
764    if src[0] == b'0' && i != 1 {
765        return None;
766    }
767
768    Some((acc, i))
769}
770
771// ---------------------------------------------------------------------------
772// encode_string
773// ---------------------------------------------------------------------------
774
775/// The C's `SS`/`SX`/`SB` macros: a cursor that always keeps one byte spare for
776/// the NUL terminator the C writes, so the capacity requirement is identical.
777struct Writer {
778    dst: *mut u8,
779    dst_len: usize,
780    pos: usize,
781}
782
783impl Writer {
784    /// `SS(str)`: `if (pp_len >= dst_len) return ARGON2_ENCODING_FAIL;`.
785    fn put(&mut self, bytes: &[u8]) -> Result<(), Error> {
786        let remaining = self.dst_len - self.pos;
787        if bytes.len() >= remaining {
788            return Err(Error::EncodingFail);
789        }
790        // SAFETY: the capacity check leaves room for `bytes`, and this write
791        // does not read the destination.
792        unsafe {
793            self.dst
794                .add(self.pos)
795                .copy_from_nonoverlapping(bytes.as_ptr(), bytes.len());
796        }
797        self.pos += bytes.len();
798        Ok(())
799    }
800
801    /// `SX(x)`: the decimal form of `x`, no allocation.
802    fn put_u32(&mut self, value: u32) -> Result<(), Error> {
803        // `u32::MAX` is 4294967295: ten digits.
804        let mut buf = [0u8; 10];
805        let mut i = buf.len();
806        let mut n = value;
807        loop {
808            i -= 1;
809            buf[i] = b'0' + (n % 10) as u8;
810            n /= 10;
811            if n == 0 {
812                break;
813            }
814        }
815        self.put(&buf[i..])
816    }
817
818    /// `SB(buf, len)`: base64, which does its own capacity check.
819    fn put_base64(&mut self, src: &[u8]) -> Result<(), Error> {
820        // SAFETY: `self.pos <= self.dst_len`; `to_base64_raw` only writes.
821        let written = to_base64_raw(
822            unsafe { self.dst.add(self.pos) },
823            self.dst_len - self.pos,
824            src,
825        )?;
826        self.pos += written;
827        Ok(())
828    }
829}
830
831/// `validate_inputs(ctx)` as `encode_string` and `decode_string` run it.
832///
833/// `out_len` is the *tag* length, which is what `ctx->outlen` holds in both
834/// call sites, and `pwd_len` is 0 (see the module-level divergence note).
835fn validate_for_string(params: &Params, salt_len: usize, hash_len: usize) -> Result<(), Error> {
836    validate_inputs(
837        hash_len,
838        0,
839        salt_len,
840        0,
841        0,
842        params.memory_kib(),
843        params.passes(),
844        params.lanes(),
845        params.threads(),
846    )
847}
848
849/// `encode_string(dst, dst_len, ctx, type)`.
850///
851/// Writes the PHC string into `dst` (no NUL terminator) and returns its length.
852/// Always emits `$v=`, as the C does.
853///
854/// `dst` must hold the string **plus one byte**, because the C reserves room
855/// for its NUL terminator and this port keeps the capacity rule identical:
856/// a buffer of exactly [`encoded_len`] bytes is what succeeds, in Rust and in
857/// C alike.
858///
859/// `hash.len()` plays the role of `ctx->outlen` — it is the length that gets
860/// encoded, so it, and not [`Params::tag_len_bytes`], is what the leading
861/// `validate_inputs()` checks. For a tag produced from these `params` the two
862/// are the same value.
863///
864/// # Errors
865///
866/// [`Error::EncodingFail`] if `dst` is too small, or whatever
867/// [`crate::params::validate_inputs`] returns — `encode_string` runs it first,
868/// exactly like the C.
869pub fn encode_string(
870    dst: &mut [u8],
871    algorithm: Algorithm,
872    version: Version,
873    params: &Params,
874    salt: &[u8],
875    hash: &[u8],
876) -> Result<usize, Error> {
877    encode_string_raw(dst.as_mut_ptr(), dst.len(), algorithm, version, params, salt, hash)
878}
879
880fn encode_string_raw(
881    dst: *mut u8,
882    dst_len: usize,
883    algorithm: Algorithm,
884    version: Version,
885    params: &Params,
886    salt: &[u8],
887    hash: &[u8],
888) -> Result<usize, Error> {
889    validate_for_string(params, salt.len(), hash.len())?;
890
891    let mut w = Writer {
892        dst,
893        dst_len,
894        pos: 0,
895    };
896
897    w.put(b"$")?;
898    w.put(algorithm.as_str().as_bytes())?;
899
900    w.put(b"$v=")?;
901    w.put_u32(version.as_u32())?;
902
903    w.put(b"$m=")?;
904    w.put_u32(params.memory_kib())?;
905    w.put(b",t=")?;
906    w.put_u32(params.passes())?;
907    w.put(b",p=")?;
908    w.put_u32(params.lanes())?;
909
910    w.put(b"$")?;
911    w.put_base64(salt)?;
912
913    w.put(b"$")?;
914    w.put_base64(hash)?;
915
916    Ok(w.pos)
917}
918
919/// [`encode_string`] into a freshly allocated [`String`].
920///
921/// # Errors
922///
923/// As [`encode_string`], plus [`Error::MemoryAllocationError`] if the buffer
924/// cannot be allocated (the C returns the same code when its `malloc` fails).
925pub fn encode_string_alloc(
926    algorithm: Algorithm,
927    version: Version,
928    params: &Params,
929    salt: &[u8],
930    hash: &[u8],
931) -> Result<String, Error> {
932    // Validate before allocating, so an over-long salt reports SaltTooLong
933    // rather than failing to allocate a buffer sized from it.
934    validate_for_string(params, salt.len(), hash.len())?;
935
936    let capacity = encoded_len_usize(
937        algorithm,
938        params.passes(),
939        params.memory_kib(),
940        params.lanes(),
941        salt.len(),
942        hash.len(),
943    );
944
945    let mut buf = reserve_vec(capacity)?;
946    let written = encode_string_raw(
947        buf.as_mut_ptr(),
948        capacity,
949        algorithm,
950        version,
951        params,
952        salt,
953        hash,
954    )?;
955    // SAFETY: `encode_string` wrote `written` ASCII bytes.
956    unsafe {
957        buf.set_len(written);
958    }
959    Ok(ascii_to_string(buf))
960}
961
962/// Spare capacity only — len stays 0 until the caller writes and `set_len`s.
963fn reserve_vec(cap: usize) -> Result<Vec<u8>, Error> {
964    let mut v = Vec::new();
965    if cap != 0 {
966        v.try_reserve_exact(cap)
967            .map_err(|_| Error::MemoryAllocationError)?;
968    }
969    Ok(v)
970}
971
972/// The PHC alphabet and punctuation are ASCII.
973fn ascii_to_string(buf: Vec<u8>) -> String {
974    // SAFETY: every byte came from `b64_byte_to_char`, a decimal digit, or a
975    // `$` / `v` / `m` / `t` / `p` / `=` / `,` literal.
976    unsafe { String::from_utf8_unchecked(buf) }
977}
978
979// ---------------------------------------------------------------------------
980// decode_string
981// ---------------------------------------------------------------------------
982
983/// The fields a PHC string yields.
984#[derive(Debug, Clone, PartialEq, Eq)]
985pub struct Decoded {
986    /// The algorithm named by the string.
987    ///
988    /// [`decode_string`] requires the caller to pass this type and fails on a
989    /// mismatch. [`decode_phc`] reads it from the `$argon2*` prefix.
990    pub algorithm: Algorithm,
991    /// The version. `0x10` when the `$v=` field is absent.
992    pub version: Version,
993    /// `m_cost`, `t_cost`, `lanes`, `threads == lanes`, and
994    /// `output_len == hash.len()`.
995    pub params: Params,
996    /// The decoded salt.
997    pub salt: Vec<u8>,
998    /// The decoded tag.
999    pub hash: Vec<u8>,
1000    /// Associated data from a `data=` parameter, if the string had one.
1001    ///
1002    /// The C `decode_string` has no such field; [`decode_string`] always
1003    /// leaves this empty. [`decode_phc`] fills it for `@phc/format` /
1004    /// node-argon2 strings.
1005    pub ad: Vec<u8>,
1006}
1007
1008/// The remainder of `src` from `pos`, never panicking.
1009#[inline]
1010fn rest(src: &[u8], pos: usize) -> &[u8] {
1011    src.get(pos..).unwrap_or(&[])
1012}
1013
1014/// The `CC(prefix)` macro: consume `prefix` or fail.
1015fn expect(src: &[u8], pos: &mut usize, prefix: &[u8]) -> Result<(), Error> {
1016    if rest(src, *pos).starts_with(prefix) {
1017        *pos += prefix.len();
1018        Ok(())
1019    } else {
1020        Err(Error::DecodingFail)
1021    }
1022}
1023
1024/// The `CC_opt(prefix, code)` macro: consume `prefix` if it is there.
1025fn expect_opt(src: &[u8], pos: &mut usize, prefix: &[u8]) -> bool {
1026    if rest(src, *pos).starts_with(prefix) {
1027        *pos += prefix.len();
1028        true
1029    } else {
1030        false
1031    }
1032}
1033
1034/// The `DECIMAL_U32(x)` macro.
1035fn decimal_u32(src: &[u8], pos: &mut usize) -> Result<u32, Error> {
1036    let (value, consumed) = decode_decimal(rest(src, *pos)).ok_or(Error::DecodingFail)?;
1037    if value > u32::MAX as u64 {
1038        return Err(Error::DecodingFail);
1039    }
1040    *pos += consumed;
1041    Ok(value as u32)
1042}
1043
1044/// The `BIN(buf, max_len, len)` macro.
1045///
1046/// The C sizes the destination at `strlen(encoded)` (see `argon2_verify`), so
1047/// the "output buffer too small" branch of `from_base64` is unreachable there.
1048/// The bound used here — three bytes out per four characters in — is likewise
1049/// never exceeded, so the two agree.
1050fn decode_bin(src: &[u8], pos: &mut usize) -> Result<Vec<u8>, Error> {
1051    let tail = rest(src, *pos);
1052    // `n` base64 characters decode to `floor(3n / 4)` bytes; `n/4*3 + 3` is an
1053    // upper bound for every `n`, and cannot overflow for any real slice.
1054    let max_len = tail.len() / 4 * 3 + 3;
1055
1056    let mut buf = reserve_vec(max_len)?;
1057    let (written, consumed) = from_base64_raw(buf.as_mut_ptr(), max_len, tail)?;
1058    // `bin_len > UINT32_MAX` is a decoding failure in the C.
1059    if written > u32::MAX as usize {
1060        return Err(Error::DecodingFail);
1061    }
1062    // SAFETY: `from_base64_raw` wrote `written` bytes and `written <= max_len`.
1063    unsafe {
1064        buf.set_len(written);
1065    }
1066    *pos += consumed;
1067    Ok(buf)
1068}
1069
1070/// `decode_string(ctx, str, type)`.
1071///
1072/// # Errors
1073///
1074/// [`Error::DecodingFail`] for a malformed string, or whatever
1075/// [`crate::params::validate_inputs`] returns — the C runs the full validation
1076/// before accepting the string, so a well-formed string with a zero-length salt
1077/// yields [`Error::SaltTooShort`] and not [`Error::DecodingFail`].
1078///
1079/// See the module documentation for the known divergences from the C
1080/// (unrepresentable versions, embedded NULs, and raw/non-ASCII input).
1081pub fn decode_string(encoded: &str, algorithm: Algorithm) -> Result<Decoded, Error> {
1082    let src = encoded.as_bytes();
1083    let mut pos = 0usize;
1084
1085    // argon2.c:268-271
1086    //   encoded_len = strlen(encoded);
1087    //   if (encoded_len > UINT32_MAX) return ARGON2_DECODING_FAIL;
1088    //
1089    // The C puts this in `argon2_verify`, one level up, and computes
1090    // `max_field_len` from it. Here it lives in the decoder because all four
1091    // verify entry points funnel through this function, so one check covers
1092    // them and cannot drift; through the public API the behaviour is identical.
1093    // Reachable only where `usize` is wider than `u32`, from a `&str` at least
1094    // 4 GiB long.
1095    if src.len() > u32::MAX as usize {
1096        return Err(Error::DecodingFail);
1097    }
1098
1099    // CC("$"); CC(type_string);
1100    //
1101    // No `ARGON2_INCORRECT_TYPE` branch: `argon2_type2string` only returns NULL
1102    // for a type outside the enum, which `Algorithm` cannot represent.
1103    expect(src, &mut pos, b"$")?;
1104    expect(src, &mut pos, algorithm.as_str().as_bytes())?;
1105
1106    // ctx->version = ARGON2_VERSION_10; CC_opt("$v=", DECIMAL_U32(version));
1107    let mut version_value = Version::V0x10.as_u32();
1108    if expect_opt(src, &mut pos, b"$v=") {
1109        version_value = decimal_u32(src, &mut pos)?;
1110    }
1111
1112    expect(src, &mut pos, b"$m=")?;
1113    let m_cost = decimal_u32(src, &mut pos)?;
1114    expect(src, &mut pos, b",t=")?;
1115    let t_cost = decimal_u32(src, &mut pos)?;
1116    expect(src, &mut pos, b",p=")?;
1117    let lanes = decimal_u32(src, &mut pos)?;
1118    // `ctx->threads = ctx->lanes;`
1119    let threads = lanes;
1120
1121    expect(src, &mut pos, b"$")?;
1122    let salt = decode_bin(src, &mut pos)?;
1123    expect(src, &mut pos, b"$")?;
1124    let hash = decode_bin(src, &mut pos)?;
1125
1126    // "On return, must have valid context": the full validate_inputs(), in the
1127    // C's order, before the trailing-character check.
1128    validate_inputs(
1129        hash.len(),
1130        0,
1131        salt.len(),
1132        0,
1133        0,
1134        m_cost,
1135        t_cost,
1136        lanes,
1137        threads,
1138    )?;
1139
1140    // "Can't have any additional characters".
1141    if pos != src.len() {
1142        return Err(Error::DecodingFail);
1143    }
1144
1145    // Last, so that every error code above still matches the C exactly.
1146    let version = Version::from_u32(version_value).ok_or(Error::DecodingFail)?;
1147
1148    // Attacker-chosen values from the string, through the same validation any
1149    // caller's parameters get. Both conversions into the typed units widen —
1150    // `m_cost` is a `u32`, and `hash.len()` is a `usize` — so neither can lose a
1151    // bit before `build()` range-checks it.
1152    //
1153    // `build()` cannot actually reject anything here: `validate_inputs` above
1154    // already accepted this `m_cost`, `t_cost`, `lanes`, `threads` and
1155    // `hash.len()`, and `build()` re-runs exactly that check with a salt length
1156    // that is valid by construction. Propagating rather than unwrapping keeps
1157    // that a fact about today's checks instead of an assumption baked into a
1158    // panic. Named setters also mean `m=` cannot land in the pass count: `m=`
1159    // was parsed into `m_cost` above and only `.memory()` receives it.
1160    let params = Params::builder()
1161        .memory(Memory::kib(u64::from(m_cost)))
1162        .passes(t_cost)
1163        .lanes(lanes)
1164        .threads(threads)
1165        .tag_len(TagLen::bytes(hash.len() as u64))
1166        .build()?;
1167
1168    Ok(Decoded {
1169        algorithm,
1170        version,
1171        params,
1172        salt,
1173        hash,
1174        ad: Vec::new(),
1175    })
1176}
1177
1178/// Decode a PHC string, detecting the algorithm from the `$argon2*` prefix.
1179///
1180/// Unlike [`decode_string`] (C `decode_string`, fixed `$m=,t=,p=`), this
1181/// accepts:
1182///
1183/// * any order of `m`, `t`, `p`
1184/// * an optional `data=` associated-data field (node-argon2 / `@phc/format`)
1185/// * unknown keys such as `keyid` (ignored)
1186///
1187/// `$v=` is still optional and still defaults to version 16.
1188///
1189/// The C-style encoder ([`encode_string`], [`crate::Argon2::hash_encoded`]) never
1190/// writes `data=`. This decoder exists so a verifier can still honour strings
1191/// other producers emit.
1192///
1193/// ```
1194/// use argon2_rust::{Algorithm, decode_phc};
1195///
1196/// // node-argon2 / `@phc/format` write `m,p,t`.
1197/// let d = decode_phc(
1198///     "$argon2id$v=19$m=64,p=1,t=1$c29tZXNhbHQ$cpx6VEQbwTVZvcpxNIxOVUWZ5xnAipUmAe1cg2GMG70",
1199/// )?;
1200/// assert_eq!(d.algorithm, Algorithm::Argon2id);
1201/// assert_eq!(d.params.memory_kib(), 64);
1202/// assert_eq!(d.params.passes(), 1);
1203/// assert_eq!(d.salt, b"somesalt");
1204/// assert!(d.ad.is_empty());
1205/// # Ok::<(), argon2_rust::Error>(())
1206/// ```
1207///
1208/// # Errors
1209///
1210/// [`Error::DecodingFail`] for a malformed string, or whatever
1211/// [`crate::params::validate_inputs`] returns.
1212pub fn decode_phc(encoded: &str) -> Result<Decoded, Error> {
1213    let src = encoded.as_bytes();
1214    if src.len() > u32::MAX as usize {
1215        return Err(Error::DecodingFail);
1216    }
1217    let mut pos = 0usize;
1218
1219    expect(src, &mut pos, b"$")?;
1220    let algorithm = if expect_opt(src, &mut pos, b"argon2id") {
1221        Algorithm::Argon2id
1222    } else if expect_opt(src, &mut pos, b"argon2i") {
1223        Algorithm::Argon2i
1224    } else if expect_opt(src, &mut pos, b"argon2d") {
1225        Algorithm::Argon2d
1226    } else {
1227        return Err(Error::DecodingFail);
1228    };
1229
1230    let mut version_value = Version::V0x10.as_u32();
1231    if expect_opt(src, &mut pos, b"$v=") {
1232        version_value = decimal_u32(src, &mut pos)?;
1233    }
1234
1235    expect(src, &mut pos, b"$")?;
1236    let param_end = find_byte(rest(src, pos), b'$').ok_or(Error::DecodingFail)?;
1237    let param_bytes = rest(src, pos).get(..param_end).unwrap_or(&[]);
1238    let (m_cost, t_cost, lanes, ad) = parse_phc_params(param_bytes)?;
1239    pos += param_end;
1240
1241    // Isolate each Base64 field so the SIMD decoder sees a clean alphabet
1242    // run. Feeding it `salt$hash` puts `$` in the first vector and the
1243    // prefix falls back to scalar.
1244    expect(src, &mut pos, b"$")?;
1245    let salt_end = find_byte(rest(src, pos), b'$').ok_or(Error::DecodingFail)?;
1246    let salt = decode_base64(&rest(src, pos)[..salt_end])?;
1247    pos += salt_end;
1248
1249    expect(src, &mut pos, b"$")?;
1250    // `decode_base64` requires the tail to be entirely alphabet, which is
1251    // the trailing-junk check `decode_string` does with `pos != src.len()`.
1252    let hash = decode_base64(rest(src, pos))?;
1253
1254    validate_inputs(
1255        hash.len(),
1256        0,
1257        salt.len(),
1258        0,
1259        ad.len(),
1260        m_cost,
1261        t_cost,
1262        lanes,
1263        lanes,
1264    )?;
1265
1266    let version = Version::from_u32(version_value).ok_or(Error::DecodingFail)?;
1267    let params = Params::builder()
1268        .memory(Memory::kib(u64::from(m_cost)))
1269        .passes(t_cost)
1270        .lanes(lanes)
1271        .threads(lanes)
1272        .tag_len(TagLen::bytes(hash.len() as u64))
1273        .build()?;
1274
1275    Ok(Decoded {
1276        algorithm,
1277        version,
1278        params,
1279        salt,
1280        hash,
1281        ad,
1282    })
1283}
1284
1285fn parse_phc_params(src: &[u8]) -> Result<(u32, u32, u32, Vec<u8>), Error> {
1286    let mut memory = None;
1287    let mut passes = None;
1288    let mut lanes = None;
1289    let mut ad = Vec::new();
1290    let mut rest = src;
1291    if rest.is_empty() {
1292        return Err(Error::DecodingFail);
1293    }
1294    loop {
1295        let (field, next) = match find_byte(rest, b',') {
1296            Some(i) => (&rest[..i], &rest[i + 1..]),
1297            None => (rest, [].as_slice()),
1298        };
1299        let eq = find_byte(field, b'=').ok_or(Error::DecodingFail)?;
1300        let key = &field[..eq];
1301        let value = &field[eq + 1..];
1302        match key {
1303            b"m" => memory = Some(parse_param_u32(value)?),
1304            b"t" => passes = Some(parse_param_u32(value)?),
1305            b"p" => lanes = Some(parse_param_u32(value)?),
1306            b"data" => ad = decode_base64(value)?,
1307            _ => {}
1308        }
1309        if next.is_empty() {
1310            break;
1311        }
1312        rest = next;
1313    }
1314    match (memory, passes, lanes) {
1315        (Some(m), Some(t), Some(p)) => Ok((m, t, p, ad)),
1316        _ => Err(Error::DecodingFail),
1317    }
1318}
1319
1320fn parse_param_u32(src: &[u8]) -> Result<u32, Error> {
1321    let (value, consumed) = decode_decimal(src).ok_or(Error::DecodingFail)?;
1322    if consumed != src.len() || value > u64::from(u32::MAX) {
1323        return Err(Error::DecodingFail);
1324    }
1325    Ok(value as u32)
1326}
1327
1328/// First index of `needle`. Delegates to [`memchr`], which has its own
1329/// runtime SIMD cascade when this crate's `std` feature is on.
1330#[inline]
1331fn find_byte(haystack: &[u8], needle: u8) -> Option<usize> {
1332    memchr::memchr(needle, haystack)
1333}
1334
1335#[cfg(test)]
1336mod tests {
1337    use super::*;
1338    use alloc::vec;
1339
1340    // From `phc-winner-argon2/src/test.c`.
1341    const V13_ARGON2I: &str = "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ\
1342                               $wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA";
1343    const V10_ARGON2I: &str = "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ\
1344                               $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1345    const V13_ARGON2ID: &str = "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ\
1346                                $CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc";
1347
1348    /// `f6c4db4a...`, the raw tag of the v=0x10 Argon2i vector in test.c.
1349    const V10_TAG: [u8; 32] = [
1350        0xf6, 0xc4, 0xdb, 0x4a, 0x54, 0xe2, 0xa3, 0x70, 0x62, 0x7a, 0xff, 0x3d, 0xb6, 0x17, 0x6b,
1351        0x94, 0xa2, 0xa2, 0x09, 0xa6, 0x2c, 0x8e, 0x36, 0x15, 0x27, 0x11, 0x80, 0x2f, 0x7b, 0x30,
1352        0xc6, 0x94,
1353    ];
1354
1355    fn b64(src: &[u8]) -> Vec<u8> {
1356        let mut out = vec![0u8; b64_len_usize(src.len()) + 1];
1357        let n = to_base64(&mut out, src).expect("buffer sized by b64_len");
1358        out.truncate(n);
1359        out
1360    }
1361
1362    fn unb64(src: &[u8]) -> Result<Vec<u8>, Error> {
1363        let mut out = vec![0u8; src.len()];
1364        let (n, consumed) = from_base64(&mut out, src)?;
1365        assert_eq!(consumed, src.len(), "test inputs are pure base64");
1366        out.truncate(n);
1367        Ok(out)
1368    }
1369
1370    fn b64_with_backend(
1371        dst: &mut [u8],
1372        src: &[u8],
1373        backend: Base64Backend,
1374    ) -> Result<usize, Error> {
1375        assert!(backend.is_available());
1376        // SAFETY: the assertion establishes this test process can execute the
1377        // requested backend; slice bounds remain checked by the implementation.
1378        unsafe { to_base64_with_backend(dst, src, backend) }
1379    }
1380
1381    fn unb64_with_backend(
1382        dst: &mut [u8],
1383        src: &[u8],
1384        backend: Base64Backend,
1385    ) -> Result<(usize, usize), Error> {
1386        assert!(backend.is_available());
1387        // SAFETY: as in `b64_with_backend`, availability is proved immediately
1388        // above and both pointer/length pairs come from live slices.
1389        unsafe { from_base64_with_backend(dst, src, backend) }
1390    }
1391
1392    // -- lengths ------------------------------------------------------------
1393
1394    #[test]
1395    fn encode_base64_matches_known_and_round_trips() {
1396        assert_eq!(encode_base64(b"somesalt").unwrap(), "c29tZXNhbHQ");
1397        assert_eq!(decode_base64(b"c29tZXNhbHQ").unwrap(), b"somesalt");
1398        for n in [0usize, 1, 2, 8, 16, 24, 32, 48] {
1399            let src: Vec<u8> = (0..n).map(|i| i as u8).collect();
1400            let encoded = encode_base64(&src).unwrap();
1401            assert_eq!(decode_base64(encoded.as_bytes()).unwrap(), src, "n={n}");
1402        }
1403    }
1404
1405    #[test]
1406    fn decode_base64_rejects_junk_and_bad_length() {
1407        assert_eq!(decode_base64(b"A"), Err(Error::DecodingFail));
1408        assert_eq!(decode_base64(b"????"), Err(Error::DecodingFail));
1409        assert_eq!(decode_base64(b"c29tZXNhbHQ!"), Err(Error::DecodingFail));
1410    }
1411
1412    #[test]
1413    fn find_byte_matches_iterator_position() {
1414        for n in [0usize, 1, 15, 16, 17, 31, 32, 64, 80] {
1415            let mut src = vec![b'A'; n];
1416            assert_eq!(find_byte(&src, b'$'), None, "n={n} miss");
1417            if n == 0 {
1418                continue;
1419            }
1420            src[n / 2] = b'$';
1421            assert_eq!(find_byte(&src, b'$'), Some(n / 2), "n={n} mid");
1422            src[n / 2] = b'A';
1423            src[n - 1] = b'$';
1424            assert_eq!(find_byte(&src, b'$'), Some(n - 1), "n={n} last");
1425            src[0] = b'$';
1426            assert_eq!(find_byte(&src, b'$'), Some(0), "n={n} first");
1427        }
1428    }
1429
1430    #[test]
1431    fn b64_len_matches_c() {
1432        assert_eq!(b64_len(0), 0);
1433        assert_eq!(b64_len(1), 2);
1434        assert_eq!(b64_len(2), 3);
1435        assert_eq!(b64_len(3), 4);
1436        assert_eq!(b64_len(4), 6);
1437        // "somesalt" -> "c29tZXNhbHQ", "…" -> the 43-char tag.
1438        assert_eq!(b64_len(8), 11);
1439        assert_eq!(b64_len(32), 43);
1440        // Cross-check against the encoder for every small length.
1441        for len in 0u32..64 {
1442            let src = vec![0xABu8; len as usize];
1443            assert_eq!(b64(&src).len(), b64_len(len), "len {len}");
1444        }
1445    }
1446
1447    #[test]
1448    fn num_len_matches_c() {
1449        assert_eq!(num_len(0), 1);
1450        assert_eq!(num_len(9), 1);
1451        assert_eq!(num_len(10), 2);
1452        assert_eq!(num_len(19), 2);
1453        assert_eq!(num_len(65536), 5);
1454        assert_eq!(num_len(u32::MAX), 10);
1455    }
1456
1457    #[test]
1458    fn encoded_len_matches_the_c_vector() {
1459        // $argon2id$v=19$m=65536,t=2,p=1$<11>$<43> is 86 chars + NUL.
1460        let n = encoded_len(Algorithm::Argon2id, 2, 65536, 1, 8, 32);
1461        assert_eq!(n, V13_ARGON2ID.len() + 1);
1462        assert_eq!(n, 87);
1463        assert_eq!(
1464            encoded_len(Algorithm::Argon2i, 2, 65536, 1, 8, 32),
1465            V13_ARGON2I.len() + 1
1466        );
1467    }
1468
1469    // Pins the claim in `encoded_len`'s `# Argument order` section: the C's
1470    // `argon2_encodedlen` (`argon2.c:447`) takes `t_cost` before `m_cost`, and
1471    // this port keeps that order. `ParamsBuilder` has no argument order to
1472    // reverse — `.memory()` and `.passes()` are named — so this positional
1473    // signature is now the only place in the crate where the two costs can be
1474    // transposed at all.
1475    //
1476    // A caller who supplies the two costs the other way round gets no error
1477    // back, and the reason is arithmetic rather than luck. `t_cost` and `m_cost`
1478    // each reach the result through exactly one term, `num_len(t_cost)` and
1479    // `num_len(m_cost)`, and the two terms are added, so the sum does not depend
1480    // on which digit count came from which cost. Nothing else in the body reads
1481    // either value. The swap is therefore *always* harmless, not usually
1482    // harmless: the swapped call returns the same number, and that number is
1483    // also the correct one. Nothing passed to `encoded_len` reaches the emitted
1484    // string either -- `encode_string` writes the `m=` and `t=` fields out of
1485    // the `&Params` it is handed. A swapped call site is a cosmetic
1486    // inconsistency, not a latent bug.
1487    //
1488    // That makes the equality a property of this one formula and not a rule
1489    // about the crate. This test pins it at every digit-count boundary and will
1490    // fail here first if the length ever stops being a plain sum of the two
1491    // terms, at which point the doc gets fixed with it.
1492    #[test]
1493    fn encoded_len_is_symmetric_in_m_and_t() {
1494        // The pair the doc example uses, the C's own test vector, and the
1495        // extreme: `u32::MAX` is ten digits against one, the widest the two
1496        // terms can differ.
1497        assert_eq!(encoded_len(Algorithm::Argon2id, 3, 65536, 1, 16, 32), 98);
1498        assert_eq!(encoded_len(Algorithm::Argon2id, 65536, 3, 1, 16, 32), 98);
1499        assert_eq!(encoded_len(Algorithm::Argon2id, 2, 65536, 1, 8, 32), 87);
1500        assert_eq!(encoded_len(Algorithm::Argon2id, 65536, 2, 1, 8, 32), 87);
1501        assert_eq!(encoded_len(Algorithm::Argon2id, 1, u32::MAX, 1, 16, 32), 103);
1502        assert_eq!(encoded_len(Algorithm::Argon2id, u32::MAX, 1, 1, 16, 32), 103);
1503
1504        // Every place `num_len` changes answer, both sides of each step, over
1505        // all three algorithm strings, so the property is pinned rather than
1506        // sampled at a few lucky points.
1507        const BOUNDARIES: [u32; 22] = [
1508            0,
1509            1,
1510            9,
1511            10,
1512            99,
1513            100,
1514            999,
1515            1_000,
1516            9_999,
1517            10_000,
1518            99_999,
1519            100_000,
1520            999_999,
1521            1_000_000,
1522            9_999_999,
1523            10_000_000,
1524            99_999_999,
1525            100_000_000,
1526            999_999_999,
1527            1_000_000_000,
1528            65536,
1529            u32::MAX,
1530        ];
1531        for algorithm in [Algorithm::Argon2d, Algorithm::Argon2i, Algorithm::Argon2id] {
1532            for t_cost in BOUNDARIES {
1533                for m_cost in BOUNDARIES {
1534                    assert_eq!(
1535                        encoded_len(algorithm, t_cost, m_cost, 1, 16, 32),
1536                        encoded_len(algorithm, m_cost, t_cost, 1, 16, 32),
1537                        "{algorithm:?} t_cost={t_cost} m_cost={m_cost}"
1538                    );
1539                }
1540            }
1541        }
1542    }
1543
1544    // -- base64 -------------------------------------------------------------
1545
1546    #[test]
1547    fn b64_tables_are_the_standard_alphabet() {
1548        const ALPHABET: &[u8; 64] =
1549            b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1550        for (value, &ch) in ALPHABET.iter().enumerate() {
1551            assert_eq!(b64_byte_to_char(value as u32), ch, "value {value}");
1552            assert_eq!(b64_char_to_byte(ch as u32), value as u32, "char {ch}");
1553        }
1554    }
1555
1556    #[test]
1557    fn b64_char_to_byte_rejects_everything_else() {
1558        // The 'A' quirk: a computed 0 is only valid for 'A'.
1559        assert_eq!(b64_char_to_byte(b'A' as u32), 0);
1560        for c in 0u32..256 {
1561            let is_b64 = (c as u8).is_ascii_alphanumeric() || c == b'+' as u32 || c == b'/' as u32;
1562            if !is_b64 {
1563                assert_eq!(b64_char_to_byte(c), 0xFF, "char {c} must be invalid");
1564            }
1565        }
1566        assert_eq!(b64_char_to_byte(b'=' as u32), 0xFF); // no padding
1567        assert_eq!(b64_char_to_byte(0), 0xFF); // NUL terminator
1568        assert_eq!(b64_char_to_byte(b'$' as u32), 0xFF); // field separator
1569
1570        // Bytes >= 0x80 are rejected here. The C's are not, wherever `char` is
1571        // signed — it returns 63 for all 128 of them. See the module docs; this
1572        // loop is the pin for the port's (stricter, portable) choice.
1573        for c in 0x80u32..256 {
1574            assert_eq!(b64_char_to_byte(c), 0xFF, "byte {c:#04x} must be invalid");
1575        }
1576    }
1577
1578    /// `b64_char_to_byte` over `0..=127`, dumped from the C reference.
1579    ///
1580    /// Produced by a harness that `#include`s `encoding.c` (the function is
1581    /// `static`) and prints `b64_char_to_byte(i)` for every `i`, linked against
1582    /// `phc-winner-argon2/libargon2.a`. Only the ASCII half is transcribed: the
1583    /// upper half is the signed-`char` bug documented at the top of this file,
1584    /// where the C returns 63 for all 128 values.
1585    #[rustfmt::skip]
1586    const C_CHAR_TO_BYTE_ASCII: [u32; 128] = [
1587        255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
1588        255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
1589        255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,  62, 255, 255, 255,  63,
1590         52,  53,  54,  55,  56,  57,  58,  59,  60,  61, 255, 255, 255, 255, 255, 255,
1591        255,   0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,
1592         15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25, 255, 255, 255, 255, 255,
1593        255,  26,  27,  28,  29,  30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,
1594         41,  42,  43,  44,  45,  46,  47,  48,  49,  50,  51, 255, 255, 255, 255, 255,
1595    ];
1596
1597    #[test]
1598    fn b64_char_to_byte_matches_the_c_dump() {
1599        for (c, &want) in C_CHAR_TO_BYTE_ASCII.iter().enumerate() {
1600            assert_eq!(b64_char_to_byte(c as u32), want, "char {c}");
1601        }
1602    }
1603
1604    /// `b64_byte_to_char` over `0..64`, dumped from the same C harness.
1605    #[rustfmt::skip]
1606    const C_BYTE_TO_CHAR: [u8; 64] = [
1607        65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
1608        81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102,
1609        103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118,
1610        119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 47,
1611    ];
1612
1613    #[test]
1614    fn b64_byte_to_char_matches_the_c_dump() {
1615        for (x, &want) in C_BYTE_TO_CHAR.iter().enumerate() {
1616            assert_eq!(b64_byte_to_char(x as u32), want, "value {x}");
1617        }
1618    }
1619
1620    #[test]
1621    fn non_ascii_bytes_are_rejected_in_a_field() {
1622        // Measured: the C decodes both of these, identically, to the salt
1623        // ff ff 6d 65 73 61 6c 74, because 0xC3 and 0xA9 each read as '/'.
1624        // This port rejects the first and accepts the second.
1625        let utf8 = "$argon2i$v=19$m=65536,t=2,p=1$é9tZXNhbHQ\
1626                    $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1627        assert_eq!(
1628            decode_string(utf8, Algorithm::Argon2i),
1629            Err(Error::DecodingFail)
1630        );
1631        let slashes = "$argon2i$v=19$m=65536,t=2,p=1$//9tZXNhbHQ\
1632                       $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1633        let d = decode_string(slashes, Algorithm::Argon2i).unwrap();
1634        assert_eq!(d.salt, [0xff, 0xff, 0x6d, 0x65, 0x73, 0x61, 0x6c, 0x74]);
1635    }
1636
1637    #[test]
1638    fn base64_known_vectors() {
1639        assert_eq!(b64(b"somesalt"), b"c29tZXNhbHQ");
1640        assert_eq!(b64(b"diffsalt"), b"ZGlmZnNhbHQ");
1641        assert_eq!(
1642            b64(&V10_TAG),
1643            b"9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
1644        );
1645        assert_eq!(unb64(b"c29tZXNhbHQ").unwrap(), b"somesalt");
1646        assert_eq!(
1647            unb64(b"9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ").unwrap(),
1648            &V10_TAG
1649        );
1650    }
1651
1652    #[test]
1653    fn base64_round_trips_every_short_length() {
1654        for len in 0usize..96 {
1655            let src: Vec<u8> = (0..len)
1656                .map(|i| (i as u8).wrapping_mul(37) ^ 0x5A)
1657                .collect();
1658            let encoded = b64(&src);
1659            assert_eq!(unb64(&encoded).unwrap(), src, "len {len}");
1660        }
1661    }
1662
1663    /// Every executable SIMD backend against the scalar oracle, across the
1664    /// block boundaries and tails each implementation can take. This checks
1665    /// the primitive directly rather than relying on PHC vectors whose usual
1666    /// 16/32-byte fields exercise only two shapes.
1667    #[test]
1668    fn every_base64_backend_matches_scalar_across_lengths() {
1669        for len in 0usize..=512 {
1670            let src: Vec<u8> = (0..len)
1671                .map(|i| (i as u8).wrapping_mul(197) ^ (len as u8).wrapping_mul(11))
1672                .collect();
1673            let capacity = b64_len_usize(len) + 1;
1674            let mut expected = vec![0xa5; capacity];
1675            let expected_len =
1676                b64_with_backend(&mut expected, &src, Base64Backend::Scalar).unwrap();
1677
1678            for &backend in Base64Backend::ALL {
1679                if !backend.is_available()
1680                    || (cfg!(miri) && backend != Base64Backend::Scalar)
1681                {
1682                    continue;
1683                }
1684                let mut actual = vec![0xa5; capacity];
1685                let actual_len = b64_with_backend(&mut actual, &src, backend).unwrap();
1686                assert_eq!(actual_len, expected_len, "{backend} length {len}");
1687                assert_eq!(actual, expected, "{backend} bytes at length {len}");
1688
1689                let mut scalar_decoded = vec![0x5a; len];
1690                let scalar_result = unb64_with_backend(
1691                    &mut scalar_decoded,
1692                    &expected[..expected_len],
1693                    Base64Backend::Scalar,
1694                );
1695                let mut simd_decoded = vec![0x5a; len];
1696                let simd_result = unb64_with_backend(
1697                    &mut simd_decoded,
1698                    &expected[..expected_len],
1699                    backend,
1700                );
1701                assert_eq!(simd_result, scalar_result, "{backend} decode length {len}");
1702                assert_eq!(simd_decoded, scalar_decoded, "{backend} decode bytes {len}");
1703                assert_eq!(simd_decoded, src, "{backend} round trip {len}");
1704            }
1705        }
1706    }
1707
1708    /// An invalid byte in a vector must not make the SIMD prefix lose the C
1709    /// decoder's exact stopping position. The vector is retried by scalar, so
1710    /// both the return value and all bytes written before an error match.
1711    #[test]
1712    fn every_base64_backend_matches_scalar_on_invalid_bytes_and_short_outputs() {
1713        let raw: Vec<u8> = (0..96)
1714            .map(|i| (i as u8).wrapping_mul(37) ^ 0x5a)
1715            .collect();
1716        let encoded = b64(&raw);
1717
1718        for &backend in Base64Backend::ALL {
1719            if !backend.is_available() || (cfg!(miri) && backend != Base64Backend::Scalar) {
1720                continue;
1721            }
1722
1723            for pos in 0..encoded.len() {
1724                for invalid in [0, b'$', b'=', 0x80, 0xff] {
1725                    let mut input = encoded.clone();
1726                    input[pos] = invalid;
1727                    let mut expected = vec![0xa5; raw.len()];
1728                    let expected_result = unb64_with_backend(
1729                        &mut expected,
1730                        &input,
1731                        Base64Backend::Scalar,
1732                    );
1733                    let mut actual = vec![0xa5; raw.len()];
1734                    let actual_result = unb64_with_backend(&mut actual, &input, backend);
1735                    assert_eq!(actual_result, expected_result, "{backend} pos {pos} byte {invalid:#x}");
1736                    assert_eq!(actual, expected, "{backend} output at pos {pos} byte {invalid:#x}");
1737                }
1738            }
1739
1740            for dst_len in 0..raw.len() {
1741                let mut expected = vec![0xa5; dst_len];
1742                let expected_result = unb64_with_backend(
1743                    &mut expected,
1744                    &encoded,
1745                    Base64Backend::Scalar,
1746                );
1747                let mut actual = vec![0xa5; dst_len];
1748                let actual_result = unb64_with_backend(&mut actual, &encoded, backend);
1749                assert_eq!(actual_result, expected_result, "{backend} dst length {dst_len}");
1750                assert_eq!(actual, expected, "{backend} dst bytes at length {dst_len}");
1751            }
1752        }
1753    }
1754
1755    #[test]
1756    fn to_base64_rejects_a_tight_buffer() {
1757        // The C requires dst_len > olen, i.e. room for the NUL as well.
1758        let mut exact = [0u8; 11];
1759        assert_eq!(to_base64(&mut exact, b"somesalt"), Err(Error::EncodingFail));
1760        let mut roomy = [0u8; 12];
1761        assert_eq!(to_base64(&mut roomy, b"somesalt"), Ok(11));
1762        // Even an empty input needs one byte for the terminator.
1763        assert_eq!(to_base64(&mut [], b""), Err(Error::EncodingFail));
1764        assert_eq!(to_base64(&mut [0u8; 1], b""), Ok(0));
1765    }
1766
1767    #[test]
1768    fn from_base64_stops_at_the_first_non_b64_char() {
1769        let mut out = [0u8; 16];
1770        let (written, consumed) = from_base64(&mut out, b"c29tZXNhbHQ$rest").unwrap();
1771        assert_eq!(written, 8);
1772        assert_eq!(consumed, 11);
1773        assert_eq!(&out[..8], b"somesalt");
1774
1775        // An empty run is fine and consumes nothing (this is how the C accepts
1776        // "$argon2i$m=…,p=1$$<tag>" and only later reports SaltTooShort).
1777        let (written, consumed) = from_base64(&mut out, b"$tag").unwrap();
1778        assert_eq!((written, consumed), (0, 0));
1779    }
1780
1781    #[test]
1782    fn from_base64_rejects_leftover_bits() {
1783        let mut out = [0u8; 16];
1784        // 5 chars -> 30 bits -> acc_len == 6 > 4.
1785        assert_eq!(
1786            from_base64(&mut out, b"AAAAA"),
1787            Err(Error::DecodingFail),
1788            "acc_len > 4"
1789        );
1790        // 1 char -> 6 bits -> acc_len == 6 > 4.
1791        assert_eq!(from_base64(&mut out, b"A"), Err(Error::DecodingFail));
1792        // 2 chars, 4 buffered bits, non-zero: 'B' == 1.
1793        assert_eq!(from_base64(&mut out, b"AB"), Err(Error::DecodingFail));
1794        // Same shape but the buffered bits are zero.
1795        assert_eq!(from_base64(&mut out, b"AA"), Ok((1, 2)));
1796        // 3 chars, 2 buffered bits, non-zero: 'B' == 000001.
1797        assert_eq!(from_base64(&mut out, b"AAB"), Err(Error::DecodingFail));
1798        assert_eq!(from_base64(&mut out, b"AAA"), Ok((2, 3)));
1799    }
1800
1801    #[test]
1802    fn from_base64_rejects_a_short_buffer() {
1803        let mut out = [0u8; 4];
1804        assert_eq!(
1805            from_base64(&mut out, b"c29tZXNhbHQ"),
1806            Err(Error::DecodingFail)
1807        );
1808        // The C's `if ((len++) >= *dst_len)` tests the pre-increment value, so
1809        // an exactly-sized buffer is fine and one byte less is not.
1810        let mut exact = [0u8; 8];
1811        assert_eq!(from_base64(&mut exact, b"c29tZXNhbHQ"), Ok((8, 11)));
1812        assert_eq!(&exact, b"somesalt");
1813        let mut tight = [0u8; 7];
1814        assert_eq!(
1815            from_base64(&mut tight, b"c29tZXNhbHQ"),
1816            Err(Error::DecodingFail)
1817        );
1818    }
1819
1820    // -- decode_decimal -----------------------------------------------------
1821
1822    #[test]
1823    fn decode_decimal_matches_c() {
1824        assert_eq!(decode_decimal(b"0"), Some((0, 1)));
1825        assert_eq!(decode_decimal(b"19"), Some((19, 2)));
1826        assert_eq!(decode_decimal(b"65536,t=2"), Some((65536, 5)));
1827        assert_eq!(decode_decimal(b"4294967295"), Some((4294967295, 10)));
1828        // No digits at all.
1829        assert_eq!(decode_decimal(b""), None);
1830        assert_eq!(decode_decimal(b"$"), None);
1831        assert_eq!(decode_decimal(b"x1"), None);
1832        // Non-minimal.
1833        assert_eq!(decode_decimal(b"01"), None);
1834        assert_eq!(decode_decimal(b"00"), None);
1835        assert_eq!(decode_decimal(b"0019"), None);
1836        // Overflow of `unsigned long`.
1837        assert_eq!(
1838            decode_decimal(b"18446744073709551615"),
1839            Some((u64::MAX, 20))
1840        );
1841        assert_eq!(decode_decimal(b"18446744073709551616"), None);
1842        assert_eq!(decode_decimal(b"99999999999999999999999"), None);
1843    }
1844
1845    #[test]
1846    fn decode_decimal_matches_the_c_dump() {
1847        // Every pair below was produced by running the C's `decode_decimal`
1848        // (it is `static`, so via a harness that #includes encoding.c) linked
1849        // against phc-winner-argon2/libargon2.a. `None` is the C's NULL.
1850        /// `(input, decode_decimal(input))`, the C's NULL being `None`.
1851        type Case = (&'static [u8], Option<(u64, usize)>);
1852
1853        let cases: &[Case] = &[
1854            (b"", None),
1855            (b"0", Some((0, 1))),
1856            (b"00", None),
1857            (b"000", None),
1858            (b"01", None),
1859            (b"0019", None),
1860            (b"1", Some((1, 1))),
1861            (b"9", Some((9, 1))),
1862            (b"19", Some((19, 2))),
1863            (b"10", Some((10, 2))),
1864            (b"007", None),
1865            (b"4294967295", Some((4294967295, 10))),
1866            (b"4294967296", Some((4294967296, 10))),
1867            (b"9223372036854775807", Some((9223372036854775807, 19))),
1868            (b"18446744073709551615", Some((18446744073709551615, 20))),
1869            (b"18446744073709551616", None),
1870            (b"18446744073709551620", None),
1871            (b"19999999999999999999", None),
1872            (b"20000000000000000000", None),
1873            (b"99999999999999999999", None),
1874            (b"1000000000000000000000000", None),
1875            (b"65536,t=2", Some((65536, 5))),
1876            (b"1,t=2", Some((1, 1))),
1877            (b"x", None),
1878            (b"1x", Some((1, 1))),
1879            (b" 1", None),
1880            (b"+1", None),
1881            (b"-1", None),
1882            (b"1 ", Some((1, 1))),
1883            // "0" is minimal on its own, so the 'x' just ends the run.
1884            (b"0x10", Some((0, 1))),
1885            (b"12$", Some((12, 2))),
1886            (b"0,", Some((0, 1))),
1887            (b"10$", Some((10, 2))),
1888            (b"$", None),
1889            (b"1234567890", Some((1234567890, 10))),
1890        ];
1891        for (input, want) in cases {
1892            assert_eq!(
1893                decode_decimal(input),
1894                *want,
1895                "input {:?}",
1896                core::str::from_utf8(input).unwrap_or("<non-utf8>")
1897            );
1898        }
1899    }
1900
1901    #[test]
1902    fn decimal_fields_are_u32_bounded() {
1903        // DECIMAL_U32 rejects anything above UINT32_MAX.
1904        let too_big = "$argon2i$v=19$m=4294967296,t=2,p=1$c29tZXNhbHQ\
1905                       $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1906        assert_eq!(
1907            decode_string(too_big, Algorithm::Argon2i),
1908            Err(Error::DecodingFail)
1909        );
1910        let leading_zero = "$argon2i$v=19$m=065536,t=2,p=1$c29tZXNhbHQ\
1911                            $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1912        assert_eq!(
1913            decode_string(leading_zero, Algorithm::Argon2i),
1914            Err(Error::DecodingFail)
1915        );
1916    }
1917
1918    // -- encode -------------------------------------------------------------
1919
1920    #[test]
1921    fn encode_matches_the_official_vectors() {
1922        let params = Params::builder()
1923            .memory(Memory::kib(65536))
1924            .passes(2)
1925            .lanes(1)
1926            .tag_len(TagLen::bytes(32))
1927            .build()
1928            .unwrap();
1929        let tag = unb64(b"wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA").unwrap();
1930        let encoded = encode_string_alloc(
1931            Algorithm::Argon2i,
1932            Version::V0x13,
1933            &params,
1934            b"somesalt",
1935            &tag,
1936        )
1937        .unwrap();
1938        assert_eq!(encoded, V13_ARGON2I);
1939
1940        let tag = unb64(b"CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc").unwrap();
1941        let encoded = encode_string_alloc(
1942            Algorithm::Argon2id,
1943            Version::V0x13,
1944            &params,
1945            b"somesalt",
1946            &tag,
1947        )
1948        .unwrap();
1949        assert_eq!(encoded, V13_ARGON2ID);
1950
1951        // The C always emits "$v=", even for 0x10.
1952        let encoded = encode_string_alloc(
1953            Algorithm::Argon2i,
1954            Version::V0x10,
1955            &params,
1956            b"somesalt",
1957            &V10_TAG,
1958        )
1959        .unwrap();
1960        assert_eq!(
1961            encoded,
1962            "$argon2i$v=16$m=65536,t=2,p=1$c29tZXNhbHQ\
1963             $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
1964        );
1965    }
1966
1967    #[test]
1968    fn encode_needs_encoded_len_bytes_exactly() {
1969        let params = Params::builder()
1970            .memory(Memory::kib(65536))
1971            .passes(2)
1972            .lanes(1)
1973            .tag_len(TagLen::bytes(32))
1974            .build()
1975            .unwrap();
1976        let want = encoded_len(Algorithm::Argon2i, 2, 65536, 1, 8, 32);
1977        assert_eq!(want, V13_ARGON2I.len() + 1);
1978
1979        let mut buf = vec![0u8; want];
1980        let n = encode_string(
1981            &mut buf,
1982            Algorithm::Argon2i,
1983            Version::V0x13,
1984            &params,
1985            b"somesalt",
1986            &V10_TAG,
1987        )
1988        .unwrap();
1989        assert_eq!(n, want - 1);
1990
1991        // One byte less is a failure, exactly as in the C.
1992        let mut buf = vec![0u8; want - 1];
1993        assert_eq!(
1994            encode_string(
1995                &mut buf,
1996                Algorithm::Argon2i,
1997                Version::V0x13,
1998                &params,
1999                b"somesalt",
2000                &V10_TAG,
2001            ),
2002            Err(Error::EncodingFail)
2003        );
2004    }
2005
2006    #[test]
2007    fn encode_validates_first() {
2008        let params = Params::builder()
2009            .memory(Memory::kib(65536))
2010            .passes(2)
2011            .lanes(1)
2012            .tag_len(TagLen::bytes(32))
2013            .build()
2014            .unwrap();
2015        // Short salt: validate_inputs runs before anything is written.
2016        assert_eq!(
2017            encode_string_alloc(
2018                Algorithm::Argon2i,
2019                Version::V0x13,
2020                &params,
2021                b"short",
2022                &V10_TAG
2023            ),
2024            Err(Error::SaltTooShort)
2025        );
2026        // Short tag.
2027        assert_eq!(
2028            encode_string_alloc(
2029                Algorithm::Argon2i,
2030                Version::V0x13,
2031                &params,
2032                b"somesalt",
2033                &[0u8; 3]
2034            ),
2035            Err(Error::OutputTooShort)
2036        );
2037    }
2038
2039    // -- decode -------------------------------------------------------------
2040
2041    #[test]
2042    fn decode_the_v13_vector() {
2043        let d = decode_string(V13_ARGON2I, Algorithm::Argon2i).unwrap();
2044        assert_eq!(d.algorithm, Algorithm::Argon2i);
2045        assert_eq!(d.version, Version::V0x13);
2046        assert_eq!(d.params.memory_kib(), 65536);
2047        assert_eq!(d.params.passes(), 2);
2048        assert_eq!(d.params.lanes(), 1);
2049        assert_eq!(d.params.tag_len_bytes(), 32);
2050        assert_eq!(d.salt, b"somesalt");
2051        assert!(d.ad.is_empty());
2052        assert_eq!(
2053            d.hash,
2054            unb64(b"wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA").unwrap()
2055        );
2056    }
2057
2058    #[test]
2059    fn decode_defaults_the_version_to_0x10() {
2060        let d = decode_string(V10_ARGON2I, Algorithm::Argon2i).unwrap();
2061        assert_eq!(d.version, Version::V0x10);
2062        assert_eq!(d.salt, b"somesalt");
2063        assert_eq!(d.hash, &V10_TAG);
2064    }
2065
2066    #[test]
2067    fn decode_sets_threads_to_lanes() {
2068        let s = "$argon2id$v=19$m=65536,t=2,p=4$c29tZXNhbHQ\
2069                 $CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc";
2070        let d = decode_string(s, Algorithm::Argon2id).unwrap();
2071        assert_eq!(d.params.lanes(), 4);
2072        assert_eq!(d.params.threads(), 4);
2073        assert!(d.ad.is_empty());
2074    }
2075
2076    #[test]
2077    fn decode_phc_accepts_c_strings_and_detects_the_algorithm() {
2078        let d = decode_phc(V13_ARGON2ID).unwrap();
2079        let c = decode_string(V13_ARGON2ID, Algorithm::Argon2id).unwrap();
2080        assert_eq!(d, c);
2081        assert!(d.ad.is_empty());
2082        assert_eq!(decode_phc(V13_ARGON2I).unwrap().algorithm, Algorithm::Argon2i);
2083        assert_eq!(decode_phc(V10_ARGON2I).unwrap().version, Version::V0x10);
2084    }
2085
2086    #[test]
2087    fn decode_phc_accepts_m_p_t_order_and_unknown_keys() {
2088        let reordered = "$argon2id$v=19$m=65536,p=1,t=2$c29tZXNhbHQ\
2089                         $CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc";
2090        let d = decode_phc(reordered).unwrap();
2091        assert_eq!(d.params.memory_kib(), 65536);
2092        assert_eq!(d.params.passes(), 2);
2093        assert_eq!(d.params.lanes(), 1);
2094        assert_eq!(d.salt, b"somesalt");
2095        assert!(d.ad.is_empty());
2096
2097        let with_keyid = "$argon2id$v=19$m=65536,t=2,p=1,keyid=abc$c29tZXNhbHQ\
2098                          $CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc";
2099        let k = decode_phc(with_keyid).unwrap();
2100        assert_eq!(k.hash, d.hash);
2101        assert!(k.ad.is_empty());
2102
2103        // C-strict decoder still requires `$m=,t=,p=`.
2104        assert_eq!(
2105            decode_string(reordered, Algorithm::Argon2id),
2106            Err(Error::DecodingFail)
2107        );
2108    }
2109
2110    #[test]
2111    fn decode_phc_reads_data_associated_data() {
2112        let encoded = "$argon2id$v=19$m=65536,t=2,p=1,data=c29tZXNhbHQ$c29tZXNhbHQ\
2113                       $CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc";
2114        let d = decode_phc(encoded).unwrap();
2115        assert_eq!(d.ad, b"somesalt");
2116        assert_eq!(d.salt, b"somesalt");
2117        assert_eq!(decode_string(encoded, Algorithm::Argon2id), Err(Error::DecodingFail));
2118    }
2119
2120    #[test]
2121    fn decode_phc_rejects_missing_required_params() {
2122        assert_eq!(
2123            decode_phc("$argon2id$v=19$m=65536,t=2$c29tZXNhbHQ$AAAA"),
2124            Err(Error::DecodingFail)
2125        );
2126        assert_eq!(decode_phc("$argon2x$v=19$m=8,t=1,p=1$c29tZXNhbHQ$AAAA"), Err(Error::DecodingFail));
2127        assert_eq!(decode_phc("argon2id$v=19$m=8,t=1,p=1$c29tZXNhbHQ$AAAA"), Err(Error::DecodingFail));
2128    }
2129
2130    #[test]
2131    fn encode_decode_round_trip() {
2132        let salt = b"0123456789abcdef";
2133        let tag: Vec<u8> = (0u8..48).collect();
2134        for algorithm in Algorithm::ALL {
2135            for version in Version::ALL {
2136                for lanes in [1u32, 2, 255] {
2137                    let params = Params::builder()
2138                        .memory(Memory::kib(1 << 16))
2139                        .passes(3)
2140                        .lanes(lanes)
2141                        .tag_len(TagLen::bytes(tag.len() as u64))
2142                        .build()
2143                        .unwrap();
2144                    let encoded =
2145                        encode_string_alloc(algorithm, version, &params, salt, &tag).unwrap();
2146                    let d = decode_string(&encoded, algorithm).unwrap();
2147                    assert_eq!(d.algorithm, algorithm);
2148                    assert_eq!(d.version, version);
2149                    assert!(d.ad.is_empty());
2150                    assert_eq!(d.params, params);
2151                    assert_eq!(d.salt, salt);
2152                    assert_eq!(d.hash, tag);
2153                    // And re-encoding is byte-identical.
2154                    assert_eq!(
2155                        encode_string_alloc(d.algorithm, d.version, &d.params, &d.salt, &d.hash)
2156                            .unwrap(),
2157                        encoded
2158                    );
2159                }
2160            }
2161        }
2162    }
2163
2164    // The four malformed strings from `src/test.c`, both version flavours.
2165
2166    #[test]
2167    fn decode_rejects_a_missing_dollar_before_the_salt() {
2168        // "…,p=1c29tZXNhbHQ$…": the '$' after p=1 is gone.
2169        let v10 = "$argon2i$m=65536,t=2,p=1c29tZXNhbHQ\
2170                   $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
2171        assert_eq!(
2172            decode_string(v10, Algorithm::Argon2i),
2173            Err(Error::DecodingFail)
2174        );
2175        let v13 = "$argon2i$v=19$m=65536,t=2,p=1c29tZXNhbHQ\
2176                   $wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA";
2177        assert_eq!(
2178            decode_string(v13, Algorithm::Argon2i),
2179            Err(Error::DecodingFail)
2180        );
2181    }
2182
2183    #[test]
2184    fn decode_rejects_a_missing_dollar_before_the_tag() {
2185        // The salt and tag run together into one 54-character base64 field,
2186        // which decodes cleanly (54 chars leave 4 zero bits); the failure comes
2187        // from the CC("$") that follows.
2188        let v10 = "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ\
2189                   9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
2190        assert_eq!(
2191            decode_string(v10, Algorithm::Argon2i),
2192            Err(Error::DecodingFail)
2193        );
2194        let v13 = "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ\
2195                   wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA";
2196        assert_eq!(
2197            decode_string(v13, Algorithm::Argon2i),
2198            Err(Error::DecodingFail)
2199        );
2200    }
2201
2202    #[test]
2203    fn decode_reports_salt_too_short_not_decoding_fail() {
2204        // This is the distinction tests/vectors.rs relies on: the string parses,
2205        // and it is validate_inputs() that rejects it.
2206        let v10 = "$argon2i$m=65536,t=2,p=1$\
2207                   $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
2208        assert_eq!(
2209            decode_string(v10, Algorithm::Argon2i),
2210            Err(Error::SaltTooShort)
2211        );
2212        let v13 = "$argon2i$v=19$m=65536,t=2,p=1$\
2213                   $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
2214        assert_eq!(
2215            decode_string(v13, Algorithm::Argon2i),
2216            Err(Error::SaltTooShort)
2217        );
2218        // A 7-byte salt is also too short, and still not a DecodingFail.
2219        let short = "$argon2i$v=19$m=65536,t=2,p=1$c2hvcnRz\
2220                     $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
2221        assert_eq!(
2222            decode_string(short, Algorithm::Argon2i),
2223            Err(Error::SaltTooShort)
2224        );
2225    }
2226
2227    #[test]
2228    fn decode_argon2i_is_a_prefix_of_argon2id() {
2229        // CC("argon2i") matches the first seven characters of "argon2id"; the
2230        // leftover 'd' then fails the CC("$m=") (or the CC_opt("$v=")).
2231        assert_eq!(
2232            decode_string(V13_ARGON2ID, Algorithm::Argon2i),
2233            Err(Error::DecodingFail)
2234        );
2235        let v10_id = "$argon2id$m=65536,t=2,p=1$c29tZXNhbHQ\
2236                      $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
2237        assert_eq!(
2238            decode_string(v10_id, Algorithm::Argon2i),
2239            Err(Error::DecodingFail)
2240        );
2241        // And the other way round: "argon2i" is not "argon2id".
2242        assert_eq!(
2243            decode_string(V13_ARGON2I, Algorithm::Argon2id),
2244            Err(Error::DecodingFail)
2245        );
2246        assert_eq!(
2247            decode_string(V13_ARGON2I, Algorithm::Argon2d),
2248            Err(Error::DecodingFail)
2249        );
2250        // The correct type still works, of course.
2251        assert!(decode_string(V13_ARGON2ID, Algorithm::Argon2id).is_ok());
2252    }
2253
2254    #[test]
2255    fn decode_rejects_structural_damage() {
2256        let cases: &[&str] = &[
2257            "",
2258            "$",
2259            "argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2260            "$argon2i",
2261            "$argon2i$v=19",
2262            "$argon2i$v=19$m=65536,t=2,p=1",
2263            "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ",
2264            // 'x' is not a decimal digit.
2265            "$argon2i$v=19$m=x,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2266            // Fields out of order.
2267            "$argon2i$v=19$t=2,m=65536,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2268            // Trailing junk after the tag.
2269            "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ$",
2270            "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ ",
2271            // '=' padding is not part of the alphabet, so it ends the field.
2272            "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ=$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2273        ];
2274        for case in cases {
2275            assert_eq!(
2276                decode_string(case, Algorithm::Argon2i),
2277                Err(Error::DecodingFail),
2278                "expected DecodingFail for {case:?}"
2279            );
2280        }
2281    }
2282
2283    #[test]
2284    fn decode_surfaces_the_c_validation_codes() {
2285        // A 3-byte tag: outlen is checked first.
2286        assert_eq!(
2287            decode_string(
2288                "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$AAAA",
2289                Algorithm::Argon2i
2290            ),
2291            Err(Error::OutputTooShort)
2292        );
2293        // m_cost < ARGON2_MIN_MEMORY.
2294        assert_eq!(
2295            decode_string(
2296                "$argon2i$v=19$m=1,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2297                Algorithm::Argon2i
2298            ),
2299            Err(Error::MemoryTooLittle)
2300        );
2301        // m_cost < 8 * lanes.
2302        assert_eq!(
2303            decode_string(
2304                "$argon2i$v=19$m=16,t=2,p=4$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2305                Algorithm::Argon2i
2306            ),
2307            Err(Error::MemoryTooLittle)
2308        );
2309        // t_cost < ARGON2_MIN_TIME.
2310        assert_eq!(
2311            decode_string(
2312                "$argon2i$v=19$m=65536,t=0,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2313                Algorithm::Argon2i
2314            ),
2315            Err(Error::TimeTooSmall)
2316        );
2317        // lanes < ARGON2_MIN_LANES.
2318        assert_eq!(
2319            decode_string(
2320                "$argon2i$v=19$m=65536,t=2,p=0$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2321                Algorithm::Argon2i
2322            ),
2323            Err(Error::LanesTooFew)
2324        );
2325        // lanes > ARGON2_MAX_LANES (16777215).
2326        #[cfg(target_pointer_width = "64")]
2327        assert_eq!(
2328            decode_string(
2329                "$argon2i$v=19$m=4294967295,t=2,p=16777216$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2330                Algorithm::Argon2i
2331            ),
2332            Err(Error::LanesTooMany)
2333        );
2334        // On a 32-bit target ARGON2_MAX_MEMORY is 2 MiB (the C's own
2335        // pointer-width rule), so — exactly as the C on 32-bit — the memory
2336        // check fires before the lanes check gets a chance to.
2337        #[cfg(target_pointer_width = "32")]
2338        assert_eq!(
2339            decode_string(
2340                "$argon2i$v=19$m=4294967295,t=2,p=16777216$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2341                Algorithm::Argon2i
2342            ),
2343            Err(Error::MemoryTooMuch)
2344        );
2345    }
2346
2347    #[test]
2348    fn validation_runs_before_the_trailing_character_check() {
2349        // Both wrong: the C returns SALT_TOO_SHORT because validate_inputs()
2350        // comes first.
2351        let s = "$argon2i$v=19$m=65536,t=2,p=1$\
2352                 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ!!!";
2353        assert_eq!(
2354            decode_string(s, Algorithm::Argon2i),
2355            Err(Error::SaltTooShort)
2356        );
2357    }
2358
2359    #[test]
2360    fn decode_rejects_an_unrepresentable_version() {
2361        // Documented divergence: the C accepts this (validate_inputs never
2362        // looks at the version) and treats it as 0x13.
2363        let s = "$argon2i$v=99$m=65536,t=2,p=1$c29tZXNhbHQ\
2364                 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
2365        assert_eq!(
2366            decode_string(s, Algorithm::Argon2i),
2367            Err(Error::DecodingFail)
2368        );
2369        // …but an earlier error still wins, so the codes stay C-compatible.
2370        let s = "$argon2i$v=99$m=65536,t=2,p=1$$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
2371        assert_eq!(
2372            decode_string(s, Algorithm::Argon2i),
2373            Err(Error::SaltTooShort)
2374        );
2375    }
2376
2377    #[test]
2378    fn decode_accepts_a_long_salt_and_tag() {
2379        let salt: Vec<u8> = (0u8..=255).collect();
2380        let tag: Vec<u8> = (0u8..=200).rev().collect();
2381        let params = Params::builder()
2382            .memory(Memory::kib(1 << 16))
2383            .passes(1)
2384            .lanes(1)
2385            .tag_len(TagLen::bytes(tag.len() as u64))
2386            .build()
2387            .unwrap();
2388        let encoded =
2389            encode_string_alloc(Algorithm::Argon2d, Version::V0x13, &params, &salt, &tag).unwrap();
2390        let d = decode_string(&encoded, Algorithm::Argon2d).unwrap();
2391        assert_eq!(d.salt, salt);
2392        assert_eq!(d.hash, tag);
2393        assert_eq!(d.params.tag_len_bytes(), tag.len());
2394    }
2395}