1use crate::{Algorithm, Argon2, Error, Result, Version, SYNC_POINTS};
4use base64ct::{Base64Unpadded as B64, Encoding};
5use core::str::FromStr;
6
7#[cfg(feature = "password-hash")]
8use password_hash::{ParamsString, PasswordHash};
9
10#[derive(Clone, Debug, Eq, PartialEq)]
14pub struct Params {
15 m_cost: u32,
19
20 t_cost: u32,
24
25 p_cost: u32,
29
30 keyid: KeyId,
32
33 data: AssociatedData,
35
36 output_len: Option<usize>,
38}
39
40impl Params {
41 pub const DEFAULT_M_COST: u32 = 19 * 1024;
43
44 #[allow(clippy::cast_possible_truncation)]
46 pub const MIN_M_COST: u32 = 2 * SYNC_POINTS as u32; pub const MAX_M_COST: u32 = u32::MAX;
50
51 pub const DEFAULT_T_COST: u32 = 2;
53
54 pub const MIN_T_COST: u32 = 1;
56
57 pub const MAX_T_COST: u32 = u32::MAX;
59
60 pub const DEFAULT_P_COST: u32 = 1;
62
63 pub const MIN_P_COST: u32 = 1;
65
66 pub const MAX_P_COST: u32 = 0xFFFFFF;
68
69 pub const MAX_KEYID_LEN: usize = 8;
71
72 pub const MAX_DATA_LEN: usize = 32;
74
75 pub const DEFAULT_OUTPUT_LEN: usize = 32;
77
78 pub const MIN_OUTPUT_LEN: usize = 4;
80
81 pub const MAX_OUTPUT_LEN: usize = 0xFFFFFFFF;
83
84 pub const DEFAULT: Self = Params {
86 m_cost: Self::DEFAULT_M_COST,
87 t_cost: Self::DEFAULT_T_COST,
88 p_cost: Self::DEFAULT_P_COST,
89 keyid: KeyId {
90 bytes: [0u8; Self::MAX_KEYID_LEN],
91 len: 0,
92 },
93 data: AssociatedData {
94 bytes: [0u8; Self::MAX_DATA_LEN],
95 len: 0,
96 },
97 output_len: None,
98 };
99
100 pub const fn new(
108 m_cost: u32,
109 t_cost: u32,
110 p_cost: u32,
111 output_len: Option<usize>,
112 ) -> Result<Self> {
113 if m_cost < Params::MIN_M_COST {
114 return Err(Error::MemoryTooLittle);
115 }
116
117 if m_cost < p_cost * 8 {
120 return Err(Error::MemoryTooLittle);
121 }
122
123 if t_cost < Params::MIN_T_COST {
124 return Err(Error::TimeTooSmall);
125 }
126
127 if p_cost < Params::MIN_P_COST {
130 return Err(Error::ThreadsTooFew);
131 }
132
133 if p_cost > Params::MAX_P_COST {
134 return Err(Error::ThreadsTooMany);
135 }
136
137 if let Some(len) = output_len {
138 if len < Params::MIN_OUTPUT_LEN {
139 return Err(Error::OutputTooShort);
140 }
141
142 if len > Params::MAX_OUTPUT_LEN {
143 return Err(Error::OutputTooLong);
144 }
145 }
146
147 Ok(Params {
148 m_cost,
149 t_cost,
150 p_cost,
151 keyid: KeyId::EMPTY,
152 data: AssociatedData::EMPTY,
153 output_len,
154 })
155 }
156
157 pub const fn m_cost(&self) -> u32 {
161 self.m_cost
162 }
163
164 pub const fn t_cost(&self) -> u32 {
168 self.t_cost
169 }
170
171 pub const fn p_cost(&self) -> u32 {
175 self.p_cost
176 }
177
178 pub fn keyid(&self) -> &[u8] {
190 self.keyid.as_bytes()
191 }
192
193 pub fn data(&self) -> &[u8] {
201 self.data.as_bytes()
202 }
203
204 pub const fn output_len(&self) -> Option<usize> {
206 self.output_len
207 }
208
209 #[allow(clippy::cast_possible_truncation)]
211 pub(crate) const fn lanes(&self) -> usize {
212 self.p_cost as usize
213 }
214
215 pub(crate) const fn lane_length(&self) -> usize {
217 self.segment_length() * SYNC_POINTS
218 }
219
220 pub(crate) const fn segment_length(&self) -> usize {
224 let m_cost = self.m_cost as usize;
225
226 let memory_blocks = if m_cost < 2 * SYNC_POINTS * self.lanes() {
227 2 * SYNC_POINTS * self.lanes()
228 } else {
229 m_cost
230 };
231
232 memory_blocks / (self.lanes() * SYNC_POINTS)
233 }
234
235 pub const fn block_count(&self) -> usize {
237 self.segment_length() * self.lanes() * SYNC_POINTS
238 }
239}
240
241impl Default for Params {
242 fn default() -> Params {
243 Params::DEFAULT
244 }
245}
246
247macro_rules! param_buf {
248 ($ty:ident, $name:expr, $max_len:expr, $error:expr, $doc:expr) => {
249 #[doc = $doc]
250 #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
251 pub struct $ty {
252 bytes: [u8; Self::MAX_LEN],
254
255 len: usize,
257 }
258
259 impl $ty {
260 pub const MAX_LEN: usize = $max_len;
262
263 #[doc = "Create a new"]
264 #[doc = $name]
265 #[doc = "from a slice."]
266 pub fn new(slice: &[u8]) -> Result<Self> {
267 let mut bytes = [0u8; Self::MAX_LEN];
268 let len = slice.len();
269 bytes.get_mut(..len).ok_or($error)?.copy_from_slice(slice);
270
271 Ok(Self { bytes, len })
272 }
273
274 pub const EMPTY: Self = Self {
276 bytes: [0u8; Self::MAX_LEN],
277 len: 0,
278 };
279
280 #[doc = "Decode"]
281 #[doc = $name]
282 #[doc = " from a B64 string"]
283 pub fn from_b64(s: &str) -> Result<Self> {
284 let mut bytes = [0u8; Self::MAX_LEN];
285 let len = B64::decode(s, &mut bytes)?.len();
286
287 Ok(Self { bytes, len })
288 }
289
290 pub fn as_bytes(&self) -> &[u8] {
292 &self.bytes[..self.len]
293 }
294
295 pub const fn len(&self) -> usize {
297 self.len
298 }
299
300 pub const fn is_empty(&self) -> bool {
302 self.len() == 0
303 }
304 }
305
306 impl AsRef<[u8]> for $ty {
307 fn as_ref(&self) -> &[u8] {
308 self.as_bytes()
309 }
310 }
311
312 impl FromStr for $ty {
313 type Err = Error;
314
315 fn from_str(s: &str) -> Result<Self> {
316 Self::from_b64(s)
317 }
318 }
319
320 impl TryFrom<&[u8]> for $ty {
321 type Error = Error;
322
323 fn try_from(bytes: &[u8]) -> Result<Self> {
324 Self::new(bytes)
325 }
326 }
327 };
328}
329
330param_buf!(
332 KeyId,
333 "KeyId",
334 Params::MAX_KEYID_LEN,
335 Error::KeyIdTooLong,
336 "Key identifier"
337);
338
339param_buf!(
341 AssociatedData,
342 "AssociatedData",
343 Params::MAX_DATA_LEN,
344 Error::AdTooLong,
345 "Associated data"
346);
347
348#[cfg(feature = "password-hash")]
349#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
350impl<'a> TryFrom<&'a PasswordHash<'a>> for Params {
351 type Error = password_hash::Error;
352
353 fn try_from(hash: &'a PasswordHash<'a>) -> password_hash::Result<Self> {
354 let mut builder = ParamsBuilder::new();
355
356 for (ident, value) in hash.params.iter() {
357 match ident.as_str() {
358 "m" => {
359 builder.m_cost(value.decimal()?);
360 }
361 "t" => {
362 builder.t_cost(value.decimal()?);
363 }
364 "p" => {
365 builder.p_cost(value.decimal()?);
366 }
367 "keyid" => {
368 builder.keyid(value.as_str().parse()?);
369 }
370 "data" => {
371 builder.data(value.as_str().parse()?);
372 }
373 _ => return Err(password_hash::Error::ParamNameInvalid),
374 }
375 }
376
377 if let Some(output) = &hash.hash {
378 builder.output_len(output.len());
379 }
380
381 Ok(builder.build()?)
382 }
383}
384
385#[cfg(feature = "password-hash")]
386#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
387impl TryFrom<Params> for ParamsString {
388 type Error = password_hash::Error;
389
390 fn try_from(params: Params) -> password_hash::Result<ParamsString> {
391 ParamsString::try_from(¶ms)
392 }
393}
394
395#[cfg(feature = "password-hash")]
396#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
397impl TryFrom<&Params> for ParamsString {
398 type Error = password_hash::Error;
399
400 fn try_from(params: &Params) -> password_hash::Result<ParamsString> {
401 let mut output = ParamsString::new();
402 output.add_decimal("m", params.m_cost)?;
403 output.add_decimal("t", params.t_cost)?;
404 output.add_decimal("p", params.p_cost)?;
405
406 if !params.keyid.is_empty() {
407 output.add_b64_bytes("keyid", params.keyid.as_bytes())?;
408 }
409
410 if !params.data.is_empty() {
411 output.add_b64_bytes("data", params.data.as_bytes())?;
412 }
413
414 Ok(output)
415 }
416}
417
418#[derive(Clone, Debug, Eq, PartialEq)]
420pub struct ParamsBuilder {
421 m_cost: u32,
422 t_cost: u32,
423 p_cost: u32,
424 keyid: Option<KeyId>,
425 data: Option<AssociatedData>,
426 output_len: Option<usize>,
427}
428
429impl ParamsBuilder {
430 pub const fn new() -> Self {
432 Self::DEFAULT
433 }
434
435 pub fn m_cost(&mut self, m_cost: u32) -> &mut Self {
437 self.m_cost = m_cost;
438 self
439 }
440
441 pub fn t_cost(&mut self, t_cost: u32) -> &mut Self {
443 self.t_cost = t_cost;
444 self
445 }
446
447 pub fn p_cost(&mut self, p_cost: u32) -> &mut Self {
449 self.p_cost = p_cost;
450 self
451 }
452
453 pub fn keyid(&mut self, keyid: KeyId) -> &mut Self {
455 self.keyid = Some(keyid);
456 self
457 }
458
459 pub fn data(&mut self, data: AssociatedData) -> &mut Self {
461 self.data = Some(data);
462 self
463 }
464
465 pub fn output_len(&mut self, len: usize) -> &mut Self {
467 self.output_len = Some(len);
468 self
469 }
470
471 pub const fn build(&self) -> Result<Params> {
476 let mut params = match Params::new(self.m_cost, self.t_cost, self.p_cost, self.output_len) {
477 Ok(params) => params,
478 Err(err) => return Err(err),
479 };
480
481 if let Some(keyid) = self.keyid {
482 params.keyid = keyid;
483 }
484
485 if let Some(data) = self.data {
486 params.data = data;
487 };
488
489 Ok(params)
490 }
491
492 pub fn context(&self, algorithm: Algorithm, version: Version) -> Result<Argon2<'_>> {
494 Ok(Argon2::new(algorithm, version, self.build()?))
495 }
496 pub const DEFAULT: ParamsBuilder = {
498 let params = Params::DEFAULT;
499 Self {
500 m_cost: params.m_cost,
501 t_cost: params.t_cost,
502 p_cost: params.p_cost,
503 keyid: None,
504 data: None,
505 output_len: params.output_len,
506 }
507 };
508}
509
510impl Default for ParamsBuilder {
511 fn default() -> Self {
512 Self::DEFAULT
513 }
514}
515
516impl TryFrom<ParamsBuilder> for Params {
517 type Error = Error;
518
519 fn try_from(builder: ParamsBuilder) -> Result<Params> {
520 builder.build()
521 }
522}
523
524#[cfg(all(test, feature = "alloc", feature = "password-hash"))]
525mod tests {
526
527 use super::*;
528
529 #[test]
530 fn params_builder_bad_values() {
531 assert_eq!(
532 ParamsBuilder::new().m_cost(Params::MIN_M_COST - 1).build(),
533 Err(Error::MemoryTooLittle)
534 );
535 assert_eq!(
536 ParamsBuilder::new().t_cost(Params::MIN_T_COST - 1).build(),
537 Err(Error::TimeTooSmall)
538 );
539 assert_eq!(
540 ParamsBuilder::new().p_cost(Params::MIN_P_COST - 1).build(),
541 Err(Error::ThreadsTooFew)
542 );
543 assert_eq!(
544 ParamsBuilder::new()
545 .m_cost(Params::DEFAULT_P_COST * 8 - 1)
546 .build(),
547 Err(Error::MemoryTooLittle)
548 );
549 assert_eq!(
550 ParamsBuilder::new()
551 .m_cost((Params::MAX_P_COST + 1) * 8)
552 .p_cost(Params::MAX_P_COST + 1)
553 .build(),
554 Err(Error::ThreadsTooMany)
555 );
556 }
557
558 #[test]
559 fn associated_data_too_long() {
560 let ret = AssociatedData::new(&[0u8; Params::MAX_DATA_LEN + 1]);
561 assert_eq!(ret, Err(Error::AdTooLong));
562 }
563
564 #[test]
565 fn keyid_too_long() {
566 let ret = KeyId::new(&[0u8; Params::MAX_KEYID_LEN + 1]);
567 assert_eq!(ret, Err(Error::KeyIdTooLong));
568 }
569}