Skip to main content

mm1_address/
address_range.rs

1use std::fmt;
2
3use crate::address::Address;
4use crate::subnet::{NetAddress, NetMask};
5
6/// A range of addresses corresponding to some [`NetAddress`].
7///
8/// The only way to create an instance of [`AddressRange`] is from a
9/// [`NetAddress`] or [`Address`], thus only valid ranges are represented by
10/// this type.
11///
12/// This type can be used as a key in a key-value collection that uses `Ord` for
13/// its keys. (**NB:** the equality is defined as "if the ranges overlap — they
14/// are equal")
15///
16/// Because overlap-as-equality is not transitive, this `Ord` is a valid total
17/// order only over a set of **disjoint** ranges. That is the intended use: a
18/// point range (`from(Address)`) compares *equal* to the single disjoint subnet
19/// that contains it, which is how the runtime looks a subnet up by address. Any
20/// collection keyed by `AddressRange` must keep its keys disjoint (callers
21/// check for overlap before inserting); mixing overlapping-but-distinct ranges
22/// makes lookups unspecified.
23#[derive(Debug, Clone, Copy)]
24pub struct AddressRange {
25    lo: Address,
26    hi: Address,
27}
28
29impl AddressRange {
30    fn new(lo: Address, hi: Address) -> Self {
31        debug_assert!(lo <= hi, "AddressRange lo must not exceed hi: {lo}-{hi}");
32        Self { lo, hi }
33    }
34
35    pub fn lo(&self) -> Address {
36        self.lo
37    }
38
39    pub fn hi(&self) -> Address {
40        self.hi
41    }
42}
43
44impl From<Address> for AddressRange {
45    fn from(addr: Address) -> Self {
46        Self::new(addr, addr)
47    }
48}
49impl From<NetAddress> for AddressRange {
50    fn from(net: NetAddress) -> Self {
51        let lo = net.address;
52        let hi = Address::from_u64(net.address.into_u64() | !net.mask.into_u64());
53
54        Self::new(lo, hi)
55    }
56}
57
58impl From<AddressRange> for NetAddress {
59    fn from(range: AddressRange) -> Self {
60        let AddressRange { lo, hi } = range;
61        let address = lo;
62        let mask_bits = !(lo.into_u64() ^ hi.into_u64());
63        let mask = NetMask::try_from(mask_bits.leading_ones() as u8).expect("should be fine");
64        Self { address, mask }
65    }
66}
67
68impl Ord for AddressRange {
69    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
70        use std::cmp::Ordering::*;
71
72        // `lo <= hi` is guaranteed at construction, so it is not re-checked on
73        // this hot comparison path.
74        match (self.hi.cmp(&other.lo), self.lo.cmp(&other.hi)) {
75            (Less, Less) => Less,
76            (Greater, Greater) => Greater,
77            (..) => Equal,
78        }
79    }
80}
81impl PartialEq for AddressRange {
82    fn eq(&self, other: &Self) -> bool {
83        Ord::cmp(self, other).is_eq()
84    }
85}
86impl Eq for AddressRange {}
87impl PartialOrd for AddressRange {
88    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
89        Some(Ord::cmp(self, other))
90    }
91}
92
93impl fmt::Display for AddressRange {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        write!(f, "{}-{}", self.lo, self.hi)
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    fn range(lo: u64, hi: u64) -> AddressRange {
104        AddressRange::new(Address::from_u64(lo), Address::from_u64(hi))
105    }
106
107    // Locks the intended semantics for #148: overlap-as-equality is used for
108    // containment lookup, and disjoint ranges form a proper total order.
109    #[test]
110    fn ordering_semantics() {
111        use std::cmp::Ordering::*;
112
113        // A point inside a range compares equal to that range (containment).
114        assert_eq!(range(5, 5).cmp(&range(0, 10)), Equal);
115        assert_eq!(range(0, 10).cmp(&range(5, 5)), Equal);
116
117        // Disjoint ranges order by position.
118        assert_eq!(range(0, 10).cmp(&range(11, 20)), Less);
119        assert_eq!(range(11, 20).cmp(&range(0, 10)), Greater);
120
121        // Touching boundaries overlap, hence compare equal.
122        assert_eq!(range(0, 10).cmp(&range(10, 20)), Equal);
123    }
124}