extern crate alloc;
use super::super::super::support::arb_vv;
use crate::metis::{DecodeError, VersionVector};
use alloc::vec::Vec;
use proptest::prelude::*;
#[derive(Debug, Clone)]
enum FrameMutation {
FlipByte { offset: usize, xor: u8 },
Truncate { keep: usize },
Append(Vec<u8>),
}
fn arb_mutation() -> impl Strategy<Value = FrameMutation> {
prop_oneof![
(any::<usize>(), 1u8..=u8::MAX)
.prop_map(|(offset, xor)| FrameMutation::FlipByte { offset, xor }),
any::<usize>().prop_map(|keep| FrameMutation::Truncate { keep }),
prop::collection::vec(any::<u8>(), 1..16).prop_map(FrameMutation::Append),
]
}
proptest! {
#[test]
fn prop_vv_bytes_round_trip(v in arb_vv()) {
prop_assert_eq!(v.encoded_len(), v.to_bytes().len());
prop_assert_eq!(VersionVector::from_bytes(&v.to_bytes()), Ok(v));
}
#[test]
fn prop_vv_bytes_are_canonical(
v in arb_vv(),
noise in prop::collection::vec(0u32..6, 0..4),
) {
let mut other = v.clone();
for station in noise {
other.observe(station, 0);
}
prop_assert_eq!(&v, &other);
prop_assert_eq!(v.to_bytes(), other.to_bytes());
}
#[test]
fn prop_vv_from_bytes_never_panics(bytes in prop::collection::vec(any::<u8>(), 0..64)) {
if let Ok(v) = VersionVector::from_bytes(&bytes) {
prop_assert_eq!(v.to_bytes(), bytes);
}
}
#[test]
fn prop_vv_decode_total_on_mutated_frames(
v in arb_vv(),
mutation in arb_mutation(),
) {
let mut bytes = v.to_bytes();
match mutation {
FrameMutation::FlipByte { offset, xor } => {
let len = bytes.len();
bytes[offset % len] ^= xor;
}
FrameMutation::Truncate { keep } => {
bytes.truncate(keep % (bytes.len() + 1));
}
FrameMutation::Append(junk) => bytes.extend_from_slice(&junk),
}
if let Ok(decoded) = VersionVector::from_bytes(&bytes) {
let reencoded = decoded.to_bytes();
prop_assert_eq!(&reencoded, &bytes);
}
if let Ok((prefix, tail)) = VersionVector::from_prefix(&bytes) {
let consumed = bytes.len() - tail.len();
let reencoded = prefix.to_bytes();
prop_assert_eq!(reencoded.as_slice(), &bytes[..consumed]);
prop_assert_eq!(tail, &bytes[consumed..]);
}
}
#[test]
fn prop_vv_rejects_shaped_non_canonical_frames(
station_a in any::<u32>(),
station_delta in any::<u32>(),
counter_a in any::<u64>(),
counter_b in any::<u64>(),
break_order in any::<bool>(),
) {
let (station_b, counter_a, counter_b) = if break_order {
(
station_a.saturating_sub(station_delta),
counter_a.max(1),
counter_b.max(1),
)
} else {
let station_b = station_a.saturating_add((station_delta % 8) + 1);
if station_delta.is_multiple_of(2) {
(station_b, 0, counter_b)
} else {
(station_b, counter_a.max(1), 0)
}
};
let mut bytes = Vec::with_capacity(29);
bytes.push(0x01);
bytes.extend_from_slice(&2u32.to_be_bytes());
bytes.extend_from_slice(&station_a.to_be_bytes());
bytes.extend_from_slice(&counter_a.to_be_bytes());
bytes.extend_from_slice(&station_b.to_be_bytes());
bytes.extend_from_slice(&counter_b.to_be_bytes());
let result = VersionVector::from_bytes(&bytes);
if counter_a == 0 {
prop_assert_eq!(result, Err(DecodeError::ZeroCounter { station: station_a }));
} else if break_order && station_b <= station_a {
prop_assert_eq!(
result,
Err(DecodeError::NonAscendingStations {
previous: station_a,
found: station_b,
})
);
} else if counter_b == 0 {
prop_assert_eq!(result, Err(DecodeError::ZeroCounter { station: station_b }));
} else {
prop_assert!(false, "shaped generator produced a canonical frame");
}
}
}