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}