use super::Digest;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InvalidOutputLen {
pub expected: usize,
pub got: usize,
}
impl core::fmt::Display for InvalidOutputLen {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"digest output buffer must be exactly {} bytes, got {}",
self.expected, self.got
)
}
}
impl core::error::Error for InvalidOutputLen {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UnknownHashAlgorithm;
impl core::fmt::Display for UnknownHashAlgorithm {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("unknown hash algorithm name")
}
}
impl core::error::Error for UnknownHashAlgorithm {}
#[derive(Clone, Copy)]
pub struct HashOutput {
bytes: [u8; HashAlgorithm::MAX_OUTPUT_LEN],
len: u8,
}
#[allow(clippy::len_without_is_empty)] impl HashOutput {
#[inline]
pub fn as_slice(&self) -> &[u8] {
&self.bytes[..self.len as usize]
}
#[inline]
pub fn len(&self) -> usize {
self.len as usize
}
}
impl AsRef<[u8]> for HashOutput {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_slice()
}
}
impl core::ops::Deref for HashOutput {
type Target = [u8];
#[inline]
fn deref(&self) -> &[u8] {
self.as_slice()
}
}
impl PartialEq for HashOutput {
fn eq(&self, other: &Self) -> bool {
use crate::ct::ConstantTimeEq;
bool::from(self.as_slice().ct_eq(other.as_slice()))
}
}
impl Eq for HashOutput {}
impl core::fmt::Debug for HashOutput {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
for b in self.as_slice() {
write!(f, "{b:02x}")?;
}
Ok(())
}
}
impl core::fmt::Display for HashOutput {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Debug::fmt(self, f)
}
}
pub trait DynDigest {
fn output_len(&self) -> usize;
fn block_len(&self) -> usize;
fn algorithm(&self) -> Option<HashAlgorithm> {
None
}
fn update(&mut self, data: &[u8]);
fn finalize_into(&mut self, out: &mut [u8]) -> Result<(), InvalidOutputLen>;
fn reset(&mut self);
fn zeroize(&mut self);
}
macro_rules! impl_dyn_digest {
($ty:ty $(, $alg:expr)?) => {
impl $crate::hash::DynDigest for $ty {
#[inline]
fn output_len(&self) -> usize {
<$ty as $crate::hash::Digest>::OUTPUT_LEN
}
#[inline]
fn block_len(&self) -> usize {
<$ty as $crate::hash::Digest>::BLOCK_LEN
}
$(
#[inline]
fn algorithm(&self) -> Option<$crate::hash::HashAlgorithm> {
Some($alg)
}
)?
#[inline]
fn update(&mut self, data: &[u8]) {
<$ty as $crate::hash::Digest>::update(self, data)
}
fn finalize_into(
&mut self,
out: &mut [u8],
) -> Result<(), $crate::hash::InvalidOutputLen> {
let expected = <$ty as $crate::hash::Digest>::OUTPUT_LEN;
if out.len() != expected {
return Err($crate::hash::InvalidOutputLen {
expected,
got: out.len(),
});
}
let fresh = <$ty as $crate::hash::Digest>::new();
let digest =
<$ty as $crate::hash::Digest>::finalize(core::mem::replace(self, fresh));
out.copy_from_slice(digest.as_ref());
Ok(())
}
#[inline]
fn reset(&mut self) {
let mut old =
core::mem::replace(self, <$ty as $crate::hash::Digest>::new());
<$ty as $crate::hash::Digest>::zeroize(&mut old);
}
#[inline]
fn zeroize(&mut self) {
<$ty as $crate::hash::Digest>::zeroize(self)
}
}
};
}
pub(crate) use impl_dyn_digest;
macro_rules! hash_algorithms {
($(
$(#[$attr:meta])*
$variant:ident => $ty:ty, $name:literal $(| $alias:literal)*, legacy: $legacy:literal;
)*) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum HashAlgorithm {
$( $(#[$attr])* $variant, )*
}
impl HashAlgorithm {
pub const ALL: &'static [HashAlgorithm] = &[$(HashAlgorithm::$variant,)*];
pub const fn output_len(self) -> usize {
match self {
$(HashAlgorithm::$variant => <$ty as Digest>::OUTPUT_LEN,)*
}
}
pub const fn block_len(self) -> usize {
match self {
$(HashAlgorithm::$variant => <$ty as Digest>::BLOCK_LEN,)*
}
}
pub const fn name(self) -> &'static str {
match self {
$(HashAlgorithm::$variant => $name,)*
}
}
pub const fn is_legacy(self) -> bool {
match self {
$(HashAlgorithm::$variant => $legacy,)*
}
}
pub fn from_name(name: &str) -> Option<Self> {
$(
if name.eq_ignore_ascii_case($name)
$(|| name.eq_ignore_ascii_case($alias))*
{
return Some(HashAlgorithm::$variant);
}
)*
None
}
pub fn hasher(self) -> Hasher {
Hasher(match self {
$(HashAlgorithm::$variant => HasherState::$variant(<$ty as Digest>::new()),)*
})
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone)]
enum HasherState {
$( $variant($ty), )*
}
impl Hasher {
pub fn algorithm(&self) -> HashAlgorithm {
match &self.0 {
$(HasherState::$variant(_) => HashAlgorithm::$variant,)*
}
}
fn update_bytes(&mut self, data: &[u8]) {
match &mut self.0 {
$(HasherState::$variant(h) => Digest::update(h, data),)*
}
}
pub fn zeroize(&mut self) {
match &mut self.0 {
$(HasherState::$variant(h) => Digest::zeroize(h),)*
}
}
fn finalize_reset_into(&mut self, out: &mut [u8]) {
match &mut self.0 {
$(HasherState::$variant(h) => {
let fresh = <$ty as Digest>::new();
let digest = Digest::finalize(core::mem::replace(h, fresh));
out.copy_from_slice(digest.as_ref());
})*
}
}
}
$( impl_dyn_digest!($ty, HashAlgorithm::$variant); )*
};
}
hash_algorithms! {
Sha224 => crate::hash::Sha224, "sha224" | "sha-224" | "sha2-224", legacy: false;
Sha256 => crate::hash::Sha256, "sha256" | "sha-256" | "sha2-256", legacy: false;
Sha384 => crate::hash::Sha384, "sha384" | "sha-384" | "sha2-384", legacy: false;
Sha512 => crate::hash::Sha512, "sha512" | "sha-512" | "sha2-512", legacy: false;
Sha512_224 => crate::hash::Sha512_224, "sha512-224" | "sha512/224" | "sha-512/224", legacy: false;
Sha512_256 => crate::hash::Sha512_256, "sha512-256" | "sha512/256" | "sha-512/256", legacy: false;
Sha3_224 => crate::hash::Sha3_224, "sha3-224" | "sha3_224", legacy: false;
Sha3_256 => crate::hash::Sha3_256, "sha3-256" | "sha3_256", legacy: false;
Sha3_384 => crate::hash::Sha3_384, "sha3-384" | "sha3_384", legacy: false;
Sha3_512 => crate::hash::Sha3_512, "sha3-512" | "sha3_512", legacy: false;
Keccak256 => crate::hash::Keccak256, "keccak256" | "keccak-256", legacy: false;
Blake2b256 => crate::hash::Blake2b256, "blake2b256" | "blake2b-256", legacy: false;
Blake2b384 => crate::hash::Blake2b384, "blake2b384" | "blake2b-384", legacy: false;
Blake2b512 => crate::hash::Blake2b512, "blake2b512" | "blake2b-512" | "blake2b", legacy: false;
Blake2s256 => crate::hash::Blake2s256, "blake2s256" | "blake2s-256" | "blake2s", legacy: false;
Blake3 => crate::hash::Blake3, "blake3", legacy: false;
Sm3 => crate::hash::Sm3, "sm3", legacy: false;
Streebog256 => crate::hash::Streebog256, "streebog256" | "streebog-256", legacy: false;
Streebog512 => crate::hash::Streebog512, "streebog512" | "streebog-512", legacy: false;
Whirlpool => crate::hash::Whirlpool, "whirlpool", legacy: false;
Ripemd160 => crate::hash::Ripemd160, "ripemd160" | "ripemd-160", legacy: true;
Sha1 => crate::hash::Sha1, "sha1" | "sha-1", legacy: true;
Md5 => crate::hash::Md5, "md5", legacy: true;
Md4 => crate::hash::Md4, "md4", legacy: true;
Md2 => crate::hash::Md2, "md2", legacy: true;
}
#[macro_export]
macro_rules! dispatch_digest {
($alg:expr, |$d:ident| $body:block, _ => $fallback:expr $(,)?) => {
match $alg {
$crate::hash::HashAlgorithm::Sha224 => {
type $d = $crate::hash::Sha224;
$body
}
$crate::hash::HashAlgorithm::Sha256 => {
type $d = $crate::hash::Sha256;
$body
}
$crate::hash::HashAlgorithm::Sha384 => {
type $d = $crate::hash::Sha384;
$body
}
$crate::hash::HashAlgorithm::Sha512 => {
type $d = $crate::hash::Sha512;
$body
}
$crate::hash::HashAlgorithm::Sha512_224 => {
type $d = $crate::hash::Sha512_224;
$body
}
$crate::hash::HashAlgorithm::Sha512_256 => {
type $d = $crate::hash::Sha512_256;
$body
}
$crate::hash::HashAlgorithm::Sha3_224 => {
type $d = $crate::hash::Sha3_224;
$body
}
$crate::hash::HashAlgorithm::Sha3_256 => {
type $d = $crate::hash::Sha3_256;
$body
}
$crate::hash::HashAlgorithm::Sha3_384 => {
type $d = $crate::hash::Sha3_384;
$body
}
$crate::hash::HashAlgorithm::Sha3_512 => {
type $d = $crate::hash::Sha3_512;
$body
}
$crate::hash::HashAlgorithm::Keccak256 => {
type $d = $crate::hash::Keccak256;
$body
}
$crate::hash::HashAlgorithm::Blake2b256 => {
type $d = $crate::hash::Blake2b256;
$body
}
$crate::hash::HashAlgorithm::Blake2b384 => {
type $d = $crate::hash::Blake2b384;
$body
}
$crate::hash::HashAlgorithm::Blake2b512 => {
type $d = $crate::hash::Blake2b512;
$body
}
$crate::hash::HashAlgorithm::Blake2s256 => {
type $d = $crate::hash::Blake2s256;
$body
}
$crate::hash::HashAlgorithm::Blake3 => {
type $d = $crate::hash::Blake3;
$body
}
$crate::hash::HashAlgorithm::Sm3 => {
type $d = $crate::hash::Sm3;
$body
}
$crate::hash::HashAlgorithm::Streebog256 => {
type $d = $crate::hash::Streebog256;
$body
}
$crate::hash::HashAlgorithm::Streebog512 => {
type $d = $crate::hash::Streebog512;
$body
}
$crate::hash::HashAlgorithm::Whirlpool => {
type $d = $crate::hash::Whirlpool;
$body
}
$crate::hash::HashAlgorithm::Ripemd160 => {
type $d = $crate::hash::Ripemd160;
$body
}
$crate::hash::HashAlgorithm::Sha1 => {
type $d = $crate::hash::Sha1;
$body
}
$crate::hash::HashAlgorithm::Md5 => {
type $d = $crate::hash::Md5;
$body
}
$crate::hash::HashAlgorithm::Md4 => {
type $d = $crate::hash::Md4;
$body
}
$crate::hash::HashAlgorithm::Md2 => {
type $d = $crate::hash::Md2;
$body
}
#[allow(unreachable_patterns)]
_ => $fallback,
}
};
}
impl HashAlgorithm {
pub const MAX_OUTPUT_LEN: usize = 64;
pub const MAX_BLOCK_LEN: usize = 144;
pub fn digest(self, data: impl AsRef<[u8]>) -> HashOutput {
let mut h = self.hasher();
h.update(data);
h.finalize()
}
pub fn digest_into(
self,
data: impl AsRef<[u8]>,
out: &mut [u8],
) -> Result<(), InvalidOutputLen> {
let mut h = self.hasher();
h.update(data);
DynDigest::finalize_into(&mut h, out)
}
pub fn digest_parts<'a>(self, parts: impl IntoIterator<Item = &'a [u8]>) -> HashOutput {
let mut h = self.hasher();
for part in parts {
h.update(part);
}
h.finalize()
}
}
impl core::fmt::Display for HashAlgorithm {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.name())
}
}
impl core::str::FromStr for HashAlgorithm {
type Err = UnknownHashAlgorithm;
fn from_str(s: &str) -> Result<Self, Self::Err> {
HashAlgorithm::from_name(s).ok_or(UnknownHashAlgorithm)
}
}
pub struct Hasher(HasherState);
impl Hasher {
pub fn new(alg: HashAlgorithm) -> Self {
alg.hasher()
}
pub fn output_len(&self) -> usize {
self.algorithm().output_len()
}
pub fn block_len(&self) -> usize {
self.algorithm().block_len()
}
#[inline]
pub fn update(&mut self, data: impl AsRef<[u8]>) {
self.update_bytes(data.as_ref());
}
#[must_use]
pub fn chain(mut self, data: impl AsRef<[u8]>) -> Self {
self.update_bytes(data.as_ref());
self
}
pub fn finalize(mut self) -> HashOutput {
self.finalize_reset()
}
pub fn finalize_reset(&mut self) -> HashOutput {
let len = self.output_len();
let mut out = HashOutput {
bytes: [0u8; HashAlgorithm::MAX_OUTPUT_LEN],
len: len as u8,
};
self.finalize_reset_into(&mut out.bytes[..len]);
out
}
pub fn reset(&mut self) {
self.zeroize();
*self = Hasher::new(self.algorithm());
}
}
impl Clone for Hasher {
fn clone(&self) -> Self {
Hasher(self.0.clone())
}
}
impl core::fmt::Debug for Hasher {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("Hasher").field(&self.algorithm()).finish()
}
}
impl Drop for Hasher {
fn drop(&mut self) {
self.zeroize();
}
}
impl core::fmt::Write for Hasher {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
self.update_bytes(s.as_bytes());
Ok(())
}
}
#[cfg(feature = "std")]
impl std::io::Write for Hasher {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.update_bytes(buf);
Ok(buf.len())
}
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
self.update_bytes(buf);
Ok(())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl DynDigest for Hasher {
fn output_len(&self) -> usize {
Hasher::output_len(self)
}
fn block_len(&self) -> usize {
Hasher::block_len(self)
}
fn algorithm(&self) -> Option<HashAlgorithm> {
Some(Hasher::algorithm(self))
}
fn update(&mut self, data: &[u8]) {
self.update_bytes(data)
}
fn finalize_into(&mut self, out: &mut [u8]) -> Result<(), InvalidOutputLen> {
let expected = self.output_len();
if out.len() != expected {
return Err(InvalidOutputLen {
expected,
got: out.len(),
});
}
self.finalize_reset_into(out);
Ok(())
}
fn reset(&mut self) {
Hasher::reset(self)
}
fn zeroize(&mut self) {
Hasher::zeroize(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hash::{Sha256, Sha512};
#[test]
fn metadata_matches_the_concrete_hashers() {
for &alg in HashAlgorithm::ALL {
let h = alg.hasher();
assert_eq!(h.algorithm(), alg);
assert_eq!(h.output_len(), alg.output_len(), "{alg}");
assert_eq!(h.block_len(), alg.block_len(), "{alg}");
assert!(alg.output_len() > 0 && alg.output_len() <= HashAlgorithm::MAX_OUTPUT_LEN);
assert!(alg.block_len() > 0 && alg.block_len() <= HashAlgorithm::MAX_BLOCK_LEN);
assert_eq!(alg.digest(b"abc").len(), alg.output_len(), "{alg}");
}
assert_eq!(
HashAlgorithm::ALL
.iter()
.map(|a| a.output_len())
.max()
.unwrap(),
HashAlgorithm::MAX_OUTPUT_LEN
);
assert_eq!(
HashAlgorithm::ALL
.iter()
.map(|a| a.block_len())
.max()
.unwrap(),
HashAlgorithm::MAX_BLOCK_LEN
);
}
#[test]
fn names_round_trip_and_are_unique() {
let mut upper = [0u8; 32];
for (i, &alg) in HashAlgorithm::ALL.iter().enumerate() {
assert_eq!(HashAlgorithm::from_name(alg.name()), Some(alg));
assert_eq!(alg.name().parse::<HashAlgorithm>(), Ok(alg));
let n = alg.name().len();
upper[..n].copy_from_slice(alg.name().as_bytes());
upper[..n].make_ascii_uppercase();
let upper = core::str::from_utf8(&upper[..n]).unwrap();
assert_eq!(HashAlgorithm::from_name(upper), Some(alg));
for &other in &HashAlgorithm::ALL[i + 1..] {
assert_ne!(alg.name(), other.name());
}
}
assert_eq!(
HashAlgorithm::from_name("SHA-256"),
Some(HashAlgorithm::Sha256)
);
assert_eq!(
HashAlgorithm::from_name("sha512/256"),
Some(HashAlgorithm::Sha512_256)
);
assert_eq!(HashAlgorithm::from_name("nope"), None);
assert_eq!("nope".parse::<HashAlgorithm>(), Err(UnknownHashAlgorithm));
}
#[test]
fn runtime_digests_match_the_static_ones() {
let msg = b"purecrypto runtime hash dispatch";
assert_eq!(
HashAlgorithm::Sha256.digest(msg).as_slice(),
&crate::hash::sha256(msg)[..]
);
assert_eq!(
HashAlgorithm::Sha512.digest(msg).as_slice(),
&crate::hash::sha512(msg)[..]
);
assert_eq!(
HashAlgorithm::Sha3_256.digest(msg).as_slice(),
&crate::hash::sha3_256(msg)[..]
);
assert_eq!(
HashAlgorithm::Blake2b512.digest(msg).as_slice(),
&crate::hash::blake2b512(msg)[..]
);
assert_eq!(
HashAlgorithm::Blake3.digest(msg).as_slice(),
&crate::hash::blake3(msg)[..]
);
assert_eq!(
HashAlgorithm::Sha1.digest(msg).as_slice(),
&crate::hash::sha1(msg)[..]
);
assert_eq!(
HashAlgorithm::Whirlpool.digest(msg).as_slice(),
&crate::hash::whirlpool(msg)[..]
);
}
#[test]
fn chunked_updates_match_one_shot() {
let msg: [u8; 300] = core::array::from_fn(|i| i as u8);
for &alg in HashAlgorithm::ALL {
let mut h = alg.hasher();
for chunk in msg.chunks(7) {
h.update(chunk);
}
assert_eq!(h.finalize(), alg.digest(msg), "{alg}");
}
}
#[test]
fn finalize_resets_the_state() {
let mut h = HashAlgorithm::Sha256.hasher();
h.update(b"first");
let first = h.finalize_reset();
assert_eq!(first, HashAlgorithm::Sha256.digest(b"first"));
h.update(b"second");
assert_eq!(h.finalize(), HashAlgorithm::Sha256.digest(b"second"));
}
#[test]
fn reset_discards_absorbed_data() {
let mut h = HashAlgorithm::Sha3_512.hasher();
h.update(b"discard me");
h.reset();
h.update(b"kept");
assert_eq!(h.finalize(), HashAlgorithm::Sha3_512.digest(b"kept"));
}
#[test]
fn dyn_dispatch_over_concrete_and_runtime_hashers() {
fn hash_through_dyn(h: &mut dyn DynDigest, data: &[u8]) -> HashOutput {
h.update(data);
let mut out = HashOutput {
bytes: [0u8; HashAlgorithm::MAX_OUTPUT_LEN],
len: h.output_len() as u8,
};
let n = h.output_len();
h.finalize_into(&mut out.bytes[..n]).unwrap();
out
}
let msg = b"one interface, two hashers";
let expected = HashAlgorithm::Sha256.digest(msg);
let mut concrete = <Sha256 as Digest>::new();
assert_eq!(hash_through_dyn(&mut concrete, msg), expected);
assert_eq!(DynDigest::algorithm(&concrete), Some(HashAlgorithm::Sha256));
let mut runtime = HashAlgorithm::Sha256.hasher();
assert_eq!(hash_through_dyn(&mut runtime, msg), expected);
let mut out = [0u8; 64];
let mut d = <Sha512 as Digest>::new();
DynDigest::update(&mut d, b"a");
DynDigest::finalize_into(&mut d, &mut out).unwrap();
assert_eq!(out, crate::hash::sha512(b"a"));
DynDigest::update(&mut d, b"a");
DynDigest::finalize_into(&mut d, &mut out).unwrap();
assert_eq!(out, crate::hash::sha512(b"a"));
}
#[test]
fn wrong_output_length_is_rejected() {
let mut short = [0u8; 31];
let mut long = [0u8; 33];
for buf in [&mut short[..], &mut long[..]] {
let mut h = HashAlgorithm::Sha256.hasher();
h.update(b"data");
let err = DynDigest::finalize_into(&mut h, buf).unwrap_err();
assert_eq!(err.expected, 32);
assert_eq!(err.got, buf.len());
assert_eq!(h.finalize(), HashAlgorithm::Sha256.digest(b"data"));
}
let mut h = <Sha256 as Digest>::new();
assert!(DynDigest::finalize_into(&mut h, &mut short).is_err());
assert!(
HashAlgorithm::Sha256
.digest_into(b"data", &mut short)
.is_err()
);
let mut exact = [0u8; 32];
assert!(
HashAlgorithm::Sha256
.digest_into(b"data", &mut exact)
.is_ok()
);
assert_eq!(exact, crate::hash::sha256(b"data"));
}
#[test]
fn helpers_accept_every_byte_like_input() {
let expected = HashAlgorithm::Sha256.digest(b"hello");
assert_eq!(HashAlgorithm::Sha256.digest("hello"), expected);
assert_eq!(HashAlgorithm::Sha256.digest(*b"hello"), expected);
assert_eq!(HashAlgorithm::Sha256.digest(&b"hello"[..]), expected);
assert_eq!(
HashAlgorithm::Sha256.digest_parts([&b"he"[..], &b"l"[..], &b"lo"[..]]),
expected
);
assert_eq!(
HashAlgorithm::Sha256
.hasher()
.chain("he")
.chain(b"llo")
.finalize(),
expected
);
assert_eq!(expected.first(), Some(&0x2c));
}
#[cfg(feature = "alloc")]
#[test]
fn owned_inputs_and_hex_display() {
let expected = HashAlgorithm::Sha256.digest("hello");
assert_eq!(
HashAlgorithm::Sha256.digest(alloc::string::String::from("hello")),
expected
);
assert_eq!(
HashAlgorithm::Sha256.digest(alloc::vec![b'h', b'e', b'l', b'l', b'o']),
expected
);
assert_eq!(
alloc::format!("{expected}"),
"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
);
}
#[test]
fn hasher_is_a_fmt_write_target() {
use core::fmt::Write as _;
let mut h = HashAlgorithm::Sha256.hasher();
write!(h, "id:{}", 42).unwrap();
assert_eq!(h.finalize(), HashAlgorithm::Sha256.digest("id:42"));
}
#[cfg(feature = "std")]
#[test]
fn hasher_is_an_io_write_target() {
let mut h = HashAlgorithm::Sha512.hasher();
std::io::copy(&mut std::io::Cursor::new(&b"streamed"[..]), &mut h).unwrap();
assert_eq!(h.finalize(), HashAlgorithm::Sha512.digest("streamed"));
}
#[test]
fn dispatch_digest_covers_every_variant() {
for &alg in HashAlgorithm::ALL {
let (out, block, digest) = crate::dispatch_digest!(alg, |D| {
(
<D as Digest>::OUTPUT_LEN,
<D as Digest>::BLOCK_LEN,
<D as Digest>::digest(b"abc").as_ref().to_vec(),
)
}, _ => panic!("dispatch_digest! has no arm for {alg}"));
assert_eq!(out, alg.output_len(), "{alg}");
assert_eq!(block, alg.block_len(), "{alg}");
assert_eq!(digest, alg.digest(b"abc").as_slice(), "{alg}");
}
}
#[test]
fn legacy_flags() {
for &alg in HashAlgorithm::ALL {
let expect = matches!(
alg,
HashAlgorithm::Md2
| HashAlgorithm::Md4
| HashAlgorithm::Md5
| HashAlgorithm::Sha1
| HashAlgorithm::Ripemd160
);
assert_eq!(alg.is_legacy(), expect, "{alg}");
}
}
}