use core::{iter::FusedIterator, ops::Deref};
use wbase::simd::fast_key_eq;
use whasher::{HashMap, HashMapExt, HashSet, hash_set_with_capacity};
use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FieldValueRef<'a> {
pub value: &'a [u8],
pub expire_at_ms: Option<u64>,
}
impl<'a> Deref for FieldValueRef<'a> {
type Target = [u8];
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.value
}
}
impl<'a> AsRef<[u8]> for FieldValueRef<'a> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.value
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HashEntryRef<'a> {
pub field: &'a [u8],
pub value: &'a [u8],
pub expire_at_ms: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct CompactHashIter<'a> {
slice: &'a [u8],
offset: usize,
remaining: usize,
}
impl<'a> Iterator for CompactHashIter<'a> {
type Item = HashEntryRef<'a>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 || self.offset >= self.slice.len() {
return None;
}
let (entry_len, entry) = CompactHashCodec::parse_entry(self.slice, self.offset).ok()?;
self.offset += entry_len;
self.remaining -= 1;
Some(entry)
}
#[inline(always)]
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl<'a> ExactSizeIterator for CompactHashIter<'a> {
#[inline(always)]
fn len(&self) -> usize {
self.remaining
}
}
impl<'a> FusedIterator for CompactHashIter<'a> {}
pub const COMPACT_HASH_COUNT_SIZE: usize = 2;
pub const COMPACT_HASH_LEN_SIZE: usize = 2;
pub const COMPACT_HASH_EXPIRE_FLAG_SIZE: usize = 1;
pub const COMPACT_HASH_EXPIRE_TIME_SIZE: usize = 8;
pub struct CompactHashCodec;
impl CompactHashCodec {
#[inline]
pub const fn count(slice: &[u8]) -> Result<usize> {
match slice {
[b0, b1, ..] => Ok(u16::from_be_bytes([*b0, *b1]) as usize),
_ => Err(Error::BufferTooShort {
expected: COMPACT_HASH_COUNT_SIZE,
actual: slice.len(),
}),
}
}
#[inline]
pub(crate) fn parse_entry(slice: &[u8], offset: usize) -> Result<(usize, HashEntryRef<'_>)> {
if offset + COMPACT_HASH_LEN_SIZE > slice.len() {
return Err(Error::BufferTooShort {
expected: offset + COMPACT_HASH_LEN_SIZE,
actual: slice.len(),
});
}
let f_len = u16::from_be_bytes([slice[offset], slice[offset + 1]]) as usize;
let f_start = offset + COMPACT_HASH_LEN_SIZE;
let f_end = f_start + f_len;
if f_end + COMPACT_HASH_LEN_SIZE > slice.len() {
return Err(Error::BufferTooShort {
expected: f_end + COMPACT_HASH_LEN_SIZE,
actual: slice.len(),
});
}
let v_len = u16::from_be_bytes([slice[f_end], slice[f_end + 1]]) as usize;
let v_start = f_end + COMPACT_HASH_LEN_SIZE;
let v_end = v_start + v_len;
if v_end >= slice.len() {
return Err(Error::BufferTooShort {
expected: v_end + 1,
actual: slice.len(),
});
}
let expire_flag = slice[v_end];
let (entry_len, expire_at_ms) = if expire_flag != 0 {
let exp_start = v_end + COMPACT_HASH_EXPIRE_FLAG_SIZE;
let exp_end = exp_start + COMPACT_HASH_EXPIRE_TIME_SIZE;
if exp_end > slice.len() {
return Err(Error::BufferTooShort {
expected: exp_end,
actual: slice.len(),
});
}
let exp = match slice[exp_start..exp_end].first_chunk::<COMPACT_HASH_EXPIRE_TIME_SIZE>() {
Some(b) => u64::from_be_bytes(*b),
None => {
return Err(Error::BufferTooShort {
expected: exp_end,
actual: slice.len(),
});
}
};
(exp_end - offset, Some(exp))
} else {
(v_end + COMPACT_HASH_EXPIRE_FLAG_SIZE - offset, None)
};
Ok((
entry_len,
HashEntryRef {
field: &slice[f_start..f_end],
value: &slice[v_start..v_end],
expire_at_ms,
},
))
}
#[inline]
pub(crate) fn write_entry_bytes(
dst: &mut [u8],
field: &[u8],
value: &[u8],
expire_at_ms: Option<u64>,
) {
let mut cur = 0;
dst[cur..cur + COMPACT_HASH_LEN_SIZE].copy_from_slice(&(field.len() as u16).to_be_bytes());
cur += COMPACT_HASH_LEN_SIZE;
dst[cur..cur + field.len()].copy_from_slice(field);
cur += field.len();
dst[cur..cur + COMPACT_HASH_LEN_SIZE].copy_from_slice(&(value.len() as u16).to_be_bytes());
cur += COMPACT_HASH_LEN_SIZE;
dst[cur..cur + value.len()].copy_from_slice(value);
cur += value.len();
if let Some(exp) = expire_at_ms {
dst[cur] = 1;
cur += COMPACT_HASH_EXPIRE_FLAG_SIZE;
dst[cur..cur + COMPACT_HASH_EXPIRE_TIME_SIZE].copy_from_slice(&exp.to_be_bytes());
} else {
dst[cur] = 0;
}
}
#[inline]
pub fn find<'a>(slice: &'a [u8], field: &[u8]) -> Option<FieldValueRef<'a>> {
let count = Self::count(slice).ok()?;
let mut rest = slice.get(COMPACT_HASH_COUNT_SIZE..)?;
let target_len = field.len();
for _ in 0..count {
let (len_bytes, after_f_len) = rest.split_first_chunk::<COMPACT_HASH_LEN_SIZE>()?;
let f_len = u16::from_be_bytes(*len_bytes) as usize;
let (f_bytes, after_field) = after_f_len.split_at_checked(f_len)?;
if f_len == target_len && fast_key_eq(f_bytes, field) {
let (v_len_bytes, after_v_len) =
after_field.split_first_chunk::<COMPACT_HASH_LEN_SIZE>()?;
let v_len = u16::from_be_bytes(*v_len_bytes) as usize;
let (val, after_val) = after_v_len.split_at_checked(v_len)?;
let (&flag, after_flag) = after_val.split_first()?;
let expire_at_ms = if flag != 0 {
let (exp_bytes, _) = after_flag.split_first_chunk::<COMPACT_HASH_EXPIRE_TIME_SIZE>()?;
Some(u64::from_be_bytes(*exp_bytes))
} else {
None
};
return Some(FieldValueRef {
value: val,
expire_at_ms,
});
}
let (v_len_bytes, after_v_len) = after_field.split_first_chunk::<COMPACT_HASH_LEN_SIZE>()?;
let v_len = u16::from_be_bytes(*v_len_bytes) as usize;
let (_, after_val) = after_v_len.split_at_checked(v_len)?;
let (&flag, after_flag) = after_val.split_first()?;
let exp_size = if flag != 0 {
COMPACT_HASH_EXPIRE_TIME_SIZE
} else {
0
};
let (_, next_rest) = after_flag.split_at_checked(exp_size)?;
rest = next_rest;
}
None
}
#[inline(always)]
pub fn find_field<'a>(slice: &'a [u8], field: &[u8]) -> Option<&'a [u8]> {
Self::find(slice, field).map(|r| r.value)
}
pub fn set_field(
buf: &mut Vec<u8>,
field: &[u8],
value: &[u8],
expire_at_ms: Option<u64>,
) -> Result<bool> {
if field.len() > u16::MAX as usize {
return Err(Error::KeyLengthOverflow(field.len()));
}
if value.len() > u16::MAX as usize {
return Err(Error::ValueLengthOverflow(value.len()));
}
if buf.is_empty() {
buf.extend_from_slice(&0u16.to_be_bytes());
} else if buf.len() < COMPACT_HASH_COUNT_SIZE {
return Err(Error::BufferTooShort {
expected: COMPACT_HASH_COUNT_SIZE,
actual: buf.len(),
});
}
let count = u16::from_be_bytes([buf[0], buf[1]]) as usize;
let mut offset = COMPACT_HASH_COUNT_SIZE;
let mut found = None;
for _ in 0..count {
let (entry_len, entry) = Self::parse_entry(buf, offset)?;
if entry.field.len() == field.len() && fast_key_eq(entry.field, field) {
found = Some((offset, offset + entry_len));
break;
}
offset += entry_len;
}
let new_entry_len = Self::entry_len(field, value, &expire_at_ms);
if let Some((start, end)) = found {
let old_len = end - start;
let old_buf_len = buf.len();
if new_entry_len > old_len {
let diff = new_entry_len - old_len;
buf.reserve(diff);
buf.resize(old_buf_len + diff, 0);
buf.copy_within(end..old_buf_len, start + new_entry_len);
} else if new_entry_len < old_len {
let diff = old_len - new_entry_len;
buf.copy_within(end..old_buf_len, start + new_entry_len);
buf.truncate(old_buf_len - diff);
}
Self::write_entry_bytes(
&mut buf[start..start + new_entry_len],
field,
value,
expire_at_ms,
);
Ok(false)
} else {
if count >= u16::MAX as usize {
return Err(Error::CompactCountOverflow(count + 1));
}
let new_count = (count + 1) as u16;
buf[0..COMPACT_HASH_COUNT_SIZE].copy_from_slice(&new_count.to_be_bytes());
let old_buf_len = buf.len();
buf.reserve(new_entry_len);
buf.resize(old_buf_len + new_entry_len, 0);
Self::write_entry_bytes(
&mut buf[old_buf_len..old_buf_len + new_entry_len],
field,
value,
expire_at_ms,
);
Ok(true)
}
}
pub fn delete_field(buf: &mut Vec<u8>, field: &[u8]) -> Result<bool> {
if buf.len() < COMPACT_HASH_COUNT_SIZE {
return Ok(false);
}
let count = u16::from_be_bytes([buf[0], buf[1]]) as usize;
let mut offset = COMPACT_HASH_COUNT_SIZE;
for _ in 0..count {
let (entry_len, entry) = Self::parse_entry(buf, offset)?;
if entry.field.len() == field.len() && fast_key_eq(entry.field, field) {
buf.copy_within(offset + entry_len.., offset);
buf.truncate(buf.len() - entry_len);
let new_count = (count - 1) as u16;
buf[0..COMPACT_HASH_COUNT_SIZE].copy_from_slice(&new_count.to_be_bytes());
return Ok(true);
}
offset += entry_len;
}
Ok(false)
}
pub fn purge_expired(buf: &mut Vec<u8>, now: u64) -> Result<usize> {
if buf.len() < COMPACT_HASH_COUNT_SIZE {
return Ok(0);
}
let count = u16::from_be_bytes([buf[0], buf[1]]) as usize;
let mut offset = COMPACT_HASH_COUNT_SIZE;
let mut write_offset = COMPACT_HASH_COUNT_SIZE;
let mut purged = 0;
let mut new_count = 0u16;
for _ in 0..count {
let (entry_len, entry) = Self::parse_entry(buf, offset)?;
let is_expired = entry.expire_at_ms.is_some_and(|exp| exp <= now);
if is_expired {
purged += 1;
} else {
if write_offset != offset {
buf.copy_within(offset..offset + entry_len, write_offset);
}
write_offset += entry_len;
new_count += 1;
}
offset += entry_len;
}
if purged > 0 {
buf.truncate(write_offset);
buf[0..COMPACT_HASH_COUNT_SIZE].copy_from_slice(&new_count.to_be_bytes());
}
Ok(purged)
}
pub fn purge_and_delete(buf: &mut Vec<u8>, fields: &[&[u8]], now: u64) -> Result<(usize, usize)> {
if buf.len() < COMPACT_HASH_COUNT_SIZE {
return Ok((0, 0));
}
let count = u16::from_be_bytes([buf[0], buf[1]]) as usize;
let mut targets: HashSet<&[u8]> = hash_set_with_capacity(fields.len());
for &f in fields {
targets.insert(f);
}
let mut offset = COMPACT_HASH_COUNT_SIZE;
let mut write_offset = COMPACT_HASH_COUNT_SIZE;
let mut purged = 0;
let mut deleted = 0;
let mut new_count = 0u16;
for _ in 0..count {
let (entry_len, entry) = Self::parse_entry(buf, offset)?;
let expired = entry.expire_at_ms.is_some_and(|exp| exp <= now);
if expired {
purged += 1;
} else if targets.remove(entry.field) {
deleted += 1;
} else {
if write_offset != offset {
buf.copy_within(offset..offset + entry_len, write_offset);
}
write_offset += entry_len;
new_count += 1;
}
offset += entry_len;
}
if purged + deleted > 0 {
buf.truncate(write_offset);
buf[0..COMPACT_HASH_COUNT_SIZE].copy_from_slice(&new_count.to_be_bytes());
}
Ok((purged, deleted))
}
#[inline]
pub fn iter_fields(slice: &[u8]) -> CompactHashIter<'_> {
let count = if slice.len() >= COMPACT_HASH_COUNT_SIZE {
u16::from_be_bytes([slice[0], slice[1]]) as usize
} else {
0
};
CompactHashIter {
slice,
offset: COMPACT_HASH_COUNT_SIZE,
remaining: count,
}
}
pub fn encode<'a, I>(entries: I) -> Result<Vec<u8>>
where
I: IntoIterator<Item = (&'a [u8], &'a [u8], Option<u64>)>,
{
let iter = entries.into_iter();
let (lower, _) = iter.size_hint();
let mut items: Vec<(&[u8], &[u8], Option<u64>)> = Vec::with_capacity(lower);
let mut index: HashMap<&[u8], usize> = HashMap::with_capacity(lower);
for (field, value, expire_at_ms) in iter {
if field.len() > u16::MAX as usize {
return Err(Error::KeyLengthOverflow(field.len()));
}
if value.len() > u16::MAX as usize {
return Err(Error::ValueLengthOverflow(value.len()));
}
match index.get(field) {
Some(&i) => {
items[i].1 = value;
items[i].2 = expire_at_ms;
}
None => {
index.insert(field, items.len());
items.push((field, value, expire_at_ms));
}
}
}
if items.len() > u16::MAX as usize {
return Err(Error::CompactCountOverflow(items.len()));
}
let payload_len: usize = items.iter().map(|(f, v, e)| Self::entry_len(f, v, e)).sum();
let mut buf = Vec::with_capacity(COMPACT_HASH_COUNT_SIZE + payload_len);
buf.extend_from_slice(&(items.len() as u16).to_be_bytes());
for (field, value, expire_at_ms) in items {
buf.extend_from_slice(&(field.len() as u16).to_be_bytes());
buf.extend_from_slice(field);
buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
buf.extend_from_slice(value);
match expire_at_ms {
Some(exp) => {
buf.push(1);
buf.extend_from_slice(&exp.to_be_bytes());
}
None => buf.push(0),
}
}
Ok(buf)
}
#[inline(always)]
fn entry_len(field: &[u8], value: &[u8], expire_at_ms: &Option<u64>) -> usize {
COMPACT_HASH_LEN_SIZE
+ field.len()
+ COMPACT_HASH_LEN_SIZE
+ value.len()
+ COMPACT_HASH_EXPIRE_FLAG_SIZE
+ if expire_at_ms.is_some() {
COMPACT_HASH_EXPIRE_TIME_SIZE
} else {
0
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct CompactHash {
raw: Vec<u8>,
}
impl CompactHash {
#[inline]
pub fn new() -> Self {
Self {
raw: vec![0u8; COMPACT_HASH_COUNT_SIZE],
}
}
#[inline]
pub fn with_capacity(cap: usize) -> Self {
let mut raw = Vec::with_capacity(cap.max(COMPACT_HASH_COUNT_SIZE));
raw.extend_from_slice(&0u16.to_be_bytes());
Self { raw }
}
#[inline]
pub fn from_vec(raw: Vec<u8>) -> Result<Self> {
let count = CompactHashCodec::count(&raw)?;
let mut offset = COMPACT_HASH_COUNT_SIZE;
for _ in 0..count {
let (entry_len, _) = CompactHashCodec::parse_entry(&raw, offset)?;
offset += entry_len;
}
if offset != raw.len() {
return Err(Error::CorruptedCompactData(
"紧凑哈希条目总长度与缓冲区不一致",
));
}
Ok(Self { raw })
}
#[inline(always)]
pub fn as_slice(&self) -> &[u8] {
&self.raw
}
#[inline(always)]
pub fn into_vec(self) -> Vec<u8> {
self.raw
}
#[inline(always)]
pub fn len(&self) -> usize {
CompactHashCodec::count(&self.raw).unwrap_or(0)
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline(always)]
pub fn find(&self, field: &[u8]) -> Option<FieldValueRef<'_>> {
CompactHashCodec::find(&self.raw, field)
}
#[inline(always)]
pub fn find_field(&self, field: &[u8]) -> Option<&[u8]> {
CompactHashCodec::find_field(&self.raw, field)
}
#[inline(always)]
pub fn set_field(
&mut self,
field: &[u8],
value: &[u8],
expire_at_ms: Option<u64>,
) -> Result<bool> {
CompactHashCodec::set_field(&mut self.raw, field, value, expire_at_ms)
}
#[inline(always)]
pub fn delete_field(&mut self, field: &[u8]) -> Result<bool> {
CompactHashCodec::delete_field(&mut self.raw, field)
}
#[inline(always)]
pub fn purge_expired(&mut self, now: u64) -> Result<usize> {
CompactHashCodec::purge_expired(&mut self.raw, now)
}
#[inline(always)]
pub fn purge_and_delete(&mut self, fields: &[&[u8]], now: u64) -> Result<(usize, usize)> {
CompactHashCodec::purge_and_delete(&mut self.raw, fields, now)
}
#[inline(always)]
pub fn iter_fields(&self) -> CompactHashIter<'_> {
CompactHashCodec::iter_fields(&self.raw)
}
}
impl Deref for CompactHash {
type Target = [u8];
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.raw
}
}
impl AsRef<[u8]> for CompactHash {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
&self.raw
}
}