cuprate_p2p_bucket/
lib.rs

1//! Bucket data structure
2//!
3//! A collection data structure that discriminates its unique items and place them into "buckets".
4//!
5//! The item must implement the [`Bucketable`] trait that defines how to create the discriminant
6//! from the item type. The data structure will internally contain any item into "buckets" or vectors
7//! of sized capacity `N` that regroup all the stored items with this specific discriminant.
8//!
9//! A practical example of this data structure is for storing `N` amount of IP discriminated by their subnets.
10//! You can store in each "buckets" corresponding to a `/16` subnet up to `N` IPs of that subnet.
11//!
12//! # Example
13//!
14//! ```
15//! use cuprate_p2p_bucket::Bucket;
16//! use std::net::Ipv4Addr;
17//!
18//! // Create a new bucket that can store at most 2 IPs in a particular `/16` subnet.
19//! let mut bucket = Bucket::<2,Ipv4Addr>::new();
20//!
21//! // Fulfill the `96.96.0.0/16` bucket.
22//! bucket.push("96.96.0.1".parse().unwrap());
23//! bucket.push("96.96.0.2".parse().unwrap());
24//! assert_eq!(2, bucket.len());
25//! assert_eq!(2, bucket.len_bucket(&[96_u8,96_u8]).unwrap());
26//!
27//! // Push a new IP from another subnet
28//! bucket.push("127.0.0.1".parse().unwrap());
29//! assert_eq!(3, bucket.len());
30//! assert_eq!(2, bucket.len_bucket(&[96_u8,96_u8]).unwrap());
31//! assert_eq!(1, bucket.len_bucket(&[127_u8,0_u8]).unwrap());
32//!
33//! // Attempting to push a new IP within `96.96.0.0/16` bucket will return the IP back
34//! // as this subnet is already full.
35//! let pushed = bucket.push("96.96.0.3".parse().unwrap());
36//! assert!(pushed.is_some());
37//! assert_eq!(2, bucket.len_bucket(&[96_u8,96_u8]).unwrap());
38//!
39//! ```
40
41use arrayvec::{ArrayVec, CapacityError};
42use rand::random;
43
44use std::{collections::BTreeMap, net::Ipv4Addr};
45
46/// A discriminant that can be computed from the type.
47pub trait Bucketable: Sized + Eq + Clone {
48    /// The type of the discriminant being used in the Binary tree.
49    type Discriminant: Ord + AsRef<[u8]>;
50
51    /// Method that can compute the discriminant from the item.
52    fn discriminant(&self) -> Self::Discriminant;
53}
54
55/// A collection data structure discriminating its unique items
56/// with a specified method. Limiting the amount of items stored
57/// with that discriminant to the const `N`.
58pub struct Bucket<const N: usize, I: Bucketable> {
59    /// The storage of the bucket
60    storage: BTreeMap<I::Discriminant, ArrayVec<I, N>>,
61}
62
63impl<const N: usize, I: Bucketable> Bucket<N, I> {
64    /// Create a new Bucket
65    pub const fn new() -> Self {
66        Self {
67            storage: BTreeMap::new(),
68        }
69    }
70
71    /// Push a new element into the Bucket
72    ///
73    /// Will internally create a new vector for each new discriminant being
74    /// generated from an item.
75    ///
76    /// This function WILL NOT push the element if it already exists.
77    ///
78    /// Return `None` if the item has been pushed or ignored. `Some(I)` if
79    /// the vector is full.
80    ///
81    /// # Example
82    ///
83    /// ```
84    /// use cuprate_p2p_bucket::Bucket;
85    /// use std::net::Ipv4Addr;
86    ///
87    /// let mut bucket = Bucket::<8,Ipv4Addr>::new();
88    ///
89    /// // Push a first IP address.
90    /// bucket.push("127.0.0.1".parse().unwrap());
91    /// assert_eq!(1, bucket.len());
92    ///
93    /// // Push the same IP address a second time.
94    /// bucket.push("127.0.0.1".parse().unwrap());
95    /// assert_eq!(1, bucket.len());
96    /// ```
97    pub fn push(&mut self, item: I) -> Option<I> {
98        let discriminant = item.discriminant();
99
100        if let Some(vec) = self.storage.get_mut(&discriminant) {
101            // Push the item if it doesn't exist.
102            if !vec.contains(&item) {
103                return vec.try_push(item).err().map(CapacityError::element);
104            }
105        } else {
106            // Initialize the vector if not found.
107            let mut vec = ArrayVec::<I, N>::new();
108            vec.push(item);
109            self.storage.insert(discriminant, vec);
110        }
111
112        None
113    }
114
115    /// Will attempt to remove an item from the bucket.
116    pub fn remove(&mut self, item: &I) -> Option<I> {
117        self.storage.get_mut(&item.discriminant()).and_then(|vec| {
118            vec.iter()
119                .enumerate()
120                .find_map(|(i, v)| (item == v).then_some(i))
121                .map(|index| vec.swap_remove(index))
122        })
123    }
124
125    /// Return the number of item stored within the storage
126    pub fn len(&self) -> usize {
127        self.storage.values().map(ArrayVec::len).sum()
128    }
129
130    /// Return the number of item stored with a specific discriminant.
131    ///
132    /// This method returns None if the bucket with this discriminant
133    /// doesn't exist.
134    pub fn len_bucket(&self, discriminant: &I::Discriminant) -> Option<usize> {
135        self.storage.get(discriminant).map(ArrayVec::len)
136    }
137
138    /// Return `true` if the storage contains no items
139    pub fn is_empty(&self) -> bool {
140        self.len() == 0
141    }
142
143    /// Return a reference to an item chosen at random.
144    ///
145    /// Repeated use of this function will provide a normal distribution of
146    /// items based on their discriminants.
147    pub fn get_random(&mut self) -> Option<&I> {
148        // Get the total amount of discriminants to explore.
149        let len = self.storage.len();
150
151        // Get a random bucket.
152        let (_, vec) = self.storage.iter().nth(random::<usize>() / len).unwrap();
153
154        // Return a reference chose at random.
155        vec.get(random::<usize>() / vec.len())
156    }
157}
158
159impl<const N: usize, I: Bucketable> Default for Bucket<N, I> {
160    fn default() -> Self {
161        Self::new()
162    }
163}
164
165impl Bucketable for Ipv4Addr {
166    /// We are discriminating by `/16` subnets.
167    type Discriminant = [u8; 2];
168
169    fn discriminant(&self) -> Self::Discriminant {
170        [self.octets()[0], self.octets()[1]]
171    }
172}