struct TestFile { inner: Box>, } 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> { self.consume(name).map(|s| hex::decode(s).unwrap()) } fn consume_u64(&mut self, name: &str) -> Option { 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); } }; } // So that rustfmt uses one line on test declaration use sha2_const_stable as s2cs; tests!(sha224, crate::s2cs::Sha224, "SHA224"); tests!(sha256, crate::s2cs::Sha256, "SHA256"); tests!(sha384, crate::s2cs::Sha384, "SHA384"); tests!(sha512, crate::s2cs::Sha512, "SHA512"); tests!(sha512_224, crate::s2cs::Sha512_224, "SHA512_224"); tests!(sha512_256, crate::s2cs::Sha512_256, "SHA512_256");