Skip to main content

ts_bart/
base_index.rs

1//! Prefix mapping described in Hariguchi's Allotment Routing Table paper.
2//!
3//! Ported fairly literally from the golang bart repo (where this file is
4//! `internal/art.go`).
5
6use core::{
7    fmt::{Debug, Display, Formatter},
8    num::NonZeroU8,
9};
10
11/// A "base index" as described in Hariguchi's ART paper; a linearized address
12/// for a prefix/octet tuple when considering the hierarchy of possible prefixes
13/// for a given 8-bit address.
14///
15/// Note that this only covers up to /7, fringes (/8s) would require a ninth
16/// bit, which is encoded structurally in the trie as `children` (vs
17/// `prefixes`).
18#[repr(transparent)]
19#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub struct BaseIndex(pub NonZeroU8);
21
22impl Debug for BaseIndex {
23    #[inline]
24    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
25        let (pfx, bits) = self.prefix();
26        write!(f, "BaseIndex({} => {pfx}/{bits})", self.0.get())
27    }
28}
29
30impl Display for BaseIndex {
31    #[inline]
32    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
33        Display::fmt(&self.0.get(), f)
34    }
35}
36
37impl BaseIndex {
38    /// Construct a new index value.
39    ///
40    /// # Panics
41    ///
42    /// If `val` is zero.
43    #[inline]
44    pub const fn new(val: u8) -> Self {
45        Self::try_new(val).expect("index value was zero")
46    }
47
48    /// Construct a new index value. Fails if the value is zero.
49    #[inline]
50    pub const fn try_new(val: u8) -> Option<Self> {
51        let Some(idx) = NonZeroU8::new(val) else {
52            return None;
53        };
54
55        Some(Self(idx))
56    }
57
58    /// Retrieve the value of this index in its `u8` representation.
59    #[inline]
60    pub const fn get(&self) -> u8 {
61        self.0.get()
62    }
63
64    /// Maps 8-bit prefixes to numbers. The prefixes range from 0/0 to 255/7.
65    /// Return values range from 1 to 255.
66    ///
67    /// # Panics
68    ///
69    /// If `prefix_len` >= 8.
70    #[inline]
71    pub const fn from_prefix(octet: u8, prefix_len: u8) -> Self {
72        assert!(
73            prefix_len <= 7,
74            "BaseIndex prefix length must be between 0 and 7"
75        );
76        BaseIndex::new(octet.unbounded_shr(8 - prefix_len as u32) + (1 << prefix_len))
77    }
78
79    /// Maps octet/7 prefixes to indices in `128..255`. Optimization over
80    /// [`Self::from_prefix`] which saves a little bit of math if the prefix
81    /// length is known to be `/7`.
82    #[inline]
83    pub const fn from_pfx_7(octet: u8) -> Self {
84        let ret = Self::new(0x80 + (octet.unbounded_shr(1)));
85        debug_assert!(ret.prefix().1 == 7);
86        ret
87    }
88
89    /// Computes the octet and prefix len of this index.
90    /// Inverse of [`Self::from_prefix`].
91    pub const fn prefix(self) -> (u8, u8) {
92        let pfx_len = self.len() - 1;
93
94        let shift_bits = 8 - pfx_len;
95        let mask = 0xffu8.unbounded_shr(shift_bits as _);
96
97        let octet = (self.get() & mask).unbounded_shl(shift_bits as _);
98
99        (octet, pfx_len)
100    }
101
102    /// Compute the bit position of a prefix represented by this index at a
103    /// given trie depth.
104    #[inline]
105    pub const fn prefix_bits(&self, depth: usize) -> u8 {
106        let pfx_len_in_stride = self.len() - 1;
107        let base_bits = depth * 8;
108
109        base_bits as u8 + pfx_len_in_stride
110    }
111
112    /// Range of octets covered by this index.
113    ///
114    /// This base index encodes a prefix of up to 8 bits inside a single stride
115    /// (octet). This function computes the numerical start and end of the value
116    /// range for that prefix.
117    #[inline]
118    pub const fn range(&self) -> core::ops::RangeInclusive<u8> {
119        let (first, pfx_len) = self.prefix();
120        let last = first | !net_mask(pfx_len);
121
122        first..=last
123    }
124
125    /// Like go's `bits.Len8`: compute the number of bits required to represent
126    /// this index.
127    #[inline]
128    #[allow(clippy::len_without_is_empty)]
129    pub const fn len(&self) -> u8 {
130        (u8::BITS - self.get().leading_zeros()) as _
131    }
132
133    /// Sort indexes in prefix sort order.
134    #[inline]
135    pub fn cmp_rank(&self, other: &Self) -> core::cmp::Ordering {
136        let (a_octet, a_bits) = self.prefix();
137        let (b_octet, b_bits) = other.prefix();
138
139        a_octet.cmp(&b_octet).then(a_bits.cmp(&b_bits))
140    }
141
142    /// Return a formatter for prefix notation: `addr/len`.
143    #[inline]
144    pub const fn fmt_prefix(&self) -> impl Debug + use<> {
145        struct PrefixFormatter(u8, u8);
146        impl Debug for PrefixFormatter {
147            #[inline]
148            fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
149                write!(f, "{}/{}", self.0, self.1)
150            }
151        }
152
153        let (prefix, bits) = self.prefix();
154        PrefixFormatter(prefix, bits)
155    }
156
157    /// Return the direct parent of this index (if it's not the 0/0 index).
158    ///
159    /// # Examples
160    ///
161    /// ```rust
162    /// # use ts_bart::BaseIndex;
163    /// let idx = BaseIndex::from_prefix(0, 4);
164    /// assert_eq!(idx.parent(), Some(BaseIndex::from_prefix(0, 3)));
165    /// ```
166    #[inline]
167    pub const fn parent(&self) -> Option<BaseIndex> {
168        let val = self.0.get();
169        if val == 1 {
170            return None;
171        }
172
173        Some(BaseIndex::new(val / 2))
174    }
175
176    /// Return the two direct children of this index (if it's not a /7).
177    ///
178    /// # Examples
179    ///
180    /// ```rust
181    /// # use ts_bart::BaseIndex;
182    /// let idx = BaseIndex::from_prefix(0, 0);
183    /// assert_eq!(
184    ///     idx.children(),
185    ///     Some((BaseIndex::from_prefix(0, 1), BaseIndex::from_prefix(128, 1)))
186    /// );
187    /// ```
188    #[inline]
189    pub const fn children(&self) -> Option<(BaseIndex, BaseIndex)> {
190        let val = self.0.get();
191        if val >= 128 {
192            return None;
193        }
194
195        Some((BaseIndex::new(val * 2), BaseIndex::new(val * 2 + 1)))
196    }
197}
198
199impl From<BaseIndex> for u8 {
200    #[inline]
201    fn from(value: BaseIndex) -> Self {
202        value.get()
203    }
204}
205
206impl From<BaseIndex> for NonZeroU8 {
207    #[inline]
208    fn from(value: BaseIndex) -> Self {
209        value.0
210    }
211}
212
213impl From<NonZeroU8> for BaseIndex {
214    #[inline]
215    fn from(value: NonZeroU8) -> Self {
216        Self(value)
217    }
218}
219
220impl TryFrom<u8> for BaseIndex {
221    type Error = ();
222
223    #[inline]
224    fn try_from(value: u8) -> Result<Self, Self::Error> {
225        Self::try_new(value).ok_or(())
226    }
227}
228
229/// 8-bit left-aligned network mask for the given number of prefix bits.
230#[inline]
231pub const fn net_mask(bits: u8) -> u8 {
232    assert!(bits <= 8);
233
234    0xffu8.unbounded_shl((8 - bits) as _)
235}
236
237/// Ported directly from bart.
238#[cfg(test)]
239mod test {
240    use super::*;
241
242    #[test]
243    fn test_octet_to_idx() {
244        assert_eq!(128, BaseIndex::from_pfx_7(0).get());
245        assert_eq!(255, BaseIndex::from_pfx_7(255).get());
246        assert_eq!(192, BaseIndex::from_pfx_7(128).get());
247    }
248
249    #[test]
250    fn test_pfx_bits() {
251        assert_eq!(0, BaseIndex::new(1).prefix_bits(0));
252        assert_eq!(4, BaseIndex::new(19).prefix_bits(0));
253        assert_eq!(124, BaseIndex::new(19).prefix_bits(15));
254    }
255
256    #[test]
257    fn test_pfx_to_idx() {
258        assert_eq!(1, BaseIndex::from_prefix(0, 0).get());
259        assert_eq!(2, BaseIndex::from_prefix(0, 1).get());
260        assert_eq!(3, BaseIndex::from_prefix(128, 1).get());
261        assert_eq!(21, BaseIndex::from_prefix(80, 4).get());
262        assert_eq!(255, BaseIndex::from_prefix(255, 7).get());
263    }
264
265    #[test]
266    fn test_idx_to_pfx() {
267        assert_eq!((0, 0), BaseIndex::new(1).prefix());
268        assert_eq!((224, 3), BaseIndex::new(15).prefix());
269        assert_eq!((254, 7), BaseIndex::new(255).prefix());
270
271        // From nodebasics_test "special cases"
272        assert_eq!((0, 1), BaseIndex::new(2).prefix());
273        assert_eq!((128, 1), BaseIndex::new(3).prefix());
274
275        assert_eq!((0, 2), BaseIndex::new(4).prefix());
276        assert_eq!((64, 2), BaseIndex::new(5).prefix());
277        assert_eq!((128, 2), BaseIndex::new(6).prefix());
278        assert_eq!((192, 2), BaseIndex::new(7).prefix());
279
280        assert_eq!((224, 3), BaseIndex::new(15).prefix());
281        assert_eq!((240, 4), BaseIndex::new(31).prefix());
282        assert_eq!((248, 5), BaseIndex::new(63).prefix());
283        assert_eq!((252, 6), BaseIndex::new(127).prefix());
284        assert_eq!((254, 7), BaseIndex::new(255).prefix());
285    }
286
287    #[test]
288    fn test_idx_to_range() {
289        assert_eq!(0..=255, BaseIndex::new(1).range());
290        assert_eq!(0..=127, BaseIndex::new(2).range());
291        assert_eq!(128..=255, BaseIndex::new(3).range());
292        assert_eq!(0..=63, BaseIndex::new(4).range());
293        assert_eq!(0..=31, BaseIndex::new(8).range());
294        assert_eq!(160..=191, BaseIndex::new(13).range(),);
295        assert_eq!(68..=71, BaseIndex::new(81).range());
296        assert_eq!(252..=253, BaseIndex::new(254).range());
297        assert_eq!(254..=255, BaseIndex::new(255).range());
298    }
299
300    #[test]
301    fn test_net_mask() {
302        assert_eq!(0, net_mask(0));
303        assert_eq!(0x80, net_mask(1));
304        assert_eq!(0xc0, net_mask(2));
305        assert_eq!(0xe0, net_mask(3));
306        assert_eq!(0xf0, net_mask(4));
307        assert_eq!(0xf8, net_mask(5));
308        assert_eq!(0xfc, net_mask(6));
309        assert_eq!(0xfe, net_mask(7));
310        assert_eq!(0xff, net_mask(8));
311    }
312
313    #[test]
314    #[should_panic]
315    fn test_net_mask_panics() {
316        net_mask(9);
317    }
318
319    #[test]
320    fn roundtrip_pfx_7() {
321        let idx = BaseIndex::from_pfx_7(131);
322        std::println!("{idx:?}");
323
324        // from_pfx_7 maps to /7s
325        let (_pfx, bits) = idx.prefix();
326        assert_eq!(bits, 7);
327    }
328}