aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Twisted/py2/twisted/names/_rfc1982.py
blob: a895d82c4e213b170b0187d675f607bce6763b46 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# -*- test-case-name: twisted.names.test.test_rfc1982 -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.

"""
Utilities for handling RFC1982 Serial Number Arithmetic.

@see: U{http://tools.ietf.org/html/rfc1982}

@var RFC4034_TIME_FORMAT: RRSIG Time field presentation format. The Signature
   Expiration Time and Inception Time field values MUST be represented either
   as an unsigned decimal integer indicating seconds since 1 January 1970
   00:00:00 UTC, or in the form YYYYMMDDHHmmSS in UTC. See U{RRSIG Presentation
   Format<https://tools.ietf.org/html/rfc4034#section-3.2>}
"""

from __future__ import division, absolute_import

import calendar
from datetime import datetime, timedelta

from twisted.python.compat import nativeString
from twisted.python.util import FancyStrMixin


RFC4034_TIME_FORMAT = '%Y%m%d%H%M%S'



class SerialNumber(FancyStrMixin, object):
    """
    An RFC1982 Serial Number.

    This class implements RFC1982 DNS Serial Number Arithmetic.

    SNA is used in DNS and specifically in DNSSEC as defined in RFC4034 in the
    DNSSEC Signature Expiration and Inception Fields.

    @see: U{https://tools.ietf.org/html/rfc1982}
    @see: U{https://tools.ietf.org/html/rfc4034}

    @ivar _serialBits: See C{serialBits} of L{__init__}.
    @ivar _number: See C{number} of L{__init__}.
    @ivar _modulo: The value at which wrapping will occur.
    @ivar _halfRing: Half C{_modulo}. If another L{SerialNumber} value is larger
        than this, it would lead to a wrapped value which is larger than the
        first and comparisons are therefore ambiguous.
    @ivar _maxAdd: Half C{_modulo} plus 1. If another L{SerialNumber} value is
        larger than this, it would lead to a wrapped value which is larger than
        the first. Comparisons with the original value would therefore be
        ambiguous.
    """

    showAttributes = (
        ('_number', 'number', '%d'),
        ('_serialBits', 'serialBits', '%d'),
    )

    def __init__(self, number, serialBits=32):
        """
        Construct an L{SerialNumber} instance.

        @param number: An L{int} which will be stored as the modulo
            C{number % 2 ^ serialBits}
        @type number: L{int}

        @param serialBits: The size of the serial number space. The power of two
            which results in one larger than the largest integer corresponding
            to a serial number value.
        @type serialBits: L{int}
        """
        self._serialBits = serialBits
        self._modulo = 2 ** serialBits
        self._halfRing = 2 ** (serialBits - 1)
        self._maxAdd = 2 ** (serialBits - 1) - 1
        self._number = int(number) % self._modulo


    def _convertOther(self, other):
        """
        Check that a foreign object is suitable for use in the comparison or
        arithmetic magic methods of this L{SerialNumber} instance. Raise
        L{TypeError} if not.

        @param other: The foreign L{object} to be checked.
        @return: C{other} after compatibility checks and possible coercion.
        @raises: L{TypeError} if C{other} is not compatible.
        """
        if not isinstance(other, SerialNumber):
            raise TypeError(
                'cannot compare or combine %r and %r' % (self, other))

        if self._serialBits != other._serialBits:
            raise TypeError(
                'cannot compare or combine SerialNumber instances with '
                'different serialBits. %r and %r' % (self, other))

        return other


    def __str__(self):
        """
        Return a string representation of this L{SerialNumber} instance.

        @rtype: L{nativeString}
        """
        return nativeString('%d' % (self._number,))


    def __int__(self):
        """
        @return: The integer value of this L{SerialNumber} instance.
        @rtype: L{int}
        """
        return self._number


    def __eq__(self, other):
        """
        Allow rich equality comparison with another L{SerialNumber} instance.

        @type other: L{SerialNumber}
        """
        other = self._convertOther(other)
        return other._number == self._number


    def __ne__(self, other):
        """
        Allow rich equality comparison with another L{SerialNumber} instance.

        @type other: L{SerialNumber}
        """
        return not self.__eq__(other)


    def __lt__(self, other):
        """
        Allow I{less than} comparison with another L{SerialNumber} instance.

        @type other: L{SerialNumber}
        """
        other = self._convertOther(other)
        return (
            (self._number < other._number
             and (other._number - self._number) < self._halfRing)
            or
            (self._number > other._number
             and (self._number - other._number) > self._halfRing)
        )


    def __gt__(self, other):
        """
        Allow I{greater than} comparison with another L{SerialNumber} instance.

        @type other: L{SerialNumber}
        @rtype: L{bool}
        """
        other = self._convertOther(other)
        return (
            (self._number < other._number
             and (other._number - self._number) > self._halfRing)
            or
            (self._number > other._number
             and (self._number - other._number) < self._halfRing)
        )


    def __le__(self, other):
        """
        Allow I{less than or equal} comparison with another L{SerialNumber}
        instance.

        @type other: L{SerialNumber}
        @rtype: L{bool}
        """
        other = self._convertOther(other)
        return self == other or self < other


    def __ge__(self, other):
        """
        Allow I{greater than or equal} comparison with another L{SerialNumber}
        instance.

        @type other: L{SerialNumber}
        @rtype: L{bool}
        """
        other = self._convertOther(other)
        return self == other or self > other


    def __add__(self, other):
        """
        Allow I{addition} with another L{SerialNumber} instance.

        Serial numbers may be incremented by the addition of a positive
        integer n, where n is taken from the range of integers
        [0 .. (2^(SERIAL_BITS - 1) - 1)].  For a sequence number s, the
        result of such an addition, s', is defined as

        s' = (s + n) modulo (2 ^ SERIAL_BITS)

        where the addition and modulus operations here act upon values that are
        non-negative values of unbounded size in the usual ways of integer
        arithmetic.

        Addition of a value outside the range
        [0 .. (2^(SERIAL_BITS - 1) - 1)] is undefined.

        @see: U{http://tools.ietf.org/html/rfc1982#section-3.1}

        @type other: L{SerialNumber}
        @rtype: L{SerialNumber}
        @raises: L{ArithmeticError} if C{other} is more than C{_maxAdd}
            ie more than half the maximum value of this serial number.
        """
        other = self._convertOther(other)
        if other._number <= self._maxAdd:
            return SerialNumber(
                (self._number + other._number) % self._modulo,
                serialBits=self._serialBits)
        else:
            raise ArithmeticError(
                'value %r outside the range 0 .. %r' % (
                    other._number, self._maxAdd,))


    def __hash__(self):
        """
        Allow L{SerialNumber} instances to be hashed for use as L{dict} keys.

        @rtype: L{int}
        """
        return hash(self._number)


    @classmethod
    def fromRFC4034DateString(cls, utcDateString):
        """
        Create an L{SerialNumber} instance from a date string in format
        'YYYYMMDDHHMMSS' described in U{RFC4034
        3.2<https://tools.ietf.org/html/rfc4034#section-3.2>}.

        The L{SerialNumber} instance stores the date as a 32bit UNIX timestamp.

        @see: U{https://tools.ietf.org/html/rfc4034#section-3.1.5}

        @param utcDateString: A UTC date/time string of format I{YYMMDDhhmmss}
            which will be converted to seconds since the UNIX epoch.
        @type utcDateString: L{unicode}

        @return: An L{SerialNumber} instance containing the supplied date as a
            32bit UNIX timestamp.
        """
        parsedDate = datetime.strptime(utcDateString, RFC4034_TIME_FORMAT)
        secondsSinceEpoch = calendar.timegm(parsedDate.utctimetuple())
        return cls(secondsSinceEpoch, serialBits=32)


    def toRFC4034DateString(self):
        """
        Calculate a date by treating the current L{SerialNumber} value as a UNIX
        timestamp and return a date string in the format described in
        U{RFC4034 3.2<https://tools.ietf.org/html/rfc4034#section-3.2>}.

        @return: The date string.
        """
        # Can't use datetime.utcfromtimestamp, because it seems to overflow the
        # signed 32bit int used in the underlying C library. SNA is unsigned
        # and capable of handling all timestamps up to 2**32.
        d = datetime(1970, 1, 1) + timedelta(seconds=self._number)
        return nativeString(d.strftime(RFC4034_TIME_FORMAT))



__all__ = ['SerialNumber']