extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
use core::num::NonZeroU16;
use quickcheck::{Arbitrary, Gen, quickcheck};
use crate::block::{
Block, BlockLoadError, BlockType, DataBlock, DataBlockHeader, GlobalBlock, GlobalHeader,
SlotHeader, SlotHeaderBlock, SlotState, deserialize_block, serialize_block,
};
const TEST_BLOCK_SIZE: usize = 128;
impl Arbitrary for BlockType {
fn arbitrary(g: &mut Gen) -> Self {
*g.choose(&[Self::Data, Self::Free, Self::Global, Self::Slot])
.unwrap()
}
}
impl Arbitrary for SlotState {
fn arbitrary(g: &mut Gen) -> Self {
*g.choose(&[Self::Empty, Self::Valid, Self::Ghost]).unwrap()
}
}
impl Arbitrary for GlobalHeader {
fn arbitrary(g: &mut Gen) -> Self {
Self {
slot_count: u16::arbitrary(g),
}
}
}
impl Arbitrary for SlotHeader {
fn arbitrary(g: &mut Gen) -> Self {
Self {
state: SlotState::arbitrary(g),
logical_slot_id: u8::arbitrary(g),
first_data_block: NonZeroU16::new(u16::arbitrary(g)),
first_metadata_block: NonZeroU16::new(u16::arbitrary(g)),
generation: u32::arbitrary(g),
crc32: u32::arbitrary(g),
length: u32::arbitrary(g),
metadata_length: u32::arbitrary(g),
metadata_crc32: u32::arbitrary(g),
}
}
}
impl Arbitrary for DataBlockHeader {
fn arbitrary(g: &mut Gen) -> Self {
Self {
next_block: NonZeroU16::new(u16::arbitrary(g)),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
enum OwnedBlock {
Free,
Global {
header: GlobalHeader,
game_identifier: [u8; 32],
},
SlotHeader {
header: SlotHeader,
metadata: Vec<u8>,
},
Data {
header: DataBlockHeader,
data: Vec<u8>,
},
}
impl Arbitrary for OwnedBlock {
fn arbitrary(g: &mut Gen) -> Self {
match BlockType::arbitrary(g) {
BlockType::Free => OwnedBlock::Free,
BlockType::Global => {
let mut game_identifier = [0u8; 32];
for byte in &mut game_identifier {
*byte = u8::arbitrary(g);
}
OwnedBlock::Global {
header: GlobalHeader::arbitrary(g),
game_identifier,
}
}
BlockType::Slot => {
let metadata_size = TEST_BLOCK_SIZE - SlotHeaderBlock::header_size();
let mut metadata = Vec::with_capacity(metadata_size);
for _ in 0..metadata_size {
metadata.push(u8::arbitrary(g));
}
OwnedBlock::SlotHeader {
header: SlotHeader::arbitrary(g),
metadata,
}
}
BlockType::Data => {
let data_size = TEST_BLOCK_SIZE - DataBlock::header_size();
let data: Vec<u8> = (0..data_size).map(|_| u8::arbitrary(g)).collect();
OwnedBlock::Data {
header: DataBlockHeader::arbitrary(g),
data,
}
}
}
}
}
impl OwnedBlock {
fn to_block(&self) -> Block<'_> {
match self {
OwnedBlock::Free => Block::Free,
OwnedBlock::Global {
header,
game_identifier,
} => Block::Global(GlobalBlock {
header: header.clone(),
game_identifier,
}),
OwnedBlock::SlotHeader { header, metadata } => Block::SlotHeader(SlotHeaderBlock {
header: header.clone(),
metadata,
}),
OwnedBlock::Data { header, data } => Block::Data(DataBlock {
header: header.clone(),
data,
}),
}
}
fn from_block(block: &Block<'_>) -> Self {
match block {
Block::Free => OwnedBlock::Free,
Block::Global(g) => {
let mut game_identifier = [0u8; 32];
game_identifier.copy_from_slice(&g.game_identifier[..32]);
OwnedBlock::Global {
header: g.header.clone(),
game_identifier,
}
}
Block::SlotHeader(s) => OwnedBlock::SlotHeader {
header: s.header.clone(),
metadata: s.metadata.to_vec(),
},
Block::Data(d) => OwnedBlock::Data {
header: d.header.clone(),
data: d.data.to_vec(),
},
}
}
}
quickcheck! {
fn block_roundtrip(block: OwnedBlock) -> bool {
let mut buffer = [0u8; TEST_BLOCK_SIZE];
serialize_block(block.to_block(), &mut buffer);
match deserialize_block(&buffer) {
Ok(deserialized) => {
let roundtripped = OwnedBlock::from_block(&deserialized);
block == roundtripped
}
Err(_) => false,
}
}
fn free_block_roundtrip() -> bool {
let mut buffer = [0u8; TEST_BLOCK_SIZE];
let block = Block::Free;
serialize_block(block, &mut buffer);
matches!(deserialize_block(&buffer), Ok(Block::Free))
}
fn global_block_roundtrip(slot_count: u16, game_id: Vec<u8>) -> bool {
let mut game_identifier = [0u8; 32];
for (i, &byte) in game_id.iter().take(32).enumerate() {
game_identifier[i] = byte;
}
let mut buffer = [0u8; TEST_BLOCK_SIZE];
let block = Block::Global(GlobalBlock {
header: GlobalHeader { slot_count },
game_identifier: &game_identifier,
});
serialize_block(block, &mut buffer);
match deserialize_block(&buffer) {
Ok(Block::Global(g)) => {
g.header.slot_count == slot_count && g.game_identifier[..32] == game_identifier
}
_ => false,
}
}
fn data_block_roundtrip(header: DataBlockHeader, data: Vec<u8>) -> bool {
let data_size = TEST_BLOCK_SIZE - DataBlock::header_size();
let mut padded_data = vec![0u8; data_size];
for (i, &byte) in data.iter().take(data_size).enumerate() {
padded_data[i] = byte;
}
let mut buffer = [0u8; TEST_BLOCK_SIZE];
let next_block = header.next_block;
let block = Block::Data(DataBlock {
header,
data: &padded_data,
});
serialize_block(block, &mut buffer);
match deserialize_block(&buffer) {
Ok(Block::Data(d)) => {
d.header.next_block == next_block && d.data == padded_data.as_slice()
}
_ => false,
}
}
fn slot_header_roundtrip(header: SlotHeader, metadata_seed: Vec<u8>) -> bool {
let metadata_size = TEST_BLOCK_SIZE - SlotHeaderBlock::header_size();
let mut padded_metadata = vec![0u8; metadata_size];
for (i, &byte) in metadata_seed.iter().take(metadata_size).enumerate() {
padded_metadata[i] = byte;
}
let mut buffer = [0u8; TEST_BLOCK_SIZE];
let block = Block::SlotHeader(SlotHeaderBlock {
header: header.clone(),
metadata: &padded_metadata,
});
serialize_block(block, &mut buffer);
match deserialize_block(&buffer) {
Ok(Block::SlotHeader(s)) => {
s.header == header && s.metadata == padded_metadata.as_slice()
}
_ => false,
}
}
fn corrupted_byte_detected(block: OwnedBlock, corrupt_offset: usize, corrupt_xor: u8) -> bool {
let mut buffer = [0u8; TEST_BLOCK_SIZE];
serialize_block(block.to_block(), &mut buffer);
let offset = 2 + (corrupt_offset % (TEST_BLOCK_SIZE - 2));
let xor_value = if corrupt_xor == 0 { 1 } else { corrupt_xor };
buffer[offset] ^= xor_value;
deserialize_block(&buffer).is_err()
}
fn corrupted_crc_detected(block: OwnedBlock, corrupt_xor: u8) -> bool {
let mut buffer = [0u8; TEST_BLOCK_SIZE];
serialize_block(block.to_block(), &mut buffer);
let xor_value = if corrupt_xor == 0 { 1 } else { corrupt_xor };
buffer[0] ^= xor_value;
matches!(deserialize_block(&buffer), Err(BlockLoadError::CrcMismatch))
}
}