#![cfg(kani)]
use alloc::{vec, vec::Vec};
use crate::{
IdOutOfBounds, LayoutIndex, OffsetIntegrityIssue, OffsetOverflow, build_offset_index,
check_offset_section, check_offsets_monotonic, check_value_range, id_to_slot, index_from_usize,
slot_or_max,
};
#[kani::proof]
fn id_to_slot_total_u32() {
let id: u32 = kani::any();
let count: usize = kani::any();
let result = id_to_slot::<u32>(id, count);
if let Ok(slot) = result {
assert!(slot < count);
}
}
#[kani::proof]
fn id_to_slot_classification_u32() {
let id: u32 = kani::any();
let count: usize = kani::any();
match id_to_slot::<u32>(id, count) {
Ok(slot) => {
assert_eq!(slot, id as usize);
assert!(slot < count);
}
Err(IdOutOfBounds::OutOfRange { slot, count: c }) => {
assert_eq!(slot, id as usize);
assert_eq!(c, count);
assert!(slot >= c);
}
Err(IdOutOfBounds::UsizeOverflow) => {
kani::cover!(false, "u32 always fits in usize on kani targets");
}
}
}
#[kani::proof]
fn slot_or_max_total_u64() {
let id: u64 = kani::any();
let _ = slot_or_max::<u64>(id);
}
#[kani::proof]
fn index_from_usize_total_u16() {
let value: usize = kani::any();
match index_from_usize::<u16>(value) {
Ok(idx) => {
assert_eq!(idx as usize, value);
assert!(value <= u16::MAX as usize);
}
Err(OffsetOverflow::IndexOverflow { value: v }) => {
assert_eq!(v, value);
assert!(value > u16::MAX as usize);
}
}
}
#[kani::proof]
#[kani::unwind(4)]
fn build_offset_index_contract_2x2_u32() {
let bucket0_storage: [u32; 2] = kani::any();
let bucket0_take: usize = kani::any();
kani::assume(bucket0_take <= 2);
let bucket0: Vec<u32> = bucket0_storage[..bucket0_take].to_vec();
let bucket0_len = bucket0.len();
let bucket1_storage: [u32; 2] = kani::any();
let bucket1_take: usize = kani::any();
kani::assume(bucket1_take <= 2);
let bucket1: Vec<u32> = bucket1_storage[..bucket1_take].to_vec();
let bucket1_len = bucket1.len();
let buckets = vec![bucket0, bucket1];
let (offsets, items) = match build_offset_index::<u32, u32>(buckets) {
Ok(value) => value,
Err(_) => return,
};
assert_eq!(offsets.len(), 3);
assert_eq!(offsets[0], 0);
assert!(offsets[0] <= offsets[1]);
assert!(offsets[1] <= offsets[2]);
assert_eq!(offsets[2] as usize, items.len());
assert_eq!((offsets[1] - offsets[0]) as usize, bucket0_len);
assert_eq!((offsets[2] - offsets[1]) as usize, bucket1_len);
}
#[kani::proof]
#[kani::unwind(4)]
fn build_offset_index_total_single_bucket_u32() {
let bucket_storage: [u32; 2] = kani::any();
let take: usize = kani::any();
kani::assume(take <= 2);
let bucket: Vec<u32> = bucket_storage[..take].to_vec();
let buckets = vec![bucket];
let _ = build_offset_index::<u32, u32>(buckets);
}
#[kani::proof]
fn build_offset_index_empty() {
let buckets: Vec<Vec<u32>> = Vec::new();
match build_offset_index::<u32, u32>(buckets) {
Ok((offsets, items)) => {
assert_eq!(offsets.len(), 1);
assert_eq!(offsets[0], 0);
assert!(items.is_empty());
}
Err(_) => {
kani::cover!(false, "empty buckets must succeed");
}
}
}
#[kani::proof]
fn build_index_roundtrip_u16() {
let value: usize = kani::any();
let Some(index) = <u16 as LayoutIndex>::from_usize(value) else {
return;
};
match <u16 as LayoutIndex>::to_usize(index) {
Some(back) => assert_eq!(back, value),
None => kani::cover!(false, "u16 always fits in usize"),
}
}
#[kani::proof]
#[kani::unwind(8)]
fn check_offsets_monotonic_total_u32_n4() {
let offsets: [u32; 4] = kani::any();
let take: usize = kani::any();
kani::assume(take <= 4);
let _ = check_offsets_monotonic(&offsets[..take]);
}
#[kani::proof]
#[kani::unwind(8)]
fn check_offsets_monotonic_predicate_u32_n3() {
let offsets: [u32; 3] = kani::any();
let take: usize = kani::any();
kani::assume(take <= 3);
let slice = &offsets[..take];
if check_offsets_monotonic(slice).is_ok() {
if !slice.is_empty() {
assert_eq!(slice[0], 0);
}
for index in 1..slice.len() {
assert!(slice[index - 1] <= slice[index]);
}
}
}
#[kani::proof]
#[kani::unwind(8)]
fn check_value_range_total_u32_n4() {
let values: [u32; 4] = kani::any();
let take: usize = kani::any();
kani::assume(take <= 4);
let bound: usize = kani::any();
let _ = check_value_range(&values[..take], bound);
}
#[kani::proof]
#[kani::unwind(8)]
fn check_value_range_predicate_u32_n3() {
let values: [u32; 3] = kani::any();
let take: usize = kani::any();
kani::assume(take <= 3);
let bound: usize = kani::any();
let slice = &values[..take];
if check_value_range(slice, bound).is_ok() {
for &value in slice {
assert!((value as usize) < bound);
}
}
}
#[kani::proof]
#[kani::unwind(8)]
fn check_offset_section_total_u32_n4() {
let offsets: [u32; 4] = kani::any();
let take: usize = kani::any();
kani::assume(take <= 4);
let expected_count: usize = kani::any();
kani::assume(expected_count <= 4);
let value_len: usize = kani::any();
let _ = check_offset_section(&offsets[..take], expected_count, value_len);
}
#[kani::proof]
#[kani::unwind(8)]
fn check_offset_section_predicate_u32_n3() {
let offsets: [u32; 4] = kani::any();
let take: usize = kani::any();
kani::assume(take <= 4);
let expected_count: usize = kani::any();
kani::assume(expected_count <= 3);
let value_len: usize = kani::any();
kani::assume(value_len <= u32::MAX as usize);
let slice = &offsets[..take];
if check_offset_section(slice, expected_count, value_len).is_ok() {
assert_eq!(slice.len(), expected_count + 1);
assert_eq!(slice[0], 0);
for index in 1..slice.len() {
assert!(slice[index - 1] <= slice[index]);
}
assert_eq!(slice[slice.len() - 1] as usize, value_len);
for &offset in slice {
assert!(offset as usize <= value_len);
}
}
}
#[kani::proof]
fn check_offset_section_count_overflow() {
let offsets: [u32; 1] = kani::any();
let value_len: usize = kani::any();
let result = check_offset_section(&offsets, usize::MAX, value_len);
assert!(matches!(
result,
Err(OffsetIntegrityIssue::CountOverflow { count }) if count == usize::MAX
));
}
#[kani::proof]
fn check_offsets_monotonic_first_zero_required() {
let first: u32 = kani::any();
kani::assume(first != 0);
let result = check_offsets_monotonic(&[first]);
match result {
Err(OffsetIntegrityIssue::FirstNonZero { actual }) => {
assert_eq!(actual, first as usize);
}
Err(_) => {}
Ok(()) => kani::cover!(false, "non-zero first offset must be rejected"),
}
}