use crate::PinaPod;
use crate::PinaPodCompact;
use crate::String;
use crate::Vec;
use crate::pod;
#[derive(PinaPod)]
#[pinapod(compact)]
struct Bounded {
pub seq: u8,
label: String<2>,
values: Vec<u16, 2>,
note: Option<String<2>>,
}
const _: () = assert!(<Bounded as PinaPodCompact>::HEADER_SIZE == 5);
const _: () = assert!(<Bounded as PinaPodCompact>::MIN_SIZE == 5);
const _: () = assert!(<Bounded as PinaPodCompact>::MAX_SIZE == 14);
const _: () = assert!(<Bounded as PinaPodCompact>::TAIL_ALIGNMENT == 1);
const LABEL: &str = "aa";
const NOTE: &str = "nn";
const STORAGE: usize = <Bounded as PinaPodCompact>::MAX_SIZE;
struct SymbolicValues([u16; 2]);
impl SymbolicValues {
fn new() -> Self {
let base: u16 = kani::any();
Self([base, base.wrapping_add(1)])
}
fn pod(&self) -> [pod::PodU16; 2] {
[pod::PodU16::from(self.0[0]), pod::PodU16::from(self.0[1])]
}
}
#[kani::proof]
#[kani::unwind(12)]
fn validated_accessors_are_bounded() {
let data: [u8; STORAGE] = kani::any();
if let Ok(view) = Bounded::read_prefix(&data) {
assert!(view.label().len() <= 2);
assert!(view.values().len() <= 2);
match view.note() {
Some(note) => assert!(note.len() <= 2),
None => {}
}
assert!(view.encoded_len() <= view.storage_len());
assert!(view.encoded_len() >= <Bounded as PinaPodCompact>::HEADER_SIZE);
assert_eq!(
view.spare_capacity(),
view.storage_len() - view.encoded_len()
);
}
}
#[kani::proof]
#[kani::unwind(12)]
fn initialize_builds_a_valid_view() {
let seq: u8 = kani::any();
let label_len: usize = kani::any();
kani::assume(label_len <= 2);
let values = SymbolicValues::new();
let values_len: usize = kani::any();
kani::assume(values_len <= 2);
let note_present: bool = kani::any();
let note_len: usize = kani::any();
kani::assume(note_len <= 2);
let mut data: [u8; STORAGE] = [0xFF; STORAGE];
let values_pod = values.pod();
let patch = BoundedPatch::new()
.seq(seq)
.label(&LABEL[..label_len])
.replace_values(&values_pod[..values_len])
.note(if note_present {
Some(&NOTE[..note_len])
} else {
None
});
let encoded_len =
Bounded::initialize(&mut data, &patch).expect("initialize with a valid patch must succeed");
assert!(encoded_len <= STORAGE);
let view = Bounded::read_prefix(&data).expect("initialized bytes must validate");
assert_eq!(view.seq, seq);
assert_eq!(view.label(), &LABEL[..label_len]);
assert_eq!(view.values(), &values_pod[..values_len]);
match (view.note(), note_present) {
(Some(note), true) => assert_eq!(note, &NOTE[..note_len]),
(None, false) => {}
_ => panic!("option round-trip mismatch"),
}
}
#[kani::proof]
#[kani::unwind(12)]
fn update_preserves_roundtrip() {
let mut data: [u8; STORAGE] = [0x7F; STORAGE];
let first_values = SymbolicValues::new().pod();
let first = BoundedPatch::new()
.seq(0)
.label(LABEL)
.replace_values(&first_values);
Bounded::initialize(&mut data, &first).expect("initialize with a valid patch must succeed");
let seq: u8 = kani::any();
let label_len: usize = kani::any();
kani::assume(label_len <= 2);
let values = SymbolicValues::new();
let values_len: usize = kani::any();
kani::assume(values_len <= 2);
let note_present: bool = kani::any();
let note_len: usize = kani::any();
kani::assume(note_len <= 2);
let values_pod = values.pod();
let second = BoundedPatch::new()
.seq(seq)
.label(&LABEL[..label_len])
.replace_values(&values_pod[..values_len])
.note(if note_present {
Some(&NOTE[..note_len])
} else {
None
});
let encoded_len = Bounded::update(&mut data, &second)
.expect("updating valid bytes with a valid patch must succeed");
assert!(encoded_len <= STORAGE);
let view = Bounded::read_prefix(&data).expect("updated bytes must validate");
assert_eq!(view.seq, seq);
assert_eq!(view.label(), &LABEL[..label_len]);
assert_eq!(view.values(), &values_pod[..values_len]);
match (view.note(), note_present) {
(Some(note), true) => assert_eq!(note, &NOTE[..note_len]),
(None, false) => {}
_ => panic!("option round-trip mismatch"),
}
}