use crate::Error;
#[inline]
pub(crate) fn decode_u32_le_at(bytes: &[u8], byte_offset: usize) -> u32 {
u32::from_le_bytes([
bytes[byte_offset],
bytes[byte_offset + 1],
bytes[byte_offset + 2],
bytes[byte_offset + 3],
])
}
#[doc(hidden)]
pub fn validate_struct_prologue(
offset_bytes: &[u8],
count: usize,
stride: usize,
payload_len: u32,
) -> Result<(), Error> {
let mut last: Option<u32> = None;
for i in 0..count {
let o = decode_u32_le_at(offset_bytes, i * stride);
if o > payload_len {
return Err(Error::OptimisedOffsetOutOfRange {
offset: o,
payload_len,
});
}
if let Some(prev) = last
&& o <= prev
{
return Err(Error::OptimisedOffsetsNonMonotonic);
}
last = Some(o);
}
Ok(())
}
#[doc(hidden)]
pub fn validate_map_prologue(
offset_table: &[u8],
count: usize,
keys_region_len: u32,
vals_region_len: u32,
) -> Result<(), Error> {
if offset_table.len() != count.saturating_mul(8) {
return Err(Error::OptimisedOffsetsNonMonotonic);
}
validate_struct_prologue(offset_table, count, 8, keys_region_len)?;
validate_struct_prologue(&offset_table[4..], count, 8, vals_region_len)?;
Ok(())
}
#[doc(hidden)]
pub fn validate_key_region_ascending(
keys_region: &[u8],
offset_table: &[u8],
count: usize,
) -> Result<(), Error> {
if count < 2 {
return Ok(());
}
let mut prev_start = decode_u32_le_at(offset_table, 0) as usize;
let mut curr_start = decode_u32_le_at(offset_table, 8) as usize;
for i in 1..count {
let curr_end = if i + 1 < count {
decode_u32_le_at(offset_table, (i + 1) * 8) as usize
} else {
keys_region.len()
};
let prev = &keys_region[prev_start..curr_start];
let curr = &keys_region[curr_start..curr_end];
if curr <= prev {
return Err(Error::OptimisedKeyRegionNotAscending);
}
prev_start = curr_start;
curr_start = curr_end;
}
Ok(())
}
#[doc(hidden)]
#[inline]
pub fn validate_seq_prologue(
elem_offset_bytes: &[u8],
count: usize,
payload_len: u32,
) -> Result<(), Error> {
validate_struct_prologue(elem_offset_bytes, count, 4, payload_len)
}
#[cfg(test)]
mod tests {
use super::*;
fn pack_offsets(offsets: &[u32]) -> Vec<u8> {
let mut out = Vec::with_capacity(offsets.len() * 4);
for &o in offsets {
out.extend_from_slice(&o.to_le_bytes());
}
out
}
fn pack_interleaved(key_offsets: &[u32], val_offsets: &[u32]) -> Vec<u8> {
assert_eq!(key_offsets.len(), val_offsets.len());
let mut out = Vec::with_capacity(key_offsets.len() * 8);
for (k, v) in key_offsets.iter().zip(val_offsets.iter()) {
out.extend_from_slice(&k.to_le_bytes());
out.extend_from_slice(&v.to_le_bytes());
}
out
}
#[test]
fn struct_prologue_accepts_monotonic_in_range() {
let bytes = pack_offsets(&[0, 4, 12, 20]);
assert!(validate_struct_prologue(&bytes, 4, 4, 24).is_ok());
}
#[test]
fn struct_prologue_rejects_out_of_range() {
let bytes = pack_offsets(&[0, 4, 100]);
let err = validate_struct_prologue(&bytes, 3, 4, 50).unwrap_err();
assert!(matches!(
err,
Error::OptimisedOffsetOutOfRange {
offset: 100,
payload_len: 50
}
));
}
#[test]
fn struct_prologue_rejects_non_monotonic() {
let dup = pack_offsets(&[0, 4, 4, 8]);
assert!(matches!(
validate_struct_prologue(&dup, 4, 4, 16).unwrap_err(),
Error::OptimisedOffsetsNonMonotonic
));
let desc = pack_offsets(&[8, 4]);
assert!(matches!(
validate_struct_prologue(&desc, 2, 4, 16).unwrap_err(),
Error::OptimisedOffsetsNonMonotonic
));
}
#[test]
fn map_prologue_rejects_mismatched_lengths() {
let bytes = pack_interleaved(&[0, 4], &[0, 4]);
assert!(matches!(
validate_map_prologue(&bytes, 3, 8, 12).unwrap_err(),
Error::OptimisedOffsetsNonMonotonic
));
}
#[test]
fn key_region_ascending_accepts_sorted() {
let keys = b"abc";
let table = pack_interleaved(&[0, 1, 2], &[0, 0, 0]);
assert!(validate_key_region_ascending(keys, &table, 3).is_ok());
}
#[test]
fn key_region_ascending_rejects_unsorted() {
let keys = b"ba";
let table = pack_interleaved(&[0, 1], &[0, 0]);
assert!(matches!(
validate_key_region_ascending(keys, &table, 2).unwrap_err(),
Error::OptimisedKeyRegionNotAscending
));
}
#[test]
fn key_region_ascending_rejects_duplicates() {
let keys = b"aa";
let table = pack_interleaved(&[0, 1], &[0, 0]);
assert!(matches!(
validate_key_region_ascending(keys, &table, 2).unwrap_err(),
Error::OptimisedKeyRegionNotAscending
));
}
#[test]
fn key_region_ascending_handles_empty() {
assert!(validate_key_region_ascending(&[], &[], 0).is_ok());
}
#[test]
fn key_region_ascending_handles_single() {
let table = pack_interleaved(&[0], &[0]);
assert!(validate_key_region_ascending(b"x", &table, 1).is_ok());
}
}