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 (LRU counter == 0),
96    /// insertion is guaranteed.
97    ///
98    /// Returns true if any item was kicked. Returns false if no item was kicked and the new item
99    /// was not moved out of [`DataBlock`].
100    pub(crate) fn kick_lru<T: BorrowMut<[u8]>>(
101        &mut self,
102        data_block: &mut DataBlock<T>,
103        configuration: &CuckooConfiguration,
104        lru_config: &(LruConfig, DataBlockFieldConfiguration),
105    ) -> bool {
106        let mut min = data_block.get_lru_counter(lru_config);
107        if min == 1 {
108            // TODO: What happens if LRU is really at 1?
109            // Currently 1 is the default for new items
110            min = u32::MAX;
111        }
112        let mut pos = configuration.bucket_size;
113        for i in 0..configuration.bucket_size {
114            let data = self.get_data_block(i, configuration);
115            let counter = data.get_lru_counter(lru_config);
116            if counter < min {
117                min = counter;
118                pos = i;
119            }
120        }
121
122        if pos < configuration.bucket_size {
123            self.get_data_block(pos, configuration).swap(data_block);
124            true
125        } else {
126            false
127        }
128    }
129
130    /// Looks for the fingerprint in this bucket.
131    ///
132    /// If fingerprint is found, its LRU and generic counters are also incremented (if enabled).
133    ///
134    /// Returns true if the fingeprint is stored in this bucket.
135    pub(crate) fn contains(
136        &mut self,
137        fingerprint: &Fingerprint,
138        configuration: &CuckooConfiguration,
139        update: &LookupValues,
140    ) -> bool {
141        for i in 0..configuration.bucket_size {
142            let mut data = self.get_data_block(i, configuration);
143            let stored = data.get_fingerprint(configuration);
144
145            if stored == *fingerprint {
146                if let Some(counter_config) = configuration.counter_field_config.as_ref() {
147                    data.update_counter(
148                        counter_config,
149                        update
150                            .counter_diff
151                            .unwrap_or(counter_config.0.change_on_lookup),
152                    );
153                }
154                if let Some(ttl_config) = configuration.ttl_field_config.as_ref()
155                    && let Some(ttl) = update.ttl
156                {
157                    data.set_ttl(ttl_config, ttl);
158                }
159                if let Some(lru_config) = configuration.lru_field_config.as_ref() {
160                    data.inc_lru_counter(lru_config);
161                }
162                return true;
163            }
164        }
165        false
166    }
167
168    /// Looks for the fingerprint in this bucket and returns its associated data.
169    ///
170    /// Returns the data associated with the fingerprint, if found.
171    pub(crate) fn get_associated_data(
172        &mut self,
173        fingerprint: &Fingerprint,
174        configuration: &CuckooConfiguration,
175        update: &LookupValues,
176    ) -> Option<AssociatedData> {
177        for i in 0..configuration.bucket_size {
178            let mut data = self.get_data_block(i, configuration);
179            let stored = data.get_fingerprint(configuration);
180
181            if stored == *fingerprint {
182                if let Some(counter_config) = configuration.counter_field_config.as_ref() {
183                    data.update_counter(
184                        counter_config,
185                        update
186                            .counter_diff
187                            .unwrap_or(counter_config.0.change_on_lookup),
188                    );
189                }
190                if let Some(ttl_config) = configuration.ttl_field_config.as_ref()
191                    && let Some(ttl) = update.ttl
192                {
193                    data.set_ttl(ttl_config, ttl);
194                }
195                if let Some(lru_config) = configuration.lru_field_config.as_ref() {
196                    data.inc_lru_counter(lru_config);
197                }
198                return Some(AssociatedData::new(
199                    self.get_data_block(i, configuration),
200                    configuration.clone(),
201                ));
202            }
203        }
204        None
205    }
206
207    /// Removes the fingerprint from this bucket, by clearing out its slot.
208    ///
209    /// Returns true if fingerprint was found and removed, false if it was not found.
210    pub(crate) fn remove(
211        &mut self,
212        fingerprint: &Fingerprint,
213        configuration: &CuckooConfiguration,
214    ) -> bool {
215        for i in 0..configuration.bucket_size {
216            let mut data = self.get_data_block(i, configuration);
217            let stored = data.get_fingerprint(configuration);
218
219            if stored == *fingerprint {
220                data.reset();
221                return true;
222            }
223        }
224        false
225    }
226
227    /// Ages all LRU counters in this bucket.
228    pub(crate) fn age_lru_counters(
229        &mut self,
230        configuration: &CuckooConfiguration,
231        lru_config: &(LruConfig, DataBlockFieldConfiguration),
232    ) -> usize {
233        let mut removed = 0;
234        for i in 0..configuration.bucket_size {
235            let db = self.get_data_block(i, configuration);
236            if db.occupied(configuration) {
237                removed += if self
238                    .get_data_block(i, configuration)
239                    .age_lru_counter(lru_config)
240                {
241                    1
242                } else {
243                    0
244                }
245            }
246        }
247        removed
248    }
249
250    /// Ages all TTL counters in this bucket.
251    ///
252    /// Returns the number of removed items after aging.
253    pub(crate) fn age_ttl_counters(
254        &mut self,
255        configuration: &CuckooConfiguration,
256        ttl_config: &(TtlConfig, DataBlockFieldConfiguration),
257    ) -> usize {
258        let mut removed = 0;
259        for i in 0..configuration.bucket_size {
260            let db = self.get_data_block(i, configuration);
261            if db.occupied(configuration) {
262                removed += if self
263                    .get_data_block(i, configuration)
264                    .age_ttl_counter(ttl_config)
265                {
266                    1
267                } else {
268                    0
269                }
270            }
271        }
272        removed
273    }
274
275    pub(crate) fn occupied_count(&self, configuration: &CuckooConfiguration) -> usize {
276        (0..configuration.bucket_size)
277            .map(|i| {
278                let size = configuration.data_block_size;
279                DataBlock::from(&self.data[(i * size)..((i + 1) * size)])
280            })
281            .filter(|db| db.occupied(configuration))
282            .count()
283    }
284
285    pub(crate) fn export(&self, mut writer: impl Write) -> std::io::Result<()> {
286        writer.write_all(&self.data)
287    }
288
289    fn get_data_block(
290        &mut self,
291        index: usize,
292        configuration: &CuckooConfiguration,
293    ) -> DataBlock<&mut [u8]> {
294        let size = configuration.data_block_size;
295        (&mut self.data[(index * size)..((index + 1) * size)]).into()
296    }
297}
298
299/// Values to store with the fingerprint on insertion.
300#[derive(Default)]
301pub struct InsertValues {
302    /// TTL to set for the fingerprint on insertion.
303    /// This is ignored if the TTL configuration is not enabled.
304    pub ttl: Option<u32>,
305    /// Counter to set to the fingerprint on insertion.
306    /// This is ignored if the counter configuration is not enabled.
307    pub counter: Option<i32>,
308}
309
310/// Values to store with the fingerprint on lookups.
311#[derive(Default)]
312pub struct LookupValues {
313    /// TTL to set for the fingerprint on lookup.
314    /// This is ignored if the TTL configuration is not enabled.
315    pub ttl: Option<u32>,
316    /// Counter diff to apply (add/remove) to the fingerprint on lookups.
317    /// This is ignored if the counter configuration is not enabled.
318    pub counter_diff: Option<i32>,
319}