use std::fmt::Debug;
use std::hash::Hash;
use std::io::Write;
use std::{io, str};
use zerocopy::{ConvertError, FromBytes, Immutable, IntoBytes, KnownLayout};
use super::read_err;
#[repr(C)]
#[derive(Copy, Clone, Debug, FromBytes, Immutable, IntoBytes, KnownLayout)]
pub(super) struct Header {
pub key_type: [u8; 8],
pub buckets_pos: u64,
pub buckets_count: u64,
}
pub(super) type ValuesLen = u32;
pub(super) type BucketOffset = u64;
pub enum ReadResult<T> {
Ok(T),
Incomplete,
Invalid(io::Error),
}
pub trait Key: Debug + PartialEq + Sync + Hash {
const ALIGN: usize;
const NAME: [u8; 8];
const KEY_SIZE_EST: usize;
fn write_bytes(&self) -> usize;
fn write(&self, buf: &mut impl Write) -> io::Result<()>;
fn matches(&self, buf: &[u8]) -> bool;
fn from_bytes(buf: &[u8]) -> ReadResult<&Self>;
}
impl Key for str {
const ALIGN: usize = align_of::<u8>();
const KEY_SIZE_EST: usize = 512;
const NAME: [u8; 8] = *b"str\0\0\0\0\0";
fn write_bytes(&self) -> usize {
self.len() + 1
}
fn write(&self, buf: &mut impl Write) -> io::Result<()> {
buf.write_all(self.as_bytes())?;
buf.write_all(&[0xFF])?; Ok(())
}
fn matches(&self, buf: &[u8]) -> bool {
buf.get(..self.len()) == Some(IntoBytes::as_bytes(self))
&& buf.get(self.len()) == Some(&0xFF)
}
fn from_bytes(buf: &[u8]) -> ReadResult<&Self> {
let Some(len) = buf.iter().position(|&b| b == 0xFF) else {
return ReadResult::Incomplete;
};
match str::from_utf8(&buf[..len]) {
Err(_) => ReadResult::Invalid(read_err("String key is not valid UTF-8")),
Ok(s) => ReadResult::Ok(s),
}
}
}
impl Key for i64 {
const ALIGN: usize = align_of::<i64>();
const KEY_SIZE_EST: usize = size_of::<i64>();
const NAME: [u8; 8] = *b"i64\0\0\0\0\0";
fn write_bytes(&self) -> usize {
size_of::<i64>()
}
fn write(&self, buf: &mut impl Write) -> io::Result<()> {
buf.write_all(self.as_bytes())
}
fn matches(&self, buf: &[u8]) -> bool {
buf.get(..size_of::<i64>()) == Some(self.as_bytes())
}
fn from_bytes(buf: &[u8]) -> ReadResult<&Self> {
match i64::ref_from_prefix(buf) {
Ok((res, _)) => ReadResult::Ok(res),
Err(ConvertError::Alignment(_)) => {
ReadResult::Invalid(read_err("i64 key is not properly aligned"))
}
Err(ConvertError::Size(_)) => ReadResult::Incomplete,
}
}
}
impl Key for u128 {
const ALIGN: usize = size_of::<u128>();
const KEY_SIZE_EST: usize = size_of::<u128>();
const NAME: [u8; 8] = *b"u128\0\0\0\0";
fn write_bytes(&self) -> usize {
size_of::<u128>()
}
fn write(&self, buf: &mut impl Write) -> io::Result<()> {
buf.write_all(self.as_bytes())
}
fn matches(&self, buf: &[u8]) -> bool {
buf.get(..size_of::<u128>()) == Some(self.as_bytes())
}
fn from_bytes(buf: &[u8]) -> ReadResult<&Self> {
match Self::ref_from_prefix(buf) {
Ok((res, _)) => ReadResult::Ok(res),
Err(ConvertError::Alignment(_)) => {
ReadResult::Invalid(read_err("u128 key is not properly aligned"))
}
Err(ConvertError::Size(_)) => ReadResult::Incomplete,
}
}
}
#[derive(Copy, Clone)]
#[expect(clippy::enum_variant_names)]
pub enum MaybeIncompleteEntryKind {
KeyOnly,
KeyAndValuesLen,
KeyAndValues,
}
impl MaybeIncompleteEntryKind {
pub fn estimated_size<K: Key + ?Sized, V>(self) -> usize {
let field2key = K::KEY_SIZE_EST;
let field3pad = size_of::<V >().saturating_sub(1);
let field4len = size_of::<ValuesLen>();
let field5pad = size_of::<V>().saturating_sub(1);
let field6vals = size_of::<V>() * 2;
match self {
Self::KeyOnly => field2key,
Self::KeyAndValuesLen => field2key + field3pad + field4len,
Self::KeyAndValues => field2key + field3pad + field4len + field5pad + field6vals,
}
}
}
pub(super) enum MaybeIncompleteEntry<'a, K: Key + ?Sized, V> {
None,
Key(&'a K),
KeyAndValuesLen(&'a K, u32),
KeyAndValues(&'a K, &'a [V], &'a [u8]),
}
impl<K: Key + ?Sized, V> Copy for MaybeIncompleteEntry<'_, K, V> {}
impl<K: Key + ?Sized, V> Clone for MaybeIncompleteEntry<'_, K, V> {
fn clone(&self) -> Self {
*self
}
}
impl<'a, K: Key + ?Sized, V: FromBytes + Immutable> MaybeIncompleteEntry<'a, K, V> {
pub fn satisfies_kind(self, kind: MaybeIncompleteEntryKind) -> bool {
match kind {
MaybeIncompleteEntryKind::KeyOnly => self.key().is_some(),
MaybeIncompleteEntryKind::KeyAndValuesLen => {
self.key().is_some() && self.values_len().is_some()
}
MaybeIncompleteEntryKind::KeyAndValues => {
self.key().is_some() && self.values().is_some()
}
}
}
pub fn key(self) -> Option<&'a K> {
match self {
MaybeIncompleteEntry::None => None,
MaybeIncompleteEntry::Key(key) => Some(key),
MaybeIncompleteEntry::KeyAndValuesLen(key, _) => Some(key),
MaybeIncompleteEntry::KeyAndValues(key, _, _) => Some(key),
}
}
pub fn values_len(self) -> Option<u32> {
match self {
MaybeIncompleteEntry::None => None,
MaybeIncompleteEntry::Key(_) => None,
MaybeIncompleteEntry::KeyAndValuesLen(_, values_len) => Some(values_len),
MaybeIncompleteEntry::KeyAndValues(_, values, _) => Some(values.len() as u32),
}
}
pub fn values(self) -> Option<&'a [V]> {
match self {
MaybeIncompleteEntry::None => None,
MaybeIncompleteEntry::Key(_) => None,
MaybeIncompleteEntry::KeyAndValuesLen(_, _) => None,
MaybeIncompleteEntry::KeyAndValues(_, values, _) => Some(values),
}
}
pub fn partial_parse(buf: &'a [u8]) -> io::Result<MaybeIncompleteEntry<'a, K, V>> {
let Some(buf) = align_slice_to(K::ALIGN, buf) else {
return Ok(MaybeIncompleteEntry::None);
};
let key = match K::from_bytes(buf) {
ReadResult::Ok(k) => k,
ReadResult::Incomplete => return Ok(MaybeIncompleteEntry::None),
ReadResult::Invalid(e) => return Err(e),
};
let Some(buf) = buf.get(key.write_bytes()..) else {
return Ok(MaybeIncompleteEntry::Key(key));
};
let Some(buf) = align_slice_to(size_of::<V >(), buf) else {
return Ok(MaybeIncompleteEntry::Key(key));
};
let (&values_len, buf) = match ValuesLen::ref_from_prefix(buf) {
Ok(v) => v,
Err(ConvertError::Alignment(_)) => {
return Err(read_err("ValuesLen is not properly aligned"));
}
Err(ConvertError::Size(_)) => return Ok(MaybeIncompleteEntry::Key(key)),
};
let Some(buf) = align_slice_to(size_of::<V>(), buf) else {
return Ok(MaybeIncompleteEntry::KeyAndValuesLen(key, values_len));
};
let (values, buf) = match <[V]>::ref_from_prefix_with_elems(buf, values_len as usize) {
Ok(v) => v,
Err(ConvertError::Alignment(_)) => {
return Err(read_err("Values are not properly aligned"));
}
Err(ConvertError::Size(_)) => {
return Ok(MaybeIncompleteEntry::KeyAndValuesLen(key, values_len));
}
};
Ok(MaybeIncompleteEntry::KeyAndValues(key, values, buf))
}
}
fn align_slice_to(alignment: usize, data: &[u8]) -> Option<&[u8]> {
data.get(data.as_ptr().align_offset(alignment)..)
}