domain/base/
serial.rs

1//! Serial numbers.
2//!
3//! DNS uses 32 bit serial numbers in various places that are conceptionally
4//! viewed as the 32 bit modulus of a larger number space. Because of that,
5//! special rules apply when processing these values. This module provides
6//! the type [`Serial`] that implements these rules.
7
8use super::cmp::CanonicalOrd;
9use super::scan::{Scan, Scanner};
10use super::wire::{Compose, Composer, Parse, ParseError};
11#[cfg(feature = "chrono")]
12use chrono::{DateTime, TimeZone};
13use core::cmp::Ordering;
14use core::{cmp, fmt, str};
15#[cfg(all(feature = "std", test))]
16use mock_instant::thread_local::{SystemTime, UNIX_EPOCH};
17use octseq::parse::Parser;
18#[cfg(all(feature = "std", not(test)))]
19use std::time::{SystemTime, UNIX_EPOCH};
20
21//------------ Serial --------------------------------------------------------
22
23/// A serial number.
24///
25/// Serial numbers are used in DNS to track changes to resources. For
26/// instance, the [`Soa`][crate::rdata::rfc1035::Soa] record type provides
27/// a serial number that expresses the version of the zone. Since these
28/// numbers are only 32 bits long, they
29/// can wrap. [RFC 1982] defined the semantics for doing arithmetics in the
30/// face of these wrap-arounds. This type implements these semantics atop a
31/// native `u32`.
32///
33/// The RFC defines two operations: addition and comparison.
34///
35/// For addition, the amount added can only be a positive number of up to
36/// `2^31 - 1`. Because of this, we decided to not implement the
37/// [`Add`] trait but rather have a dedicated method `add` so as to not cause
38/// surprise panics.
39///
40/// Serial numbers only implement a partial ordering. That is, there are
41/// pairs of values that are not equal but there still isn’t one value larger
42/// than the other. Since this is neatly implemented by the [`PartialOrd`]
43/// trait, the type implements that.
44///
45/// [`Add`]: std::ops::Add
46/// [RFC 1982]: https://tools.ietf.org/html/rfc1982
47#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
49pub struct Serial(pub u32);
50
51impl Serial {
52    /// Returns a serial number for the current Unix time.
53    #[cfg(feature = "std")]
54    #[must_use]
55    pub fn now() -> Self {
56        let now = SystemTime::now();
57        let value = match now.duration_since(UNIX_EPOCH) {
58            Ok(value) => value,
59            Err(_) => UNIX_EPOCH.duration_since(now).unwrap(),
60        };
61        Self(value.as_secs() as u32)
62    }
63
64    /// Creates a new serial number from its octets in big endian notation.
65    #[must_use]
66    pub fn from_be_bytes(bytes: [u8; 4]) -> Self {
67        Self(u32::from_be_bytes(bytes))
68    }
69
70    /// Returns the serial number as a raw integer.
71    #[must_use]
72    pub fn into_int(self) -> u32 {
73        self.0
74    }
75
76    /// Add `other` to `self`.
77    ///
78    /// Serial numbers only allow values of up to `2^31 - 1` to be added to
79    /// them. Therefore, this method requires `other` to be a `u32` instead
80    /// of a `Serial` to indicate that you cannot simply add two serials
81    /// together. This is also why we don’t implement the `Add` trait.
82    ///
83    /// # Panics
84    ///
85    /// This method panics if `other` is greater than `2^31 - 1`.
86    #[allow(clippy::should_implement_trait)]
87    #[must_use]
88    pub fn add(self, other: u32) -> Self {
89        assert!(other <= 0x7FFF_FFFF);
90        Serial(self.0.wrapping_add(other))
91    }
92
93    pub fn scan<S: Scanner>(scanner: &mut S) -> Result<Self, S::Error> {
94        u32::scan(scanner).map(Into::into)
95    }
96}
97
98/// # Parsing and Composing
99///
100impl Serial {
101    pub const COMPOSE_LEN: u16 = u32::COMPOSE_LEN;
102
103    pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
104        parser: &mut Parser<Octs>,
105    ) -> Result<Self, ParseError> {
106        u32::parse(parser).map(Into::into)
107    }
108
109    pub fn compose<Target: Composer + ?Sized>(
110        &self,
111        target: &mut Target,
112    ) -> Result<(), Target::AppendError> {
113        self.0.compose(target)
114    }
115}
116
117//--- From and FromStr
118
119impl From<u32> for Serial {
120    fn from(value: u32) -> Serial {
121        Serial(value)
122    }
123}
124
125impl From<Serial> for u32 {
126    fn from(serial: Serial) -> u32 {
127        serial.0
128    }
129}
130
131#[cfg(feature = "chrono")]
132#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
133impl<T: TimeZone> From<DateTime<T>> for Serial {
134    fn from(value: DateTime<T>) -> Self {
135        Self(value.timestamp() as u32)
136    }
137}
138
139impl str::FromStr for Serial {
140    type Err = <u32 as str::FromStr>::Err;
141
142    fn from_str(s: &str) -> Result<Self, Self::Err> {
143        <u32 as str::FromStr>::from_str(s).map(Into::into)
144    }
145}
146
147//--- Display
148
149impl fmt::Display for Serial {
150    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
151        write!(f, "{}", self.0)
152    }
153}
154
155//--- PartialOrd
156
157impl cmp::PartialOrd for Serial {
158    fn partial_cmp(&self, other: &Serial) -> Option<cmp::Ordering> {
159        match self.0.cmp(&other.0) {
160            Ordering::Equal => Some(Ordering::Equal),
161            Ordering::Less => {
162                let sub = other.0 - self.0;
163                match sub.cmp(&0x8000_0000) {
164                    Ordering::Less => Some(Ordering::Less),
165                    Ordering::Greater => Some(Ordering::Greater),
166                    Ordering::Equal => None,
167                }
168            }
169            Ordering::Greater => {
170                let sub = self.0 - other.0;
171                match sub.cmp(&0x8000_0000) {
172                    Ordering::Less => Some(Ordering::Greater),
173                    Ordering::Greater => Some(Ordering::Less),
174                    Ordering::Equal => None,
175                }
176            }
177        }
178    }
179}
180
181impl CanonicalOrd for Serial {
182    fn canonical_cmp(&self, other: &Self) -> cmp::Ordering {
183        self.0.cmp(&other.0)
184    }
185}
186
187//============ Errors ========================================================
188
189#[derive(Clone, Copy, Debug)]
190pub struct IllegalSignatureTime(());
191
192impl fmt::Display for IllegalSignatureTime {
193    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
194        f.write_str("illegal signature time")
195    }
196}
197
198#[cfg(feature = "std")]
199impl std::error::Error for IllegalSignatureTime {}
200
201//============ Testing =======================================================
202
203#[cfg(test)]
204mod test {
205    use super::*;
206
207    #[test]
208    fn good_addition() {
209        assert_eq!(Serial(0).add(4), Serial(4));
210        assert_eq!(
211            Serial(0xFF00_0000).add(0x0F00_0000),
212            Serial(
213                ((0xFF00_0000u64 + 0x0F00_0000u64) % 0x1_0000_0000) as u32
214            )
215        );
216    }
217
218    #[test]
219    #[should_panic]
220    fn bad_addition() {
221        let _ = Serial(0).add(0x8000_0000);
222    }
223
224    #[test]
225    fn comparison() {
226        use core::cmp::Ordering::*;
227
228        assert_eq!(Serial(12), Serial(12));
229        assert_ne!(Serial(12), Serial(112));
230
231        assert_eq!(Serial(12).partial_cmp(&Serial(12)), Some(Equal));
232
233        // s1 is said to be less than s2 if [...]
234        // (i1 < i2 and i2 - i1 < 2^(SERIAL_BITS - 1))
235        assert_eq!(Serial(12).partial_cmp(&Serial(13)), Some(Less));
236        assert_ne!(
237            Serial(12).partial_cmp(&Serial(3_000_000_012)),
238            Some(Less)
239        );
240
241        // or (i1 > i2 and i1 - i2 > 2^(SERIAL_BITS - 1))
242        assert_eq!(
243            Serial(3_000_000_012).partial_cmp(&Serial(12)),
244            Some(Less)
245        );
246        assert_ne!(Serial(13).partial_cmp(&Serial(12)), Some(Less));
247
248        // s1 is said to be greater than s2 if [...]
249        // (i1 < i2 and i2 - i1 > 2^(SERIAL_BITS - 1))
250        assert_eq!(
251            Serial(12).partial_cmp(&Serial(3_000_000_012)),
252            Some(Greater)
253        );
254        assert_ne!(Serial(12).partial_cmp(&Serial(13)), Some(Greater));
255
256        // (i1 > i2 and i1 - i2 < 2^(SERIAL_BITS - 1))
257        assert_eq!(Serial(13).partial_cmp(&Serial(12)), Some(Greater));
258        assert_ne!(
259            Serial(3_000_000_012).partial_cmp(&Serial(12)),
260            Some(Greater)
261        );
262
263        // Er, I think that’s what’s left.
264        assert_eq!(Serial(1).partial_cmp(&Serial(0x8000_0001)), None);
265        assert_eq!(Serial(0x8000_0001).partial_cmp(&Serial(1)), None);
266    }
267}