domain_core/bits/
serial.rs

1//! Serial numbers.
2//!
3//! This module define a type [`Serial`] that wraps a `u32` to provide
4//! serial number arithmetics.
5//!
6//! [`Serial`]: struct.Serial.html
7
8use std::{cmp, fmt, str};
9use bytes::BufMut;
10use chrono::{Utc, TimeZone};
11use ::master::scan::{CharSource, Scan, ScanError, Scanner, SyntaxError};
12use super::compose::Compose;
13use super::parse::{Parse, ParseAll, Parser};
14
15
16//------------ Serial --------------------------------------------------------
17
18/// A serial number.
19///
20/// Serial numbers are used in DNS to track changes to resources. For
21/// instance, the [`Soa`] record type provides a serial number that expresses
22/// the version of the zone. Since these numbers are only 32 bits long, they
23/// can wrap. [RFC 1982] defined the semantics for doing arithmetics in the
24/// face of these wrap-arounds. This type implements these semantics atop a
25/// native `u32`.
26///
27/// The RFC defines addition and comparison. Addition, however, is only
28/// defined for values up to `2^31 - 1`, so we decided to not implement the
29/// `Add` trait but rather have a dedicated method `add` so as to not cause
30/// surprise panics.
31/// 
32/// Serial numbers only implement a partial ordering. That is, there are
33/// pairs of values that are not equal but there still isn’t one value larger
34/// than the other. Since this is neatly implemented by the `PartialOrd`
35/// trait, the type implements that.
36///
37/// [RFC 1982]: https://tools.ietf.org/html/rfc1982
38#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
39pub struct Serial(pub u32);
40
41impl Serial {
42    /// Add `other` to `self`.
43    ///
44    /// Serial numbers only allow values of up to `2^31 - 1` to be added to
45    /// them. Therefore, this method requires `other` to be a `u32` instead
46    /// of a `Serial` to indicate that you cannot simply add two serials
47    /// together. This is also why we don’t implement the `Add` trait.
48    ///
49    /// # Panics
50    ///
51    /// This method panics if `other` is greater than `2^31 - 1`.
52    #[allow(should_implement_trait)]
53    pub fn add(self, other: u32) -> Self {
54        assert!(other <= 0x7FFF_FFFF);
55        Serial(self.0.wrapping_add(other))
56    }
57
58    /// Scan a serial represention signature time values.
59    /// 
60    /// In RRSIG records, the expiration and inception time is given as
61    /// serial values. Their master file format can either be the signature
62    /// value or a specific date in `YYYYMMDDHHmmSS` format.
63    pub fn scan_rrsig<C: CharSource>(
64        scanner: &mut Scanner<C>
65    ) -> Result<Self, ScanError> {
66        scanner.scan_phrase(
67            (0, [0u8; 14]),
68            |&mut (ref mut pos, ref mut buf), symbol| {
69                let ch = symbol.into_digit(10)? as u8;
70                if *pos == 14 {
71                    return Err(SyntaxError::IllegalInteger) // XXX Not quite
72                }
73                buf[*pos] = ch;
74                *pos += 1;
75                Ok(())
76            },
77            |(pos, buf)| {
78                if pos <= 10 {
79                    // We have an integer. We generate it into a u64 to deal
80                    // with possible overflows.
81                    let mut res = 0u64;
82                    for ch in &buf[..pos] {
83                        res = res *10 + (u64::from(*ch));
84                    }
85                    if res > u64::from(::std::u32::MAX) {
86                        Err(SyntaxError::IllegalInteger)
87                    }
88                    else {
89                        Ok(Serial(res as u32))
90                    }
91                }
92                else if pos == 14 {
93                    let year = u32_from_buf(&buf[0..4]) as i32;
94                    let month = u32_from_buf(&buf[4..6]);
95                    let day = u32_from_buf(&buf[6..8]);
96                    let hour = u32_from_buf(&buf[8..10]);
97                    let minute = u32_from_buf(&buf[10..12]);
98                    let second = u32_from_buf(&buf[12..14]);
99                    match month {
100                        1 | 3 | 5 | 7 | 8 | 10 | 12 => {
101                            if month > 31 {
102                                return Err(SyntaxError::IllegalInteger)
103                            }
104                        }
105                        4 | 6 | 9 | 11 => {
106                            if month > 30 {
107                                return Err(SyntaxError::IllegalInteger)
108                            }
109                        }
110                        2 => {
111                            if year % 4 == 0 && year % 100 != 0 {
112                                if month > 29 {
113                                    return Err(SyntaxError::IllegalInteger)
114                                }
115                            }
116                            else if month > 28 {
117                                return Err(SyntaxError::IllegalInteger)
118                            }
119                        }
120                        _ => {
121                            return Err(SyntaxError::IllegalInteger)
122                        }
123                    }
124                    if month < 1 || hour > 23 || minute > 59 || second > 59 {
125                        return Err(SyntaxError::IllegalInteger)
126                    }
127                    Ok(Serial(
128                        Utc.ymd(year, month, day)
129                            .and_hms(hour, minute, second)
130                            .timestamp() as u32
131                    ))
132                }
133                else {
134                    Err(SyntaxError::IllegalInteger) // XXX Still not quite.
135                }
136            }
137        )
138    }
139}
140
141
142//--- From and FromStr
143
144impl From<u32> for Serial {
145    fn from(value: u32) -> Serial {
146        Serial(value)
147    }
148}
149
150impl From<Serial> for u32 {
151    fn from(serial: Serial) -> u32 {
152        serial.0
153    }
154}
155
156impl str::FromStr for Serial {
157    type Err = <u32 as str::FromStr>::Err;
158
159    fn from_str(s: &str) -> Result<Self, Self::Err> {
160        <u32 as str::FromStr>::from_str(s).map(Into::into)
161    }
162}
163
164
165//--- Parse, ParseAll, and Compose
166
167impl Parse for Serial {
168    type Err = <u32 as Parse>::Err;
169
170    fn parse(parser: &mut Parser) -> Result<Self, Self::Err> {
171        u32::parse(parser).map(Into::into)
172    }
173
174    fn skip(parser: &mut Parser) -> Result<(), Self::Err> {
175        u32::skip(parser)
176    }
177}
178
179impl ParseAll for Serial {
180    type Err = <u32 as ParseAll>::Err;
181
182    fn parse_all(parser: &mut Parser, len: usize) -> Result<Self, Self::Err> {
183        u32::parse_all(parser, len).map(Into::into)
184    }
185}
186
187impl Compose for Serial {
188    fn compose_len(&self) -> usize {
189        self.0.compose_len()
190    }
191
192    fn compose<B: BufMut>(&self, buf: &mut B) {
193        self.0.compose(buf)
194    }
195}
196
197
198//--- Scan and Display
199
200impl Scan for Serial {
201    fn scan<C: CharSource>(scanner: &mut Scanner<C>)
202                           -> Result<Self, ScanError> {
203        u32::scan(scanner).map(Into::into)
204    }
205}
206
207impl fmt::Display for Serial {
208    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
209        write!(f, "{}", self.0)
210    }
211}
212
213
214//--- PartialOrd
215
216impl cmp::PartialOrd for Serial {
217    fn partial_cmp(&self, other: &Serial) -> Option<cmp::Ordering> {
218        if self.0 == other.0 {
219            Some(cmp::Ordering::Equal)
220        }
221        else if self.0 < other.0 {
222            let sub = other.0 - self.0;
223            if sub < 0x8000_0000 {
224                Some(cmp::Ordering::Less)
225            }
226            else if sub > 0x8000_0000 {
227                Some(cmp::Ordering::Greater)
228            }
229            else {
230                None
231            }
232        }
233        else {
234            let sub = self.0 - other.0;
235            if sub < 0x8000_0000 {
236                Some(cmp::Ordering::Greater)
237            }
238            else if sub > 0x8000_0000 {
239                Some(cmp::Ordering::Less)
240            }
241            else {
242                None
243            }
244        }
245    }
246}
247
248
249//------------ Helper Functions ----------------------------------------------
250
251fn u32_from_buf(buf: &[u8]) -> u32 {
252    let mut res = 0;
253    for ch in buf {
254        res = res * 10 + (u32::from(*ch));
255    }
256    res
257}
258
259
260//============ Testing =======================================================
261
262#[cfg(test)]
263mod test {
264    use super::*;
265
266    #[test]
267    fn good_addition() {
268        assert_eq!(Serial(0).add(4), Serial(4));
269        assert_eq!(Serial(0xFF00_0000).add(0x0F00_0000),
270                   Serial(((0xFF00_0000u64 + 0x0F00_0000u64)
271                           % 0x1_0000_0000) as u32));
272    }
273
274    #[test]
275    #[should_panic]
276    fn bad_addition() {
277        let _ = Serial(0).add(0x8000_0000);
278    }
279
280    #[test]
281    fn comparison() {
282        use std::cmp::Ordering::*;
283
284        assert_eq!(Serial(12), Serial(12));
285        assert_ne!(Serial(12), Serial(112));
286
287        assert_eq!(Serial(12).partial_cmp(&Serial(12)), Some(Equal));
288
289        // s1 is said to be less than s2 if [...]
290        // (i1 < i2 and i2 - i1 < 2^(SERIAL_BITS - 1))
291        assert_eq!(Serial(12).partial_cmp(&Serial(13)), Some(Less));
292        assert_ne!(Serial(12).partial_cmp(&Serial(3_000_000_012)), Some(Less));
293
294        // or (i1 > i2 and i1 - i2 > 2^(SERIAL_BITS - 1))
295        assert_eq!(Serial(3_000_000_012).partial_cmp(&Serial(12)), Some(Less));
296        assert_ne!(Serial(13).partial_cmp(&Serial(12)), Some(Less));
297
298        // s1 is said to be greater than s2 if [...]
299        // (i1 < i2 and i2 - i1 > 2^(SERIAL_BITS - 1))
300        assert_eq!(Serial(12).partial_cmp(&Serial(3_000_000_012)),
301                   Some(Greater));
302        assert_ne!(Serial(12).partial_cmp(&Serial(13)), Some(Greater));
303
304        // (i1 > i2 and i1 - i2 < 2^(SERIAL_BITS - 1))
305        assert_eq!(Serial(13).partial_cmp(&Serial(12)), Some(Greater));
306        assert_ne!(Serial(3_000_000_012).partial_cmp(&Serial(12)),
307                   Some(Greater));
308        
309        // Er, I think that’s what’s left.
310        assert_eq!(Serial(1).partial_cmp(&Serial(0x8000_0001)), None);
311        assert_eq!(Serial(0x8000_0001).partial_cmp(&Serial(1)), None);
312    }
313}