use crate::error::Error;
pub const MIN_LANES: u32 = 1;
pub const MAX_LANES: u32 = 0x00FF_FFFF;
pub const MIN_THREADS: u32 = 1;
pub const MAX_THREADS: u32 = 0x00FF_FFFF;
pub const SYNC_POINTS: u32 = 4;
pub const MIN_OUTLEN: u32 = 4;
pub const MAX_OUTLEN: u32 = 0xFFFF_FFFF;
pub const MIN_MEMORY: u32 = 2 * SYNC_POINTS;
pub const MAX_MEMORY_BITS: u32 = {
let ptr_bits = (size_of::<*const u8>() * 8) as u32;
let bits = ptr_bits - 10 - 1;
if bits < 32 { bits } else { 32 }
};
pub const MAX_MEMORY: u32 = {
let candidate: u64 = 1u64 << MAX_MEMORY_BITS;
if candidate < 0xFFFF_FFFF {
candidate as u32
} else {
0xFFFF_FFFF
}
};
pub const MIN_TIME: u32 = 1;
pub const MAX_TIME: u32 = 0xFFFF_FFFF;
pub const MIN_PWD_LENGTH: u32 = 0;
pub const MAX_PWD_LENGTH: u32 = 0xFFFF_FFFF;
pub const MIN_AD_LENGTH: u32 = 0;
pub const MAX_AD_LENGTH: u32 = 0xFFFF_FFFF;
pub const MIN_SALT_LENGTH: u32 = 8;
pub const MAX_SALT_LENGTH: u32 = 0xFFFF_FFFF;
pub const MIN_SECRET: u32 = 0;
pub const MAX_SECRET: u32 = 0xFFFF_FFFF;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Memory(u64);
impl Memory {
#[inline]
#[must_use]
pub const fn kib(kib: u64) -> Memory {
Memory(kib)
}
#[inline]
#[must_use]
pub const fn mib(mib: u64) -> Memory {
Memory(mib.saturating_mul(1024))
}
#[inline]
#[must_use]
pub const fn gib(gib: u64) -> Memory {
Memory(gib.saturating_mul(1024 * 1024))
}
#[inline]
#[must_use]
pub const fn as_kib(self) -> u64 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TagLen(u64);
impl TagLen {
#[inline]
#[must_use]
pub const fn bytes(bytes: u64) -> TagLen {
TagLen(bytes)
}
#[inline]
#[must_use]
pub const fn as_bytes(self) -> u64 {
self.0
}
}
pub const BLOCK_SIZE: usize = 1024;
pub const QWORDS_IN_BLOCK: usize = BLOCK_SIZE / 8;
pub const OWORDS_IN_BLOCK: usize = BLOCK_SIZE / 16;
pub const HWORDS_IN_BLOCK: usize = BLOCK_SIZE / 32;
pub const BITS512_WORDS_IN_BLOCK: usize = BLOCK_SIZE / 64;
pub const ADDRESSES_IN_BLOCK: usize = 128;
pub const PREHASH_DIGEST_LENGTH: usize = 64;
pub const PREHASH_SEED_LENGTH: usize = 72;
#[cfg(test)]
const MAX_DECODED_LANES: u32 = 255;
#[cfg(test)]
const MIN_DECODED_SALT_LEN: u32 = 8;
#[cfg(test)]
const MIN_DECODED_OUT_LEN: u32 = 12;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(u32)]
pub enum Algorithm {
Argon2d = 0,
Argon2i = 1,
#[default]
Argon2id = 2,
}
impl Algorithm {
#[inline]
#[must_use]
pub const fn as_u32(self) -> u32 {
self as u32
}
#[inline]
#[must_use]
pub const fn from_u32(value: u32) -> Option<Algorithm> {
match value {
0 => Some(Algorithm::Argon2d),
1 => Some(Algorithm::Argon2i),
2 => Some(Algorithm::Argon2id),
_ => None,
}
}
#[inline]
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Algorithm::Argon2d => "argon2d",
Algorithm::Argon2i => "argon2i",
Algorithm::Argon2id => "argon2id",
}
}
#[inline]
#[must_use]
pub const fn as_str_uppercase(self) -> &'static str {
match self {
Algorithm::Argon2d => "Argon2d",
Algorithm::Argon2i => "Argon2i",
Algorithm::Argon2id => "Argon2id",
}
}
pub const ALL: [Algorithm; 3] = [Algorithm::Argon2d, Algorithm::Argon2i, Algorithm::Argon2id];
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(u32)]
pub enum Version {
V0x10 = 0x10,
#[default]
V0x13 = 0x13,
}
impl Version {
pub const DEFAULT: Version = Version::V0x13;
pub const ALL: [Version; 2] = [Version::V0x10, Version::V0x13];
#[inline]
#[must_use]
pub const fn as_u32(self) -> u32 {
self as u32
}
#[inline]
#[must_use]
pub const fn from_u32(value: u32) -> Option<Version> {
match value {
0x10 => Some(Version::V0x10),
0x13 => Some(Version::V0x13),
_ => None,
}
}
}
#[allow(clippy::absurd_extreme_comparisons)]
#[allow(clippy::too_many_arguments)]
pub const fn validate_inputs(
out_len: usize,
pwd_len: usize,
salt_len: usize,
secret_len: usize,
ad_len: usize,
m_cost: u32,
t_cost: u32,
lanes: u32,
threads: u32,
) -> Result<(), Error> {
if out_len < MIN_OUTLEN as usize {
return Err(Error::OutputTooShort);
}
if out_len > MAX_OUTLEN as usize {
return Err(Error::OutputTooLong);
}
if pwd_len > MAX_PWD_LENGTH as usize {
return Err(Error::PwdTooLong);
}
if salt_len < MIN_SALT_LENGTH as usize {
return Err(Error::SaltTooShort);
}
if salt_len > MAX_SALT_LENGTH as usize {
return Err(Error::SaltTooLong);
}
if secret_len > MAX_SECRET as usize {
return Err(Error::SecretTooLong);
}
if ad_len > MAX_AD_LENGTH as usize {
return Err(Error::AdTooLong);
}
if m_cost < MIN_MEMORY {
return Err(Error::MemoryTooLittle);
}
if m_cost > MAX_MEMORY {
return Err(Error::MemoryTooMuch);
}
if m_cost < 8u32.wrapping_mul(lanes) {
return Err(Error::MemoryTooLittle);
}
if t_cost < MIN_TIME {
return Err(Error::TimeTooSmall);
}
if t_cost > MAX_TIME {
return Err(Error::TimeTooLarge);
}
if lanes < MIN_LANES {
return Err(Error::LanesTooFew);
}
if lanes > MAX_LANES {
return Err(Error::LanesTooMany);
}
if threads < MIN_THREADS {
return Err(Error::ThreadsTooFew);
}
if threads > MAX_THREADS {
return Err(Error::ThreadsTooMany);
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Params {
m_cost: u32,
t_cost: u32,
lanes: u32,
threads: u32,
output_len: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ParamsBuilder {
memory: Memory,
passes: u32,
lanes: u32,
threads: Option<u32>,
tag_len: TagLen,
}
impl ParamsBuilder {
pub const DEFAULT: ParamsBuilder = ParamsBuilder {
memory: Memory::kib(19456),
passes: 2,
lanes: 1,
threads: None,
tag_len: TagLen::bytes(32),
};
#[inline]
#[must_use]
pub const fn memory(mut self, memory: Memory) -> ParamsBuilder {
self.memory = memory;
self
}
#[inline]
#[must_use]
pub const fn passes(mut self, passes: u32) -> ParamsBuilder {
self.passes = passes;
self
}
#[inline]
#[must_use]
pub const fn lanes(mut self, lanes: u32) -> ParamsBuilder {
self.lanes = lanes;
self
}
#[inline]
#[must_use]
pub const fn threads(mut self, threads: u32) -> ParamsBuilder {
self.threads = Some(threads);
self
}
#[inline]
#[must_use]
pub const fn tag_len(mut self, tag_len: TagLen) -> ParamsBuilder {
self.tag_len = tag_len;
self
}
pub const fn build(self) -> Result<Params, Error> {
let bytes = self.tag_len.as_bytes();
if bytes > MAX_OUTLEN as u64 {
return Err(Error::OutputTooLong);
}
if bytes < MIN_OUTLEN as u64 {
return Err(Error::OutputTooShort);
}
let kib = self.memory.as_kib();
if kib > MAX_MEMORY as u64 {
return Err(Error::MemoryTooMuch);
}
let threads = match self.threads {
Some(threads) => threads,
None => self.lanes,
};
match validate_inputs(
bytes as usize,
0,
MIN_SALT_LENGTH as usize,
0,
0,
kib as u32,
self.passes,
self.lanes,
threads,
) {
Ok(()) => {}
Err(e) => return Err(e),
}
Ok(Params {
m_cost: kib as u32,
t_cost: self.passes,
lanes: self.lanes,
threads,
output_len: bytes as u32,
})
}
#[must_use]
pub const fn build_or_panic(self) -> Params {
match self.build() {
Ok(params) => params,
Err(_) => panic!("invalid Argon2 parameters"),
}
}
}
impl Default for ParamsBuilder {
fn default() -> ParamsBuilder {
ParamsBuilder::DEFAULT
}
}
impl Params {
pub const DEFAULT: Params = ParamsBuilder::DEFAULT.build_or_panic();
pub const OWASP: Params = Params::DEFAULT;
pub const RFC9106_HIGH_MEMORY: Params = ParamsBuilder::DEFAULT
.memory(Memory::gib(2))
.passes(1)
.lanes(4)
.build_or_panic();
pub const RFC9106_LOW_MEMORY: Params = ParamsBuilder::DEFAULT
.memory(Memory::mib(64))
.passes(3)
.lanes(4)
.build_or_panic();
#[inline]
#[must_use]
pub const fn builder() -> ParamsBuilder {
ParamsBuilder::DEFAULT
}
#[inline]
#[must_use]
pub const fn to_builder(self) -> ParamsBuilder {
ParamsBuilder {
memory: Memory::kib(self.m_cost as u64),
passes: self.t_cost,
lanes: self.lanes,
threads: Some(self.threads),
tag_len: TagLen::bytes(self.output_len as u64),
}
}
#[inline]
#[must_use]
pub const fn memory(&self) -> Memory {
Memory::kib(self.m_cost as u64)
}
#[inline]
#[must_use]
pub const fn memory_kib(&self) -> u32 {
self.m_cost
}
#[inline]
#[must_use]
pub const fn passes(&self) -> u32 {
self.t_cost
}
#[inline]
#[must_use]
pub const fn tag_len(&self) -> TagLen {
TagLen::bytes(self.output_len as u64)
}
#[inline]
#[must_use]
pub const fn tag_len_bytes(&self) -> usize {
self.output_len as usize
}
pub const fn validate_for(
&self,
pwd_len: usize,
salt_len: usize,
secret_len: usize,
ad_len: usize,
) -> Result<(), Error> {
validate_inputs(
self.output_len as usize,
pwd_len,
salt_len,
secret_len,
ad_len,
self.m_cost,
self.t_cost,
self.lanes,
self.threads,
)
}
#[inline]
#[must_use]
pub const fn lanes(&self) -> u32 {
self.lanes
}
#[inline]
#[must_use]
pub const fn threads(&self) -> u32 {
self.threads
}
#[inline]
#[must_use]
pub const fn effective_threads(&self) -> u32 {
if self.threads > self.lanes {
self.lanes
} else {
self.threads
}
}
#[inline]
#[must_use]
pub const fn memory_layout(&self) -> (u32, u32, u32) {
let lanes_x_sync = self.lanes * SYNC_POINTS;
let min_blocks = 2 * SYNC_POINTS * self.lanes;
let mut memory_blocks = self.m_cost;
if memory_blocks < min_blocks {
memory_blocks = min_blocks;
}
let segment_length = memory_blocks / lanes_x_sync;
memory_blocks = segment_length * lanes_x_sync;
let lane_length = segment_length * SYNC_POINTS;
(memory_blocks, segment_length, lane_length)
}
#[inline]
#[must_use]
pub const fn memory_blocks(&self) -> u32 {
self.memory_layout().0
}
#[inline]
#[must_use]
pub const fn segment_length(&self) -> u32 {
self.memory_layout().1
}
#[inline]
#[must_use]
pub const fn lane_length(&self) -> u32 {
self.memory_layout().2
}
}
impl Default for Params {
fn default() -> Params {
Params::DEFAULT
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constants_match_the_c_preprocessor() {
assert_eq!(MIN_MEMORY, 8);
assert_eq!(MAX_LANES, 16_777_215);
assert_eq!(MAX_OUTLEN, 4_294_967_295);
if size_of::<*const u8>() == 8 {
assert_eq!(MAX_MEMORY_BITS, 32);
assert_eq!(MAX_MEMORY, 4_294_967_295);
}
assert_eq!(BLOCK_SIZE, 1024);
assert_eq!(QWORDS_IN_BLOCK, 128);
assert_eq!(OWORDS_IN_BLOCK, 64);
assert_eq!(HWORDS_IN_BLOCK, 32);
assert_eq!(BITS512_WORDS_IN_BLOCK, 16);
assert_eq!(PREHASH_SEED_LENGTH - PREHASH_DIGEST_LENGTH, 8);
}
#[test]
fn decoded_mirrors_are_not_decoder_bounds() {
const { assert!(MAX_DECODED_LANES < MAX_LANES) }
const { assert!(MIN_DECODED_OUT_LEN > MIN_OUTLEN) }
const { assert!(MIN_DECODED_SALT_LEN == MIN_SALT_LENGTH) }
}
#[test]
fn decoded_bound_docs_quote_strings_this_crate_still_produces() {
use crate::Argon2;
let params = Params::builder()
.memory(Memory::kib(2400))
.passes(1)
.lanes(300)
.tag_len(TagLen::bytes(32))
.build()
.unwrap();
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
let encoded = argon2.hash_encoded(b"password", b"somesalt").unwrap();
assert_eq!(
encoded,
"$argon2id$v=19$m=2400,t=1,p=300$c29tZXNhbHQ$tPLI8hre65Crk/uP5eIGCZzn3TQ7RzRoXIkGzt5jQoI"
);
assert_eq!(
Argon2::verify_encoded(&encoded, b"password", Algorithm::Argon2id),
Ok(())
);
let params = Params::builder()
.memory(Memory::kib(2400))
.passes(1)
.lanes(1)
.tag_len(TagLen::bytes(8))
.build()
.unwrap();
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
let encoded = argon2.hash_encoded(b"password", b"somesalt").unwrap();
assert_eq!(
encoded,
"$argon2id$v=19$m=2400,t=1,p=1$c29tZXNhbHQ$kQGQLZpZJIk"
);
assert_eq!(
Argon2::verify_encoded(&encoded, b"password", Algorithm::Argon2id),
Ok(())
);
}
#[test]
fn default_params_are_valid() {
let d = Params::default();
assert!(d.validate_for(0, 8, 0, 0).is_ok());
}
#[test]
fn validate_order_salt_before_m_cost() {
assert_eq!(
validate_inputs(32, 0, 0, 0, 0, 0, 1, 1, 1),
Err(Error::SaltTooShort)
);
}
#[test]
fn validate_order_out_len_first() {
assert_eq!(
validate_inputs(0, 0, 0, 0, 0, 0, 0, 0, 0),
Err(Error::OutputTooShort)
);
}
#[test]
fn m_cost_lanes_product_wraps_like_c() {
assert_eq!(
validate_inputs(32, 0, 8, 0, 0, 1 << 16, 1, 0xFFFF_FFFF, 1),
Err(Error::MemoryTooLittle)
);
assert_eq!(
validate_inputs(32, 0, 8, 0, 0, 16, 1, 4, 4),
Err(Error::MemoryTooLittle)
);
assert_eq!(validate_inputs(32, 0, 8, 0, 0, 32, 1, 4, 4), Ok(()));
}
#[test]
fn lanes_zero_is_lanes_too_few() {
assert_eq!(
validate_inputs(32, 0, 8, 0, 0, 8, 1, 0, 1),
Err(Error::LanesTooFew)
);
}
#[test]
fn memory_layout_matches_argon2_ctx() {
let p = Params::builder()
.memory(Memory::kib(8))
.passes(1)
.lanes(1)
.tag_len(TagLen::bytes(32))
.build()
.unwrap();
assert_eq!(p.memory_layout(), (8, 2, 8));
let p = Params::builder()
.memory(Memory::kib(1 << 16))
.passes(2)
.lanes(1)
.tag_len(TagLen::bytes(32))
.build()
.unwrap();
assert_eq!(p.memory_layout(), (65536, 16384, 65536));
let p = Params::builder()
.memory(Memory::kib(1 << 16))
.passes(2)
.lanes(4)
.tag_len(TagLen::bytes(32))
.build()
.unwrap();
assert_eq!(p.memory_layout(), (65536, 4096, 16384));
let p = Params::builder()
.memory(Memory::kib(100))
.passes(1)
.lanes(3)
.tag_len(TagLen::bytes(32))
.build()
.unwrap();
let (blocks, seg, lane) = p.memory_layout();
assert_eq!(seg, 100 / 12);
assert_eq!(blocks, seg * 12);
assert_eq!(lane, seg * 4);
}
#[test]
fn effective_threads_is_min() {
let p = Params::builder()
.memory(Memory::kib(1 << 16))
.passes(1)
.lanes(2)
.threads(8)
.tag_len(TagLen::bytes(32))
.build()
.unwrap();
assert_eq!(p.threads(), 8);
assert_eq!(p.effective_threads(), 2);
}
#[test]
fn memory_units_convert() {
assert_eq!(Memory::kib(65536), Memory::mib(64));
assert_eq!(Memory::mib(1024), Memory::gib(1));
assert_eq!(Memory::gib(2).as_kib(), 2 * 1024 * 1024);
assert_eq!(Memory::kib(19456).as_kib(), 19456);
}
#[test]
fn memory_saturates_instead_of_overflowing() {
assert_eq!(Memory::mib(u64::MAX).as_kib(), u64::MAX);
assert_eq!(Memory::gib(u64::MAX).as_kib(), u64::MAX);
assert_eq!(Memory::gib(u64::MAX / 1024).as_kib(), u64::MAX);
}
#[test]
fn memory_orders_by_size() {
assert!(Memory::mib(64) > Memory::kib(19456));
assert!(Memory::gib(1) > Memory::mib(64));
}
#[test]
fn tag_len_carries_bytes() {
assert_eq!(TagLen::bytes(32).as_bytes(), 32);
assert!(TagLen::bytes(64) > TagLen::bytes(32));
}
#[test]
fn units_are_const() {
const M: Memory = Memory::mib(64);
const T: TagLen = TagLen::bytes(32);
assert_eq!(M.as_kib(), 65536);
assert_eq!(T.as_bytes(), 32);
}
const CONST_BUILT: Params = Params::builder()
.memory(Memory::mib(64))
.passes(3)
.lanes(4)
.build_or_panic();
#[test]
fn builder_builds_in_a_const_item() {
assert_eq!(CONST_BUILT.memory_kib(), 65536);
assert_eq!(CONST_BUILT.passes(), 3);
assert_eq!(CONST_BUILT.lanes(), 4);
assert_eq!(CONST_BUILT.threads(), 4);
assert_eq!(CONST_BUILT.tag_len_bytes(), 32);
}
#[test]
fn threads_defaults_to_lanes_but_an_explicit_value_survives() {
let implicit = Params::builder().lanes(4).build().unwrap();
assert_eq!((implicit.lanes(), implicit.threads()), (4, 4));
let explicit = Params::builder().threads(2).lanes(4).build().unwrap();
assert_eq!((explicit.lanes(), explicit.threads()), (4, 2));
assert_eq!(explicit.effective_threads(), 2);
}
#[test]
fn typed_and_raw_accessors_agree() {
let p = Params::builder()
.memory(Memory::mib(64))
.tag_len(TagLen::bytes(64))
.build()
.unwrap();
assert_eq!(p.memory(), Memory::mib(64));
assert_eq!(p.memory().as_kib(), u64::from(p.memory_kib()));
assert_eq!(p.tag_len(), TagLen::bytes(64));
assert_eq!(p.tag_len().as_bytes() as usize, p.tag_len_bytes());
}
#[test]
fn presets_hold_their_documented_numbers() {
assert_eq!(Params::OWASP, Params::DEFAULT);
assert_eq!(Params::DEFAULT, Params::default());
assert_eq!(Params::OWASP.memory_kib(), 19456);
assert_eq!((Params::OWASP.passes(), Params::OWASP.lanes()), (2, 1));
assert_eq!(Params::OWASP.tag_len_bytes(), 32);
assert_eq!(Params::RFC9106_HIGH_MEMORY.memory(), Memory::gib(2));
assert_eq!(
(
Params::RFC9106_HIGH_MEMORY.passes(),
Params::RFC9106_HIGH_MEMORY.lanes()
),
(1, 4)
);
assert_eq!(Params::RFC9106_LOW_MEMORY.memory(), Memory::mib(64));
assert_eq!(
(
Params::RFC9106_LOW_MEMORY.passes(),
Params::RFC9106_LOW_MEMORY.lanes()
),
(3, 4)
);
}
#[test]
fn to_builder_round_trips_every_preset() {
for preset in [
Params::OWASP,
Params::RFC9106_HIGH_MEMORY,
Params::RFC9106_LOW_MEMORY,
] {
assert_eq!(preset.to_builder().build(), Ok(preset));
}
}
#[test]
fn a_preset_can_be_adjusted() {
let narrow = Params::RFC9106_LOW_MEMORY
.to_builder()
.lanes(1)
.build()
.unwrap();
assert_eq!(narrow.memory(), Memory::mib(64));
assert_eq!(narrow.passes(), 3);
assert_eq!(narrow.lanes(), 1);
}
#[test]
fn build_rejects_every_out_of_range_value() {
let b = Params::builder();
assert_eq!(
b.memory(Memory::gib(9999)).build(),
Err(Error::MemoryTooMuch)
);
assert_eq!(
b.memory(Memory::kib(4)).build(),
Err(Error::MemoryTooLittle)
);
assert_eq!(
b.tag_len(TagLen::bytes(3)).build(),
Err(Error::OutputTooShort)
);
assert_eq!(
b.tag_len(TagLen::bytes(1 << 40)).build(),
Err(Error::OutputTooLong)
);
assert_eq!(b.passes(0).build(), Err(Error::TimeTooSmall));
assert_eq!(b.lanes(0).build(), Err(Error::LanesTooFew));
assert_eq!(b.threads(0).build(), Err(Error::ThreadsTooFew));
}
#[test]
fn tag_len_is_checked_before_memory_like_the_c() {
let too_long = Params::builder()
.memory(Memory::gib(9999))
.tag_len(TagLen::bytes(1 << 40))
.build();
assert_eq!(too_long, Err(Error::OutputTooLong));
let too_short = Params::builder()
.memory(Memory::kib(MAX_MEMORY as u64 + 1))
.tag_len(TagLen::bytes(3))
.build();
assert_eq!(too_short, Err(Error::OutputTooShort));
}
#[test]
fn a_saturated_memory_is_rejected() {
assert_eq!(
Params::builder().memory(Memory::gib(u64::MAX)).build(),
Err(Error::MemoryTooMuch)
);
}
#[test]
fn memory_is_range_checked_before_it_is_narrowed() {
assert_eq!(
Params::builder().memory(Memory::kib(1u64 << 33)).build(),
Err(Error::MemoryTooMuch)
);
assert_eq!((1u64 << 33) as u32, 0);
assert!(0 < MIN_MEMORY);
}
#[test]
fn algorithm_and_version_round_trip() {
for a in Algorithm::ALL {
assert_eq!(Algorithm::from_u32(a.as_u32()), Some(a));
}
assert_eq!(Algorithm::from_u32(3), None);
assert_eq!(Algorithm::Argon2id.as_str(), "argon2id");
assert_eq!(Algorithm::Argon2i.as_str_uppercase(), "Argon2i");
for v in Version::ALL {
assert_eq!(Version::from_u32(v.as_u32()), Some(v));
}
assert_eq!(Version::from_u32(0x11), None);
assert_eq!(Version::default(), Version::V0x13);
assert_eq!(Algorithm::default(), Algorithm::Argon2id);
}
}