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}