Skip to main content

cuckoo_clock/
bucket.rs

1use std::{
2    borrow::{Borrow, BorrowMut},
3    io::{Read, Write},
4};
5
6use crate::{
7    associated_data::AssociatedData,
8    config::{CuckooConfiguration, LruConfig, TtlConfig},
9    data_block::{DataBlock, DataBlockFieldConfiguration, Fingerprint},
10};
11
12/// A single bucket in the filter, holding all the fingerprints and their associated data
13/// Everything is stored as a single [`Vec<u8>`], where each fingerprint together with its
14/// associated data is aligned to [`u8`].
15pub(crate) struct Bucket {
16    data: Vec<u8>,
17}
18
19impl Bucket {
20    /// Creates a new bucket based on [`CuckooConfiguration`].
21    ///
22    /// ## Panics
23    ///
24    /// Panics on OOM errors (if the requested bucket byte size is too high).
25    pub(crate) fn new(configuration: &CuckooConfiguration) -> Self {
26        Self {
27            data: vec![0; configuration.bucket_byte_size],
28        }
29    }
30
31    /// Creates a new bucket based on exported state found in the provided reader.
32    ///
33    /// ## Panics
34    ///
35    /// Panics on OOM errors (if the requested bucket byte size is too high).
36    pub(crate) fn take_from(
37        mut reader: impl Read,
38        configuration: &CuckooConfiguration,
39    ) -> std::io::Result<Self> {
40        let mut data = vec![0; configuration.bucket_byte_size];
41        reader.read_exact(&mut data)?;
42        Ok(Self { data })
43    }
44
45    /// Inserts a new fingerprint into this bucket.
46    ///
47    /// Sets TTL to the default value (if enabled), increments both LRU and generic counters by 1
48    /// (if enabled). This is done when the same fingerprint is inserted again, restarting TTL and
49    /// increasing counters.
50    ///
51    /// Returns false if the insertion has failed (if the bucket is fully occupied). In that case,
52    /// alternate bucket should be tried and if that fails too, kicking process should be started.
53    pub(crate) fn insert<T: Borrow<[u8]>>(
54        &mut self,
55        data_block: &DataBlock<T>,
56        configuration: &CuckooConfiguration,
57    ) -> bool {
58        let fingerprint = data_block.get_fingerprint(configuration);
59        for i in 0..configuration.bucket_size {
60            let mut data = self.get_data_block(i, configuration);
61            let stored = data.get_fingerprint(configuration);
62
63            let reinsert = stored == fingerprint;
64
65            if !reinsert {
66                if stored.is_empty() {
67                    data.copy_from(data_block);
68                } else {
69                    continue;
70                }
71            } else {
72                data.merge_associated_from(data_block, configuration);
73            }
74            return true;
75        }
76        false
77    }
78
79    /// Kicks a random item from this bucket, by exchaging that [`DataBlock`] with the one
80    /// provided.
81    ///
82    /// This doesn't return. It always succeeds and the kicked item can be found in the provided
83    /// [`DataBlock`].
84    pub(crate) fn kick_random<T: BorrowMut<[u8]>>(
85        &mut self,
86        data_block: &mut DataBlock<T>,
87        configuration: &CuckooConfiguration,
88    ) {
89        let index = rand::random_range(0..configuration.bucket_size);
90        self.get_data_block(index, configuration).swap(data_block);
91    }
92
93    /// Kicks an item from this bucket, based on LRU - kicks out the lowest LRU counter item from
94    /// this bucket, that has lower LRU counter than the new item. If the new item has the lowest
95    /// LRU counter, kick fails and false is returned. For completely new items insertion is guaranteed.
96    ///
97    /// Returns true if any item was kicked. Returns false if no item was kicked and the new item
98    /// was not moved out of [`DataBlock`].
99    pub(crate) fn kick_lru<T: BorrowMut<[u8]>>(
100        &mut self,
101        data_block: &mut DataBlock<T>,
102        configuration: &CuckooConfiguration,
103        lru_config: &(LruConfig, DataBlockFieldConfiguration),
104        new_item: bool,
105    ) -> bool {
106        let mut min = data_block.get_lru_counter(lru_config);
107        if new_item {
108            min = u32::MAX;
109        }
110        let mut pos = configuration.bucket_size;
111        for i in 0..configuration.bucket_size {
112            let data = self.get_data_block(i, configuration);
113            let counter = data.get_lru_counter(lru_config);
114            if counter < min {
115                min = counter;
116                pos = i;
117            }
118        }
119
120        if pos < configuration.bucket_size {
121            self.get_data_block(pos, configuration).swap(data_block);
122            true
123        } else {
124            false
125        }
126    }
127
128    /// Looks for the fingerprint in this bucket.
129    ///
130    /// If fingerprint is found, its LRU and generic counters are also incremented (if enabled).
131    ///
132    /// Returns true if the fingeprint is stored in this bucket.
133    pub(crate) fn contains(
134        &mut self,
135        fingerprint: &Fingerprint,
136        configuration: &CuckooConfiguration,
137        update: &LookupValues,
138    ) -> bool {
139        for i in 0..configuration.bucket_size {
140            let mut data = self.get_data_block(i, configuration);
141            let stored = data.get_fingerprint(configuration);
142
143            if stored == *fingerprint {
144                if let Some(counter_config) = configuration.counter_field_config.as_ref() {
145                    data.update_counter(
146                        counter_config,
147                        update
148                            .counter_diff
149                            .unwrap_or(counter_config.0.change_on_lookup),
150                    );
151                }
152                if let Some(ttl_config) = configuration.ttl_field_config.as_ref()
153                    && let Some(ttl) = update.ttl
154                {
155                    data.set_ttl(ttl_config, ttl);
156                }
157                if let Some(lru_config) = configuration.lru_field_config.as_ref() {
158                    data.inc_lru_counter(lru_config);
159                }
160                return true;
161            }
162        }
163        false
164    }
165
166    /// Looks for the fingerprint in this bucket and returns its associated data.
167    ///
168    /// Returns the data associated with the fingerprint, if found.
169    pub(crate) fn get_associated_data(
170        &mut self,
171        fingerprint: &Fingerprint,
172        configuration: &CuckooConfiguration,
173        update: &LookupValues,
174    ) -> Option<AssociatedData> {
175        for i in 0..configuration.bucket_size {
176            let mut data = self.get_data_block(i, configuration);
177            let stored = data.get_fingerprint(configuration);
178
179            if stored == *fingerprint {
180                if let Some(counter_config) = configuration.counter_field_config.as_ref() {
181                    data.update_counter(
182                        counter_config,
183                        update
184                            .counter_diff
185                            .unwrap_or(counter_config.0.change_on_lookup),
186                    );
187                }
188                if let Some(ttl_config) = configuration.ttl_field_config.as_ref()
189                    && let Some(ttl) = update.ttl
190                {
191                    data.set_ttl(ttl_config, ttl);
192                }
193                if let Some(lru_config) = configuration.lru_field_config.as_ref() {
194                    data.inc_lru_counter(lru_config);
195                }
196                return Some(AssociatedData::new(
197                    self.get_data_block(i, configuration),
198                    configuration.clone(),
199                ));
200            }
201        }
202        None
203    }
204
205    /// Removes the fingerprint from this bucket, by clearing out its slot.
206    ///
207    /// Returns true if fingerprint was found and removed, false if it was not found.
208    pub(crate) fn remove(
209        &mut self,
210        fingerprint: &Fingerprint,
211        configuration: &CuckooConfiguration,
212    ) -> bool {
213        for i in 0..configuration.bucket_size {
214            let mut data = self.get_data_block(i, configuration);
215            let stored = data.get_fingerprint(configuration);
216
217            if stored == *fingerprint {
218                data.reset();
219                return true;
220            }
221        }
222        false
223    }
224
225    /// Ages all LRU counters in this bucket.
226    pub(crate) fn age_lru_counters(
227        &mut self,
228        configuration: &CuckooConfiguration,
229        lru_config: &(LruConfig, DataBlockFieldConfiguration),
230    ) -> usize {
231        let mut removed = 0;
232        for i in 0..configuration.bucket_size {
233            let db = self.get_data_block(i, configuration);
234            if db.occupied(configuration) {
235                removed += if self
236                    .get_data_block(i, configuration)
237                    .age_lru_counter(lru_config)
238                {
239                    1
240                } else {
241                    0
242                }
243            }
244        }
245        removed
246    }
247
248    /// Ages all TTL counters in this bucket.
249    ///
250    /// Returns the number of removed items after aging.
251    pub(crate) fn age_ttl_counters(
252        &mut self,
253        configuration: &CuckooConfiguration,
254        ttl_config: &(TtlConfig, DataBlockFieldConfiguration),
255    ) -> usize {
256        let mut removed = 0;
257        for i in 0..configuration.bucket_size {
258            let db = self.get_data_block(i, configuration);
259            if db.occupied(configuration) {
260                removed += if self
261                    .get_data_block(i, configuration)
262                    .age_ttl_counter(ttl_config)
263                {
264                    1
265                } else {
266                    0
267                }
268            }
269        }
270        removed
271    }
272
273    pub(crate) fn occupied_count(&self, configuration: &CuckooConfiguration) -> usize {
274        (0..configuration.bucket_size)
275            .map(|i| {
276                let size = configuration.data_block_size;
277                DataBlock::from(&self.data[(i * size)..((i + 1) * size)])
278            })
279            .filter(|db| db.occupied(configuration))
280            .count()
281    }
282
283    pub(crate) fn export(&self, mut writer: impl Write) -> std::io::Result<()> {
284        writer.write_all(&self.data)
285    }
286
287    fn get_data_block(
288        &mut self,
289        index: usize,
290        configuration: &CuckooConfiguration,
291    ) -> DataBlock<&mut [u8]> {
292        let size = configuration.data_block_size;
293        (&mut self.data[(index * size)..((index + 1) * size)]).into()
294    }
295}
296
297/// Values to store with the fingerprint on insertion.
298#[derive(Default)]
299pub struct InsertValues {
300    /// TTL to set for the fingerprint on insertion.
301    /// This is ignored if the TTL configuration is not enabled.
302    pub ttl: Option<u32>,
303    /// Counter to set to the fingerprint on insertion.
304    /// This is ignored if the counter configuration is not enabled.
305    pub counter: Option<i32>,
306}
307
308/// Values to store with the fingerprint on lookups.
309#[derive(Default)]
310pub struct LookupValues {
311    /// TTL to set for the fingerprint on lookup.
312    /// This is ignored if the TTL configuration is not enabled.
313    pub ttl: Option<u32>,
314    /// Counter diff to apply (add/remove) to the fingerprint on lookups.
315    /// This is ignored if the counter configuration is not enabled.
316    pub counter_diff: Option<i32>,
317}