1use crate::errors::InvalidParams;
2
3#[cfg(feature = "phc")]
4use {
5 core::{
6 fmt::{self, Display},
7 str::FromStr,
8 },
9 password_hash::{
10 Error,
11 phc::{Decimal, Output, ParamsString, PasswordHash},
12 },
13};
14
15#[cfg(all(feature = "phc", doc))]
16use password_hash::PasswordHasher;
17
18#[derive(Clone, Copy, Debug, PartialEq)]
20pub struct Params {
21 pub(crate) log_n: u8,
22 pub(crate) r: u32,
23 pub(crate) p: u32,
24 #[cfg(feature = "password-hash")]
25 pub(crate) len: Option<usize>,
26}
27
28impl Params {
29 pub const RECOMMENDED_LOG_N: u8 = 17;
31
32 pub const RECOMMENDED_R: u32 = 8;
34
35 pub const RECOMMENDED_P: u32 = 1;
37
38 pub const RECOMMENDED_LEN: usize = 32;
40
41 pub const RECOMMENDED: Self = Self {
48 log_n: Self::RECOMMENDED_LOG_N,
49 r: Self::RECOMMENDED_R,
50 p: Self::RECOMMENDED_P,
51 #[cfg(feature = "password-hash")]
52 len: None,
53 };
54
55 pub fn new(log_n: u8, r: u32, p: u32) -> Result<Params, InvalidParams> {
67 let cond1 = (log_n as usize) < usize::BITS as usize;
68 let cond2 = size_of::<usize>() >= size_of::<u32>();
69 let cond3 = r <= usize::MAX as u32 && p < usize::MAX as u32;
70 if !(r > 0 && p > 0 && cond1 && (cond2 || cond3)) {
71 return Err(InvalidParams);
72 }
73
74 let r = r as usize;
75 let p = p as usize;
76
77 let n: usize = 1 << log_n;
78
79 let r128 = r.checked_mul(128).ok_or(InvalidParams)?;
81
82 r128.checked_mul(n).ok_or(InvalidParams)?;
84
85 r128.checked_mul(p).ok_or(InvalidParams)?;
87
88 if (log_n as usize) >= r * 16 {
92 return Err(InvalidParams);
93 }
94
95 if r * p >= 0x4000_0000 {
100 return Err(InvalidParams);
101 }
102
103 Ok(Params {
104 log_n,
105 r: r as u32,
106 p: p as u32,
107 #[cfg(feature = "password-hash")]
108 len: None,
109 })
110 }
111
112 #[cfg(feature = "phc")]
122 pub fn new_with_output_len(
123 log_n: u8,
124 r: u32,
125 p: u32,
126 len: usize,
127 ) -> Result<Params, InvalidParams> {
128 if !(Output::MIN_LENGTH..=Output::MAX_LENGTH).contains(&len) {
129 return Err(InvalidParams);
130 }
131
132 let mut ret = Self::new(log_n, r, p)?;
133 ret.len = Some(len);
134 Ok(ret)
135 }
136
137 #[deprecated(since = "0.12.0", note = "use Params::RECOMMENDED instead")]
139 pub const fn recommended() -> Params {
140 Self::RECOMMENDED
141 }
142
143 pub const fn log_n(&self) -> u8 {
148 self.log_n
149 }
150
151 pub const fn n(&self) -> u64 {
156 1 << self.log_n
157 }
158
159 pub const fn r(&self) -> u32 {
164 self.r
165 }
166
167 pub const fn p(&self) -> u32 {
169 self.p
170 }
171}
172
173impl Default for Params {
174 fn default() -> Params {
175 Params::RECOMMENDED
176 }
177}
178
179#[cfg(feature = "phc")]
180impl Display for Params {
181 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182 ParamsString::try_from(self).map_err(|_| fmt::Error)?.fmt(f)
183 }
184}
185
186#[cfg(feature = "phc")]
187impl FromStr for Params {
188 type Err = Error;
189
190 fn from_str(s: &str) -> password_hash::Result<Self> {
191 let params_string = ParamsString::from_str(s).map_err(|_| Error::ParamsInvalid)?;
192 Self::try_from(¶ms_string)
193 }
194}
195
196#[cfg(feature = "phc")]
197impl TryFrom<&ParamsString> for Params {
198 type Error = Error;
199
200 fn try_from(params: &ParamsString) -> password_hash::Result<Self> {
201 let mut log_n = Self::RECOMMENDED_LOG_N;
202 let mut r = Self::RECOMMENDED_R;
203 let mut p = Self::RECOMMENDED_P;
204
205 for (ident, value) in params.iter() {
206 match ident.as_str() {
207 "ln" => {
208 log_n = value
209 .decimal()
210 .ok()
211 .and_then(|dec| dec.try_into().ok())
212 .ok_or(Error::ParamInvalid { name: "ln" })?;
213 }
214 "r" => {
215 r = value
216 .decimal()
217 .map_err(|_| Error::ParamInvalid { name: "r" })?;
218 }
219 "p" => {
220 p = value
221 .decimal()
222 .map_err(|_| Error::ParamInvalid { name: "p" })?;
223 }
224 _ => return Err(Error::ParamsInvalid),
225 }
226 }
227
228 Params::new(log_n, r, p).map_err(|_| Error::ParamsInvalid)
229 }
230}
231
232#[cfg(feature = "phc")]
233impl TryFrom<&PasswordHash> for Params {
234 type Error = Error;
235
236 fn try_from(hash: &PasswordHash) -> password_hash::Result<Self> {
237 if hash.version.is_some() {
238 return Err(Error::Version);
239 }
240
241 let mut params = Params::try_from(&hash.params)?;
242
243 params.len = Some(
244 hash.hash
245 .map(|out| out.len())
246 .unwrap_or(Self::RECOMMENDED_LEN),
247 );
248
249 Ok(params)
250 }
251}
252
253#[cfg(feature = "phc")]
254impl TryFrom<Params> for ParamsString {
255 type Error = Error;
256
257 fn try_from(params: Params) -> Result<ParamsString, Error> {
258 Self::try_from(¶ms)
259 }
260}
261
262#[cfg(feature = "phc")]
263impl TryFrom<&Params> for ParamsString {
264 type Error = Error;
265
266 fn try_from(input: &Params) -> Result<ParamsString, Error> {
267 let mut output = ParamsString::new();
268
269 for (name, value) in [
270 ("ln", input.log_n as Decimal),
271 ("r", input.r),
272 ("p", input.p),
273 ] {
274 output
275 .add_decimal(name, value)
276 .map_err(|_| Error::ParamInvalid { name })?;
277 }
278
279 Ok(output)
280 }
281}