sha2-const 0.1.3

const fn implementation of the SHA-2 family of hash functions
Documentation
struct TestFile {
    inner: Box<dyn Iterator<Item = &'static str>>,
}

impl TestFile {
    fn new(contents: &'static str) -> Self {
        Self {
            inner: Box::new(
                contents
                    .lines()
                    .filter(|s| !s.is_empty() && !s.starts_with('#')),
            ),
        }
    }

    fn read_line(&mut self) -> Option<&'static str> {
        self.inner.next()
    }

    fn consume(&mut self, name: &str) -> Option<&'static str> {
        self.read_line().map(|s| {
            let prefix = format!("{name} = ");
            assert!(s.starts_with(&prefix), "unexpected line");
            &s[prefix.len()..]
        })
    }

    fn consume_bytes(&mut self, name: &str) -> Option<Vec<u8>> {
        self.consume(name).map(|s| hex::decode(s).unwrap())
    }

    fn consume_u64(&mut self, name: &str) -> Option<u64> {
        self.consume(name).map(|s| s.parse().unwrap())
    }
}

macro_rules! test_file {
    ($file_prefix:literal, $file_suffix:literal) => {{
        TestFile::new(include_str!(concat!(
            "data/",
            $file_prefix,
            $file_suffix,
            ".rsp"
        )))
    }};
}

macro_rules! known_answer_test {
    ($name:ident, $ty:ty, $file_prefix:literal, $file_suffix:literal) => {
        #[test]
        fn $name() {
            let mut f = test_file!($file_prefix, $file_suffix);
            assert_eq!(
                format!("[L = {}]", <$ty>::DIGEST_SIZE),
                f.read_line().unwrap()
            );

            while let Some(length) = f.consume_u64("Len") {
                let mut input = f.consume_bytes("Msg").unwrap();
                if length == 0 {
                    assert_eq!(input, &[0]);
                    input.pop();
                }
                assert_eq!(length, (input.len() as u64) * 8);
                let digest = <$ty>::new().update(&input).finalize();
                assert_eq!(&digest[..], &f.consume_bytes("MD").unwrap()[..]);
            }
        }
    };
}

macro_rules! monte_carlo {
    ($ty:ty, $file_prefix:literal) => {
        #[test]
        fn monte_carlo() {
            let mut f = test_file!($file_prefix, "Monte");
            assert_eq!(
                format!("[L = {}]", <$ty>::DIGEST_SIZE),
                f.read_line().unwrap()
            );

            let mut seed: [u8; <$ty>::DIGEST_SIZE] = [0; <$ty>::DIGEST_SIZE];
            seed.copy_from_slice(&f.consume_bytes("Seed").unwrap());

            let mut expected_count = 0;
            while let Some(count) = f.consume_u64("COUNT") {
                assert_eq!(count, expected_count);
                expected_count += 1;

                let mut md_0 = seed;
                let mut md_1 = seed;
                let mut md_2 = seed;

                for _ in 0..1000 {
                    let md_i = <$ty>::new()
                        .update(&md_0)
                        .update(&md_1)
                        .update(&md_2)
                        .finalize();
                    md_0 = md_1;
                    md_1 = md_2;
                    md_2 = md_i;
                }

                assert_eq!(&md_2[..], &f.consume_bytes("MD").unwrap()[..]);
                seed = md_2;
            }
        }
    };
}

macro_rules! tests {
    ($mod:ident, $ty:ty, $file_prefix:literal) => {
        mod $mod {
            use super::TestFile;
            known_answer_test!(short_msg, $ty, $file_prefix, "ShortMsg");
            known_answer_test!(long_msg, $ty, $file_prefix, "LongMsg");
            monte_carlo!($ty, $file_prefix);
        }
    };
}

tests!(sha224, sha2_const::Sha224, "SHA224");
tests!(sha256, sha2_const::Sha256, "SHA256");
tests!(sha384, sha2_const::Sha384, "SHA384");
tests!(sha512, sha2_const::Sha512, "SHA512");
tests!(sha512_224, sha2_const::Sha512_224, "SHA512_224");
tests!(sha512_256, sha2_const::Sha512_256, "SHA512_256");