1# This file is dual licensed under the terms of the Apache License, Version
2# 2.0, and the BSD License. See the LICENSE file in the root of this repository
3# for complete details.
4
5from __future__ import absolute_import, division, print_function
6
7import abc
8import binascii
9import inspect
10import sys
11import warnings
12
13
14# We use a UserWarning subclass, instead of DeprecationWarning, because CPython
15# decided deprecation warnings should be invisble by default.
16class CryptographyDeprecationWarning(UserWarning):
17    pass
18
19
20# Several APIs were deprecated with no specific end-of-life date because of the
21# ubiquity of their use. They should not be removed until we agree on when that
22# cycle ends.
23PersistentlyDeprecated = CryptographyDeprecationWarning
24DeprecatedIn21 = CryptographyDeprecationWarning
25DeprecatedIn23 = CryptographyDeprecationWarning
26DeprecatedIn25 = CryptographyDeprecationWarning
27
28
29def _check_bytes(name, value):
30    if not isinstance(value, bytes):
31        raise TypeError("{0} must be bytes".format(name))
32
33
34def _check_byteslike(name, value):
35    try:
36        memoryview(value)
37    except TypeError:
38        raise TypeError("{0} must be bytes-like".format(name))
39
40
41def read_only_property(name):
42    return property(lambda self: getattr(self, name))
43
44
45def register_interface(iface):
46    def register_decorator(klass):
47        verify_interface(iface, klass)
48        iface.register(klass)
49        return klass
50    return register_decorator
51
52
53def register_interface_if(predicate, iface):
54    def register_decorator(klass):
55        if predicate:
56            verify_interface(iface, klass)
57            iface.register(klass)
58        return klass
59    return register_decorator
60
61
62if hasattr(int, "from_bytes"):
63    int_from_bytes = int.from_bytes
64else:
65    def int_from_bytes(data, byteorder, signed=False):
66        assert byteorder == 'big'
67        assert not signed
68
69        return int(binascii.hexlify(data), 16)
70
71
72if hasattr(int, "to_bytes"):
73    def int_to_bytes(integer, length=None):
74        return integer.to_bytes(
75            length or (integer.bit_length() + 7) // 8 or 1, 'big'
76        )
77else:
78    def int_to_bytes(integer, length=None):
79        hex_string = '%x' % integer
80        if length is None:
81            n = len(hex_string)
82        else:
83            n = length * 2
84        return binascii.unhexlify(hex_string.zfill(n + (n & 1)))
85
86
87class InterfaceNotImplemented(Exception):
88    pass
89
90
91if hasattr(inspect, "signature"):
92    signature = inspect.signature
93else:
94    signature = inspect.getargspec
95
96
97def verify_interface(iface, klass):
98    for method in iface.__abstractmethods__:
99        if not hasattr(klass, method):
100            raise InterfaceNotImplemented(
101                "{0} is missing a {1!r} method".format(klass, method)
102            )
103        if isinstance(getattr(iface, method), abc.abstractproperty):
104            # Can't properly verify these yet.
105            continue
106        sig = signature(getattr(iface, method))
107        actual = signature(getattr(klass, method))
108        if sig != actual:
109            raise InterfaceNotImplemented(
110                "{0}.{1}'s signature differs from the expected. Expected: "
111                "{2!r}. Received: {3!r}".format(
112                    klass, method, sig, actual
113                )
114            )
115
116
117# No longer needed as of 2.2, but retained because we have external consumers
118# who use it.
119def bit_length(x):
120    return x.bit_length()
121
122
123class _DeprecatedValue(object):
124    def __init__(self, value, message, warning_class):
125        self.value = value
126        self.message = message
127        self.warning_class = warning_class
128
129
130class _ModuleWithDeprecations(object):
131    def __init__(self, module):
132        self.__dict__["_module"] = module
133
134    def __getattr__(self, attr):
135        obj = getattr(self._module, attr)
136        if isinstance(obj, _DeprecatedValue):
137            warnings.warn(obj.message, obj.warning_class, stacklevel=2)
138            obj = obj.value
139        return obj
140
141    def __setattr__(self, attr, value):
142        setattr(self._module, attr, value)
143
144    def __delattr__(self, attr):
145        obj = getattr(self._module, attr)
146        if isinstance(obj, _DeprecatedValue):
147            warnings.warn(obj.message, obj.warning_class, stacklevel=2)
148
149        delattr(self._module, attr)
150
151    def __dir__(self):
152        return ["_module"] + dir(self._module)
153
154
155def deprecated(value, module_name, message, warning_class):
156    module = sys.modules[module_name]
157    if not isinstance(module, _ModuleWithDeprecations):
158        sys.modules[module_name] = _ModuleWithDeprecations(module)
159    return _DeprecatedValue(value, message, warning_class)
160
161
162def cached_property(func):
163    cached_name = "_cached_{0}".format(func)
164    sentinel = object()
165
166    def inner(instance):
167        cache = getattr(instance, cached_name, sentinel)
168        if cache is not sentinel:
169            return cache
170        result = func(instance)
171        setattr(instance, cached_name, result)
172        return result
173    return property(inner)
174