Skip to main content

walnut_dns/rr/
sequence.rs

1use std::{cmp::Ordering, ops::Add};
2
3use rusqlite::{ToSql, types::FromSql};
4
5/// An RFC 1982 number indicating when something changed
6///
7/// This type wraps an inner u32 to provide the proper
8/// wrapping and comparison semantics.
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct SerialNumber(u32);
11
12impl SerialNumber {
13    pub const ZERO: SerialNumber = SerialNumber(0);
14
15    /// Get the raw u32 value of this serial number
16    ///
17    /// Returns the underlying 32-bit unsigned integer value.
18    ///
19    /// # Returns
20    ///
21    /// The raw serial number value
22    pub fn get(&self) -> u32 {
23        self.0
24    }
25}
26
27impl From<u32> for SerialNumber {
28    fn from(value: u32) -> Self {
29        SerialNumber(value)
30    }
31}
32
33/// Serial Number Addition, see RFC 1982, section 3.1
34///
35/// The result is a wrapping add.
36impl Add for SerialNumber {
37    type Output = Self;
38
39    fn add(self, rhs: Self) -> Self::Output {
40        Self(self.0.wrapping_add(rhs.0))
41    }
42}
43
44impl Add<u32> for SerialNumber {
45    type Output = Self;
46
47    fn add(self, rhs: u32) -> Self::Output {
48        Self(self.0.wrapping_add(rhs))
49    }
50}
51
52/// Serial Number Comparison, see RFC 1982, section 3.2
53impl PartialOrd for SerialNumber {
54    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
55        const SERIAL_BITS_HALF: u32 = 1 << (u32::BITS - 1);
56
57        let i1 = self.0;
58        let i2 = other.0;
59
60        if i1 == i2 {
61            Some(Ordering::Equal)
62        } else if (i1 < i2 && (i2 - i1) < SERIAL_BITS_HALF)
63            || (i1 > i2 && (i1 - i2) > SERIAL_BITS_HALF)
64        {
65            Some(Ordering::Less)
66        } else if (i1 < i2 && (i2 - i1) > SERIAL_BITS_HALF)
67            || (i1 > i2 && (i1 - i2) < SERIAL_BITS_HALF)
68        {
69            Some(Ordering::Greater)
70        } else {
71            None
72        }
73    }
74}
75
76impl ToSql for SerialNumber {
77    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
78        self.0.to_sql()
79    }
80}
81
82impl FromSql for SerialNumber {
83    fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
84        match value {
85            rusqlite::types::ValueRef::Integer(i) => Ok(SerialNumber(i as u32)),
86            _ => Err(rusqlite::types::FromSqlError::InvalidType),
87        }
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn test_serial_number_creation() {
97        let sn = SerialNumber::from(123);
98        assert_eq!(sn.get(), 123);
99
100        let zero = SerialNumber::ZERO;
101        assert_eq!(zero.get(), 0);
102    }
103
104    #[test]
105    fn test_serial_number_addition() {
106        let sn1 = SerialNumber::from(10);
107        let sn2 = SerialNumber::from(20);
108        let result = sn1 + sn2;
109        assert_eq!(result.get(), 30);
110    }
111
112    #[test]
113    fn test_serial_number_wrapping_addition() {
114        let sn1 = SerialNumber::from(u32::MAX);
115        let sn2 = SerialNumber::from(1);
116        let result = sn1 + sn2;
117        assert_eq!(result.get(), 0); // Wrapping addition
118    }
119
120    #[test]
121    fn test_serial_number_equality() {
122        let sn1 = SerialNumber::from(42);
123        let sn2 = SerialNumber::from(42);
124        let sn3 = SerialNumber::from(43);
125
126        assert_eq!(sn1, sn2);
127        assert_ne!(sn1, sn3);
128        assert_eq!(sn1.partial_cmp(&sn2), Some(Ordering::Equal));
129    }
130
131    #[test]
132    fn test_serial_number_comparison_simple() {
133        let sn1 = SerialNumber::from(10);
134        let sn2 = SerialNumber::from(20);
135
136        assert_eq!(sn1.partial_cmp(&sn2), Some(Ordering::Less));
137        assert_eq!(sn2.partial_cmp(&sn1), Some(Ordering::Greater));
138    }
139
140    #[test]
141    fn test_serial_number_comparison_wrap_around() {
142        // Test RFC 1982 serial number arithmetic wrap-around behavior
143        let sn1 = SerialNumber::from(100);
144        let sn2 = SerialNumber::from(100 + 0x40000000); // Within valid comparison range
145
146        // According to RFC 1982, comparison should be well-defined within valid range
147        let result = sn1.partial_cmp(&sn2);
148        assert_eq!(result, Some(std::cmp::Ordering::Less));
149
150        // Test the boundary case that results in undefined comparison
151        let sn3 = SerialNumber::from(0);
152        let sn4 = SerialNumber::from(0x80000000); // Exactly half the range - undefined
153        let result_undefined = sn3.partial_cmp(&sn4);
154        assert_eq!(result_undefined, None); // Should be undefined
155    }
156
157    #[test]
158    fn test_serial_number_comparison_undefined() {
159        // Test case where comparison is undefined (exactly half the range apart)
160        let sn1 = SerialNumber::from(0);
161        let sn2 = SerialNumber::from(0x80000000); // Exactly half the range
162
163        // Should return None for undefined comparison
164        assert_eq!(sn1.partial_cmp(&sn2), None);
165    }
166
167    #[test]
168    fn test_serial_number_rfc_examples() {
169        // Examples from RFC 1982
170        let s = SerialNumber::from(10);
171        let s_plus_1 = SerialNumber::from(11);
172        let s_plus_100 = SerialNumber::from(110);
173
174        assert_eq!(s.partial_cmp(&s_plus_1), Some(Ordering::Less));
175        assert_eq!(s.partial_cmp(&s_plus_100), Some(Ordering::Less));
176        assert_eq!(s_plus_1.partial_cmp(&s_plus_100), Some(Ordering::Less));
177    }
178
179    #[test]
180    fn test_serial_number_clone_and_copy() {
181        let sn1 = SerialNumber::from(42);
182        let sn2 = sn1; // Copy
183        let sn3 = sn1; // Clone
184
185        assert_eq!(sn1, sn2);
186        assert_eq!(sn1, sn3);
187        assert_eq!(sn1.get(), 42);
188        assert_eq!(sn2.get(), 42);
189        assert_eq!(sn3.get(), 42);
190    }
191}