kitsune2_api/arc.rs
1//! Kitsune2 arcs represent a range of hash locations on the DHT.
2//!
3//! When used in the context of an agent, it represents the range of hash locations
4//! that agent is responsible for.
5
6use serde::{Deserialize, Serialize};
7
8/// The definition of a storage arc compatible with the concept of
9/// storage and querying of items in a store that fall within that arc.
10#[derive(
11 Debug, Clone, Copy, Serialize, Deserialize, Hash, Eq, PartialEq, Default,
12)]
13#[serde(untagged)]
14pub enum DhtArc {
15 /// No DHT locations are contained within this arc.
16 #[default]
17 Empty,
18 /// A specific range of DHT locations are contained within this arc.
19 ///
20 /// The lower and upper bounds are inclusive.
21 Arc(u32, u32),
22}
23
24impl DhtArc {
25 /// A full arc that contains all DHT locations.
26 pub const FULL: DhtArc = DhtArc::Arc(0, u32::MAX);
27
28 /// Get the min distance from a location to an arc in a wrapping u32 space.
29 /// This function will only return 0 if the location is covered by the arc.
30 /// This function will return u32::MAX if the arc is empty.
31 ///
32 /// All possible cases:
33 ///
34 /// ```text
35 /// s = arc_start
36 /// e = arc_end
37 /// l = location
38 ///
39 /// Arc wraps around, loc >= arc_start
40 ///
41 /// |----e-----------s--l--|
42 /// 0 u32::MAX
43 ///
44 /// Arc wraps around, loc <= arc_end
45 /// |-l--e-----------s-----|
46 /// 0 u32::MAX
47 ///
48 /// Arc wraps around, loc outside of arc
49 /// |----e----l------s-----|
50 /// 0 u32::MAX
51 ///
52 /// Arc does not wrap around, loc inside of arc
53 /// |---------s--l---e-----|
54 /// 0 u32::MAX
55 ///
56 /// Arc does not wrap around, loc < arc_start
57 /// |-----l---s------e-----|
58 /// 0 u32::MAX
59 ///
60 /// Arc does not wrap around, loc > arc_end
61 /// |---------s------e--l--|
62 /// 0 u32::MAX
63 /// ```
64 pub fn dist(&self, loc: u32) -> u32 {
65 match self {
66 DhtArc::Empty => u32::MAX,
67 DhtArc::Arc(arc_start, arc_end) => {
68 let (d1, d2) = if arc_start > arc_end {
69 // this arc wraps around the end of u32::MAX
70
71 if loc >= *arc_start || loc <= *arc_end {
72 return 0;
73 } else {
74 (
75 // Here we know that location is less than arc_start
76 arc_start - loc,
77 loc - arc_end,
78 )
79 }
80 } else {
81 // this arc does not wrap, arc_start <= arc_end
82
83 if loc >= *arc_start && loc <= *arc_end {
84 return 0;
85 } else if loc < *arc_start {
86 (
87 // Here we know that location is less than arc_start
88 arc_start - loc,
89 // Add one to account for the wrap.
90 // Here we know that location is less than arc_end, but we need to
91 // compute the wrapping distance between them. Adding 1 is safe and
92 // cannot overflow.
93 u32::MAX - arc_end + loc + 1,
94 )
95 } else {
96 (u32::MAX - loc + arc_start + 1, loc - arc_end)
97 }
98 };
99
100 std::cmp::min(d1, d2)
101 }
102 }
103 }
104
105 /// Convenience function to determine if a location is contained within the arc.
106 ///
107 /// Simply checks whether the distance from the location to the arc is 0.
108 pub fn contains(&self, loc: u32) -> bool {
109 self.dist(loc) == 0
110 }
111
112 /// Determine if any part of two arcs overlap.
113 ///
114 /// All possible cases (though note the arcs can also wrap around u32::MAX):
115 ///
116 /// ```text
117 /// a = a_start
118 /// A = a_end
119 /// b = b_start
120 /// B = b_end
121 ///
122 /// The tail of a..A overlaps the head of b..B
123 ///
124 /// |---a--b-A--B---|
125 ///
126 /// The tail of b..B overlaps the head of a..A
127 ///
128 /// |---b--a-B--A---|
129 ///
130 /// b..B is fully contained by a..A
131 ///
132 /// |---a--b-B--A---|
133 ///
134 /// a..A is fully contained by b..B
135 ///
136 /// |---b--a-A--B---|
137 /// ```
138 pub fn overlaps(&self, other: &DhtArc) -> bool {
139 match (&self, &other) {
140 (DhtArc::Empty, _) | (_, DhtArc::Empty) => false,
141 (
142 this @ DhtArc::Arc(a_beg, a_end),
143 other @ DhtArc::Arc(b_beg, b_end),
144 ) => {
145 // The only way for there to be overlap is if
146 // either of a's start or end points are within b
147 // or either of b's start or end points are within a
148 this.dist(*b_beg) == 0
149 || this.dist(*b_end) == 0
150 || other.dist(*a_beg) == 0
151 || other.dist(*a_end) == 0
152 }
153 }
154 }
155
156 /// The distance from the inclusive beginning of the arc
157 /// to the exclusive end of the arc. (Or length minus one.)
158 /// Note: DhtArc::Empty and DhtArc::Arc(0, 0) will both
159 /// return arc_span() == 0. To address this, first match on
160 /// the enum variants.
161 pub fn arc_span(&self) -> u32 {
162 match self {
163 DhtArc::Empty => 0,
164 DhtArc::Arc(start, end) => {
165 if start > end {
166 u32::MAX - start + end + 1
167 } else {
168 end - start
169 }
170 }
171 }
172 }
173
174 /// Determine if the arc is empty.
175 pub fn is_empty(&self) -> bool {
176 matches!(self, DhtArc::Empty)
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use crate::DhtArc;
183
184 #[test]
185 fn arc_span_fixtures() {
186 for (expected, arc) in [
187 (0, DhtArc::Empty),
188 (0, DhtArc::Arc(0, 0)),
189 (1, DhtArc::Arc(0, 1)),
190 (1, DhtArc::Arc(u32::MAX, 0)),
191 (2, DhtArc::Arc(42, 44)),
192 (u32::MAX, DhtArc::Arc(42, 41)),
193 (u32::MAX, DhtArc::Arc(0, u32::MAX)),
194 (u32::MAX, DhtArc::Arc(u32::MAX, u32::MAX - 1)),
195 ] {
196 assert_eq!(expected, arc.arc_span());
197 }
198 }
199
200 #[test]
201 fn contains_full_arc_all_values() {
202 let arc = DhtArc::FULL;
203
204 // Contains bounds
205 assert!(arc.contains(0));
206 assert!(arc.contains(u32::MAX));
207
208 // and a value in the middle somewhere
209 assert!(arc.contains(u32::MAX / 2));
210 }
211
212 #[test]
213 fn contains_includes_bounds() {
214 let arc = DhtArc::Arc(32, 64);
215
216 assert!(arc.contains(32));
217 assert!(arc.contains(64));
218 }
219
220 #[test]
221 fn contains_wraps_around() {
222 let arc = DhtArc::Arc(u32::MAX - 32, 32);
223
224 assert!(!arc.contains(u32::MAX - 33));
225 assert!(arc.contains(u32::MAX - 32));
226 assert!(arc.contains(u32::MAX - 1));
227 assert!(arc.contains(u32::MAX));
228 assert!(arc.contains(0));
229 assert!(arc.contains(20));
230 assert!(arc.contains(32));
231 assert!(!arc.contains(33));
232 }
233
234 #[test]
235 fn contains_empty_arc_no_locations() {
236 let arc = DhtArc::Empty;
237
238 assert!(!arc.contains(0));
239 assert!(!arc.contains(u32::MAX));
240 assert!(!arc.contains(u32::MAX / 2));
241 }
242
243 #[test]
244 fn arc_dist_edge_cases() {
245 type Dist = u32;
246 type Loc = u32;
247 const F: &[(Dist, Loc, DhtArc)] = &[
248 // Empty arcs contain no values, distance is always u32::MAX
249 (u32::MAX, 0, DhtArc::Empty),
250 (u32::MAX, u32::MAX / 2, DhtArc::Empty),
251 (u32::MAX, u32::MAX, DhtArc::Empty),
252 // Unit length arcs at max value
253 (1, 0, DhtArc::Arc(u32::MAX, u32::MAX)),
254 (
255 u32::MAX / 2 + 1,
256 u32::MAX / 2,
257 DhtArc::Arc(u32::MAX, u32::MAX),
258 ),
259 // Unit length arcs at min value
260 (1, u32::MAX, DhtArc::Arc(0, 0)),
261 (u32::MAX / 2, u32::MAX / 2, DhtArc::Arc(0, 0)),
262 // Lower bound is inclusive
263 (0, 0, DhtArc::Arc(0, 1)),
264 (0, u32::MAX - 1, DhtArc::Arc(u32::MAX - 1, u32::MAX)),
265 // Distance from lower bound, non-wrapping
266 (1, 0, DhtArc::Arc(1, 2)),
267 (1, u32::MAX, DhtArc::Arc(0, 1)),
268 // Distance from upper bound, non-wrapping
269 (1, 0, DhtArc::Arc(u32::MAX - 1, u32::MAX)),
270 // Distance from upper bound, wrapping
271 (0, 0, DhtArc::Arc(u32::MAX, 0)),
272 (1, 1, DhtArc::Arc(u32::MAX, 0)),
273 // Distance from lower bound, wrapping
274 (1, u32::MAX - 1, DhtArc::Arc(u32::MAX, 0)),
275 (1, u32::MAX - 1, DhtArc::Arc(u32::MAX, 1)),
276 // Contains, wrapping
277 (0, 0, DhtArc::Arc(u32::MAX, 1)),
278 ];
279
280 for (dist, loc, arc) in F.iter() {
281 assert_eq!(
282 *dist,
283 arc.dist(*loc),
284 "While checking the distance from {} to arc {:?}",
285 loc,
286 arc
287 );
288 }
289 }
290
291 #[test]
292 fn arcs_overlap_edge_cases() {
293 type DoOverlap = bool;
294 const F: &[(DoOverlap, DhtArc, DhtArc)] = &[
295 (false, DhtArc::Arc(0, 0), DhtArc::Arc(1, 1)),
296 (false, DhtArc::Arc(0, 0), DhtArc::Arc(u32::MAX, u32::MAX)),
297 (true, DhtArc::Arc(0, 0), DhtArc::Arc(0, 0)),
298 (
299 true,
300 DhtArc::Arc(u32::MAX, u32::MAX),
301 DhtArc::Arc(u32::MAX, u32::MAX),
302 ),
303 (true, DhtArc::Arc(u32::MAX, 0), DhtArc::Arc(0, 0)),
304 (
305 true,
306 DhtArc::Arc(u32::MAX, 0),
307 DhtArc::Arc(u32::MAX, u32::MAX),
308 ),
309 (
310 true,
311 DhtArc::Arc(u32::MAX, 0),
312 DhtArc::Arc(u32::MAX, u32::MAX),
313 ),
314 (true, DhtArc::Arc(0, 3), DhtArc::Arc(1, 2)),
315 (true, DhtArc::Arc(1, 2), DhtArc::Arc(0, 3)),
316 (true, DhtArc::Arc(1, 3), DhtArc::Arc(2, 4)),
317 (true, DhtArc::Arc(2, 4), DhtArc::Arc(1, 3)),
318 (true, DhtArc::Arc(u32::MAX - 1, 1), DhtArc::Arc(u32::MAX, 0)),
319 (true, DhtArc::Arc(u32::MAX, 0), DhtArc::Arc(u32::MAX - 1, 1)),
320 (true, DhtArc::Arc(u32::MAX - 1, 0), DhtArc::Arc(u32::MAX, 1)),
321 (true, DhtArc::Arc(u32::MAX, 1), DhtArc::Arc(u32::MAX - 1, 0)),
322 ];
323
324 for (do_overlap, a, b) in F.iter() {
325 assert_eq!(
326 *do_overlap,
327 a.overlaps(b),
328 "While checking that {:?} overlaps {:?}",
329 a,
330 b
331 );
332 }
333 }
334}