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    ) {
233        for i in 0..configuration.bucket_size {
234            self.get_data_block(i, configuration)
235                .age_lru_counter(lru_config);
236        }
237    }
238
239    /// Ages all TTL counters in this bucket.
240    ///
241    /// Returns the number of removed items after aging.
242    pub(crate) fn age_ttl_counters(
243        &mut self,
244        configuration: &CuckooConfiguration,
245        ttl_config: &(TtlConfig, DataBlockFieldConfiguration),
246    ) -> usize {
247        let mut removed = 0;
248        for i in 0..configuration.bucket_size {
249            let db = self.get_data_block(i, configuration);
250            if db.occupied(configuration) {
251                removed += if self
252                    .get_data_block(i, configuration)
253                    .age_ttl_counter(ttl_config)
254                {
255                    1
256                } else {
257                    0
258                }
259            }
260        }
261        removed
262    }
263
264    pub(crate) fn occupied_count(&self, configuration: &CuckooConfiguration) -> usize {
265        (0..configuration.bucket_size)
266            .map(|i| {
267                let size = configuration.data_block_size;
268                DataBlock::from(&self.data[(i * size)..((i + 1) * size)])
269            })
270            .filter(|db| db.occupied(configuration))
271            .count()
272    }
273
274    pub(crate) fn export(&self, mut writer: impl Write) -> std::io::Result<()> {
275        writer.write_all(&self.data)
276    }
277
278    fn get_data_block(
279        &mut self,
280        index: usize,
281        configuration: &CuckooConfiguration,
282    ) -> DataBlock<&mut [u8]> {
283        let size = configuration.data_block_size;
284        (&mut self.data[(index * size)..((index + 1) * size)]).into()
285    }
286}
287
288/// Values to store with the fingerprint on insertion.
289#[derive(Default)]
290pub struct InsertValues {
291    /// TTL to set for the fingerprint on insertion.
292    /// This is ignored if the TTL configuration is not enabled.
293    pub ttl: Option<u32>,
294    /// Counter to set to the fingerprint on insertion.
295    /// This is ignored if the counter configuration is not enabled.
296    pub counter: Option<i32>,
297}
298
299/// Values to store with the fingerprint on lookups.
300#[derive(Default)]
301pub struct LookupValues {
302    /// TTL to set for the fingerprint on lookup.
303    /// This is ignored if the TTL configuration is not enabled.
304    pub ttl: Option<u32>,
305    /// Counter diff to apply (add/remove) to the fingerprint on lookups.
306    /// This is ignored if the counter configuration is not enabled.
307    pub counter_diff: Option<i32>,
308}