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}