Skip to main content

domain/new/base/
serial.rs

1//! Serial number arithmetic.
2//!
3//! See [RFC 1982](https://datatracker.ietf.org/doc/html/rfc1982).
4
5use core::{cmp::Ordering, fmt};
6
7use domain_macros::*;
8
9use super::wire::U32;
10
11//----------- Serial ---------------------------------------------------------
12
13/// Serial number arithmetic.
14///
15/// [`Serial`] implements the "Serial number arithmetic" defined in [RFC1982]
16/// with a `SERIAL_BITS` value of 32.
17///
18/// [`Serial`] should not be used interchangably or be confused with the SOA
19/// Serial Number, which is a [`Serial`] but not the sole user of "Serial
20/// number arithmetic".
21///
22/// [RFC1982]: https://datatracker.ietf.org/doc/html/rfc1982
23#[derive(
24    Copy,
25    Clone,
26    Debug,
27    PartialEq,
28    Eq,
29    Hash,
30    AsBytes,
31    BuildBytes,
32    ParseBytes,
33    ParseBytesZC,
34    SplitBytes,
35    SplitBytesZC,
36    UnsizedCopy,
37)]
38#[repr(transparent)]
39pub struct Serial(pub U32);
40
41//--- Construction
42
43impl Serial {
44    /// Construct a new [`Serial`]
45    #[must_use]
46    pub const fn new(value: u32) -> Self {
47        Serial(U32::new(value))
48    }
49
50    /// Get [`u32`] value of [`Serial`]
51    #[must_use]
52    pub const fn get(&self) -> u32 {
53        self.0.get()
54    }
55
56    /// Measure the current time (in seconds) in serial number space.
57    #[cfg(feature = "std")]
58    pub fn unix_time() -> Self {
59        use std::time::SystemTime;
60
61        let time = SystemTime::now()
62            .duration_since(SystemTime::UNIX_EPOCH)
63            .expect("The current time is after the Unix Epoch");
64        Self::from(time.as_secs() as u32)
65    }
66}
67
68//--- Interaction
69
70impl Serial {
71    /// Increment this by a non-negative number.
72    ///
73    /// The number must be in the range `[0, 2^31 - 1]`. An [`i32`] is used
74    /// instead of a [`u32`] because it is easier to understand and implement
75    /// a non-negative check versus the upper range check.
76    ///
77    /// Section 7 in [RFC1982] states, in particular for the SOA Serial
78    /// Number, but for any Serial number using a "SERIAL_BITS" value of 32:
79    /// "The maximum defined increment is 2147483647 (2^31 - 1)."
80    ///
81    /// # Panics
82    ///
83    /// Panics if the number is negative.
84    ///
85    /// [RFC1982]: https://datatracker.ietf.org/doc/html/rfc1982#section-7
86    pub fn inc(self, num: i32) -> Self {
87        assert!(num >= 0, "Cannot subtract from a `Serial`");
88        self.0.get().wrapping_add_signed(num).into()
89    }
90}
91
92//--- Ordering
93
94impl PartialOrd for Serial {
95    /// The comparison of Serial Number values is defined in Section 3.2
96    /// [RFC1982].
97    ///
98    /// [RFC1982]: https://datatracker.ietf.org/doc/html/rfc1982#section-3.2
99    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
100        let (lhs, rhs) = (self.0.get(), other.0.get());
101
102        if lhs == rhs {
103            Some(Ordering::Equal)
104        } else if lhs.abs_diff(rhs) == 1 << 31 {
105            None
106        } else if (lhs < rhs) ^ (lhs.abs_diff(rhs) > (1 << 31)) {
107            Some(Ordering::Less)
108        } else {
109            Some(Ordering::Greater)
110        }
111    }
112}
113
114//--- Conversion to and from native integer types
115
116impl From<u32> for Serial {
117    fn from(value: u32) -> Self {
118        Self(U32::new(value))
119    }
120}
121
122impl From<Serial> for u32 {
123    fn from(value: Serial) -> Self {
124        value.0.get()
125    }
126}
127
128//--- Formatting
129
130impl fmt::Display for Serial {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        self.0.get().fmt(f)
133    }
134}
135
136//============ Tests =========================================================
137
138#[cfg(test)]
139mod test {
140    use super::Serial;
141
142    #[test]
143    fn comparisons() {
144        // TODO: Use property-based testing.
145        assert!(Serial::from(u32::MAX) > Serial::from(u32::MAX / 2 + 1));
146        assert!(Serial::from(0) > Serial::from(u32::MAX));
147        assert!(Serial::from(1) > Serial::from(0));
148    }
149
150    #[test]
151    fn operations() {
152        // TODO: Use property-based testing.
153        assert_eq!(u32::from(Serial::from(1).inc(1)), 2);
154        assert_eq!(u32::from(Serial::from(u32::MAX).inc(1)), 0);
155    }
156}