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
910use 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};
2324//------------ Serial --------------------------------------------------------
2526/// 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);
5253impl Serial {
54/// Returns a serial number for the current Unix time.
55#[cfg(feature = "std")]
56 #[must_use]
57pub fn now() -> Self {
58let now = SystemTime::now();
59let value = match now.duration_since(UNIX_EPOCH) {
60Ok(value) => value,
61Err(_) => UNIX_EPOCH.duration_since(now).unwrap(),
62 };
63Self(value.as_secs() as u32)
64 }
6566/// Creates a new serial number from its octets in big endian notation.
67#[must_use]
68pub fn from_be_bytes(bytes: [u8; 4]) -> Self {
69Self(u32::from_be_bytes(bytes))
70 }
7172/// Returns the serial number as a raw integer.
73#[must_use]
74pub fn into_int(self) -> u32 {
75self.0
76}
7778/// 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]
90pub fn add(self, other: u32) -> Self {
91assert!(other <= 0x7FFF_FFFF);
92 Serial(self.0.wrapping_add(other))
93 }
9495pub fn scan<S: Scanner>(scanner: &mut S) -> Result<Self, S::Error> {
96 u32::scan(scanner).map(Into::into)
97 }
9899/// 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
106pub fn scan_rrsig<S: Scanner>(scanner: &mut S) -> Result<Self, S::Error> {
107let mut pos = 0;
108let mut buf = [0u8; 14];
109 scanner.scan_symbols(|symbol| {
110if pos >= 14 {
111return 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"))?
116as u8;
117 pos += 1;
118Ok(())
119 })?;
120if pos <= 10 {
121// We have an integer. We generate it into a u64 to deal
122 // with possible overflows.
123let mut res = 0u64;
124for ch in &buf[..pos] {
125 res = res * 10 + (u64::from(*ch));
126 }
127if res > u64::from(u32::MAX) {
128Err(S::Error::custom("illegal signature time"))
129 } else {
130Ok(Serial(res as u32))
131 }
132 } else if pos == 14 {
133let year = u32_from_buf(&buf[0..4]) as i32;
134let month = Month::try_from(u8_from_buf(&buf[4..6]))
135 .map_err(|_| S::Error::custom("illegal signature time"))?;
136let day = u8_from_buf(&buf[6..8]);
137let hour = u8_from_buf(&buf[8..10]);
138let minute = u8_from_buf(&buf[10..12]);
139let second = u8_from_buf(&buf[12..14]);
140Ok(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 {
153Err(S::Error::custom("illegal signature time"))
154 }
155 }
156157/// 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
164pub fn rrsig_from_str(src: &str) -> Result<Self, IllegalSignatureTime> {
165if !src.is_ascii() {
166return Err(IllegalSignatureTime);
167 }
168if src.len() == 14 {
169let year = u32::from_str(&src[0..4])
170 .map_err(|_| IllegalSignatureTime)?
171as i32;
172let month = Month::try_from(
173 u8::from_str(&src[4..6]).map_err(|_| IllegalSignatureTime)?,
174 )
175 .map_err(|_| IllegalSignatureTime)?;
176let day =
177 u8::from_str(&src[6..8]).map_err(|_| IllegalSignatureTime)?;
178let hour = u8::from_str(&src[8..10])
179 .map_err(|_| IllegalSignatureTime)?;
180let minute = u8::from_str(&src[10..12])
181 .map_err(|_| IllegalSignatureTime)?;
182let second = u8::from_str(&src[12..14])
183 .map_err(|_| IllegalSignatureTime)?;
184Ok(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}
199200/// # Parsing and Composing
201///
202impl Serial {
203pub const COMPOSE_LEN: u16 = u32::COMPOSE_LEN;
204205pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
206 parser: &mut Parser<Octs>,
207 ) -> Result<Self, ParseError> {
208 u32::parse(parser).map(Into::into)
209 }
210211pub fn compose<Target: Composer + ?Sized>(
212&self,
213 target: &mut Target,
214 ) -> Result<(), Target::AppendError> {
215self.0.compose(target)
216 }
217}
218219//--- From and FromStr
220221impl From<u32> for Serial {
222fn from(value: u32) -> Serial {
223 Serial(value)
224 }
225}
226227impl From<Serial> for u32 {
228fn from(serial: Serial) -> u32 {
229 serial.0
230}
231}
232233#[cfg(feature = "chrono")]
234#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
235impl<T: TimeZone> From<DateTime<T>> for Serial {
236fn from(value: DateTime<T>) -> Self {
237Self(value.timestamp() as u32)
238 }
239}
240241impl str::FromStr for Serial {
242type Err = <u32 as str::FromStr>::Err;
243244fn from_str(s: &str) -> Result<Self, Self::Err> {
245 <u32 as str::FromStr>::from_str(s).map(Into::into)
246 }
247}
248249//--- Display
250251impl fmt::Display for Serial {
252fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
253write!(f, "{}", self.0)
254 }
255}
256257//--- PartialOrd
258259impl cmp::PartialOrd for Serial {
260fn partial_cmp(&self, other: &Serial) -> Option<cmp::Ordering> {
261match self.0.cmp(&other.0) {
262 Ordering::Equal => Some(Ordering::Equal),
263 Ordering::Less => {
264let sub = other.0 - self.0;
265match 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 => {
272let sub = self.0 - other.0;
273match 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}
282283impl CanonicalOrd for Serial {
284fn canonical_cmp(&self, other: &Self) -> cmp::Ordering {
285self.0.cmp(&other.0)
286 }
287}
288289//------------ Helper Functions ----------------------------------------------
290291fn u8_from_buf(buf: &[u8]) -> u8 {
292let mut res = 0;
293for ch in buf {
294 res = res * 10 + *ch;
295 }
296 res
297}
298299fn u32_from_buf(buf: &[u8]) -> u32 {
300let mut res = 0;
301for ch in buf {
302 res = res * 10 + (u32::from(*ch));
303 }
304 res
305}
306307//============ Testing =======================================================
308309#[derive(Clone, Copy, Debug)]
310pub struct IllegalSignatureTime;
311312impl fmt::Display for IllegalSignatureTime {
313fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
314 f.write_str("illegal signature time")
315 }
316}
317318#[cfg(feature = "std")]
319impl std::error::Error for IllegalSignatureTime {}
320321//============ Testing =======================================================
322323#[cfg(test)]
324mod test {
325use super::*;
326327#[test]
328fn good_addition() {
329assert_eq!(Serial(0).add(4), Serial(4));
330assert_eq!(
331 Serial(0xFF00_0000).add(0x0F00_0000),
332 Serial(
333 ((0xFF00_0000u64 + 0x0F00_0000u64) % 0x1_0000_0000) as u32
334 )
335 );
336 }
337338#[test]
339 #[should_panic]
340fn bad_addition() {
341let _ = Serial(0).add(0x8000_0000);
342 }
343344#[test]
345fn comparison() {
346use core::cmp::Ordering::*;
347348assert_eq!(Serial(12), Serial(12));
349assert_ne!(Serial(12), Serial(112));
350351assert_eq!(Serial(12).partial_cmp(&Serial(12)), Some(Equal));
352353// s1 is said to be less than s2 if [...]
354 // (i1 < i2 and i2 - i1 < 2^(SERIAL_BITS - 1))
355assert_eq!(Serial(12).partial_cmp(&Serial(13)), Some(Less));
356assert_ne!(
357 Serial(12).partial_cmp(&Serial(3_000_000_012)),
358Some(Less)
359 );
360361// or (i1 > i2 and i1 - i2 > 2^(SERIAL_BITS - 1))
362assert_eq!(
363 Serial(3_000_000_012).partial_cmp(&Serial(12)),
364Some(Less)
365 );
366assert_ne!(Serial(13).partial_cmp(&Serial(12)), Some(Less));
367368// s1 is said to be greater than s2 if [...]
369 // (i1 < i2 and i2 - i1 > 2^(SERIAL_BITS - 1))
370assert_eq!(
371 Serial(12).partial_cmp(&Serial(3_000_000_012)),
372Some(Greater)
373 );
374assert_ne!(Serial(12).partial_cmp(&Serial(13)), Some(Greater));
375376// (i1 > i2 and i1 - i2 < 2^(SERIAL_BITS - 1))
377assert_eq!(Serial(13).partial_cmp(&Serial(12)), Some(Greater));
378assert_ne!(
379 Serial(3_000_000_012).partial_cmp(&Serial(12)),
380Some(Greater)
381 );
382383// Er, I think that’s what’s left.
384assert_eq!(Serial(1).partial_cmp(&Serial(0x8000_0001)), None);
385assert_eq!(Serial(0x8000_0001).partial_cmp(&Serial(1)), None);
386 }
387}