karyon_p2p/discovery/kademlia/
bloom.rs1use std::{hash::Hasher, sync::Arc};
2
3use bincode::{Decode, Encode};
4use parking_lot::Mutex;
5use siphasher::sip::SipHasher13;
6
7use crate::protocol::ProtocolFlags;
8
9#[derive(Encode, Decode, Clone, Copy, Debug, Default, PartialEq, Eq)]
15pub struct Bloom(u128);
16
17impl Bloom {
18 pub fn add<I: AsRef<[u8]>>(&mut self, item: I) {
20 self.0 |= item_mask(item.as_ref());
21 }
22
23 pub fn may_contain<I: AsRef<[u8]>>(&self, item: I) -> bool {
25 let mask = item_mask(item.as_ref());
26 (self.0 & mask) == mask
27 }
28
29 pub fn covers(&self, other: &Self) -> bool {
32 (self.0 & other.0) == other.0
33 }
34
35 pub fn intersects(&self, other: &Self) -> bool {
37 (self.0 & other.0) != 0
38 }
39
40 pub fn is_empty(&self) -> bool {
42 self.0 == 0
43 }
44}
45
46#[derive(Clone, Copy, Debug, Default)]
52pub struct LocalBloom {
53 pub advertised: Bloom,
54 pub required: Bloom,
55 pub preferred: Bloom,
56}
57
58impl LocalBloom {
59 pub fn matches(&self, peer: &Bloom) -> bool {
61 if !peer.covers(&self.required) {
62 return false;
63 }
64 if self.preferred.is_empty() {
65 return true;
66 }
67 peer.intersects(&self.preferred)
68 }
69}
70
71#[derive(Clone, Debug, Default)]
74pub struct BloomRef {
75 inner: Arc<Mutex<LocalBloom>>,
76}
77
78impl BloomRef {
79 pub fn new() -> Self {
81 Self::default()
82 }
83
84 pub fn add<I: AsRef<[u8]>>(&self, item: I, flags: ProtocolFlags) {
88 if flags == ProtocolFlags::empty() {
89 return;
90 }
91 let item = item.as_ref();
92 let mut local = self.inner.lock();
93 local.advertised.add(item);
94 if flags.contains(ProtocolFlags::REQUIRED) {
95 local.required.add(item);
96 }
97 if flags.contains(ProtocolFlags::PREFERRED) {
98 local.preferred.add(item);
99 }
100 }
101
102 pub fn snapshot(&self) -> LocalBloom {
104 *self.inner.lock()
105 }
106}
107
108fn item_mask(bytes: &[u8]) -> u128 {
113 let mut hasher = SipHasher13::new_with_keys(0, 0);
114 hasher.write(bytes);
115 let h = hasher.finish();
116 let a = (h as u32) % 128;
117 let b = ((h >> 32) as u32) % 128;
118 (1u128 << a) | (1u128 << b)
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn empty_filter_contains_nothing() {
127 let b = Bloom::default();
128 assert!(b.is_empty());
129 assert!(!b.may_contain("X"));
130 }
131
132 #[test]
133 fn add_then_contains() {
134 let mut b = Bloom::default();
135 b.add("ChatProto");
136 assert!(b.may_contain("ChatProto"));
137 }
138
139 #[test]
140 fn covers_subset() {
141 let mut peer = Bloom::default();
142 peer.add("X");
143 peer.add("Y");
144
145 let mut mine = Bloom::default();
146 mine.add("X");
147 assert!(peer.covers(&mine));
148
149 mine.add("Z");
150 assert!(!peer.covers(&mine));
151 }
152
153 #[test]
154 fn empty_is_always_covered() {
155 assert!(Bloom::default().covers(&Bloom::default()));
156 }
157
158 #[test]
159 fn intersects_when_overlap() {
160 let mut peer = Bloom::default();
161 peer.add("Y");
162
163 let mut mine = Bloom::default();
164 mine.add("Y");
165 assert!(peer.intersects(&mine));
166
167 let mut other = Bloom::default();
168 other.add("Q");
169 assert!(!peer.intersects(&other));
170 }
171
172 #[test]
173 fn deterministic_across_instances() {
174 let mut a = Bloom::default();
175 a.add("SomeProto");
176 let mut b = Bloom::default();
177 b.add("SomeProto");
178 assert_eq!(a, b);
179 }
180
181 #[test]
182 fn local_matches_required_and_preferred() {
183 let bloom = BloomRef::new();
184 bloom.add("Ping", ProtocolFlags::REQUIRED);
185 bloom.add("Chat", ProtocolFlags::PREFERRED);
186 let local = bloom.snapshot();
187
188 let mut peer = Bloom::default();
189 assert!(!local.matches(&peer));
190 peer.add("Ping");
191 assert!(!local.matches(&peer));
192 peer.add("Chat");
193 assert!(local.matches(&peer));
194 }
195
196 #[test]
197 fn empty_local_matches_everything() {
198 let local = LocalBloom::default();
199 assert!(local.matches(&Bloom::default()));
200 }
201
202 #[test]
203 fn custom_flags_only_advertise() {
204 let bloom = BloomRef::new();
205 bloom.add("Room", ProtocolFlags::USER);
206 let local = bloom.snapshot();
207 assert!(local.advertised.may_contain("Room"));
208 assert!(local.required.is_empty());
209 assert!(local.preferred.is_empty());
210 }
211
212 #[test]
213 fn empty_flags_do_nothing() {
214 let bloom = BloomRef::new();
215 bloom.add("Hidden", ProtocolFlags::empty());
216 assert!(bloom.snapshot().advertised.is_empty());
217 }
218}