1use core::mem::size_of;
2
3use crate::errors::InvalidParams;
4
5#[cfg(feature = "simple")]
6use password_hash::{errors::InvalidValue, Error, ParamsString, PasswordHash};
7
8#[derive(Clone, Copy, Debug)]
10pub struct Params {
11 pub(crate) log_n: u8,
12 pub(crate) r: u32,
13 pub(crate) p: u32,
14 #[allow(dead_code)] pub(crate) len: usize,
16}
17
18impl Params {
19 pub const RECOMMENDED_LOG_N: u8 = 17;
21
22 pub const RECOMMENDED_R: u32 = 8;
24
25 pub const RECOMMENDED_P: u32 = 1;
27
28 pub const RECOMMENDED_LEN: usize = 32;
30
31 pub fn new(log_n: u8, r: u32, p: u32, len: usize) -> Result<Params, InvalidParams> {
45 let cond1 = (log_n as usize) < usize::BITS as usize;
46 let cond2 = size_of::<usize>() >= size_of::<u32>();
47 let cond3 = r <= usize::MAX as u32 && p < usize::MAX as u32;
48 let cond4 = (10..=64).contains(&len);
49 if !(r > 0 && p > 0 && cond1 && (cond2 || cond3) && cond4) {
50 return Err(InvalidParams);
51 }
52
53 let r = r as usize;
54 let p = p as usize;
55
56 let n: usize = 1 << log_n;
57
58 let r128 = r.checked_mul(128).ok_or(InvalidParams)?;
60
61 r128.checked_mul(n).ok_or(InvalidParams)?;
63
64 r128.checked_mul(p).ok_or(InvalidParams)?;
66
67 if (log_n as usize) >= r * 16 {
71 return Err(InvalidParams);
72 }
73
74 if r * p >= 0x4000_0000 {
79 return Err(InvalidParams);
80 }
81
82 Ok(Params {
83 log_n,
84 r: r as u32,
85 p: p as u32,
86 len,
87 })
88 }
89
90 pub fn recommended() -> Params {
95 Params {
96 log_n: Self::RECOMMENDED_LOG_N,
97 r: Self::RECOMMENDED_R,
98 p: Self::RECOMMENDED_P,
99 len: Self::RECOMMENDED_LEN,
100 }
101 }
102
103 pub fn log_n(&self) -> u8 {
107 self.log_n
108 }
109
110 pub fn r(&self) -> u32 {
115 self.r
116 }
117
118 pub fn p(&self) -> u32 {
120 self.p
121 }
122}
123
124impl Default for Params {
125 fn default() -> Params {
126 Params::recommended()
127 }
128}
129
130#[cfg(feature = "simple")]
131#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
132impl<'a> TryFrom<&'a PasswordHash<'a>> for Params {
133 type Error = password_hash::Error;
134
135 fn try_from(hash: &'a PasswordHash<'a>) -> Result<Self, password_hash::Error> {
136 let mut log_n = Self::RECOMMENDED_LOG_N;
137 let mut r = Self::RECOMMENDED_R;
138 let mut p = Self::RECOMMENDED_P;
139
140 if hash.version.is_some() {
141 return Err(Error::Version);
142 }
143
144 for (ident, value) in hash.params.iter() {
145 match ident.as_str() {
146 "ln" => {
147 log_n = value
148 .decimal()?
149 .try_into()
150 .map_err(|_| InvalidValue::Malformed.param_error())?
151 }
152 "r" => r = value.decimal()?,
153 "p" => p = value.decimal()?,
154 _ => return Err(password_hash::Error::ParamNameInvalid),
155 }
156 }
157
158 let len = hash
159 .hash
160 .map(|out| out.len())
161 .unwrap_or(Self::RECOMMENDED_LEN);
162 Params::new(log_n, r, p, len).map_err(|_| InvalidValue::Malformed.param_error())
163 }
164}
165
166#[cfg(feature = "simple")]
167#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
168impl TryFrom<Params> for ParamsString {
169 type Error = password_hash::Error;
170
171 fn try_from(input: Params) -> Result<ParamsString, password_hash::Error> {
172 let mut output = ParamsString::new();
173 output.add_decimal("ln", input.log_n as u32)?;
174 output.add_decimal("r", input.r)?;
175 output.add_decimal("p", input.p)?;
176 Ok(output)
177 }
178}