1# Copyright (c) Twisted Matrix Laboratories.
2# See LICENSE for details.
3
4"""
5Test cases for L{twisted.names.rfc1982}.
6"""
7
8
9import calendar
10from datetime import datetime
11from functools import partial
12
13from twisted.names._rfc1982 import SerialNumber
14from twisted.trial import unittest
15
16
17class SerialNumberTests(unittest.TestCase):
18    """
19    Tests for L{SerialNumber}.
20    """
21
22    def test_serialBitsDefault(self):
23        """
24        L{SerialNumber.serialBits} has default value 32.
25        """
26        self.assertEqual(SerialNumber(1)._serialBits, 32)
27
28    def test_serialBitsOverride(self):
29        """
30        L{SerialNumber.__init__} accepts a C{serialBits} argument whose value is
31        assigned to L{SerialNumber.serialBits}.
32        """
33        self.assertEqual(SerialNumber(1, serialBits=8)._serialBits, 8)
34
35    def test_repr(self):
36        """
37        L{SerialNumber.__repr__} returns a string containing number and
38        serialBits.
39        """
40        self.assertEqual(
41            "<SerialNumber number=123 serialBits=32>",
42            repr(SerialNumber(123, serialBits=32)),
43        )
44
45    def test_str(self):
46        """
47        L{SerialNumber.__str__} returns a string representation of the current
48        value.
49        """
50        self.assertEqual(str(SerialNumber(123)), "123")
51
52    def test_int(self):
53        """
54        L{SerialNumber.__int__} returns an integer representation of the current
55        value.
56        """
57        self.assertEqual(int(SerialNumber(123)), 123)
58
59    def test_hash(self):
60        """
61        L{SerialNumber.__hash__} allows L{SerialNumber} instances to be hashed
62        for use as dictionary keys.
63        """
64        self.assertEqual(hash(SerialNumber(1)), hash(SerialNumber(1)))
65        self.assertNotEqual(hash(SerialNumber(1)), hash(SerialNumber(2)))
66
67    def test_convertOtherSerialBitsMismatch(self):
68        """
69        L{SerialNumber._convertOther} raises L{TypeError} if the other
70        SerialNumber instance has a different C{serialBits} value.
71        """
72        s1 = SerialNumber(0, serialBits=8)
73        s2 = SerialNumber(0, serialBits=16)
74
75        self.assertRaises(TypeError, s1._convertOther, s2)
76
77    def test_eq(self):
78        """
79        L{SerialNumber.__eq__} provides rich equality comparison.
80        """
81        self.assertEqual(SerialNumber(1), SerialNumber(1))
82
83    def test_eqForeignType(self):
84        """
85        == comparison of L{SerialNumber} with a non-L{SerialNumber} instance
86        returns L{NotImplemented}.
87        """
88        self.assertFalse(SerialNumber(1) == object())
89        self.assertIs(SerialNumber(1).__eq__(object()), NotImplemented)
90
91    def test_ne(self):
92        """
93        L{SerialNumber.__ne__} provides rich equality comparison.
94        """
95        self.assertFalse(SerialNumber(1) != SerialNumber(1))
96        self.assertNotEqual(SerialNumber(1), SerialNumber(2))
97
98    def test_neForeignType(self):
99        """
100        != comparison of L{SerialNumber} with a non-L{SerialNumber} instance
101        returns L{NotImplemented}.
102        """
103        self.assertTrue(SerialNumber(1) != object())
104        self.assertIs(SerialNumber(1).__ne__(object()), NotImplemented)
105
106    def test_le(self):
107        """
108        L{SerialNumber.__le__} provides rich <= comparison.
109        """
110        self.assertTrue(SerialNumber(1) <= SerialNumber(1))
111        self.assertTrue(SerialNumber(1) <= SerialNumber(2))
112
113    def test_leForeignType(self):
114        """
115        <= comparison of L{SerialNumber} with a non-L{SerialNumber} instance
116        raises L{TypeError}.
117        """
118        self.assertRaises(TypeError, lambda: SerialNumber(1) <= object())
119
120    def test_ge(self):
121        """
122        L{SerialNumber.__ge__} provides rich >= comparison.
123        """
124        self.assertTrue(SerialNumber(1) >= SerialNumber(1))
125        self.assertTrue(SerialNumber(2) >= SerialNumber(1))
126
127    def test_geForeignType(self):
128        """
129        >= comparison of L{SerialNumber} with a non-L{SerialNumber} instance
130        raises L{TypeError}.
131        """
132        self.assertRaises(TypeError, lambda: SerialNumber(1) >= object())
133
134    def test_lt(self):
135        """
136        L{SerialNumber.__lt__} provides rich < comparison.
137        """
138        self.assertTrue(SerialNumber(1) < SerialNumber(2))
139
140    def test_ltForeignType(self):
141        """
142        < comparison of L{SerialNumber} with a non-L{SerialNumber} instance
143        raises L{TypeError}.
144        """
145        self.assertRaises(TypeError, lambda: SerialNumber(1) < object())
146
147    def test_gt(self):
148        """
149        L{SerialNumber.__gt__} provides rich > comparison.
150        """
151        self.assertTrue(SerialNumber(2) > SerialNumber(1))
152
153    def test_gtForeignType(self):
154        """
155        > comparison of L{SerialNumber} with a non-L{SerialNumber} instance
156          raises L{TypeError}.
157        """
158        self.assertRaises(TypeError, lambda: SerialNumber(2) > object())
159
160    def test_add(self):
161        """
162        L{SerialNumber.__add__} allows L{SerialNumber} instances to be summed.
163        """
164        self.assertEqual(SerialNumber(1) + SerialNumber(1), SerialNumber(2))
165
166    def test_addForeignType(self):
167        """
168        Addition of L{SerialNumber} with a non-L{SerialNumber} instance raises
169        L{TypeError}.
170        """
171        self.assertRaises(TypeError, lambda: SerialNumber(1) + object())
172
173    def test_addOutOfRangeHigh(self):
174        """
175        L{SerialNumber} cannot be added with other SerialNumber values larger
176        than C{_maxAdd}.
177        """
178        maxAdd = SerialNumber(1)._maxAdd
179        self.assertRaises(
180            ArithmeticError, lambda: SerialNumber(1) + SerialNumber(maxAdd + 1)
181        )
182
183    def test_maxVal(self):
184        """
185        L{SerialNumber.__add__} returns a wrapped value when s1 plus the s2
186        would result in a value greater than the C{maxVal}.
187        """
188        s = SerialNumber(1)
189        maxVal = s._halfRing + s._halfRing - 1
190        maxValPlus1 = maxVal + 1
191        self.assertTrue(SerialNumber(maxValPlus1) > SerialNumber(maxVal))
192        self.assertEqual(SerialNumber(maxValPlus1), SerialNumber(0))
193
194    def test_fromRFC4034DateString(self):
195        """
196        L{SerialNumber.fromRFC4034DateString} accepts a datetime string argument
197        of the form 'YYYYMMDDhhmmss' and returns an L{SerialNumber} instance
198        whose value is the unix timestamp corresponding to that UTC date.
199        """
200        self.assertEqual(
201            SerialNumber(1325376000),
202            SerialNumber.fromRFC4034DateString("20120101000000"),
203        )
204
205    def test_toRFC4034DateString(self):
206        """
207        L{DateSerialNumber.toRFC4034DateString} interprets the current value as
208        a unix timestamp and returns a date string representation of that date.
209        """
210        self.assertEqual(
211            "20120101000000", SerialNumber(1325376000).toRFC4034DateString()
212        )
213
214    def test_unixEpoch(self):
215        """
216        L{SerialNumber.toRFC4034DateString} stores 32bit timestamps relative to
217        the UNIX epoch.
218        """
219        self.assertEqual(SerialNumber(0).toRFC4034DateString(), "19700101000000")
220
221    def test_Y2106Problem(self):
222        """
223        L{SerialNumber} wraps unix timestamps in the year 2106.
224        """
225        self.assertEqual(SerialNumber(-1).toRFC4034DateString(), "21060207062815")
226
227    def test_Y2038Problem(self):
228        """
229        L{SerialNumber} raises ArithmeticError when used to add dates more than
230        68 years in the future.
231        """
232        maxAddTime = calendar.timegm(datetime(2038, 1, 19, 3, 14, 7).utctimetuple())
233
234        self.assertEqual(
235            maxAddTime,
236            SerialNumber(0)._maxAdd,
237        )
238
239        self.assertRaises(
240            ArithmeticError, lambda: SerialNumber(0) + SerialNumber(maxAddTime + 1)
241        )
242
243
244def assertUndefinedComparison(testCase, s1, s2):
245    """
246    A custom assertion for L{SerialNumber} values that cannot be meaningfully
247    compared.
248
249    "Note that there are some pairs of values s1 and s2 for which s1 is not
250    equal to s2, but for which s1 is neither greater than, nor less than, s2.
251    An attempt to use these ordering operators on such pairs of values produces
252    an undefined result."
253
254    @see: U{https://tools.ietf.org/html/rfc1982#section-3.2}
255
256    @param testCase: The L{unittest.TestCase} on which to call assertion
257        methods.
258    @type testCase: L{unittest.TestCase}
259
260    @param s1: The first value to compare.
261    @type s1: L{SerialNumber}
262
263    @param s2: The second value to compare.
264    @type s2: L{SerialNumber}
265    """
266    testCase.assertFalse(s1 == s2)
267    testCase.assertFalse(s1 <= s2)
268    testCase.assertFalse(s1 < s2)
269    testCase.assertFalse(s1 > s2)
270    testCase.assertFalse(s1 >= s2)
271
272
273serialNumber2 = partial(SerialNumber, serialBits=2)
274
275
276class SerialNumber2BitTests(unittest.TestCase):
277    """
278    Tests for correct answers to example calculations in RFC1982 5.1.
279
280    The simplest meaningful serial number space has SERIAL_BITS == 2.  In this
281    space, the integers that make up the serial number space are 0, 1, 2, and 3.
282    That is, 3 == 2^SERIAL_BITS - 1.
283
284    https://tools.ietf.org/html/rfc1982#section-5.1
285    """
286
287    def test_maxadd(self):
288        """
289        In this space, the largest integer that it is meaningful to add to a
290        sequence number is 2^(SERIAL_BITS - 1) - 1, or 1.
291        """
292        self.assertEqual(SerialNumber(0, serialBits=2)._maxAdd, 1)
293
294    def test_add(self):
295        """
296        Then, as defined 0+1 == 1, 1+1 == 2, 2+1 == 3, and 3+1 == 0.
297        """
298        self.assertEqual(serialNumber2(0) + serialNumber2(1), serialNumber2(1))
299        self.assertEqual(serialNumber2(1) + serialNumber2(1), serialNumber2(2))
300        self.assertEqual(serialNumber2(2) + serialNumber2(1), serialNumber2(3))
301        self.assertEqual(serialNumber2(3) + serialNumber2(1), serialNumber2(0))
302
303    def test_gt(self):
304        """
305        Further, 1 > 0, 2 > 1, 3 > 2, and 0 > 3.
306        """
307        self.assertTrue(serialNumber2(1) > serialNumber2(0))
308        self.assertTrue(serialNumber2(2) > serialNumber2(1))
309        self.assertTrue(serialNumber2(3) > serialNumber2(2))
310        self.assertTrue(serialNumber2(0) > serialNumber2(3))
311
312    def test_undefined(self):
313        """
314        It is undefined whether 2 > 0 or 0 > 2, and whether 1 > 3 or 3 > 1.
315        """
316        assertUndefinedComparison(self, serialNumber2(2), serialNumber2(0))
317        assertUndefinedComparison(self, serialNumber2(0), serialNumber2(2))
318        assertUndefinedComparison(self, serialNumber2(1), serialNumber2(3))
319        assertUndefinedComparison(self, serialNumber2(3), serialNumber2(1))
320
321
322serialNumber8 = partial(SerialNumber, serialBits=8)
323
324
325class SerialNumber8BitTests(unittest.TestCase):
326    """
327    Tests for correct answers to example calculations in RFC1982 5.2.
328
329    Consider the case where SERIAL_BITS == 8.  In this space the integers that
330    make up the serial number space are 0, 1, 2, ... 254, 255.  255 ==
331    2^SERIAL_BITS - 1.
332
333    https://tools.ietf.org/html/rfc1982#section-5.2
334    """
335
336    def test_maxadd(self):
337        """
338        In this space, the largest integer that it is meaningful to add to a
339        sequence number is 2^(SERIAL_BITS - 1) - 1, or 127.
340        """
341        self.assertEqual(SerialNumber(0, serialBits=8)._maxAdd, 127)
342
343    def test_add(self):
344        """
345        Addition is as expected in this space, for example: 255+1 == 0,
346        100+100 == 200, and 200+100 == 44.
347        """
348        self.assertEqual(serialNumber8(255) + serialNumber8(1), serialNumber8(0))
349        self.assertEqual(serialNumber8(100) + serialNumber8(100), serialNumber8(200))
350        self.assertEqual(serialNumber8(200) + serialNumber8(100), serialNumber8(44))
351
352    def test_gt(self):
353        """
354        Comparison is more interesting, 1 > 0, 44 > 0, 100 > 0, 100 > 44,
355        200 > 100, 255 > 200, 0 > 255, 100 > 255, 0 > 200, and 44 > 200.
356        """
357        self.assertTrue(serialNumber8(1) > serialNumber8(0))
358        self.assertTrue(serialNumber8(44) > serialNumber8(0))
359        self.assertTrue(serialNumber8(100) > serialNumber8(0))
360        self.assertTrue(serialNumber8(100) > serialNumber8(44))
361        self.assertTrue(serialNumber8(200) > serialNumber8(100))
362        self.assertTrue(serialNumber8(255) > serialNumber8(200))
363        self.assertTrue(serialNumber8(100) > serialNumber8(255))
364        self.assertTrue(serialNumber8(0) > serialNumber8(200))
365        self.assertTrue(serialNumber8(44) > serialNumber8(200))
366
367    def test_surprisingAddition(self):
368        """
369        Note that 100+100 > 100, but that (100+100)+100 < 100.  Incrementing a
370        serial number can cause it to become "smaller".  Of course, incrementing
371        by a smaller number will allow many more increments to be made before
372        this occurs.  However this is always something to be aware of, it can
373        cause surprising errors, or be useful as it is the only defined way to
374        actually cause a serial number to decrease.
375        """
376        self.assertTrue(serialNumber8(100) + serialNumber8(100) > serialNumber8(100))
377        self.assertTrue(
378            serialNumber8(100) + serialNumber8(100) + serialNumber8(100)
379            < serialNumber8(100)
380        )
381
382    def test_undefined(self):
383        """
384        The pairs of values 0 and 128, 1 and 129, 2 and 130, etc, to 127 and 255
385        are not equal, but in each pair, neither number is defined as being
386        greater than, or less than, the other.
387        """
388        assertUndefinedComparison(self, serialNumber8(0), serialNumber8(128))
389        assertUndefinedComparison(self, serialNumber8(1), serialNumber8(129))
390        assertUndefinedComparison(self, serialNumber8(2), serialNumber8(130))
391        assertUndefinedComparison(self, serialNumber8(127), serialNumber8(255))
392