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