123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- # -*- 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>}
- """
-
-
- 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):
- """
- 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: object) -> "SerialNumber":
- """
- 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.
- @raise TypeError: If C{other} is not compatible.
- """
- if not isinstance(other, SerialNumber):
- raise TypeError(f"cannot compare or combine {self!r} and {other!r}")
-
- 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) -> str:
- """
- 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: object) -> bool:
- """
- Allow rich equality comparison with another L{SerialNumber} instance.
- """
- try:
- other = self._convertOther(other)
- except TypeError:
- return NotImplemented
- return other._number == self._number
-
- def __lt__(self, other: object) -> bool:
- """
- Allow I{less than} comparison with another L{SerialNumber} instance.
- """
- try:
- other = self._convertOther(other)
- except TypeError:
- return NotImplemented
- 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: object) -> bool:
- """
- Allow I{greater than} comparison with another L{SerialNumber} instance.
- """
- try:
- other = self._convertOther(other)
- except TypeError:
- return NotImplemented
- 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: object) -> bool:
- """
- Allow I{less than or equal} comparison with another L{SerialNumber}
- instance.
- """
- try:
- other = self._convertOther(other)
- except TypeError:
- return NotImplemented
- return self == other or self < other
-
- def __ge__(self, other: object) -> bool:
- """
- Allow I{greater than or equal} comparison with another L{SerialNumber}
- instance.
- """
- try:
- other = self._convertOther(other)
- except TypeError:
- return NotImplemented
- return self == other or self > other
-
- def __add__(self, other: object) -> "SerialNumber":
- """
- 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}
-
- @raise ArithmeticError: If C{other} is more than C{_maxAdd}
- ie more than half the maximum value of this serial number.
- """
- try:
- other = self._convertOther(other)
- except TypeError:
- return NotImplemented
- 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"]
|