use crate::Hasher;
use arbitrary::{Arbitrary, Unstructured};
use core::{fmt::Debug, marker::PhantomData};
fn arbitrary_len(u: &mut Unstructured<'_>) -> arbitrary::Result<usize> {
Ok(match u.int_in_range(0..=6)? {
0 => 55,
1 => 64,
2 => 72,
3 => 119,
4 => 120,
5 => 1024,
_ => u.int_in_range(0..=1024)?,
})
}
fn arbitrary_message(u: &mut Unstructured<'_>) -> arbitrary::Result<Vec<Vec<u8>>> {
fn part(u: &mut Unstructured<'_>, len: usize) -> arbitrary::Result<Vec<u8>> {
Ok(u.bytes(len)?.to_vec())
}
match u.int_in_range(0..=4)? {
0 => Ok(vec![part(u, 8)?, part(u, 32)?, part(u, 32)?]),
1 => Ok(vec![part(u, 32)?, part(u, 32)?]),
2 => Ok(vec![part(u, 8)?, part(u, 32)?]),
3 => Ok(vec![part(u, 4)?, part(u, 32)?]),
_ => {
let len = arbitrary_len(u)?;
let split = u.int_in_range(0..=len)?;
let data = part(u, len)?;
Ok(vec![data[..split].to_vec(), data[split..].to_vec()])
}
}
}
pub struct Plan<H: Hasher> {
left: Vec<Vec<u8>>,
right: Vec<Vec<u8>>,
_hasher: PhantomData<H>,
}
impl<H: Hasher> Debug for Plan<H> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Plan")
.field("left", &self.left)
.field("right", &self.right)
.finish()
}
}
impl<H: Hasher> Arbitrary<'_> for Plan<H> {
fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result<Self> {
Ok(Self {
left: arbitrary_message(u)?,
right: arbitrary_message(u)?,
_hasher: PhantomData,
})
}
}
impl<H: Hasher> Plan<H> {
pub const fn new(left: Vec<Vec<u8>>, right: Vec<Vec<u8>>) -> Self {
Self {
left,
right,
_hasher: PhantomData,
}
}
pub fn run(self) {
let left: Vec<&[u8]> = self.left.iter().map(Vec::as_slice).collect();
let right: Vec<&[u8]> = self.right.iter().map(Vec::as_slice).collect();
let reference = |parts: &[&[u8]]| {
let mut hasher = H::default();
hasher.update(&parts.concat());
hasher.finalize().1
};
let expected_left = reference(&left);
let expected_right = reference(&right);
let mut hasher = H::default();
for part in &left {
hasher.update(part);
}
let (mut hasher, streamed_left) = hasher.finalize();
for part in &right {
hasher.update(part);
}
let (_, streamed_right) = hasher.finalize();
assert_eq!(streamed_left, expected_left);
assert_eq!(streamed_right, expected_right);
assert_eq!(H::hash(&left), expected_left);
assert_eq!(H::hash(&right), expected_right);
let (left_digest, right_digest) = H::hash_pair(&left, &right);
assert_eq!(left_digest, expected_left);
assert_eq!(right_digest, expected_right);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Blake3, Sha256};
use commonware_invariants::minifuzz;
fn test_fuzz<H: Hasher>() {
Plan::<H>::new(vec![], vec![]).run();
minifuzz::Builder::default()
.with_seed(0)
.with_search_limit(512)
.test(|u| {
u.arbitrary::<Plan<H>>()?.run();
Ok(())
});
}
#[test]
fn test_fuzz_sha256() {
test_fuzz::<Sha256>();
}
#[test]
fn test_fuzz_blake3() {
test_fuzz::<Blake3>();
}
#[cfg(feature = "std")]
#[test]
fn test_fuzz_crc32() {
test_fuzz::<crate::Crc32>();
}
}