use core::mem::{align_of, size_of};
use bitcode::{Decode, Encode};
use crate::{
codec::checked_record_size,
error::{Error, Result},
};
pub const HEADER_SIZE: usize = 16;
pub const ADDRESS_MASK: u64 = (1u64 << 48) - 1;
pub const FILLER_WORDS_SHIFT: u32 = 48;
pub const FILLER_WORDS_MASK: u64 = 0xFFu64 << FILLER_WORDS_SHIFT;
pub const FILLER_REM_SHIFT: u32 = 56;
pub const FILLER_REM_MASK: u64 = 0x07u64 << FILLER_REM_SHIFT;
pub const FILLER_TOTAL_MASK: u64 = FILLER_WORDS_MASK | FILLER_REM_MASK;
pub const MAX_FILLER_BYTES: usize = 2047;
pub const MODIFIED_BIT: u64 = 1u64 << 59;
pub const SEALED_BIT: u64 = 1u64 << 60;
pub const IN_NEW_VERSION_BIT: u64 = 1u64 << 61;
pub const READ_CACHE_BIT: u64 = 1u64 << 62;
pub const TOMBSTONE_BIT: u64 = 1u64 << 63;
const _: () = assert!(
(ADDRESS_MASK
| FILLER_TOTAL_MASK
| MODIFIED_BIT
| SEALED_BIT
| IN_NEW_VERSION_BIT
| READ_CACHE_BIT
| TOMBSTONE_BIT)
== u64::MAX
);
const _: () = assert!(size_of::<RecordHeader>() == HEADER_SIZE);
const _: () = assert!(align_of::<RecordHeader>() == 8);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
#[repr(C)]
pub struct RecordHeader {
pub prev_address: u64,
pub key_len: u32,
pub val_len: u32,
}
impl RecordHeader {
#[inline]
pub const fn new(prev_addr: u64, key_len: u32, val_len: u32, is_tombstone: bool) -> Result<Self> {
if prev_addr & !ADDRESS_MASK != 0 {
return Err(Error::AddressOverflow(prev_addr));
}
Ok(Self {
prev_address: prev_addr | if is_tombstone { TOMBSTONE_BIT } else { 0 },
key_len,
val_len,
})
}
#[inline]
pub const fn from_raw(prev_address: u64, key_len: u32, val_len: u32) -> Self {
Self {
prev_address,
key_len,
val_len,
}
}
#[inline]
pub const fn address(&self) -> u64 {
self.prev_address & ADDRESS_MASK
}
#[inline(always)]
pub const fn is_tombstone(&self) -> bool {
(self.prev_address & TOMBSTONE_BIT) != 0
}
#[inline(always)]
pub const fn is_modified(&self) -> bool {
(self.prev_address & MODIFIED_BIT) != 0
}
#[inline(always)]
pub const fn set_modified(&mut self, modified: bool) {
if modified {
self.prev_address |= MODIFIED_BIT;
} else {
self.prev_address &= !MODIFIED_BIT;
}
}
#[inline(always)]
pub const fn is_sealed(&self) -> bool {
(self.prev_address & SEALED_BIT) != 0
}
#[inline(always)]
pub const fn set_sealed(&mut self, sealed: bool) {
if sealed {
self.prev_address |= SEALED_BIT;
} else {
self.prev_address &= !SEALED_BIT;
}
}
#[inline(always)]
pub const fn is_in_new_version(&self) -> bool {
(self.prev_address & IN_NEW_VERSION_BIT) != 0
}
#[inline(always)]
pub const fn set_in_new_version(&mut self, in_new_version: bool) {
if in_new_version {
self.prev_address |= IN_NEW_VERSION_BIT;
} else {
self.prev_address &= !IN_NEW_VERSION_BIT;
}
}
#[inline(always)]
pub const fn is_read_cache(&self) -> bool {
(self.prev_address & READ_CACHE_BIT) != 0
}
#[inline(always)]
pub const fn set_read_cache(&mut self, is_read_cache: bool) {
if is_read_cache {
self.prev_address |= READ_CACHE_BIT;
} else {
self.prev_address &= !READ_CACHE_BIT;
}
}
#[inline(always)]
pub const fn filler_words(&self) -> u8 {
((self.prev_address & FILLER_WORDS_MASK) >> FILLER_WORDS_SHIFT) as u8
}
#[inline(always)]
pub const fn filler_rem(&self) -> u8 {
((self.prev_address & FILLER_REM_MASK) >> FILLER_REM_SHIFT) as u8
}
#[inline(always)]
pub const fn filler_bytes(&self) -> usize {
((self.filler_words() as usize) << 3) | (self.filler_rem() as usize)
}
#[inline(always)]
pub const fn set_filler_bytes(&mut self, total_bytes: usize) {
let clamped = if total_bytes > MAX_FILLER_BYTES {
MAX_FILLER_BYTES
} else {
total_bytes
};
let words = ((clamped >> 3) as u64) << FILLER_WORDS_SHIFT;
let rem = ((clamped & 7) as u64) << FILLER_REM_SHIFT;
self.prev_address = (self.prev_address & !FILLER_TOTAL_MASK) | words | rem;
}
#[inline(always)]
pub const fn set_filler_words(&mut self, words: u8) {
self.prev_address =
(self.prev_address & !FILLER_WORDS_MASK) | ((words as u64) << FILLER_WORDS_SHIFT);
}
#[inline(always)]
pub const fn val_capacity(&self) -> usize {
(self.val_len as usize).saturating_add(self.filler_bytes())
}
#[inline]
pub const fn key_len(&self) -> u32 {
self.key_len
}
#[inline]
pub const fn val_len(&self) -> u32 {
self.val_len
}
#[inline]
pub const fn record_size(&self) -> usize {
HEADER_SIZE
.saturating_add(self.key_len as usize)
.saturating_add(self.val_len as usize)
}
#[inline(always)]
pub const fn physical_size(&self) -> usize {
self.record_size().saturating_add(self.filler_bytes())
}
#[inline]
pub const fn checked_record_size(&self) -> Option<usize> {
checked_record_size(self.key_len as usize, self.val_len as usize)
}
#[inline(always)]
pub const fn checked_physical_size(&self) -> Option<usize> {
match self.checked_record_size() {
Some(s) => s.checked_add(self.filler_bytes()),
None => None,
}
}
#[inline]
pub const fn set_address(&mut self, prev_addr: u64) -> Result<()> {
if prev_addr & !ADDRESS_MASK != 0 {
return Err(Error::AddressOverflow(prev_addr));
}
self.prev_address = (prev_addr & ADDRESS_MASK) | (self.prev_address & !ADDRESS_MASK);
Ok(())
}
#[inline]
pub const fn set_tombstone(&mut self, is_tombstone: bool) {
if is_tombstone {
self.prev_address |= TOMBSTONE_BIT;
} else {
self.prev_address &= !TOMBSTONE_BIT;
}
}
#[inline(always)]
pub const fn flip_tombstone(&mut self) -> bool {
self.prev_address ^= TOMBSTONE_BIT;
self.is_tombstone()
}
#[inline(always)]
pub const fn can_update_in_place(&self, new_val_len: usize) -> bool {
!self.is_tombstone() && self.val_len as usize == new_val_len
}
#[inline(always)]
pub const fn can_update_with_slack(&self, new_val_len: usize) -> bool {
!self.is_tombstone()
&& new_val_len <= self.val_capacity()
&& (self.val_capacity() - new_val_len) <= MAX_FILLER_BYTES
}
#[inline]
pub fn encode_bitcode(&self) -> Vec<u8> {
bitcode::encode(self)
}
#[inline]
pub fn decode_bitcode(src: &[u8]) -> Result<Self> {
bitcode::decode(src).map_err(Error::from)
}
#[inline]
pub const fn to_bytes(&self) -> [u8; HEADER_SIZE] {
let p = self.prev_address.to_le_bytes();
let k = self.key_len.to_le_bytes();
let v = self.val_len.to_le_bytes();
[
p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], k[0], k[1], k[2], k[3], v[0], v[1], v[2],
v[3],
]
}
#[inline]
pub const fn from_bytes(bytes: [u8; HEADER_SIZE]) -> Self {
let prev_address = u64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]);
let key_len = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
let val_len = u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
Self {
prev_address,
key_len,
val_len,
}
}
#[inline]
pub fn write_to_slice(&self, dst: &mut [u8]) -> Result<()> {
if let Some(chunk) = dst.first_chunk_mut::<HEADER_SIZE>() {
*chunk = self.to_bytes();
Ok(())
} else {
Err(Error::BufferTooShort {
expected: HEADER_SIZE,
actual: dst.len(),
})
}
}
#[inline]
pub const fn from_slice(src: &[u8]) -> Result<Self> {
match src.first_chunk::<HEADER_SIZE>() {
Some(chunk) => Ok(Self::from_bytes(*chunk)),
None => Err(Error::BufferTooShort {
expected: HEADER_SIZE,
actual: src.len(),
}),
}
}
#[inline(always)]
pub const fn is_null(&self) -> bool {
self.prev_address == 0 && self.key_len == 0 && self.val_len == 0
}
}