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}