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