kitsune_p2p_dht/spacetime/
segment.rs

1use super::*;
2
3/// An Offset represents the position of the left edge of some Segment.
4/// Offsets must be paired with a *power* to map to quantum coordinates.
5/// The absolute DhtLocation of the offset is determined by the "power" of its
6/// context, and topology of the space, by:
7///
8///   dht_location = offset * 2^pow * quantum_size
9pub trait Offset: Sized + Copy + Clone + Deref<Target = u32> + From<u32> {
10    /// The type of quantum to map to, which also implies the absolute coordinates
11    type Quantum: Quantum;
12
13    /// Get the absolute coordinate for this Offset
14    fn to_absolute(&self, dim: QDim<Self>, power: u8) -> QAbs<Self>;
15
16    /// Get the quantum coordinate for this Offset
17    fn to_quantum(&self, power: u8) -> Self::Quantum;
18
19    /// Get the nearest rounded-down Offset for the given Loc
20    fn from_absolute_rounded(loc: Loc, dim: QDim<Self>, power: u8) -> Self;
21}
22
23pub(crate) type QAbs<O> = <<O as Offset>::Quantum as Quantum>::Absolute;
24pub(crate) type QDim<O> = <<O as Offset>::Quantum as Quantum>::Dim;
25
26/// An Offset in space.
27#[derive(
28    Copy,
29    Clone,
30    Debug,
31    PartialEq,
32    Eq,
33    PartialOrd,
34    Ord,
35    Hash,
36    derive_more::Add,
37    derive_more::Sub,
38    derive_more::Mul,
39    derive_more::Div,
40    derive_more::Deref,
41    derive_more::DerefMut,
42    derive_more::From,
43    derive_more::Into,
44    serde::Serialize,
45    serde::Deserialize,
46)]
47#[cfg_attr(
48    feature = "fuzzing",
49    derive(arbitrary::Arbitrary, proptest_derive::Arbitrary)
50)]
51#[serde(transparent)]
52pub struct SpaceOffset(pub u32);
53
54/// An Offset in time.
55#[derive(
56    Copy,
57    Clone,
58    Debug,
59    PartialEq,
60    Eq,
61    PartialOrd,
62    Ord,
63    Hash,
64    derive_more::Add,
65    derive_more::Sub,
66    derive_more::Mul,
67    derive_more::Div,
68    derive_more::Deref,
69    derive_more::DerefMut,
70    derive_more::From,
71    derive_more::Into,
72    serde::Serialize,
73    serde::Deserialize,
74)]
75#[cfg_attr(
76    feature = "fuzzing",
77    derive(arbitrary::Arbitrary, proptest_derive::Arbitrary)
78)]
79#[serde(transparent)]
80pub struct TimeOffset(pub u32);
81
82impl Offset for SpaceOffset {
83    type Quantum = SpaceQuantum;
84
85    /// Get the absolute coordinate for this Offset
86    fn to_absolute(&self, dim: SpaceDimension, power: u8) -> Loc {
87        self.wrapping_mul(dim.quantum)
88            .wrapping_mul(pow2(power))
89            .into()
90    }
91
92    /// Get the quantum coordinate for this Offset
93    fn to_quantum(&self, power: u8) -> Self::Quantum {
94        self.wrapping_mul(pow2(power)).into()
95    }
96
97    /// Get the nearest rounded-down Offset for the given Loc
98    fn from_absolute_rounded(loc: Loc, dim: SpaceDimension, power: u8) -> Self {
99        (loc.as_u32() / dim.quantum / pow2(power)).into()
100    }
101}
102
103impl Offset for TimeOffset {
104    type Quantum = TimeQuantum;
105
106    /// Get the absolute coordinate for this Offset
107    fn to_absolute(&self, dim: TimeDimension, power: u8) -> Timestamp {
108        Timestamp::from_micros(self.wrapping_mul(dim.quantum).wrapping_mul(pow2(power)) as i64)
109    }
110
111    /// Get the quantum coordinate for this Offset
112    fn to_quantum(&self, power: u8) -> Self::Quantum {
113        self.wrapping_mul(pow2(power)).into()
114    }
115
116    /// Get the nearest rounded-down Offset for the given Loc
117    fn from_absolute_rounded(loc: Loc, dim: TimeDimension, power: u8) -> Self {
118        (loc.as_u32() / dim.quantum / pow2(power)).into()
119    }
120}
121
122/// Any interval in space or time is represented by a node in a tree, so our
123/// way of describing intervals uses tree coordinates as well:
124/// The length of an interval is 2^(power), and the position of its left edge
125/// is at (offset * length).
126#[derive(
127    Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize,
128)]
129#[cfg_attr(
130    feature = "fuzzing",
131    derive(arbitrary::Arbitrary, proptest_derive::Arbitrary)
132)]
133pub struct Segment<O: Offset> {
134    /// The exponent, where length = 2^power
135    pub power: u8,
136    /// The offset from the origin, measured in number of lengths
137    pub offset: O,
138}
139
140impl<O: Offset> Segment<O> {
141    /// Constructor
142    pub fn new<OO: Into<O>>(power: u8, offset: OO) -> Self {
143        Self {
144            power,
145            offset: offset.into(),
146        }
147    }
148
149    /// How many quanta does this segment cover?
150    pub fn num_quanta(&self) -> u64 {
151        // If power is 32, this overflows a u32
152        2u64.pow(self.power.into())
153    }
154
155    /// The length, in absolute terms (Location or microseconds of time)
156    pub fn absolute_length(&self, dim: QDim<O>) -> u64 {
157        let q = dim.into().quantum as u64;
158        // If power is 32, this overflows a u32
159        self.num_quanta() * q
160    }
161
162    /// Get the quanta which bound this segment
163    pub fn quantum_bounds(&self, dim: QDim<O>) -> (O::Quantum, O::Quantum) {
164        let n = self.num_quanta();
165        let a = (n * u64::from(*self.offset)) as u32;
166        (
167            O::Quantum::from(a).normalized(dim),
168            O::Quantum::from(a.wrapping_add(n as u32).wrapping_sub(1)).normalized(dim),
169        )
170    }
171
172    /// The segment contains the given quantum coord
173    pub fn contains_quantum(&self, dim: QDim<O>, coord: O::Quantum) -> bool {
174        let (lo, hi) = self.quantum_bounds(dim);
175        let coord = coord.normalized(dim);
176        if lo <= hi {
177            lo <= coord && coord <= hi
178        } else {
179            lo <= coord || coord <= hi
180        }
181    }
182
183    /// Split a segment in half
184    pub fn bisect(&self) -> Option<[Self; 2]> {
185        if self.power == 0 {
186            // Can't split a quantum value (a leaf has no children)
187            None
188        } else {
189            let power = self.power - 1;
190            Some([
191                Segment::new(power, O::from(self.offset.wrapping_mul(2))),
192                Segment::new(power, O::from(self.offset.wrapping_mul(2).wrapping_add(1))),
193            ])
194        }
195    }
196}
197
198impl SpaceSegment {
199    /// Get the start and end bounds, in absolute Loc coordinates, for this segment
200    pub fn loc_bounds(&self, dim: impl SpaceDim) -> (Loc, Loc) {
201        let (a, b): (u32, u32) = space_bounds(dim.into().into(), self.power, self.offset, 1);
202        (Loc::from(a), Loc::from(b))
203    }
204}
205
206impl TimeSegment {
207    /// Get the start and end bounds, in absolute Timestamp coordinates, for this segment
208    pub fn timestamp_bounds(&self, topo: &Topology) -> (Timestamp, Timestamp) {
209        let (a, b): (i64, i64) = time_bounds64(topo.time.into(), self.power, self.offset, 1);
210        let o = topo.time_origin.as_micros();
211        (Timestamp::from_micros(a + o), Timestamp::from_micros(b + o))
212    }
213}
214
215/// Alias
216pub type SpaceSegment = Segment<SpaceOffset>;
217
218/// Alias
219pub type TimeSegment = Segment<TimeOffset>;
220
221/// Convert to a literal location in space using the given [Dimension] and optionally a power value
222/// which can further quantize the space. The power can be set to 0 to use the [Dimension]'s quantum
223/// as is.
224///
225/// The quantized input location is expressed as an offset and a count of quanta. The quantum is
226/// computed first, then the locations are then computed as:
227/// (`offset * quantum`, `offset * quantum + count * quantum`).
228///
229/// When the `power` is used, it should be a power of two, between 1 and 31. The locations are then
230/// computed as:
231/// (`offset * quantum * 2^power`, `offset * quantum * 2^power + count * quantum * 2^power`).
232pub(super) fn space_bounds<N: From<u32>>(
233    dim: Dimension,
234    power: u8,
235    offset: SpaceOffset,
236    count: u32,
237) -> (N, N) {
238    debug_assert_ne!(dim.quantum, 0);
239    debug_assert_ne!(count, 0);
240    let q = dim.quantum.wrapping_mul(pow2(power));
241    let start = offset.wrapping_mul(q);
242    let len = count.wrapping_mul(q);
243    (start.into(), start.wrapping_add(len).wrapping_sub(1).into())
244}
245
246// TODO is wrapping meaningful for time? Should we saturate instead?
247pub(super) fn time_bounds64<N: From<i64>>(
248    dim: Dimension,
249    power: u8,
250    offset: TimeOffset,
251    count: u32,
252) -> (N, N) {
253    debug_assert_ne!(dim.quantum, 0);
254    debug_assert_ne!(count, 0);
255    let q = dim.quantum as i64 * 2i64.pow(power.into());
256    let start = (*offset as i64).wrapping_mul(q);
257    let len = (count as i64).wrapping_mul(q);
258    (start.into(), start.wrapping_add(len).wrapping_sub(1).into())
259}
260
261#[cfg(test)]
262mod tests {
263    use super::*;
264
265    #[test]
266    fn simple_space_bounds() {
267        let dim = SpaceDimension::standard();
268        let (lower, upper) = space_bounds::<u32>(dim.into(), 0, 5.into(), 2);
269
270        assert_eq!(lower, dim.quantum * 5);
271        assert_eq!(upper, dim.quantum * 5 + dim.quantum * 2 - 1);
272    }
273
274    #[test]
275    fn simple_wrapping_space_bounds() {
276        let dim = SpaceDimension::standard();
277        let (lower, upper) = space_bounds::<u32>(
278            dim.into(),
279            0,
280            5.into(),
281            pow2(32 - dim.quantum_power) - 5 + 1,
282        );
283
284        assert!(upper < lower, "lower: {lower}, upper: {upper}");
285
286        assert_eq!(lower, dim.quantum * 5);
287        assert_eq!(upper, dim.quantum - 1);
288    }
289
290    #[test]
291    fn space_bounds_with_quantization() {
292        let dim = SpaceDimension::standard();
293
294        // This power further scales the quantum size.
295        // This is used to apply Arq quantization on top of the space's own quantization.
296        let (lower, upper) = space_bounds::<u32>(dim.into(), 2, 5.into(), 2);
297
298        let scaled_quantum = dim.quantum * pow2(2);
299        assert_eq!(lower, scaled_quantum * 5);
300        assert_eq!(upper, scaled_quantum * 5 + scaled_quantum * 2 - 1);
301    }
302
303    #[test]
304    fn simple_time_bounds() {
305        let dim = TimeDimension::standard();
306        let (lower, upper) = time_bounds64::<i64>(dim.into(), 0, 5.into(), 2);
307
308        assert_eq!(lower, dim.quantum as i64 * 5);
309        assert_eq!(upper, dim.quantum as i64 * 5 + dim.quantum as i64 * 2 - 1);
310    }
311
312    #[test]
313    fn time_bounds_with_quantization() {
314        let dim = TimeDimension::standard();
315
316        // This power further scales the quantum size.
317        // This is used to apply Arq quantization on top of the time's own quantization.
318        let (lower, upper) = time_bounds64::<i64>(dim.into(), 2, 5.into(), 2);
319
320        let scaled_quantum = (dim.quantum * pow2(2)) as i64;
321        assert_eq!(lower, scaled_quantum * 5);
322        assert_eq!(upper, scaled_quantum * 5 + scaled_quantum * 2 - 1);
323    }
324}