#[allow(dead_code)]
pub const SLOT_FREE: u8 = 0x00;
pub const SLOT_OCCUPIED: u8 = 0x01;
pub const SLOT_DELETED: u8 = 0x02;
pub const SLOT_HEADER_SIZE: usize = 8;
pub const fn slot_size(key_len: usize, value_len: usize) -> usize {
let raw = SLOT_HEADER_SIZE + key_len + value_len;
(raw + 7) & !7
}
pub fn compute_crc(key: &[u8], value: &[u8]) -> u32 {
let mut h = crc32fast::Hasher::new();
h.update(key);
h.update(value);
h.finalize()
}
pub fn serialize_slot(buf: &mut [u8], key: &[u8], value: &[u8]) {
let size = slot_size(key.len(), value.len());
assert!(
buf.len() >= size,
"buffer too small: {} < {}",
buf.len(),
size
);
buf[..size].fill(0);
buf[0] = SLOT_OCCUPIED;
let crc = compute_crc(key, value);
buf[4..8].copy_from_slice(&crc.to_le_bytes());
buf[SLOT_HEADER_SIZE..SLOT_HEADER_SIZE + key.len()].copy_from_slice(key);
let val_off = SLOT_HEADER_SIZE + key.len();
buf[val_off..val_off + value.len()].copy_from_slice(value);
}
pub fn serialize_deleted_slot(buf: &mut [u8]) {
assert!(!buf.is_empty(), "buffer must not be empty");
buf.fill(0);
buf[0] = SLOT_DELETED;
}
pub fn slot_status(buf: &[u8]) -> u8 {
buf[0]
}
pub fn validate_slot(buf: &[u8], key_len: usize, value_len: usize) -> Option<(&[u8], &[u8])> {
let size = slot_size(key_len, value_len);
if buf.len() < size {
return None;
}
if buf[0] != SLOT_OCCUPIED {
return None;
}
let stored_crc = u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]);
let key = &buf[SLOT_HEADER_SIZE..SLOT_HEADER_SIZE + key_len];
let val_off = SLOT_HEADER_SIZE + key_len;
let value = &buf[val_off..val_off + value_len];
let expected_crc = compute_crc(key, value);
if stored_crc != expected_crc {
return None;
}
Some((key, value))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_slot_size_alignment() {
assert_eq!(slot_size(0, 0), 8);
assert_eq!(slot_size(4, 4), 16);
assert_eq!(slot_size(8, 8), 24);
assert_eq!(slot_size(1, 0), 16);
assert_eq!(slot_size(3, 2), 16);
assert_eq!(slot_size(10, 7), 32);
for k in 0..64 {
for v in 0..64 {
assert_eq!(slot_size(k, v) % 8, 0);
}
}
}
#[test]
fn test_serialize_validate_roundtrip() {
let key = b"hello";
let value = b"world!!!";
let size = slot_size(key.len(), value.len());
let mut buf = vec![0u8; size];
serialize_slot(&mut buf, key, value);
assert_eq!(slot_status(&buf), SLOT_OCCUPIED);
let (k, v) = validate_slot(&buf, key.len(), value.len()).expect("validation should pass");
assert_eq!(k, key);
assert_eq!(v, value);
}
#[test]
fn test_corrupted_slot_fails_validation() {
let key = b"abc";
let value = b"defgh";
let size = slot_size(key.len(), value.len());
let mut buf = vec![0u8; size];
serialize_slot(&mut buf, key, value);
buf[SLOT_HEADER_SIZE + key.len()] ^= 0xFF;
assert!(
validate_slot(&buf, key.len(), value.len()).is_none(),
"corrupted slot should fail validation"
);
}
#[test]
fn test_free_slot_not_valid() {
let size = slot_size(4, 4);
let buf = vec![0u8; size];
assert_eq!(slot_status(&buf), SLOT_FREE);
assert!(
validate_slot(&buf, 4, 4).is_none(),
"free slot should not validate"
);
}
#[test]
fn test_deleted_slot() {
let key = b"key1";
let value = b"val1";
let size = slot_size(key.len(), value.len());
let mut buf = vec![0u8; size];
serialize_slot(&mut buf, key, value);
assert_eq!(slot_status(&buf), SLOT_OCCUPIED);
serialize_deleted_slot(&mut buf);
assert_eq!(slot_status(&buf), SLOT_DELETED);
assert!(
validate_slot(&buf, key.len(), value.len()).is_none(),
"deleted slot should not validate"
);
}
}