You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

_rfc1982.py 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. # -*- test-case-name: twisted.names.test.test_rfc1982 -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Utilities for handling RFC1982 Serial Number Arithmetic.
  6. @see: U{http://tools.ietf.org/html/rfc1982}
  7. @var RFC4034_TIME_FORMAT: RRSIG Time field presentation format. The Signature
  8. Expiration Time and Inception Time field values MUST be represented either
  9. as an unsigned decimal integer indicating seconds since 1 January 1970
  10. 00:00:00 UTC, or in the form YYYYMMDDHHmmSS in UTC. See U{RRSIG Presentation
  11. Format<https://tools.ietf.org/html/rfc4034#section-3.2>}
  12. """
  13. from __future__ import division, absolute_import
  14. import calendar
  15. from datetime import datetime, timedelta
  16. from twisted.python.compat import nativeString
  17. from twisted.python.util import FancyStrMixin
  18. RFC4034_TIME_FORMAT = '%Y%m%d%H%M%S'
  19. class SerialNumber(FancyStrMixin, object):
  20. """
  21. An RFC1982 Serial Number.
  22. This class implements RFC1982 DNS Serial Number Arithmetic.
  23. SNA is used in DNS and specifically in DNSSEC as defined in RFC4034 in the
  24. DNSSEC Signature Expiration and Inception Fields.
  25. @see: U{https://tools.ietf.org/html/rfc1982}
  26. @see: U{https://tools.ietf.org/html/rfc4034}
  27. @ivar _serialBits: See C{serialBits} of L{__init__}.
  28. @ivar _number: See C{number} of L{__init__}.
  29. @ivar _modulo: The value at which wrapping will occur.
  30. @ivar _halfRing: Half C{_modulo}. If another L{SerialNumber} value is larger
  31. than this, it would lead to a wrapped value which is larger than the
  32. first and comparisons are therefore ambiguous.
  33. @ivar _maxAdd: Half C{_modulo} plus 1. If another L{SerialNumber} value is
  34. larger than this, it would lead to a wrapped value which is larger than
  35. the first. Comparisons with the original value would therefore be
  36. ambiguous.
  37. """
  38. showAttributes = (
  39. ('_number', 'number', '%d'),
  40. ('_serialBits', 'serialBits', '%d'),
  41. )
  42. def __init__(self, number, serialBits=32):
  43. """
  44. Construct an L{SerialNumber} instance.
  45. @param number: An L{int} which will be stored as the modulo
  46. C{number % 2 ^ serialBits}
  47. @type number: L{int}
  48. @param serialBits: The size of the serial number space. The power of two
  49. which results in one larger than the largest integer corresponding
  50. to a serial number value.
  51. @type serialBits: L{int}
  52. """
  53. self._serialBits = serialBits
  54. self._modulo = 2 ** serialBits
  55. self._halfRing = 2 ** (serialBits - 1)
  56. self._maxAdd = 2 ** (serialBits - 1) - 1
  57. self._number = int(number) % self._modulo
  58. def _convertOther(self, other):
  59. """
  60. Check that a foreign object is suitable for use in the comparison or
  61. arithmetic magic methods of this L{SerialNumber} instance. Raise
  62. L{TypeError} if not.
  63. @param other: The foreign L{object} to be checked.
  64. @return: C{other} after compatibility checks and possible coercion.
  65. @raises: L{TypeError} if C{other} is not compatible.
  66. """
  67. if not isinstance(other, SerialNumber):
  68. raise TypeError(
  69. 'cannot compare or combine %r and %r' % (self, other))
  70. if self._serialBits != other._serialBits:
  71. raise TypeError(
  72. 'cannot compare or combine SerialNumber instances with '
  73. 'different serialBits. %r and %r' % (self, other))
  74. return other
  75. def __str__(self):
  76. """
  77. Return a string representation of this L{SerialNumber} instance.
  78. @rtype: L{nativeString}
  79. """
  80. return nativeString('%d' % (self._number,))
  81. def __int__(self):
  82. """
  83. @return: The integer value of this L{SerialNumber} instance.
  84. @rtype: L{int}
  85. """
  86. return self._number
  87. def __eq__(self, other):
  88. """
  89. Allow rich equality comparison with another L{SerialNumber} instance.
  90. @type other: L{SerialNumber}
  91. """
  92. other = self._convertOther(other)
  93. return other._number == self._number
  94. def __ne__(self, other):
  95. """
  96. Allow rich equality comparison with another L{SerialNumber} instance.
  97. @type other: L{SerialNumber}
  98. """
  99. return not self.__eq__(other)
  100. def __lt__(self, other):
  101. """
  102. Allow I{less than} comparison with another L{SerialNumber} instance.
  103. @type other: L{SerialNumber}
  104. """
  105. other = self._convertOther(other)
  106. return (
  107. (self._number < other._number
  108. and (other._number - self._number) < self._halfRing)
  109. or
  110. (self._number > other._number
  111. and (self._number - other._number) > self._halfRing)
  112. )
  113. def __gt__(self, other):
  114. """
  115. Allow I{greater than} comparison with another L{SerialNumber} instance.
  116. @type other: L{SerialNumber}
  117. @rtype: L{bool}
  118. """
  119. other = self._convertOther(other)
  120. return (
  121. (self._number < other._number
  122. and (other._number - self._number) > self._halfRing)
  123. or
  124. (self._number > other._number
  125. and (self._number - other._number) < self._halfRing)
  126. )
  127. def __le__(self, other):
  128. """
  129. Allow I{less than or equal} comparison with another L{SerialNumber}
  130. instance.
  131. @type other: L{SerialNumber}
  132. @rtype: L{bool}
  133. """
  134. other = self._convertOther(other)
  135. return self == other or self < other
  136. def __ge__(self, other):
  137. """
  138. Allow I{greater than or equal} comparison with another L{SerialNumber}
  139. instance.
  140. @type other: L{SerialNumber}
  141. @rtype: L{bool}
  142. """
  143. other = self._convertOther(other)
  144. return self == other or self > other
  145. def __add__(self, other):
  146. """
  147. Allow I{addition} with another L{SerialNumber} instance.
  148. Serial numbers may be incremented by the addition of a positive
  149. integer n, where n is taken from the range of integers
  150. [0 .. (2^(SERIAL_BITS - 1) - 1)]. For a sequence number s, the
  151. result of such an addition, s', is defined as
  152. s' = (s + n) modulo (2 ^ SERIAL_BITS)
  153. where the addition and modulus operations here act upon values that are
  154. non-negative values of unbounded size in the usual ways of integer
  155. arithmetic.
  156. Addition of a value outside the range
  157. [0 .. (2^(SERIAL_BITS - 1) - 1)] is undefined.
  158. @see: U{http://tools.ietf.org/html/rfc1982#section-3.1}
  159. @type other: L{SerialNumber}
  160. @rtype: L{SerialNumber}
  161. @raises: L{ArithmeticError} if C{other} is more than C{_maxAdd}
  162. ie more than half the maximum value of this serial number.
  163. """
  164. other = self._convertOther(other)
  165. if other._number <= self._maxAdd:
  166. return SerialNumber(
  167. (self._number + other._number) % self._modulo,
  168. serialBits=self._serialBits)
  169. else:
  170. raise ArithmeticError(
  171. 'value %r outside the range 0 .. %r' % (
  172. other._number, self._maxAdd,))
  173. def __hash__(self):
  174. """
  175. Allow L{SerialNumber} instances to be hashed for use as L{dict} keys.
  176. @rtype: L{int}
  177. """
  178. return hash(self._number)
  179. @classmethod
  180. def fromRFC4034DateString(cls, utcDateString):
  181. """
  182. Create an L{SerialNumber} instance from a date string in format
  183. 'YYYYMMDDHHMMSS' described in U{RFC4034
  184. 3.2<https://tools.ietf.org/html/rfc4034#section-3.2>}.
  185. The L{SerialNumber} instance stores the date as a 32bit UNIX timestamp.
  186. @see: U{https://tools.ietf.org/html/rfc4034#section-3.1.5}
  187. @param utcDateString: A UTC date/time string of format I{YYMMDDhhmmss}
  188. which will be converted to seconds since the UNIX epoch.
  189. @type utcDateString: L{unicode}
  190. @return: An L{SerialNumber} instance containing the supplied date as a
  191. 32bit UNIX timestamp.
  192. """
  193. parsedDate = datetime.strptime(utcDateString, RFC4034_TIME_FORMAT)
  194. secondsSinceEpoch = calendar.timegm(parsedDate.utctimetuple())
  195. return cls(secondsSinceEpoch, serialBits=32)
  196. def toRFC4034DateString(self):
  197. """
  198. Calculate a date by treating the current L{SerialNumber} value as a UNIX
  199. timestamp and return a date string in the format described in
  200. U{RFC4034 3.2<https://tools.ietf.org/html/rfc4034#section-3.2>}.
  201. @return: The date string.
  202. """
  203. # Can't use datetime.utcfromtimestamp, because it seems to overflow the
  204. # signed 32bit int used in the underlying C library. SNA is unsigned
  205. # and capable of handling all timestamps up to 2**32.
  206. d = datetime(1970, 1, 1) + timedelta(seconds=self._number)
  207. return nativeString(d.strftime(RFC4034_TIME_FORMAT))
  208. __all__ = ['SerialNumber']