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 inc_lru_counter(
302 &mut self,
303 configuration: &(LruConfig, DataBlockFieldConfiguration),
304 ) {
305 let counter = self.load_bits(&configuration.1);
306 let mut new_counter = counter.saturating_add(1);
307 if new_counter > configuration.1.value_mask() {
309 new_counter = configuration.1.value_mask();
310 }
311 self.store_bits(&configuration.1, new_counter);
312 }
313
314 pub(crate) fn age_lru_counter(
318 &mut self,
319 configuration: &(LruConfig, DataBlockFieldConfiguration),
320 ) -> bool {
321 let counter = self.load_bits(&configuration.1);
322 if configuration.0.remove_on_zero && counter == 0 {
323 self.reset();
324 true
325 } else {
326 self.store_bits(&configuration.1, counter >> 1);
327 false
328 }
329 }
330
331 pub(crate) fn age_ttl_counter(
337 &mut self,
338 configuration: &(TtlConfig, DataBlockFieldConfiguration),
339 ) -> bool {
340 let mut counter = self.load_bits(&configuration.1);
341 counter = counter.saturating_sub(1);
342 self.store_bits(&configuration.1, counter);
343 if counter == 0 {
344 self.reset();
345 }
346 counter == 0
347 }
348
349 pub(crate) fn update_counter(
352 &mut self,
353 configuration: &(CounterConfig, DataBlockFieldConfiguration),
354 by: i32,
355 ) {
356 let counter = self.load_bits(&configuration.1);
357 let mut new_counter = counter.saturating_add_signed(by);
358 if new_counter > configuration.1.value_mask() {
360 new_counter = configuration.1.value_mask();
361 }
362 self.store_bits(&configuration.1, new_counter);
363 }
364
365 pub(crate) fn set_ttl(
367 &mut self,
368 configuration: &(TtlConfig, DataBlockFieldConfiguration),
369 ttl: u32,
370 ) {
371 self.store_bits(&configuration.1, ttl);
372 }
373}
374
375#[cfg(test)]
376#[expect(clippy::unwrap_used)]
377mod tests {
378 use super::*;
379
380 #[test]
381 fn test_load_store_for_each_bit_count() {
382 let mut data = [0u8; 4];
383 let mut data_block = DataBlock::from(&mut data[..]);
384 for i in 1usize..=32usize {
385 let field_config = DataBlockFieldConfiguration::new(0..i);
386 let value: u32 = ((1u64 << i) - 1).try_into().unwrap();
388 data_block.reset();
389 data_block.store_bits(&field_config, value);
390 assert_eq!(
391 data_block.load_bits(&field_config),
392 value,
393 "Loaded value was different for bit count {i}"
394 );
395 }
396 }
397
398 #[test]
399 fn test_load_store_at_non_byte_boundary() {
400 let data_start = 13;
402 let fp_config = DataBlockFieldConfiguration::new(0..data_start);
403 let fp_value = 0b1010101010101u32;
404 let mut data = [0u8; 7];
405 let mut data_block = DataBlock::from(&mut data[..]);
406 data_block.store_bits(&fp_config, fp_value);
407 for i in 1usize..=32usize {
408 let field_config = DataBlockFieldConfiguration::new(data_start..data_start + i);
409 let value: u32 = ((1u64 << i) - 1).try_into().unwrap();
411 data_block.store_bits(&field_config, 0);
412 data_block.store_bits(&field_config, value);
413 assert_eq!(
414 data_block.load_bits(&field_config),
415 value,
416 "Loaded value was different for bit count {i}"
417 );
418 }
419 assert_eq!(
420 data_block.load_bits(&fp_config),
421 fp_value,
422 "Loads/stores wrote outside of their bits"
423 );
424 }
425
426 #[test]
427 fn test_load_store_full_config() {
428 let config = CuckooConfiguration::builder(100_000)
429 .fingerprint_bits(32.try_into().unwrap())
430 .with_lru(LruConfig::default())
431 .with_ttl(TtlConfig {
432 ttl: 10.try_into().unwrap(),
433 ttl_bits: 4.try_into().unwrap(),
434 })
435 .build()
436 .unwrap();
437 let mut data = [0u8; 6];
438 let mut data_block = DataBlock::from(&mut data[..]);
439 let fp_value = 0b1010101010101u32;
440
441 data_block.store_fingerprint(&Fingerprint { data: fp_value }, &config);
442 data_block.inc_lru_counter(config.lru_field_config.as_ref().unwrap());
443 data_block.set_ttl(config.ttl_field_config.as_ref().unwrap(), 10);
444
445 data_block.age_lru_counter(config.lru_field_config.as_ref().unwrap());
446 data_block.age_ttl_counter(config.ttl_field_config.as_ref().unwrap());
447
448 assert_eq!(data_block.get_fingerprint(&config).data(), fp_value);
449 assert_eq!(
450 data_block.get_ttl(config.ttl_field_config.as_ref().unwrap()),
451 9
452 );
453 assert_eq!(
454 data_block.get_lru_counter(config.lru_field_config.as_ref().unwrap()),
455 0
456 );
457 }
458
459 #[test]
460 fn test_inc_counter_saturation() {
461 let config = CuckooConfiguration::builder(100_000)
462 .fingerprint_bits(8.try_into().unwrap())
463 .with_lru(LruConfig {
464 counter_bits: 2.try_into().unwrap(),
465 remove_on_zero: false,
466 })
467 .build()
468 .unwrap();
469
470 let mut data = [0u8; 2];
471 let mut data_block = DataBlock::from(&mut data[..]);
472
473 let lru_config = config.lru_field_config.as_ref().unwrap();
474
475 data_block.inc_lru_counter(lru_config);
476 data_block.inc_lru_counter(lru_config);
477 data_block.inc_lru_counter(lru_config);
478
479 assert_eq!(data_block.get_lru_counter(lru_config), 3);
480
481 data_block.inc_lru_counter(lru_config);
483 assert_eq!(data_block.get_lru_counter(lru_config), 3);
484
485 data_block.age_lru_counter(lru_config);
486 assert_eq!(data_block.get_lru_counter(lru_config), 1);
487 }
488}