Skip to main content

nectar_primitives/
bin.rs

1//! Typed Kademlia bin index in the range `0..=MAX_PO`.
2//!
3//! A [`Bin`] is a routing-table slot keyed by the proximity order between an
4//! anchor address (e.g. our own overlay) and a peer's overlay. Distinguished
5//! from [`ProximityOrder`] at the type level even
6//! though they share a representation. PO is the metric, Bin is the slot.
7
8use crate::{MAX_PO, ProximityOrder};
9use derive_more::{Display, Into};
10
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13
14/// Typed Kademlia bin index, `0..=MAX_PO` (= 0..=31).
15#[repr(transparent)]
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Display, Into)]
17#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
18#[cfg_attr(feature = "serde", serde(transparent))]
19#[display("bin={_0}")]
20#[into(u8, usize)]
21pub struct Bin(u8);
22
23/// Errors from constructing a [`Bin`].
24#[non_exhaustive]
25#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
26pub enum BinError {
27    /// Value exceeded [`MAX_PO`].
28    #[error("bin index {raw} exceeds MAX_PO ({max})")]
29    OutOfRange {
30        /// The rejected value.
31        raw: u8,
32        /// The maximum permitted value.
33        max: u8,
34    },
35}
36
37impl Bin {
38    /// The first (shallowest) bin.
39    pub const ZERO: Self = Self(0);
40
41    /// The deepest bin ([`MAX_PO`] = 31).
42    pub const MAX: Self = Self(MAX_PO);
43
44    /// The number of bins in the routing table (`MAX_PO + 1` = 32).
45    pub const COUNT: usize = MAX_PO as usize + 1;
46
47    /// Construct without bounds checking. Caller must ensure `raw <= MAX_PO`.
48    #[inline]
49    pub(crate) const fn new_unchecked(raw: u8) -> Self {
50        debug_assert!(raw <= MAX_PO);
51        Self(raw)
52    }
53
54    /// Construct from a raw byte, validating the range.
55    #[inline]
56    pub const fn new(raw: u8) -> Result<Self, BinError> {
57        if raw <= MAX_PO {
58            Ok(Self(raw))
59        } else {
60            Err(BinError::OutOfRange { raw, max: MAX_PO })
61        }
62    }
63
64    /// Underlying byte value.
65    #[inline]
66    pub const fn get(self) -> u8 {
67        self.0
68    }
69
70    /// Index into a `[T; Bin::COUNT]` array.
71    #[inline]
72    pub const fn as_index(self) -> usize {
73        self.0 as usize
74    }
75}
76
77impl TryFrom<u8> for Bin {
78    type Error = BinError;
79
80    fn try_from(raw: u8) -> Result<Self, Self::Error> {
81        Self::new(raw)
82    }
83}
84
85impl From<ProximityOrder> for Bin {
86    /// Every valid [`ProximityOrder`] is a valid [`Bin`] (they share a range).
87    /// Use this when crossing the metric/slot semantic boundary.
88    fn from(po: ProximityOrder) -> Self {
89        // PO is range-validated, so this is sound.
90        Self(po.get())
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn min_max_count() {
100        assert_eq!(Bin::ZERO.get(), 0);
101        assert_eq!(Bin::MAX.get(), MAX_PO);
102        assert_eq!(Bin::COUNT, MAX_PO as usize + 1);
103    }
104
105    #[test]
106    fn new_in_range_ok() {
107        assert!(Bin::new(0).is_ok());
108        assert!(Bin::new(MAX_PO).is_ok());
109    }
110
111    #[test]
112    fn new_out_of_range_errs() {
113        let err = Bin::new(MAX_PO + 1).unwrap_err();
114        assert!(matches!(err, BinError::OutOfRange { raw: 32, max: 31 }));
115    }
116
117    #[test]
118    fn from_proximity_order() {
119        let po = ProximityOrder::new(7).unwrap();
120        let bin = Bin::from(po);
121        assert_eq!(bin.get(), 7);
122    }
123
124    #[test]
125    fn display_shows_bin() {
126        assert_eq!(format!("{}", Bin::new(3).unwrap()), "bin=3");
127    }
128}