1use crate::{
2 config::{BitCount, CounterConfig, CuckooConfiguration, LruConfig, TtlConfig},
3 filter::CuckooFilter,
4};
5use std::{
6 borrow::Borrow,
7 hash::{BuildHasher, Hash},
8};
9use std::{
10 borrow::BorrowMut,
11 ops::{Range, RangeInclusive},
12};
13
14#[derive(Debug, Hash, Clone, PartialEq, Eq)]
36pub struct Fingerprint {
37 data: u32,
38}
39
40impl Fingerprint {
41 pub(crate) const fn new(hash: u32, mask: u32) -> Self {
42 let mut fingerprint = hash & mask;
43 if fingerprint == 0 {
46 fingerprint = 1;
47 }
48
49 Self { data: fingerprint }
50 }
51
52 pub(crate) const fn is_empty(&self) -> bool {
54 self.data == 0
55 }
56
57 pub(crate) const fn data(&self) -> u32 {
59 self.data
60 }
61
62 pub fn matches_key<K: Hash + ?Sized, H: BuildHasher>(
67 &self,
68 key: &K,
69 filter: &CuckooFilter<H>,
70 ) -> bool {
71 filter.get_fingerprint(key) == *self
72 }
73}
74
75impl From<u32> for Fingerprint {
76 fn from(value: u32) -> Self {
77 Self { data: value }
78 }
79}
80
81#[derive(Clone, Debug, Eq, PartialEq)]
83pub(crate) struct DataBlockFieldConfiguration {
84 bytes: RangeInclusive<usize>,
89 mask: u64,
94 shift: usize,
103 in_value_mask: u32,
107}
108
109impl DataBlockFieldConfiguration {
110 pub(crate) fn new(bits: Range<usize>) -> Self {
115 debug_assert!(bits.len() <= BitCount::MAX.into());
117 let start_byte = bits.start / 8; let end_byte = (bits.end - 1) / 8;
119 let bytes = start_byte..=end_byte;
120 let shift = (end_byte + 1) * 8 - bits.end;
121 Self {
122 bytes,
123 shift,
124 mask: ((1u64 << bits.len()) - 1) << shift,
125 #[expect(clippy::cast_possible_truncation)]
128 in_value_mask: ((1u64 << bits.len()) - 1) as u32,
129 }
130 }
131
132 pub(crate) const fn value_mask(&self) -> u32 {
136 self.in_value_mask
137 }
138}
139
140pub(crate) struct DataBlock<T: Borrow<[u8]>>(T);
142
143impl<'a> From<&'a mut [u8]> for DataBlock<&'a mut [u8]> {
144 fn from(value: &'a mut [u8]) -> Self {
145 Self(value)
146 }
147}
148
149impl<'a> From<&'a [u8]> for DataBlock<&'a [u8]> {
150 fn from(value: &'a [u8]) -> Self {
151 Self(value)
152 }
153}
154
155impl From<Vec<u8>> for DataBlock<Vec<u8>> {
156 fn from(value: Vec<u8>) -> Self {
157 Self(value)
158 }
159}
160
161impl<T: Borrow<[u8]>> DataBlock<T> {
162 pub(crate) fn inner(&self) -> &[u8] {
164 self.0.borrow()
165 }
166
167 #[expect(clippy::cast_possible_truncation)]
169 pub(crate) fn load_bits(&self, config: &DataBlockFieldConfiguration) -> u32 {
170 let loaded = &self.0.borrow()[config.bytes.clone()];
171 let mut loaded_u64 = 0;
172 let len = loaded.len();
173 for (i, b) in loaded.iter().enumerate() {
174 loaded_u64 += (*b as u64) << ((len - (i + 1)) * 8)
175 }
176 ((loaded_u64 & config.mask) >> config.shift) as u32
177 }
178
179 pub(crate) fn get_fingerprint(&self, configuration: &CuckooConfiguration) -> Fingerprint {
181 self.load_bits(&configuration.fingerprint_field_config)
182 .into()
183 }
184
185 pub(crate) fn get_lru_counter(
187 &self,
188 configuration: &(LruConfig, DataBlockFieldConfiguration),
189 ) -> u32 {
190 self.load_bits(&configuration.1)
191 }
192
193 pub(crate) fn get_counter(
195 &self,
196 configuration: &(CounterConfig, DataBlockFieldConfiguration),
197 ) -> u32 {
198 self.load_bits(&configuration.1)
199 }
200
201 pub(crate) fn get_ttl(&self, configuration: &(TtlConfig, DataBlockFieldConfiguration)) -> u32 {
203 self.load_bits(&configuration.1)
204 }
205
206 pub(crate) fn occupied(&self, configuration: &CuckooConfiguration) -> bool {
208 !self.get_fingerprint(configuration).is_empty()
209 }
210}
211
212impl<T: BorrowMut<[u8]>> DataBlock<T> {
213 pub(crate) fn store_bits(&mut self, config: &DataBlockFieldConfiguration, value: u32) {
215 let masked_new_value = value & config.in_value_mask;
216 let loaded = &self.0.borrow()[config.bytes.clone()];
217 let len = loaded.len();
218 let mut loaded_u64 = 0;
219 for (i, b) in loaded.iter().enumerate() {
220 loaded_u64 += (*b as u64) << ((len - (i + 1)) * 8)
221 }
222 let masked_old_value = loaded_u64 & !config.mask;
223 let final_value = masked_old_value | ((masked_new_value as u64) << config.shift);
224 self.0.borrow_mut()[config.bytes.clone()]
225 .copy_from_slice(&final_value.to_be_bytes()[(8 - len)..]);
226 }
227
228 pub(crate) fn store_fingerprint(
230 &mut self,
231 fingerprint: &Fingerprint,
232 configuration: &CuckooConfiguration,
233 ) {
234 self.store_bits(&configuration.fingerprint_field_config, fingerprint.data);
235 }
236
237 pub(crate) fn reset(&mut self) {
239 self.0.borrow_mut().fill(0u8);
240 }
241
242 pub(crate) fn swap<U: BorrowMut<[u8]>>(&mut self, other: &mut DataBlock<U>) {
249 self.0.borrow_mut().swap_with_slice(other.0.borrow_mut());
250 }
251
252 pub(crate) fn copy_from<U: Borrow<[u8]>>(&mut self, other: &DataBlock<U>) {
259 self.0.borrow_mut().copy_from_slice(other.0.borrow());
260 }
261
262 pub(crate) fn merge_associated_from<U: Borrow<[u8]>>(
269 &mut self,
270 other: &DataBlock<U>,
271 configuration: &CuckooConfiguration,
272 ) {
273 if let Some(lru) = &configuration.lru_field_config {
274 let counter = self.load_bits(&lru.1);
275 let other = other.load_bits(&lru.1);
276 let mut new_counter = counter.saturating_add(other);
277 if new_counter > lru.1.value_mask() {
279 new_counter = lru.1.value_mask();
280 }
281 self.store_bits(&lru.1, new_counter);
282 }
283 if let Some(ttl) = &configuration.ttl_field_config {
284 let this_ttl = self.load_bits(&ttl.1);
285 let other = other.load_bits(&ttl.1);
286 self.store_bits(&ttl.1, this_ttl.max(other));
287 }
288 if let Some(counter_config) = &configuration.counter_field_config {
289 let counter = self.load_bits(&counter_config.1);
290 let other = other.load_bits(&counter_config.1);
291 let mut new_counter = counter.saturating_add(other);
292 if new_counter > counter_config.1.value_mask() {
294 new_counter = counter_config.1.value_mask();
295 }
296 self.store_bits(&counter_config.1, new_counter);
297 }
298 }
299
300 pub(crate) fn init_lru_counter(
302 &mut self,
303 configuration: &(LruConfig, DataBlockFieldConfiguration),
304 ) {
305 let mut value = configuration.0.starting_value;
306 if value > configuration.1.value_mask() {
308 value = configuration.1.value_mask();
309 }
310 self.store_bits(&configuration.1, value);
311 }
312
313 pub(crate) fn inc_lru_counter(
315 &mut self,
316 configuration: &(LruConfig, DataBlockFieldConfiguration),
317 ) {
318 let counter = self.load_bits(&configuration.1);
319 let mut new_counter = counter.saturating_add(configuration.0.increment);
320 if new_counter > configuration.1.value_mask() {
322 new_counter = configuration.1.value_mask();
323 }
324 self.store_bits(&configuration.1, new_counter);
325 }
326
327 pub(crate) fn age_lru_counter(
331 &mut self,
332 configuration: &(LruConfig, DataBlockFieldConfiguration),
333 ) -> bool {
334 let counter = self.load_bits(&configuration.1);
335 if configuration.0.remove_on_zero && counter == 0 {
336 self.reset();
337 true
338 } else {
339 self.store_bits(
340 &configuration.1,
341 configuration.0.aging_strategy.age_value(counter),
342 );
343 false
344 }
345 }
346
347 pub(crate) fn age_ttl_counter(
353 &mut self,
354 configuration: &(TtlConfig, DataBlockFieldConfiguration),
355 ) -> bool {
356 let mut counter = self.load_bits(&configuration.1);
357 counter = counter.saturating_sub(1);
358 self.store_bits(&configuration.1, counter);
359 if counter == 0 {
360 self.reset();
361 }
362 counter == 0
363 }
364
365 pub(crate) fn update_counter(
368 &mut self,
369 configuration: &(CounterConfig, DataBlockFieldConfiguration),
370 by: i32,
371 ) {
372 let counter = self.load_bits(&configuration.1);
373 let mut new_counter = counter.saturating_add_signed(by);
374 if new_counter > configuration.1.value_mask() {
376 new_counter = configuration.1.value_mask();
377 }
378 self.store_bits(&configuration.1, new_counter);
379 }
380
381 pub(crate) fn set_ttl(
383 &mut self,
384 configuration: &(TtlConfig, DataBlockFieldConfiguration),
385 ttl: u32,
386 ) {
387 self.store_bits(&configuration.1, ttl);
388 }
389}
390
391#[cfg(test)]
392#[expect(clippy::unwrap_used)]
393mod tests {
394 use super::*;
395
396 #[test]
397 fn test_load_store_for_each_bit_count() {
398 let mut data = [0u8; 4];
399 let mut data_block = DataBlock::from(&mut data[..]);
400 for i in 1usize..=32usize {
401 let field_config = DataBlockFieldConfiguration::new(0..i);
402 let value: u32 = ((1u64 << i) - 1).try_into().unwrap();
404 data_block.reset();
405 data_block.store_bits(&field_config, value);
406 assert_eq!(
407 data_block.load_bits(&field_config),
408 value,
409 "Loaded value was different for bit count {i}"
410 );
411 }
412 }
413
414 #[test]
415 fn test_load_store_at_non_byte_boundary() {
416 let data_start = 13;
418 let fp_config = DataBlockFieldConfiguration::new(0..data_start);
419 let fp_value = 0b1010101010101u32;
420 let mut data = [0u8; 7];
421 let mut data_block = DataBlock::from(&mut data[..]);
422 data_block.store_bits(&fp_config, fp_value);
423 for i in 1usize..=32usize {
424 let field_config = DataBlockFieldConfiguration::new(data_start..data_start + i);
425 let value: u32 = ((1u64 << i) - 1).try_into().unwrap();
427 data_block.store_bits(&field_config, 0);
428 data_block.store_bits(&field_config, value);
429 assert_eq!(
430 data_block.load_bits(&field_config),
431 value,
432 "Loaded value was different for bit count {i}"
433 );
434 }
435 assert_eq!(
436 data_block.load_bits(&fp_config),
437 fp_value,
438 "Loads/stores wrote outside of their bits"
439 );
440 }
441
442 #[test]
443 fn test_load_store_full_config() {
444 let config = CuckooConfiguration::builder(100_000)
445 .fingerprint_bits(32.try_into().unwrap())
446 .with_lru(LruConfig::default())
447 .with_ttl(TtlConfig {
448 ttl: 10.try_into().unwrap(),
449 ttl_bits: 4.try_into().unwrap(),
450 })
451 .build()
452 .unwrap();
453 let mut data = [0u8; 6];
454 let mut data_block = DataBlock::from(&mut data[..]);
455 let fp_value = 0b1010101010101u32;
456
457 data_block.store_fingerprint(&Fingerprint { data: fp_value }, &config);
458 data_block.inc_lru_counter(config.lru_field_config.as_ref().unwrap());
459 data_block.set_ttl(config.ttl_field_config.as_ref().unwrap(), 10);
460
461 data_block.age_lru_counter(config.lru_field_config.as_ref().unwrap());
462 data_block.age_ttl_counter(config.ttl_field_config.as_ref().unwrap());
463
464 assert_eq!(data_block.get_fingerprint(&config).data(), fp_value);
465 assert_eq!(
466 data_block.get_ttl(config.ttl_field_config.as_ref().unwrap()),
467 9
468 );
469 assert_eq!(
470 data_block.get_lru_counter(config.lru_field_config.as_ref().unwrap()),
471 0
472 );
473 }
474
475 #[test]
476 fn test_inc_counter_saturation() {
477 let config = CuckooConfiguration::builder(100_000)
478 .fingerprint_bits(8.try_into().unwrap())
479 .with_lru(LruConfig {
480 counter_bits: 2.try_into().unwrap(),
481 ..Default::default()
482 })
483 .build()
484 .unwrap();
485
486 let mut data = [0u8; 2];
487 let mut data_block = DataBlock::from(&mut data[..]);
488
489 let lru_config = config.lru_field_config.as_ref().unwrap();
490
491 data_block.inc_lru_counter(lru_config);
492 data_block.inc_lru_counter(lru_config);
493 data_block.inc_lru_counter(lru_config);
494
495 assert_eq!(data_block.get_lru_counter(lru_config), 3);
496
497 data_block.inc_lru_counter(lru_config);
499 assert_eq!(data_block.get_lru_counter(lru_config), 3);
500
501 data_block.age_lru_counter(lru_config);
502 assert_eq!(data_block.get_lru_counter(lru_config), 1);
503 }
504}