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//!
8//! [`Serial`]: struct.Serial.html
9
10use super::cmp::CanonicalOrd;
11use super::scan::{Scan, Scanner, ScannerError};
12use super::wire::{Compose, Composer, Parse, ParseError};
13#[cfg(feature = "chrono")]
14use chrono::{DateTime, TimeZone};
15use core::cmp::Ordering;
16use core::convert::TryFrom;
17use core::str::FromStr;
18use core::{cmp, fmt, str};
19use octseq::parse::Parser;
20#[cfg(feature = "std")]
21use std::time::{SystemTime, UNIX_EPOCH};
22use time::{Date, Month, PrimitiveDateTime, Time};
23
24//------------ Serial --------------------------------------------------------
25
26/// A serial number.
27///
28/// Serial numbers are used in DNS to track changes to resources. For
29/// instance, the [`Soa`][crate::rdata::rfc1035::Soa] record type provides
30/// a serial number that expresses the version of the zone. Since these
31/// numbers are only 32 bits long, they
32/// can wrap. [RFC 1982] defined the semantics for doing arithmetics in the
33/// face of these wrap-arounds. This type implements these semantics atop a
34/// native `u32`.
35///
36/// The RFC defines two operations: addition and comparison.
37///
38/// For addition, the amount added can only be a positive number of up to
39/// `2^31 - 1`. Because of this, we decided to not implement the
40/// `Add` trait but rather have a dedicated method `add` so as to not cause
41/// surprise panics.
42///
43/// Serial numbers only implement a partial ordering. That is, there are
44/// pairs of values that are not equal but there still isn’t one value larger
45/// than the other. Since this is neatly implemented by the `PartialOrd`
46/// trait, the type implements that.
47///
48/// [RFC 1982]: https://tools.ietf.org/html/rfc1982
49#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
50#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
51pub struct Serial(pub u32);
52
53impl Serial {
54    /// Returns a serial number for the current Unix time.
55    #[cfg(feature = "std")]
56    #[must_use]
57    pub fn now() -> Self {
58        let now = SystemTime::now();
59        let value = match now.duration_since(UNIX_EPOCH) {
60            Ok(value) => value,
61            Err(_) => UNIX_EPOCH.duration_since(now).unwrap(),
62        };
63        Self(value.as_secs() as u32)
64    }
65
66    /// Creates a new serial number from its octets in big endian notation.
67    #[must_use]
68    pub fn from_be_bytes(bytes: [u8; 4]) -> Self {
69        Self(u32::from_be_bytes(bytes))
70    }
71
72    /// Returns the serial number as a raw integer.
73    #[must_use]
74    pub fn into_int(self) -> u32 {
75        self.0
76    }
77
78    /// Add `other` to `self`.
79    ///
80    /// Serial numbers only allow values of up to `2^31 - 1` to be added to
81    /// them. Therefore, this method requires `other` to be a `u32` instead
82    /// of a `Serial` to indicate that you cannot simply add two serials
83    /// together. This is also why we don’t implement the `Add` trait.
84    ///
85    /// # Panics
86    ///
87    /// This method panics if `other` is greater than `2^31 - 1`.
88    #[allow(clippy::should_implement_trait)]
89    #[must_use]
90    pub fn add(self, other: u32) -> Self {
91        assert!(other <= 0x7FFF_FFFF);
92        Serial(self.0.wrapping_add(other))
93    }
94
95    pub fn scan<S: Scanner>(scanner: &mut S) -> Result<Self, S::Error> {
96        u32::scan(scanner).map(Into::into)
97    }
98
99    /// Scan a serial represention signature time value.
100    ///
101    /// In [RRSIG] records, the expiration and inception times are given as
102    /// serial values. Their representation format can either be the
103    /// value or a specific date in `YYYYMMDDHHmmSS` format.
104    ///
105    /// [RRSIG]: ../../rdata/rfc4034/struct.Rrsig.html
106    pub fn scan_rrsig<S: Scanner>(scanner: &mut S) -> Result<Self, S::Error> {
107        let mut pos = 0;
108        let mut buf = [0u8; 14];
109        scanner.scan_symbols(|symbol| {
110            if pos >= 14 {
111                return Err(S::Error::custom("illegal signature time"));
112            }
113            buf[pos] = symbol
114                .into_digit(10)
115                .map_err(|_| S::Error::custom("illegal signature time"))?
116                as u8;
117            pos += 1;
118            Ok(())
119        })?;
120        if pos <= 10 {
121            // We have an integer. We generate it into a u64 to deal
122            // with possible overflows.
123            let mut res = 0u64;
124            for ch in &buf[..pos] {
125                res = res * 10 + (u64::from(*ch));
126            }
127            if res > u64::from(u32::MAX) {
128                Err(S::Error::custom("illegal signature time"))
129            } else {
130                Ok(Serial(res as u32))
131            }
132        } else if pos == 14 {
133            let year = u32_from_buf(&buf[0..4]) as i32;
134            let month = Month::try_from(u8_from_buf(&buf[4..6]))
135                .map_err(|_| S::Error::custom("illegal signature time"))?;
136            let day = u8_from_buf(&buf[6..8]);
137            let hour = u8_from_buf(&buf[8..10]);
138            let minute = u8_from_buf(&buf[10..12]);
139            let second = u8_from_buf(&buf[12..14]);
140            Ok(Serial(
141                PrimitiveDateTime::new(
142                    Date::from_calendar_date(year, month, day).map_err(
143                        |_| S::Error::custom("illegal signature time"),
144                    )?,
145                    Time::from_hms(hour, minute, second).map_err(|_| {
146                        S::Error::custom("illegal signature time")
147                    })?,
148                )
149                .assume_utc()
150                .unix_timestamp() as u32,
151            ))
152        } else {
153            Err(S::Error::custom("illegal signature time"))
154        }
155    }
156
157    /// Parses a serial representing a time value from a string.
158    ///
159    /// In [RRSIG] records, the expiration and inception times are given as
160    /// serial values. Their representation format can either be the
161    /// value or a specific date in `YYYYMMDDHHmmSS` format.
162    ///
163    /// [RRSIG]: ../../rdata/rfc4034/struct.Rrsig.html
164    pub fn rrsig_from_str(src: &str) -> Result<Self, IllegalSignatureTime> {
165        if !src.is_ascii() {
166            return Err(IllegalSignatureTime);
167        }
168        if src.len() == 14 {
169            let year = u32::from_str(&src[0..4])
170                .map_err(|_| IllegalSignatureTime)?
171                as i32;
172            let month = Month::try_from(
173                u8::from_str(&src[4..6]).map_err(|_| IllegalSignatureTime)?,
174            )
175            .map_err(|_| IllegalSignatureTime)?;
176            let day =
177                u8::from_str(&src[6..8]).map_err(|_| IllegalSignatureTime)?;
178            let hour = u8::from_str(&src[8..10])
179                .map_err(|_| IllegalSignatureTime)?;
180            let minute = u8::from_str(&src[10..12])
181                .map_err(|_| IllegalSignatureTime)?;
182            let second = u8::from_str(&src[12..14])
183                .map_err(|_| IllegalSignatureTime)?;
184            Ok(Serial(
185                PrimitiveDateTime::new(
186                    Date::from_calendar_date(year, month, day)
187                        .map_err(|_| IllegalSignatureTime)?,
188                    Time::from_hms(hour, minute, second)
189                        .map_err(|_| IllegalSignatureTime)?,
190                )
191                .assume_utc()
192                .unix_timestamp() as u32,
193            ))
194        } else {
195            Serial::from_str(src).map_err(|_| IllegalSignatureTime)
196        }
197    }
198}
199
200/// # Parsing and Composing
201///
202impl Serial {
203    pub const COMPOSE_LEN: u16 = u32::COMPOSE_LEN;
204
205    pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
206        parser: &mut Parser<Octs>,
207    ) -> Result<Self, ParseError> {
208        u32::parse(parser).map(Into::into)
209    }
210
211    pub fn compose<Target: Composer + ?Sized>(
212        &self,
213        target: &mut Target,
214    ) -> Result<(), Target::AppendError> {
215        self.0.compose(target)
216    }
217}
218
219//--- From and FromStr
220
221impl From<u32> for Serial {
222    fn from(value: u32) -> Serial {
223        Serial(value)
224    }
225}
226
227impl From<Serial> for u32 {
228    fn from(serial: Serial) -> u32 {
229        serial.0
230    }
231}
232
233#[cfg(feature = "chrono")]
234#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
235impl<T: TimeZone> From<DateTime<T>> for Serial {
236    fn from(value: DateTime<T>) -> Self {
237        Self(value.timestamp() as u32)
238    }
239}
240
241impl str::FromStr for Serial {
242    type Err = <u32 as str::FromStr>::Err;
243
244    fn from_str(s: &str) -> Result<Self, Self::Err> {
245        <u32 as str::FromStr>::from_str(s).map(Into::into)
246    }
247}
248
249//--- Display
250
251impl fmt::Display for Serial {
252    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
253        write!(f, "{}", self.0)
254    }
255}
256
257//--- PartialOrd
258
259impl cmp::PartialOrd for Serial {
260    fn partial_cmp(&self, other: &Serial) -> Option<cmp::Ordering> {
261        match self.0.cmp(&other.0) {
262            Ordering::Equal => Some(Ordering::Equal),
263            Ordering::Less => {
264                let sub = other.0 - self.0;
265                match sub.cmp(&0x8000_0000) {
266                    Ordering::Less => Some(Ordering::Less),
267                    Ordering::Greater => Some(Ordering::Greater),
268                    Ordering::Equal => None,
269                }
270            }
271            Ordering::Greater => {
272                let sub = self.0 - other.0;
273                match sub.cmp(&0x8000_0000) {
274                    Ordering::Less => Some(Ordering::Greater),
275                    Ordering::Greater => Some(Ordering::Less),
276                    Ordering::Equal => None,
277                }
278            }
279        }
280    }
281}
282
283impl CanonicalOrd for Serial {
284    fn canonical_cmp(&self, other: &Self) -> cmp::Ordering {
285        self.0.cmp(&other.0)
286    }
287}
288
289//------------ Helper Functions ----------------------------------------------
290
291fn u8_from_buf(buf: &[u8]) -> u8 {
292    let mut res = 0;
293    for ch in buf {
294        res = res * 10 + *ch;
295    }
296    res
297}
298
299fn u32_from_buf(buf: &[u8]) -> u32 {
300    let mut res = 0;
301    for ch in buf {
302        res = res * 10 + (u32::from(*ch));
303    }
304    res
305}
306
307//============ Testing =======================================================
308
309#[derive(Clone, Copy, Debug)]
310pub struct IllegalSignatureTime;
311
312impl fmt::Display for IllegalSignatureTime {
313    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
314        f.write_str("illegal signature time")
315    }
316}
317
318#[cfg(feature = "std")]
319impl std::error::Error for IllegalSignatureTime {}
320
321//============ Testing =======================================================
322
323#[cfg(test)]
324mod test {
325    use super::*;
326
327    #[test]
328    fn good_addition() {
329        assert_eq!(Serial(0).add(4), Serial(4));
330        assert_eq!(
331            Serial(0xFF00_0000).add(0x0F00_0000),
332            Serial(
333                ((0xFF00_0000u64 + 0x0F00_0000u64) % 0x1_0000_0000) as u32
334            )
335        );
336    }
337
338    #[test]
339    #[should_panic]
340    fn bad_addition() {
341        let _ = Serial(0).add(0x8000_0000);
342    }
343
344    #[test]
345    fn comparison() {
346        use core::cmp::Ordering::*;
347
348        assert_eq!(Serial(12), Serial(12));
349        assert_ne!(Serial(12), Serial(112));
350
351        assert_eq!(Serial(12).partial_cmp(&Serial(12)), Some(Equal));
352
353        // s1 is said to be less than s2 if [...]
354        // (i1 < i2 and i2 - i1 < 2^(SERIAL_BITS - 1))
355        assert_eq!(Serial(12).partial_cmp(&Serial(13)), Some(Less));
356        assert_ne!(
357            Serial(12).partial_cmp(&Serial(3_000_000_012)),
358            Some(Less)
359        );
360
361        // or (i1 > i2 and i1 - i2 > 2^(SERIAL_BITS - 1))
362        assert_eq!(
363            Serial(3_000_000_012).partial_cmp(&Serial(12)),
364            Some(Less)
365        );
366        assert_ne!(Serial(13).partial_cmp(&Serial(12)), Some(Less));
367
368        // s1 is said to be greater than s2 if [...]
369        // (i1 < i2 and i2 - i1 > 2^(SERIAL_BITS - 1))
370        assert_eq!(
371            Serial(12).partial_cmp(&Serial(3_000_000_012)),
372            Some(Greater)
373        );
374        assert_ne!(Serial(12).partial_cmp(&Serial(13)), Some(Greater));
375
376        // (i1 > i2 and i1 - i2 < 2^(SERIAL_BITS - 1))
377        assert_eq!(Serial(13).partial_cmp(&Serial(12)), Some(Greater));
378        assert_ne!(
379            Serial(3_000_000_012).partial_cmp(&Serial(12)),
380            Some(Greater)
381        );
382
383        // Er, I think that’s what’s left.
384        assert_eq!(Serial(1).partial_cmp(&Serial(0x8000_0001)), None);
385        assert_eq!(Serial(0x8000_0001).partial_cmp(&Serial(1)), None);
386    }
387}