Skip to main content

argon2_rust/
params.rs

1//! Limits, [`Algorithm`], [`Version`] and [`Params`].
2//!
3//! Every constant here is transcribed from `phc-winner-argon2/include/argon2.h`
4//! and `phc-winner-argon2/src/core.h`. [`validate_inputs`] reproduces
5//! `validate_inputs()` from `src/core.c` **in the same order**, because the
6//! order decides which error code surfaces first.
7
8use crate::error::Error;
9
10// ---------------------------------------------------------------------------
11// Limits from include/argon2.h
12// ---------------------------------------------------------------------------
13
14/// `ARGON2_MIN_LANES`.
15pub const MIN_LANES: u32 = 1;
16/// `ARGON2_MAX_LANES`.
17pub const MAX_LANES: u32 = 0x00FF_FFFF;
18
19/// `ARGON2_MIN_THREADS`.
20pub const MIN_THREADS: u32 = 1;
21/// `ARGON2_MAX_THREADS`.
22pub const MAX_THREADS: u32 = 0x00FF_FFFF;
23
24/// `ARGON2_SYNC_POINTS`: synchronisation points between lanes per pass.
25pub const SYNC_POINTS: u32 = 4;
26
27/// `ARGON2_MIN_OUTLEN`.
28pub const MIN_OUTLEN: u32 = 4;
29/// `ARGON2_MAX_OUTLEN`.
30pub const MAX_OUTLEN: u32 = 0xFFFF_FFFF;
31
32/// `ARGON2_MIN_MEMORY` = `2 * ARGON2_SYNC_POINTS` (two blocks per slice).
33pub const MIN_MEMORY: u32 = 2 * SYNC_POINTS;
34
35/// `ARGON2_MAX_MEMORY_BITS` = `min(32, sizeof(void*) * CHAR_BIT - 10 - 1)`.
36///
37/// 32 on a 64-bit target, 21 on a 32-bit target.
38pub const MAX_MEMORY_BITS: u32 = {
39    let ptr_bits = (size_of::<*const u8>() * 8) as u32;
40    let bits = ptr_bits - 10 - 1;
41    if bits < 32 { bits } else { 32 }
42};
43
44/// `ARGON2_MAX_MEMORY` = `min(0xFFFFFFFF, 1 << ARGON2_MAX_MEMORY_BITS)`.
45///
46/// `0xFFFF_FFFF` on a 64-bit target, `0x0020_0000` on a 32-bit target.
47/// Verified against the C preprocessor on `aarch64-apple-darwin`.
48pub const MAX_MEMORY: u32 = {
49    let candidate: u64 = 1u64 << MAX_MEMORY_BITS;
50    if candidate < 0xFFFF_FFFF {
51        candidate as u32
52    } else {
53        0xFFFF_FFFF
54    }
55};
56
57/// `ARGON2_MIN_TIME`.
58pub const MIN_TIME: u32 = 1;
59/// `ARGON2_MAX_TIME`.
60pub const MAX_TIME: u32 = 0xFFFF_FFFF;
61
62/// `ARGON2_MIN_PWD_LENGTH`.
63pub const MIN_PWD_LENGTH: u32 = 0;
64/// `ARGON2_MAX_PWD_LENGTH`.
65pub const MAX_PWD_LENGTH: u32 = 0xFFFF_FFFF;
66
67/// `ARGON2_MIN_AD_LENGTH`.
68pub const MIN_AD_LENGTH: u32 = 0;
69/// `ARGON2_MAX_AD_LENGTH`.
70pub const MAX_AD_LENGTH: u32 = 0xFFFF_FFFF;
71
72/// `ARGON2_MIN_SALT_LENGTH`.
73pub const MIN_SALT_LENGTH: u32 = 8;
74/// `ARGON2_MAX_SALT_LENGTH`.
75pub const MAX_SALT_LENGTH: u32 = 0xFFFF_FFFF;
76
77/// `ARGON2_MIN_SECRET`.
78pub const MIN_SECRET: u32 = 0;
79/// `ARGON2_MAX_SECRET`.
80pub const MAX_SECRET: u32 = 0xFFFF_FFFF;
81
82// ---------------------------------------------------------------------------
83// Typed units
84// ---------------------------------------------------------------------------
85
86/// A memory cost, carried in kibibytes.
87///
88/// The unit is the point of this type. Argon2's `m_cost` is a count of 1 KiB
89/// blocks, so a bare `65536` at a call site could be read as bytes, KiB, MiB or
90/// blocks; `Memory::mib(64)` cannot.
91///
92/// No constructor validates or panics. The value is held as a `u64` and checked
93/// once, by [`ParamsBuilder::build`], which is the only place that knows the
94/// target's `MAX_MEMORY`.
95///
96/// ```
97/// use argon2_rust::params::Memory;
98///
99/// assert_eq!(Memory::mib(64), Memory::kib(65536));
100/// assert_eq!(Memory::gib(1), Memory::mib(1024));
101/// ```
102#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
103pub struct Memory(u64);
104
105impl Memory {
106    /// A cost in kibibytes, the unit Argon2's `m_cost` uses.
107    #[inline]
108    #[must_use]
109    pub const fn kib(kib: u64) -> Memory {
110        Memory(kib)
111    }
112
113    /// A cost in mebibytes.
114    ///
115    /// Saturating, not wrapping: `mib(u64::MAX)` yields `u64::MAX` KiB rather
116    /// than panicking in a debug build. Any saturated value is far above
117    /// [`MAX_MEMORY`] and becomes [`Error::MemoryTooMuch`] at `build()`.
118    #[inline]
119    #[must_use]
120    pub const fn mib(mib: u64) -> Memory {
121        Memory(mib.saturating_mul(1024))
122    }
123
124    /// A cost in gibibytes. Saturating, for the reason given on [`Memory::mib`].
125    #[inline]
126    #[must_use]
127    pub const fn gib(gib: u64) -> Memory {
128        Memory(gib.saturating_mul(1024 * 1024))
129    }
130
131    /// The cost in kibibytes.
132    #[inline]
133    #[must_use]
134    pub const fn as_kib(self) -> u64 {
135        self.0
136    }
137}
138
139/// Length of an Argon2 **tag** (raw hash output), in bytes.
140///
141/// RFC 9106's name for the digest. No `bits()` constructor — only whole bytes.
142/// Like [`Memory`], this validates nothing; [`ParamsBuilder::build`] does.
143///
144/// ```
145/// use argon2_rust::params::TagLen;
146///
147/// // RFC 9106's "256-bit tag"
148/// assert_eq!(TagLen::bytes(32).as_bytes(), 32);
149/// ```
150#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
151pub struct TagLen(u64);
152
153impl TagLen {
154    /// A tag length in bytes.
155    #[inline]
156    #[must_use]
157    pub const fn bytes(bytes: u64) -> TagLen {
158        TagLen(bytes)
159    }
160
161    /// The length in bytes.
162    #[inline]
163    #[must_use]
164    pub const fn as_bytes(self) -> u64 {
165        self.0
166    }
167}
168
169// ---------------------------------------------------------------------------
170// Internal constants from src/core.h
171// ---------------------------------------------------------------------------
172
173/// `ARGON2_BLOCK_SIZE`: memory block size in bytes.
174pub const BLOCK_SIZE: usize = 1024;
175/// `ARGON2_QWORDS_IN_BLOCK`: 64-bit words per block.
176pub const QWORDS_IN_BLOCK: usize = BLOCK_SIZE / 8;
177/// `ARGON2_OWORDS_IN_BLOCK`: 128-bit lanes per block (SSE2).
178pub const OWORDS_IN_BLOCK: usize = BLOCK_SIZE / 16;
179/// `ARGON2_HWORDS_IN_BLOCK`: 256-bit lanes per block (AVX2).
180pub const HWORDS_IN_BLOCK: usize = BLOCK_SIZE / 32;
181/// `ARGON2_512BIT_WORDS_IN_BLOCK`: 512-bit lanes per block (AVX-512).
182pub const BITS512_WORDS_IN_BLOCK: usize = BLOCK_SIZE / 64;
183
184/// `ARGON2_ADDRESSES_IN_BLOCK`: pseudo-random values one address block holds.
185pub const ADDRESSES_IN_BLOCK: usize = 128;
186
187/// `ARGON2_PREHASH_DIGEST_LENGTH`: length of `H0`.
188pub const PREHASH_DIGEST_LENGTH: usize = 64;
189/// `ARGON2_PREHASH_SEED_LENGTH`: `H0` plus the 4-byte block index and 4-byte lane index.
190pub const PREHASH_SEED_LENGTH: usize = 72;
191
192// ---------------------------------------------------------------------------
193// Limits from src/encoding.h
194// ---------------------------------------------------------------------------
195//
196// Mirrored for completeness, and unused — the C defines all three in
197// `encoding.h:22-24` and then never reads them, so `decode_string` here does
198// not either. Keeping them (rather than dropping them) is what makes the
199// header-for-header correspondence with the C checkable; do not add a use for
200// them without checking the C grew one first.
201//
202// `#[cfg(test)]`, and deliberately NOT public. Each one's own documentation
203// says not to bounds-check against it, which is disqualifying for a stable
204// export: the names read like enforced limits, they sit next to the `MIN_`/
205// `MAX_` constants that really are enforced, and `MIN_DECODED_SALT_LEN` even
206// holds the same value as the real bound today. A caller who reaches for one
207// gets a limit the decoder does not apply. They stay here so
208// `decoded_mirrors_are_not_decoder_bounds` can keep pinning the gap.
209
210/// `ARGON2_MAX_DECODED_LANES`.
211///
212/// Mirrored from `encoding.h:22`, and **not a bound this crate enforces**. The
213/// C defines the macro there and then never reads it, in `encoding.c` or
214/// anywhere else in the tree, so `decode_string` here does not read it either.
215/// What actually bounds the `p=` field of a decoded PHC string is
216/// [`MAX_LANES`] (`0x00FF_FFFF`), applied by [`validate_inputs`] inside
217/// `decode_string`.
218///
219/// Do not use this constant to bounds-check decoded input: a well-formed
220/// string can carry a `p` far above 255 and will decode and verify. Measured
221/// against this crate, `p=300` round-trips through `hash_encoded` and
222/// `verify_encoded`:
223///
224/// ```text
225/// $argon2id$v=19$m=2400,t=1,p=300$c29tZXNhbHQ$tPLI8hre65Crk/uP5eIGCZzn3TQ7RzRoXIkGzt5jQoI
226/// ```
227///
228/// (`m=2400` because [`validate_inputs`] requires `m_cost >= 8 * lanes`, not
229/// because 255 played any part.) The `decoded_mirrors_are_not_decoder_bounds`
230/// test pins the gap between this value and the bound that is real.
231#[cfg(test)]
232const MAX_DECODED_LANES: u32 = 255;
233/// `ARGON2_MIN_DECODED_SALT_LEN`.
234///
235/// Mirrored from `encoding.h:23`, and unread for the same reason: the C
236/// defines it and never consults it, so `decode_string` here does not either.
237/// The salt of a decoded string is bounded by [`MIN_SALT_LENGTH`], applied by
238/// [`validate_inputs`].
239///
240/// The two happen to hold the same value (8) today, which is exactly what
241/// makes this constant easy to mistake for the enforced minimum. It is not the
242/// enforced minimum, and nothing ties the two together: they come from
243/// different headers (`encoding.h` and `argon2.h`), and if [`MIN_SALT_LENGTH`]
244/// ever moves the decoder moves with it while this value stays at 8. Check
245/// decoded salts against [`MIN_SALT_LENGTH`].
246#[cfg(test)]
247const MIN_DECODED_SALT_LEN: u32 = 8;
248/// `ARGON2_MIN_DECODED_OUT_LEN`.
249///
250/// Mirrored from `encoding.h:24`, and likewise never read by the C, so
251/// `decode_string` here does not read it either. The tag length of a decoded
252/// string is bounded by [`MIN_OUTLEN`], applied by [`validate_inputs`].
253///
254/// Do not use this constant to bounds-check decoded input: [`MIN_OUTLEN`] is
255/// 4, so a decoded tag can legitimately undershoot 12. Measured against this
256/// crate, an 8-byte tag round-trips through `hash_encoded` and
257/// `verify_encoded`:
258///
259/// ```text
260/// $argon2id$v=19$m=2400,t=1,p=1$c29tZXNhbHQ$kQGQLZpZJIk
261/// ```
262#[cfg(test)]
263const MIN_DECODED_OUT_LEN: u32 = 12;
264
265// ---------------------------------------------------------------------------
266// Algorithm
267// ---------------------------------------------------------------------------
268
269/// The Argon2 primitive type (`argon2_type`).
270///
271/// The numeric values matter: `initial_hash` hashes them, and `fill_segment`
272/// puts `instance->type` into `input_block.v[5]`.
273#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
274#[repr(u32)]
275pub enum Algorithm {
276    /// `Argon2_d` (0): data-dependent addressing.
277    Argon2d = 0,
278    /// `Argon2_i` (1): data-independent addressing.
279    Argon2i = 1,
280    /// `Argon2_id` (2): first half-pass independent, rest dependent. The default.
281    #[default]
282    Argon2id = 2,
283}
284
285impl Algorithm {
286    /// The `argon2_type` numeric value.
287    #[inline]
288    #[must_use]
289    pub const fn as_u32(self) -> u32 {
290        self as u32
291    }
292
293    /// Parse an `argon2_type` numeric value.
294    #[inline]
295    #[must_use]
296    pub const fn from_u32(value: u32) -> Option<Algorithm> {
297        match value {
298            0 => Some(Algorithm::Argon2d),
299            1 => Some(Algorithm::Argon2i),
300            2 => Some(Algorithm::Argon2id),
301            _ => None,
302        }
303    }
304
305    /// `argon2_type2string(type, 0)`: the lowercase name used in PHC strings.
306    ///
307    /// Note `"argon2i"` is a prefix of `"argon2id"`; the C decoder relies on
308    /// the *next* character failing to parse, and the Rust decoder must too.
309    #[inline]
310    #[must_use]
311    pub const fn as_str(self) -> &'static str {
312        match self {
313            Algorithm::Argon2d => "argon2d",
314            Algorithm::Argon2i => "argon2i",
315            Algorithm::Argon2id => "argon2id",
316        }
317    }
318
319    /// `argon2_type2string(type, 1)`: the capitalised name (used by genkat).
320    #[inline]
321    #[must_use]
322    pub const fn as_str_uppercase(self) -> &'static str {
323        match self {
324            Algorithm::Argon2d => "Argon2d",
325            Algorithm::Argon2i => "Argon2i",
326            Algorithm::Argon2id => "Argon2id",
327        }
328    }
329
330    /// All three variants, in `argon2_type` order.
331    pub const ALL: [Algorithm; 3] = [Algorithm::Argon2d, Algorithm::Argon2i, Algorithm::Argon2id];
332}
333
334// ---------------------------------------------------------------------------
335// Version
336// ---------------------------------------------------------------------------
337
338/// The Argon2 version (`argon2_version`).
339#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
340#[repr(u32)]
341pub enum Version {
342    /// `ARGON2_VERSION_10` (0x10). Blocks are always overwritten, never XORed.
343    V0x10 = 0x10,
344    /// `ARGON2_VERSION_13` (0x13) — `ARGON2_VERSION_NUMBER`, the default.
345    #[default]
346    V0x13 = 0x13,
347}
348
349impl Version {
350    /// `ARGON2_VERSION_NUMBER`.
351    pub const DEFAULT: Version = Version::V0x13;
352
353    /// Both variants, ascending.
354    pub const ALL: [Version; 2] = [Version::V0x10, Version::V0x13];
355
356    /// The `argon2_version` numeric value.
357    #[inline]
358    #[must_use]
359    pub const fn as_u32(self) -> u32 {
360        self as u32
361    }
362
363    /// Parse an `argon2_version` numeric value.
364    #[inline]
365    #[must_use]
366    pub const fn from_u32(value: u32) -> Option<Version> {
367        match value {
368            0x10 => Some(Version::V0x10),
369            0x13 => Some(Version::V0x13),
370            _ => None,
371        }
372    }
373}
374
375// ---------------------------------------------------------------------------
376// validate_inputs
377// ---------------------------------------------------------------------------
378
379/// `validate_inputs()` from `src/core.c`, in the exact same order.
380///
381/// The order is load-bearing: when several inputs are invalid, the C reference
382/// returns the error for whichever check runs first, and the differential tests
383/// compare error codes.
384///
385/// Checks the C performs that are omitted here, with the reason:
386///
387/// * `context == NULL` → `ARGON2_INCORRECT_PARAMETER`: no null contexts in Rust.
388/// * `out == NULL` → `ARGON2_OUTPUT_PTR_NULL`: no null slices in Rust.
389/// * the four `*_PTR_MISMATCH` checks: a Rust slice always has a valid pointer.
390/// * `ARGON2_MIN_PWD_LENGTH > pwdlen`, `ARGON2_MIN_AD_LENGTH > adlen`,
391///   `ARGON2_MIN_SECRET > secretlen`: those minima are all 0, so the checks can
392///   never fire (and would be tautological comparisons in Rust).
393/// * the two allocator-callback checks: this crate has no allocator callbacks.
394///
395/// Note the C computes `8 * context->lanes` in `uint32_t`, *before* `lanes` has
396/// been range-checked, so it can wrap. [`u32::wrapping_mul`] reproduces that:
397/// `lanes = 0xFFFF_FFFF` yields `MemoryTooLittle`, not `LanesTooMany`.
398///
399/// # Prefer [`Params::validate_for`]
400///
401/// This free function is the escape hatch, not the main path.
402/// [`Params::validate_for`] calls it with five of the nine arguments filled in
403/// from the receiver: the tag length (`out_len`, from [`Params::tag_len_bytes`])
404/// and the four cost values (`m_cost`, `t_cost`, `lanes`, `threads`). It leaves
405/// the caller exactly the four buffer lengths, `pwd_len`, `salt_len`,
406/// `secret_len` and `ad_len`. Those five values come from a [`Params`] that
407/// [`ParamsBuilder::build`] already ran through this function, so they cannot
408/// drift from the costs the hash will actually run with, and `core` takes that
409/// route on every hash.
410///
411/// Reach for this function directly only when the C's exact check ordering is
412/// what is wanted, which is the one thing the `Params` route cannot give you:
413/// [`ParamsBuilder::build`] validates the cost parameters at construction time,
414/// so a caller who supplies both a bad `m_cost` and a short salt sees the
415/// `m_cost` error where the C reports `ARGON2_SALT_TOO_SHORT` (the divergence
416/// note on [`Params`] spells this out).
417/// `decode_string` is the in-crate example: it calls this function directly on
418/// the decoded fields and only builds its `Params` afterwards, so that a
419/// malformed PHC string yields the same error code `validate_inputs()`
420/// (`core.c:388-513`) yields in the C.
421///
422/// ```
423/// use argon2_rust::Error;
424/// use argon2_rust::params::{Memory, Params, validate_inputs};
425///
426/// let params = Params::builder().memory(Memory::kib(19_456)).passes(2).build()?;
427///
428/// // Four arguments. The tag length and the four costs come from `params`.
429/// assert_eq!(params.validate_for(8, 16, 0, 0), Ok(()));
430///
431/// // The same check spelled out. The five values `params` would have supplied
432/// // have to be repeated by hand and kept in step with it.
433/// assert_eq!(validate_inputs(32, 8, 16, 0, 0, 19_456, 2, 1, 1), Ok(()));
434///
435/// // `out_len` and `pwd_len` transposed, which is the pair `validate_for`
436/// // takes off the call site entirely. Both are `usize` and adjacent, so this
437/// // compiles, and there is no error to notice: the password length 8 is now
438/// // the tag length, 8 clears `MIN_OUTLEN` (4), and the call says `Ok(())`
439/// // while agreeing to a 64-bit tag.
440/// assert_eq!(validate_inputs(8, 32, 16, 0, 0, 19_456, 2, 1, 1), Ok(()));
441///
442/// // The method form cannot be told that. `out_len` is not one of its four
443/// // arguments; it comes from the `Params`, which holds it at 32.
444/// assert_eq!(params.tag_len_bytes(), 32);
445/// assert_eq!(params.validate_for(32, 16, 0, 0), Ok(()));
446/// # Ok::<(), Error>(())
447/// ```
448// `MAX_TIME` is `u32::MAX`, and so is `MAX_MEMORY` on a 64-bit target, which
449// makes those two upper-bound checks tautologically false there. They are kept
450// verbatim so the check order matches the C exactly, and because `MAX_MEMORY` is
451// `0x20_0000` on a 32-bit target, where the check is real.
452#[allow(clippy::absurd_extreme_comparisons)]
453// Nine parameters, one per `argon2_context` field the C checks. Grouping them
454// would obscure the 1:1 correspondence with `validate_inputs()`.
455#[allow(clippy::too_many_arguments)]
456pub const fn validate_inputs(
457    out_len: usize,
458    pwd_len: usize,
459    salt_len: usize,
460    secret_len: usize,
461    ad_len: usize,
462    m_cost: u32,
463    t_cost: u32,
464    lanes: u32,
465    threads: u32,
466) -> Result<(), Error> {
467    // Validate output length.
468    if out_len < MIN_OUTLEN as usize {
469        return Err(Error::OutputTooShort);
470    }
471    if out_len > MAX_OUTLEN as usize {
472        return Err(Error::OutputTooLong);
473    }
474
475    // Validate password (required param).
476    if pwd_len > MAX_PWD_LENGTH as usize {
477        return Err(Error::PwdTooLong);
478    }
479
480    // Validate salt (required param). Note the C checks the length even when
481    // `salt == NULL`, so an empty salt is `SaltTooShort`, not a ptr mismatch.
482    if salt_len < MIN_SALT_LENGTH as usize {
483        return Err(Error::SaltTooShort);
484    }
485    if salt_len > MAX_SALT_LENGTH as usize {
486        return Err(Error::SaltTooLong);
487    }
488
489    // Validate secret (optional param).
490    if secret_len > MAX_SECRET as usize {
491        return Err(Error::SecretTooLong);
492    }
493
494    // Validate associated data (optional param).
495    if ad_len > MAX_AD_LENGTH as usize {
496        return Err(Error::AdTooLong);
497    }
498
499    // Validate memory cost. Three checks, in this order.
500    if m_cost < MIN_MEMORY {
501        return Err(Error::MemoryTooLittle);
502    }
503    if m_cost > MAX_MEMORY {
504        return Err(Error::MemoryTooMuch);
505    }
506    if m_cost < 8u32.wrapping_mul(lanes) {
507        return Err(Error::MemoryTooLittle);
508    }
509
510    // Validate time cost.
511    if t_cost < MIN_TIME {
512        return Err(Error::TimeTooSmall);
513    }
514    if t_cost > MAX_TIME {
515        return Err(Error::TimeTooLarge);
516    }
517
518    // Validate lanes.
519    if lanes < MIN_LANES {
520        return Err(Error::LanesTooFew);
521    }
522    if lanes > MAX_LANES {
523        return Err(Error::LanesTooMany);
524    }
525
526    // Validate threads.
527    if threads < MIN_THREADS {
528        return Err(Error::ThreadsTooFew);
529    }
530    if threads > MAX_THREADS {
531        return Err(Error::ThreadsTooMany);
532    }
533
534    Ok(())
535}
536
537// ---------------------------------------------------------------------------
538// Params
539// ---------------------------------------------------------------------------
540
541/// Validated Argon2 cost parameters.
542///
543/// Holds exactly the fields of `argon2_context` that are *not* byte buffers:
544/// `m_cost`, `t_cost`, `lanes`, `threads` and `outlen`. Password, salt, secret
545/// and associated data are passed per call.
546///
547/// There is no public field and no public constructor: every `Params` comes out
548/// of [`ParamsBuilder::build`], which runs [`validate_inputs`], or out of a
549/// preset ([`Params::DEFAULT`], [`Params::OWASP`],
550/// [`Params::RFC9106_HIGH_MEMORY`], [`Params::RFC9106_LOW_MEMORY`]) that
551/// `build`'s `const` twin already ran. So `lanes >= 1` always holds and the
552/// derived values below never divide by zero.
553///
554/// Start from [`Params::builder`], or from [`Params::to_builder`] to adjust an
555/// existing value.
556///
557/// # Known divergence from the C reference
558///
559/// [`ParamsBuilder::build`] validates the cost parameters immediately, whereas
560/// the C checks salt length *before* `m_cost`. If a caller supplies both a bad
561/// `m_cost` and a short salt, this crate reports the `m_cost` error at
562/// `Params` construction time while the C reports `ARGON2_SALT_TOO_SHORT`.
563/// Call [`validate_inputs`] directly to reproduce the C ordering exactly.
564#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
565pub struct Params {
566    m_cost: u32,
567    t_cost: u32,
568    lanes: u32,
569    threads: u32,
570    output_len: u32,
571}
572
573/// Builder for [`Params`].
574///
575/// Every setter is a `const fn` taking `self` by value, so a `Params` can be
576/// built in a `const` item — see [`ParamsBuilder::build_or_panic`]. The builder
577/// starts from [`ParamsBuilder::DEFAULT`], so each setter is optional.
578///
579/// ```
580/// use argon2_rust::{Params, params::{Memory, TagLen}};
581///
582/// let params = Params::builder()
583///     .memory(Memory::mib(64))
584///     .passes(3)
585///     .lanes(4)
586///     .tag_len(TagLen::bytes(32))
587///     .build()?;
588/// assert_eq!(params.memory(), Memory::mib(64));
589/// # Ok::<(), argon2_rust::Error>(())
590/// ```
591#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
592pub struct ParamsBuilder {
593    memory: Memory,
594    passes: u32,
595    lanes: u32,
596    /// `None` means "track `lanes`", which is what `argon2_hash()` does: it
597    /// sets both `context.lanes` and `context.threads` from one argument.
598    /// Storing the choice rather than eagerly copying `lanes` is what makes
599    /// `.threads(2).lanes(4)` and `.lanes(4).threads(2)` agree.
600    threads: Option<u32>,
601    tag_len: TagLen,
602}
603
604impl ParamsBuilder {
605    /// The starting point: OWASP's Argon2id profile, 19 MiB, two passes, one
606    /// lane, a 32-byte tag.
607    ///
608    /// These are literals rather than a copy of [`Params::DEFAULT`]'s fields
609    /// on purpose. `Params::DEFAULT` is built *by* this builder, so reading it
610    /// here would be a cyclic `const`.
611    pub const DEFAULT: ParamsBuilder = ParamsBuilder {
612        memory: Memory::kib(19456),
613        passes: 2,
614        lanes: 1,
615        threads: None,
616        tag_len: TagLen::bytes(32),
617    };
618
619    /// Set the memory cost.
620    #[inline]
621    #[must_use]
622    pub const fn memory(mut self, memory: Memory) -> ParamsBuilder {
623        self.memory = memory;
624        self
625    }
626
627    /// Set the number of passes (`t_cost` in the C, `t=` in a PHC string).
628    #[inline]
629    #[must_use]
630    pub const fn passes(mut self, passes: u32) -> ParamsBuilder {
631        self.passes = passes;
632        self
633    }
634
635    /// Set the degree of parallelism (`p=` in a PHC string).
636    ///
637    /// This one feeds the tag. Changing it changes the hash.
638    #[inline]
639    #[must_use]
640    pub const fn lanes(mut self, lanes: u32) -> ParamsBuilder {
641        self.lanes = lanes;
642        self
643    }
644
645    /// Set the worker-thread budget.
646    ///
647    /// A pure performance knob: it does **not** affect the tag. Only `lanes`
648    /// does. Left unset it tracks [`ParamsBuilder::lanes`], which is what
649    /// `argon2_hash()` does — it sets both `context.lanes` and
650    /// `context.threads` from its single `parallelism` argument. The effective
651    /// count is `min(threads, lanes)`, see [`Params::effective_threads`].
652    ///
653    /// ```
654    /// use argon2_rust::{Algorithm, Argon2, Params, Version, params::Memory};
655    ///
656    /// // Four lanes of work, but never more than two OS threads to run them.
657    /// let budgeted = Params::builder()
658    ///     .memory(Memory::kib(64))
659    ///     .passes(1)
660    ///     .lanes(4)
661    ///     .threads(2)
662    ///     .build()?;
663    /// assert_eq!((budgeted.lanes(), budgeted.threads()), (4, 2));
664    /// assert_eq!(budgeted.effective_threads(), 2);
665    ///
666    /// // Asking for more threads than lanes is legal, and the extra workers
667    /// // simply have no lane to claim.
668    /// let oversubscribed = Params::builder()
669    ///     .memory(Memory::kib(64))
670    ///     .passes(1)
671    ///     .lanes(2)
672    ///     .threads(8)
673    ///     .build()?;
674    /// assert_eq!(oversubscribed.effective_threads(), 2);
675    ///
676    /// // Leaving it unset is exactly `threads == lanes`.
677    /// let full = Params::builder()
678    ///     .memory(Memory::kib(64))
679    ///     .passes(1)
680    ///     .lanes(4)
681    ///     .build()?;
682    /// assert_eq!(full, budgeted.to_builder().threads(4).build()?);
683    /// assert_eq!(full.threads(), 4);
684    ///
685    /// // And the knob really is free of the tag: same `lanes`, same bytes,
686    /// // whichever thread budget produced them.
687    /// let two_workers = Argon2::new(Algorithm::Argon2id, Version::V0x13, budgeted);
688    /// let four_workers = Argon2::new(Algorithm::Argon2id, Version::V0x13, full);
689    /// assert_eq!(
690    ///     two_workers.hash(b"password", b"somesalt")?,
691    ///     four_workers.hash(b"password", b"somesalt")?,
692    /// );
693    /// # Ok::<(), argon2_rust::Error>(())
694    /// ```
695    #[inline]
696    #[must_use]
697    pub const fn threads(mut self, threads: u32) -> ParamsBuilder {
698        self.threads = Some(threads);
699        self
700    }
701
702    /// Set the tag length.
703    #[inline]
704    #[must_use]
705    pub const fn tag_len(mut self, tag_len: TagLen) -> ParamsBuilder {
706        self.tag_len = tag_len;
707        self
708    }
709
710    /// Validate and produce [`Params`].
711    ///
712    /// # Errors
713    ///
714    /// Any of the cost-parameter errors from [`validate_inputs`], plus
715    /// [`Error::OutputTooLong`] and [`Error::MemoryTooMuch`] for values too
716    /// large for this target at all.
717    pub const fn build(self) -> Result<Params, Error> {
718        // `Memory` and `TagLen` hold `u64`; `validate_inputs` takes a `u32`
719        // memory cost and a `usize` output length. Range-check BEFORE
720        // narrowing: on a 32-bit target `(1u64 << 40) as usize` is 0, which
721        // would turn OutputTooLong into OutputTooShort.
722        //
723        // The order here is the C's. `validate_inputs` checks BOTH `out_len`
724        // bounds before it looks at `m_cost`, so both are checked here too —
725        // pre-checking only the upper bound would report MemoryTooMuch for a
726        // 3-byte tag combined with an over-large memory cost, where the C
727        // reports OutputTooShort. That combination is reachable from a crafted
728        // PHC string, whose tag length and `m=` are both attacker-chosen.
729        let bytes = self.tag_len.as_bytes();
730        if bytes > MAX_OUTLEN as u64 {
731            return Err(Error::OutputTooLong);
732        }
733        if bytes < MIN_OUTLEN as u64 {
734            return Err(Error::OutputTooShort);
735        }
736        let kib = self.memory.as_kib();
737        if kib > MAX_MEMORY as u64 {
738            return Err(Error::MemoryTooMuch);
739        }
740
741        let threads = match self.threads {
742            Some(threads) => threads,
743            None => self.lanes,
744        };
745
746        // Placeholder lengths that always pass their own checks, so the
747        // *relative* order of the checks that do apply is exactly the C's.
748        match validate_inputs(
749            bytes as usize,
750            0,
751            MIN_SALT_LENGTH as usize,
752            0,
753            0,
754            kib as u32,
755            self.passes,
756            self.lanes,
757            threads,
758        ) {
759            Ok(()) => {}
760            Err(e) => return Err(e),
761        }
762
763        Ok(Params {
764            m_cost: kib as u32,
765            t_cost: self.passes,
766            lanes: self.lanes,
767            threads,
768            output_len: bytes as u32,
769        })
770    }
771
772    /// Validate and produce [`Params`], panicking on invalid parameters.
773    ///
774    /// This exists for `const` items, where a panic is a compile error:
775    ///
776    /// ```
777    /// use argon2_rust::{Params, params::Memory};
778    ///
779    /// const LOGIN: Params = Params::builder()
780    ///     .memory(Memory::mib(64))
781    ///     .passes(3)
782    ///     .build_or_panic();
783    /// assert_eq!(LOGIN.passes(), 3);
784    /// ```
785    ///
786    /// # Panics
787    ///
788    /// If the parameters are invalid. Use [`ParamsBuilder::build`] anywhere a
789    /// runtime error is the right answer — it is the normal way in, and it is
790    /// why no fallible path in this crate panics.
791    #[must_use]
792    pub const fn build_or_panic(self) -> Params {
793        match self.build() {
794            Ok(params) => params,
795            Err(_) => panic!("invalid Argon2 parameters"),
796        }
797    }
798}
799
800impl Default for ParamsBuilder {
801    fn default() -> ParamsBuilder {
802        ParamsBuilder::DEFAULT
803    }
804}
805
806impl Params {
807    /// The recommended default: OWASP's Argon2id profile.
808    ///
809    /// 19 MiB, two passes, one lane, a 32-byte tag. Equal to [`Params::OWASP`]
810    /// and to `Params::default()`.
811    pub const DEFAULT: Params = ParamsBuilder::DEFAULT.build_or_panic();
812
813    /// OWASP's Argon2id profile: 19 MiB, `t=2`, `p=1`, 32-byte tag.
814    ///
815    /// The same value as [`Params::DEFAULT`], under the name that says where
816    /// the numbers come from.
817    pub const OWASP: Params = Params::DEFAULT;
818
819    /// RFC 9106 §4's first recommendation: 2 GiB, `t=1`, `p=4`, 32-byte tag.
820    ///
821    /// On a 32-bit target 2 GiB is exactly [`MAX_MEMORY`], so this constant
822    /// still compiles there — but the arena will fail to allocate inside a
823    /// 4 GiB address space. Prefer [`Params::RFC9106_LOW_MEMORY`] there.
824    pub const RFC9106_HIGH_MEMORY: Params = ParamsBuilder::DEFAULT
825        .memory(Memory::gib(2))
826        .passes(1)
827        .lanes(4)
828        .build_or_panic();
829
830    /// RFC 9106 §4's second recommendation, for memory-constrained systems:
831    /// 64 MiB, `t=3`, `p=4`, 32-byte tag.
832    pub const RFC9106_LOW_MEMORY: Params = ParamsBuilder::DEFAULT
833        .memory(Memory::mib(64))
834        .passes(3)
835        .lanes(4)
836        .build_or_panic();
837
838    /// Start building, from [`ParamsBuilder::DEFAULT`].
839    #[inline]
840    #[must_use]
841    pub const fn builder() -> ParamsBuilder {
842        ParamsBuilder::DEFAULT
843    }
844
845    /// Reopen these parameters for adjustment.
846    ///
847    /// ```
848    /// use argon2_rust::Params;
849    ///
850    /// let narrow = Params::RFC9106_LOW_MEMORY.to_builder().lanes(1).build()?;
851    /// assert_eq!(narrow.lanes(), 1);
852    /// # Ok::<(), argon2_rust::Error>(())
853    /// ```
854    #[inline]
855    #[must_use]
856    pub const fn to_builder(self) -> ParamsBuilder {
857        ParamsBuilder {
858            memory: Memory::kib(self.m_cost as u64),
859            passes: self.t_cost,
860            lanes: self.lanes,
861            // `Some`, not `None`: a round trip must preserve an explicit
862            // thread budget that differs from `lanes`.
863            threads: Some(self.threads),
864            tag_len: TagLen::bytes(self.output_len as u64),
865        }
866    }
867
868    /// The memory cost.
869    #[inline]
870    #[must_use]
871    pub const fn memory(&self) -> Memory {
872        Memory::kib(self.m_cost as u64)
873    }
874
875    /// The memory cost in kibibytes (`context.m_cost`, `m=` in a PHC string).
876    ///
877    /// A `u32`, losslessly: `build()` rejected anything wider.
878    #[inline]
879    #[must_use]
880    pub const fn memory_kib(&self) -> u32 {
881        self.m_cost
882    }
883
884    /// Number of passes (`context.t_cost`, `instance.passes`, `t=`).
885    #[inline]
886    #[must_use]
887    pub const fn passes(&self) -> u32 {
888        self.t_cost
889    }
890
891    /// The tag length.
892    #[inline]
893    #[must_use]
894    pub const fn tag_len(&self) -> TagLen {
895        TagLen::bytes(self.output_len as u64)
896    }
897
898    /// The tag length in bytes (`context.outlen`).
899    ///
900    /// A `usize`, losslessly: `build()` rejected anything wider.
901    #[inline]
902    #[must_use]
903    pub const fn tag_len_bytes(&self) -> usize {
904        self.output_len as usize
905    }
906
907    /// Run the full `validate_inputs()` sequence for a concrete call.
908    ///
909    /// `core` calls this on every hash so the salt/password/secret/ad checks
910    /// fire in the C's order.
911    ///
912    /// # Errors
913    ///
914    /// Any error from [`validate_inputs`].
915    pub const fn validate_for(
916        &self,
917        pwd_len: usize,
918        salt_len: usize,
919        secret_len: usize,
920        ad_len: usize,
921    ) -> Result<(), Error> {
922        validate_inputs(
923            self.output_len as usize,
924            pwd_len,
925            salt_len,
926            secret_len,
927            ad_len,
928            self.m_cost,
929            self.t_cost,
930            self.lanes,
931            self.threads,
932        )
933    }
934
935    /// Degree of parallelism (`context.lanes`). Affects the tag.
936    #[inline]
937    #[must_use]
938    pub const fn lanes(&self) -> u32 {
939        self.lanes
940    }
941
942    /// Requested worker threads (`context.threads`). Does not affect the tag.
943    #[inline]
944    #[must_use]
945    pub const fn threads(&self) -> u32 {
946        self.threads
947    }
948
949    /// `min(threads, lanes)`, as `argon2_ctx` computes it.
950    #[inline]
951    #[must_use]
952    pub const fn effective_threads(&self) -> u32 {
953        if self.threads > self.lanes {
954            self.lanes
955        } else {
956            self.threads
957        }
958    }
959
960    /// Step 2 of `argon2_ctx()`: align the memory size.
961    ///
962    /// ```text
963    /// memory_blocks = m_cost;
964    /// if (memory_blocks < 2 * SYNC_POINTS * lanes)
965    ///     memory_blocks = 2 * SYNC_POINTS * lanes;
966    /// segment_length = memory_blocks / (lanes * SYNC_POINTS);
967    /// memory_blocks  = segment_length * (lanes * SYNC_POINTS);
968    /// lane_length    = segment_length * SYNC_POINTS;
969    /// ```
970    ///
971    /// Returns `(memory_blocks, segment_length, lane_length)`. No overflow is
972    /// possible: `lanes <= MAX_LANES` (`0xFF_FFFF`), so `lanes * SYNC_POINTS`
973    /// fits comfortably in `u32`, and `segment_length * lanes * SYNC_POINTS`
974    /// is bounded by the original `memory_blocks <= MAX_MEMORY`.
975    #[inline]
976    #[must_use]
977    pub const fn memory_layout(&self) -> (u32, u32, u32) {
978        let lanes_x_sync = self.lanes * SYNC_POINTS;
979        let min_blocks = 2 * SYNC_POINTS * self.lanes;
980
981        let mut memory_blocks = self.m_cost;
982        if memory_blocks < min_blocks {
983            memory_blocks = min_blocks;
984        }
985
986        let segment_length = memory_blocks / lanes_x_sync;
987        memory_blocks = segment_length * lanes_x_sync;
988        let lane_length = segment_length * SYNC_POINTS;
989
990        (memory_blocks, segment_length, lane_length)
991    }
992
993    /// Number of 1 KiB blocks the arena needs (`instance.memory_blocks`).
994    #[inline]
995    #[must_use]
996    pub const fn memory_blocks(&self) -> u32 {
997        self.memory_layout().0
998    }
999
1000    /// Blocks per segment (`instance.segment_length`).
1001    #[inline]
1002    #[must_use]
1003    pub const fn segment_length(&self) -> u32 {
1004        self.memory_layout().1
1005    }
1006
1007    /// Blocks per lane (`instance.lane_length` = `segment_length * SYNC_POINTS`).
1008    #[inline]
1009    #[must_use]
1010    pub const fn lane_length(&self) -> u32 {
1011        self.memory_layout().2
1012    }
1013}
1014
1015impl Default for Params {
1016    /// [`Params::DEFAULT`]: OWASP's profile, 19 MiB, `t=2`, `p=1`, 32-byte tag.
1017    fn default() -> Params {
1018        Params::DEFAULT
1019    }
1020}
1021
1022#[cfg(test)]
1023mod tests {
1024    use super::*;
1025
1026    #[test]
1027    fn constants_match_the_c_preprocessor() {
1028        // Printed by compiling include/argon2.h on aarch64-apple-darwin:
1029        //   MAX_MEMORY_BITS = 32, MAX_MEMORY = 4294967295, MIN_MEMORY = 8,
1030        //   MAX_OUTLEN = 4294967295, MAX_LANES = 16777215
1031        assert_eq!(MIN_MEMORY, 8);
1032        assert_eq!(MAX_LANES, 16_777_215);
1033        assert_eq!(MAX_OUTLEN, 4_294_967_295);
1034        if size_of::<*const u8>() == 8 {
1035            assert_eq!(MAX_MEMORY_BITS, 32);
1036            assert_eq!(MAX_MEMORY, 4_294_967_295);
1037        }
1038        assert_eq!(BLOCK_SIZE, 1024);
1039        assert_eq!(QWORDS_IN_BLOCK, 128);
1040        assert_eq!(OWORDS_IN_BLOCK, 64);
1041        assert_eq!(HWORDS_IN_BLOCK, 32);
1042        assert_eq!(BITS512_WORDS_IN_BLOCK, 16);
1043        assert_eq!(PREHASH_SEED_LENGTH - PREHASH_DIGEST_LENGTH, 8);
1044    }
1045
1046    #[test]
1047    fn decoded_mirrors_are_not_decoder_bounds() {
1048        // The three `encoding.h` mirrors are read by nobody: not by the C, and
1049        // so not by `decode_string` here either. Each one's doc tells a caller
1050        // not to bounds-check against it. This pins the gap that makes that
1051        // advice true, so the prose cannot go stale.
1052        //
1053        // In `const` blocks so the checks run at compile time: every operand is
1054        // a constant, so a violation is a build error rather than a red test,
1055        // and clippy::assertions_on_constants stays quiet.
1056
1057        // `lanes` is bounded by `MAX_LANES` (16_777_215), roughly 65_000x this
1058        // value. Measured: `$argon2id$v=19$m=2400,t=1,p=300$...` encodes and
1059        // verifies, with `p` well past 255. A strict `<` is the point — if the
1060        // two ever met, "this is not the bound" would be false.
1061        const { assert!(MAX_DECODED_LANES < MAX_LANES) }
1062
1063        // The mirror sits ABOVE the enforced minimum (12 against 4), which is
1064        // what lets a decoded tag legitimately undershoot it. Measured: an
1065        // 8-byte tag round-trips.
1066        const { assert!(MIN_DECODED_OUT_LEN > MIN_OUTLEN) }
1067
1068        // The salt mirror is the nastiest of the three because it agrees with
1069        // the enforced bound today, which is exactly why it reads like the
1070        // enforced bound. Pinned as equal on purpose: the day `MIN_SALT_LENGTH`
1071        // moves, this fails and sends the next reader to the doc above that
1072        // says "they happen to hold the same value (8) today" — prose that
1073        // would otherwise quietly become wrong.
1074        const { assert!(MIN_DECODED_SALT_LEN == MIN_SALT_LENGTH) }
1075    }
1076
1077    /// Pins the two PHC strings the docs on `MAX_DECODED_LANES` and
1078    /// `MIN_DECODED_OUT_LEN` quote as evidence.
1079    ///
1080    /// Each of those docs tells a caller not to bounds-check decoded input
1081    /// against the constant, and each backs the advice with a measured string
1082    /// that breaks the constant and round-trips anyway: `p=300` against a
1083    /// documented 255, and an 8-byte tag against a documented 12. The strings
1084    /// were pasted in from a run of this crate and nothing recomputed them, so
1085    /// a change to `encode_string`, to the tag derivation, or to what
1086    /// `ParamsBuilder::build` does with `m_cost` would leave the docs quoting
1087    /// output the crate no longer produces, with no test failing. Reproduced
1088    /// here from the parameters those docs state, byte for byte, and verified
1089    /// back through `verify_encoded` so the word "round-trips" is pinned too.
1090    #[test]
1091    fn decoded_bound_docs_quote_strings_this_crate_still_produces() {
1092        use crate::Argon2;
1093
1094        // `MAX_DECODED_LANES` is 255 and this is `p=300`. `m=2400` is forced by
1095        // `validate_inputs`' `m_cost >= 8 * lanes` rule, not by 255.
1096        let params = Params::builder()
1097            .memory(Memory::kib(2400))
1098            .passes(1)
1099            .lanes(300)
1100            .tag_len(TagLen::bytes(32))
1101            .build()
1102            .unwrap();
1103        let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1104        let encoded = argon2.hash_encoded(b"password", b"somesalt").unwrap();
1105        assert_eq!(
1106            encoded,
1107            "$argon2id$v=19$m=2400,t=1,p=300$c29tZXNhbHQ$tPLI8hre65Crk/uP5eIGCZzn3TQ7RzRoXIkGzt5jQoI"
1108        );
1109        assert_eq!(
1110            Argon2::verify_encoded(&encoded, b"password", Algorithm::Argon2id),
1111            Ok(())
1112        );
1113
1114        // `MIN_DECODED_OUT_LEN` is 12 and this tag is 8 bytes, which `MIN_OUTLEN`
1115        // (4) allows. Same `m` and salt as above so the two strings differ only
1116        // where the docs say they do.
1117        let params = Params::builder()
1118            .memory(Memory::kib(2400))
1119            .passes(1)
1120            .lanes(1)
1121            .tag_len(TagLen::bytes(8))
1122            .build()
1123            .unwrap();
1124        let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1125        let encoded = argon2.hash_encoded(b"password", b"somesalt").unwrap();
1126        assert_eq!(
1127            encoded,
1128            "$argon2id$v=19$m=2400,t=1,p=1$c29tZXNhbHQ$kQGQLZpZJIk"
1129        );
1130        assert_eq!(
1131            Argon2::verify_encoded(&encoded, b"password", Algorithm::Argon2id),
1132            Ok(())
1133        );
1134    }
1135
1136    #[test]
1137    fn default_params_are_valid() {
1138        let d = Params::default();
1139        assert!(d.validate_for(0, 8, 0, 0).is_ok());
1140    }
1141
1142    #[test]
1143    fn validate_order_salt_before_m_cost() {
1144        // Both are bad; the C checks salt first.
1145        assert_eq!(
1146            validate_inputs(32, 0, 0, 0, 0, 0, 1, 1, 1),
1147            Err(Error::SaltTooShort)
1148        );
1149    }
1150
1151    #[test]
1152    fn validate_order_out_len_first() {
1153        assert_eq!(
1154            validate_inputs(0, 0, 0, 0, 0, 0, 0, 0, 0),
1155            Err(Error::OutputTooShort)
1156        );
1157    }
1158
1159    #[test]
1160    fn m_cost_lanes_product_wraps_like_c() {
1161        // 8 * 0xFFFF_FFFF wraps to 0xFFFF_FFF8, so any sane m_cost is "too
1162        // little" and LanesTooMany never gets a chance to fire.
1163        assert_eq!(
1164            validate_inputs(32, 0, 8, 0, 0, 1 << 16, 1, 0xFFFF_FFFF, 1),
1165            Err(Error::MemoryTooLittle)
1166        );
1167        // With lanes in range, the 8*lanes rule is the third memory check.
1168        assert_eq!(
1169            validate_inputs(32, 0, 8, 0, 0, 16, 1, 4, 4),
1170            Err(Error::MemoryTooLittle)
1171        );
1172        assert_eq!(validate_inputs(32, 0, 8, 0, 0, 32, 1, 4, 4), Ok(()));
1173    }
1174
1175    #[test]
1176    fn lanes_zero_is_lanes_too_few() {
1177        // 8 * 0 == 0, so the memory checks pass and LanesTooFew surfaces.
1178        assert_eq!(
1179            validate_inputs(32, 0, 8, 0, 0, 8, 1, 0, 1),
1180            Err(Error::LanesTooFew)
1181        );
1182    }
1183
1184    #[test]
1185    fn memory_layout_matches_argon2_ctx() {
1186        // m_cost below the floor gets bumped to 2 * SYNC_POINTS * lanes.
1187        let p = Params::builder()
1188            .memory(Memory::kib(8))
1189            .passes(1)
1190            .lanes(1)
1191            .tag_len(TagLen::bytes(32))
1192            .build()
1193            .unwrap();
1194        assert_eq!(p.memory_layout(), (8, 2, 8));
1195
1196        // 1 << 16 KiB, one lane: 65536 blocks, 16384 per segment.
1197        let p = Params::builder()
1198            .memory(Memory::kib(1 << 16))
1199            .passes(2)
1200            .lanes(1)
1201            .tag_len(TagLen::bytes(32))
1202            .build()
1203            .unwrap();
1204        assert_eq!(p.memory_layout(), (65536, 16384, 65536));
1205
1206        // Four lanes: segment_length = 65536 / 16 = 4096, lane_length = 16384.
1207        let p = Params::builder()
1208            .memory(Memory::kib(1 << 16))
1209            .passes(2)
1210            .lanes(4)
1211            .tag_len(TagLen::bytes(32))
1212            .build()
1213            .unwrap();
1214        assert_eq!(p.memory_layout(), (65536, 4096, 16384));
1215
1216        // Not a multiple of lanes * SYNC_POINTS: truncated down.
1217        let p = Params::builder()
1218            .memory(Memory::kib(100))
1219            .passes(1)
1220            .lanes(3)
1221            .tag_len(TagLen::bytes(32))
1222            .build()
1223            .unwrap();
1224        let (blocks, seg, lane) = p.memory_layout();
1225        assert_eq!(seg, 100 / 12);
1226        assert_eq!(blocks, seg * 12);
1227        assert_eq!(lane, seg * 4);
1228    }
1229
1230    #[test]
1231    fn effective_threads_is_min() {
1232        let p = Params::builder()
1233            .memory(Memory::kib(1 << 16))
1234            .passes(1)
1235            .lanes(2)
1236            .threads(8)
1237            .tag_len(TagLen::bytes(32))
1238            .build()
1239            .unwrap();
1240        assert_eq!(p.threads(), 8);
1241        assert_eq!(p.effective_threads(), 2);
1242    }
1243
1244    #[test]
1245    fn memory_units_convert() {
1246        assert_eq!(Memory::kib(65536), Memory::mib(64));
1247        assert_eq!(Memory::mib(1024), Memory::gib(1));
1248        assert_eq!(Memory::gib(2).as_kib(), 2 * 1024 * 1024);
1249        assert_eq!(Memory::kib(19456).as_kib(), 19456);
1250    }
1251
1252    /// A plain `*` would panic here in a debug build. These constructors promise
1253    /// not to, so an absurd request saturates and is rejected later, by `build()`.
1254    #[test]
1255    fn memory_saturates_instead_of_overflowing() {
1256        assert_eq!(Memory::mib(u64::MAX).as_kib(), u64::MAX);
1257        assert_eq!(Memory::gib(u64::MAX).as_kib(), u64::MAX);
1258        assert_eq!(Memory::gib(u64::MAX / 1024).as_kib(), u64::MAX);
1259    }
1260
1261    #[test]
1262    fn memory_orders_by_size() {
1263        assert!(Memory::mib(64) > Memory::kib(19456));
1264        assert!(Memory::gib(1) > Memory::mib(64));
1265    }
1266
1267    #[test]
1268    fn tag_len_carries_bytes() {
1269        assert_eq!(TagLen::bytes(32).as_bytes(), 32);
1270        assert!(TagLen::bytes(64) > TagLen::bytes(32));
1271    }
1272
1273    /// Both types must be usable in a `const` item, or the builder cannot be.
1274    #[test]
1275    fn units_are_const() {
1276        const M: Memory = Memory::mib(64);
1277        const T: TagLen = TagLen::bytes(32);
1278        assert_eq!(M.as_kib(), 65536);
1279        assert_eq!(T.as_bytes(), 32);
1280    }
1281
1282    /// A `const` item, not a `let`. This is the whole reason the setters take
1283    /// `self` by value; if constness regresses, this stops compiling.
1284    const CONST_BUILT: Params = Params::builder()
1285        .memory(Memory::mib(64))
1286        .passes(3)
1287        .lanes(4)
1288        .build_or_panic();
1289
1290    #[test]
1291    fn builder_builds_in_a_const_item() {
1292        assert_eq!(CONST_BUILT.memory_kib(), 65536);
1293        assert_eq!(CONST_BUILT.passes(), 3);
1294        assert_eq!(CONST_BUILT.lanes(), 4);
1295        assert_eq!(CONST_BUILT.threads(), 4);
1296        assert_eq!(CONST_BUILT.tag_len_bytes(), 32);
1297    }
1298
1299    #[test]
1300    fn threads_defaults_to_lanes_but_an_explicit_value_survives() {
1301        let implicit = Params::builder().lanes(4).build().unwrap();
1302        assert_eq!((implicit.lanes(), implicit.threads()), (4, 4));
1303
1304        // Order must not matter: setting lanes after threads may not clobber it.
1305        let explicit = Params::builder().threads(2).lanes(4).build().unwrap();
1306        assert_eq!((explicit.lanes(), explicit.threads()), (4, 2));
1307        assert_eq!(explicit.effective_threads(), 2);
1308    }
1309
1310    #[test]
1311    fn typed_and_raw_accessors_agree() {
1312        let p = Params::builder()
1313            .memory(Memory::mib(64))
1314            .tag_len(TagLen::bytes(64))
1315            .build()
1316            .unwrap();
1317        assert_eq!(p.memory(), Memory::mib(64));
1318        assert_eq!(p.memory().as_kib(), u64::from(p.memory_kib()));
1319        assert_eq!(p.tag_len(), TagLen::bytes(64));
1320        assert_eq!(p.tag_len().as_bytes() as usize, p.tag_len_bytes());
1321    }
1322
1323    #[test]
1324    fn presets_hold_their_documented_numbers() {
1325        assert_eq!(Params::OWASP, Params::DEFAULT);
1326        assert_eq!(Params::DEFAULT, Params::default());
1327
1328        assert_eq!(Params::OWASP.memory_kib(), 19456);
1329        assert_eq!((Params::OWASP.passes(), Params::OWASP.lanes()), (2, 1));
1330        assert_eq!(Params::OWASP.tag_len_bytes(), 32);
1331
1332        assert_eq!(Params::RFC9106_HIGH_MEMORY.memory(), Memory::gib(2));
1333        assert_eq!(
1334            (
1335                Params::RFC9106_HIGH_MEMORY.passes(),
1336                Params::RFC9106_HIGH_MEMORY.lanes()
1337            ),
1338            (1, 4)
1339        );
1340
1341        assert_eq!(Params::RFC9106_LOW_MEMORY.memory(), Memory::mib(64));
1342        assert_eq!(
1343            (
1344                Params::RFC9106_LOW_MEMORY.passes(),
1345                Params::RFC9106_LOW_MEMORY.lanes()
1346            ),
1347            (3, 4)
1348        );
1349    }
1350
1351    #[test]
1352    fn to_builder_round_trips_every_preset() {
1353        for preset in [
1354            Params::OWASP,
1355            Params::RFC9106_HIGH_MEMORY,
1356            Params::RFC9106_LOW_MEMORY,
1357        ] {
1358            assert_eq!(preset.to_builder().build(), Ok(preset));
1359        }
1360    }
1361
1362    #[test]
1363    fn a_preset_can_be_adjusted() {
1364        let narrow = Params::RFC9106_LOW_MEMORY
1365            .to_builder()
1366            .lanes(1)
1367            .build()
1368            .unwrap();
1369        assert_eq!(narrow.memory(), Memory::mib(64));
1370        assert_eq!(narrow.passes(), 3);
1371        assert_eq!(narrow.lanes(), 1);
1372    }
1373
1374    #[test]
1375    fn build_rejects_every_out_of_range_value() {
1376        let b = Params::builder();
1377        assert_eq!(
1378            b.memory(Memory::gib(9999)).build(),
1379            Err(Error::MemoryTooMuch)
1380        );
1381        assert_eq!(
1382            b.memory(Memory::kib(4)).build(),
1383            Err(Error::MemoryTooLittle)
1384        );
1385        assert_eq!(
1386            b.tag_len(TagLen::bytes(3)).build(),
1387            Err(Error::OutputTooShort)
1388        );
1389        assert_eq!(
1390            b.tag_len(TagLen::bytes(1 << 40)).build(),
1391            Err(Error::OutputTooLong)
1392        );
1393        assert_eq!(b.passes(0).build(), Err(Error::TimeTooSmall));
1394        assert_eq!(b.lanes(0).build(), Err(Error::LanesTooFew));
1395        assert_eq!(b.threads(0).build(), Err(Error::ThreadsTooFew));
1396    }
1397
1398    /// `validate_inputs` checks BOTH `out_len` bounds before `m_cost`: its
1399    /// "Validate output length" block runs before its "Validate memory cost"
1400    /// block. `build()`'s own pre-narrowing checks must keep that order, so a
1401    /// caller who gets both wrong sees the error the C would have reported.
1402    /// Both directions are pinned: checking only the upper bound first is the
1403    /// bug this test exists to catch.
1404    #[test]
1405    fn tag_len_is_checked_before_memory_like_the_c() {
1406        let too_long = Params::builder()
1407            .memory(Memory::gib(9999))
1408            .tag_len(TagLen::bytes(1 << 40))
1409            .build();
1410        assert_eq!(too_long, Err(Error::OutputTooLong));
1411
1412        // The case that a two-check implementation gets wrong: the tag is too
1413        // SHORT, so only `validate_inputs` would catch it — but the memory
1414        // pre-check would already have returned MemoryTooMuch. Reachable from a
1415        // crafted PHC string with a 3-byte tag and a large `m=`.
1416        let too_short = Params::builder()
1417            .memory(Memory::kib(MAX_MEMORY as u64 + 1))
1418            .tag_len(TagLen::bytes(3))
1419            .build();
1420        assert_eq!(too_short, Err(Error::OutputTooShort));
1421    }
1422
1423    /// A saturated `Memory` must be rejected, not truncated into a legal `u32`.
1424    #[test]
1425    fn a_saturated_memory_is_rejected() {
1426        assert_eq!(
1427            Params::builder().memory(Memory::gib(u64::MAX)).build(),
1428            Err(Error::MemoryTooMuch)
1429        );
1430    }
1431
1432    /// The narrowing guard in `build()`, pinned with the one value that makes a
1433    /// missing guard report the *wrong error* instead of no error.
1434    ///
1435    /// `Memory` holds a `u64` and `validate_inputs` takes a `u32`, so `build()`
1436    /// must range-check before it narrows. 8 TiB is chosen for exactly one
1437    /// reason: `(1u64 << 33) as u32` is **0**, and 0 is below `MIN_MEMORY`, so a
1438    /// guard that ran after the narrowing would answer `MemoryTooLittle` for an
1439    /// over-large request.
1440    ///
1441    /// What this protects is a direct builder input, not the PHC decoder:
1442    /// `decode_string` parses `m=` with `decimal_u32`, so no string can hand
1443    /// `build()` a memory value wider than `u32::MAX` KiB. A caller can, because
1444    /// `Memory::kib` takes a `u64` and `Memory::mib`/`Memory::gib` saturate into
1445    /// one — and answering `MemoryTooLittle` to a request for 8 TiB would be
1446    /// actively misleading.
1447    ///
1448    /// This is a different failure mode from the neighbours above, which is why
1449    /// it earns its own case rather than folding into them: `gib(9999)` and
1450    /// `gib(u64::MAX)` narrow to values *inside* the legal range on a 64-bit
1451    /// target, so a missing guard makes those two return `Ok`. `Memory::gib(8192)`
1452    /// is the same number if you prefer that spelling, but `kib(1u64 << 33)` is
1453    /// the one that shows the low 32 bits are zero, which is the whole point.
1454    #[test]
1455    fn memory_is_range_checked_before_it_is_narrowed() {
1456        assert_eq!(
1457            Params::builder().memory(Memory::kib(1u64 << 33)).build(),
1458            Err(Error::MemoryTooMuch)
1459        );
1460        // The claim about the low 32 bits, asserted rather than trusted.
1461        assert_eq!((1u64 << 33) as u32, 0);
1462        assert!(0 < MIN_MEMORY);
1463    }
1464
1465    #[test]
1466    fn algorithm_and_version_round_trip() {
1467        for a in Algorithm::ALL {
1468            assert_eq!(Algorithm::from_u32(a.as_u32()), Some(a));
1469        }
1470        assert_eq!(Algorithm::from_u32(3), None);
1471        assert_eq!(Algorithm::Argon2id.as_str(), "argon2id");
1472        assert_eq!(Algorithm::Argon2i.as_str_uppercase(), "Argon2i");
1473        for v in Version::ALL {
1474            assert_eq!(Version::from_u32(v.as_u32()), Some(v));
1475        }
1476        assert_eq!(Version::from_u32(0x11), None);
1477        assert_eq!(Version::default(), Version::V0x13);
1478        assert_eq!(Algorithm::default(), Algorithm::Argon2id);
1479    }
1480}