pypy /pypy/module/_hashlib/interp_hashlib.py

Language Python Lines 180
MD5 Hash 7206fc742334fa27b68aeb036d3f3930 Estimated Cost $3,403 (why?)
Repository https://bitbucket.org/pypy/pypy/ View Raw File View Project SPDX
  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
from __future__ import with_statement

from rpython.rlib import rgc, ropenssl
from rpython.rlib.objectmodel import we_are_translated
from rpython.rlib.rstring import StringBuilder
from rpython.rtyper.lltypesystem import lltype, rffi
from rpython.tool.sourcetools import func_renamer

from pypy.interpreter.baseobjspace import W_Root
from pypy.interpreter.error import OperationError, oefmt
from pypy.interpreter.gateway import unwrap_spec, interp2app
from pypy.interpreter.typedef import TypeDef, GetSetProperty
from pypy.module.thread.os_lock import Lock


algorithms = ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512')

def hash_name_mapper_callback(obj_name, userdata):
    if not obj_name:
        return
    # Ignore aliased names, they pollute the list and OpenSSL appears
    # to have a its own definition of alias as the resulting list
    # still contains duplicate and alternate names for several
    # algorithms.
    if rffi.cast(lltype.Signed, obj_name[0].c_alias):
        return
    try:
        space = global_name_fetcher.space
        w_name = space.wrap(rffi.charp2str(obj_name[0].c_name))
        global_name_fetcher.meth_names.append(w_name)
    except OperationError as e:
        global_name_fetcher.w_error = e

class NameFetcher:
    def setup(self, space):
        self.space = space
        self.meth_names = []
        self.w_error = None
    def _cleanup_(self):
        self.__dict__.clear()
global_name_fetcher = NameFetcher()

def fetch_names(space):
    global_name_fetcher.setup(space)
    ropenssl.init_digests()
    ropenssl.OBJ_NAME_do_all(ropenssl.OBJ_NAME_TYPE_MD_METH,
                             hash_name_mapper_callback, None)
    if global_name_fetcher.w_error:
        raise global_name_fetcher.w_error
    meth_names = global_name_fetcher.meth_names
    global_name_fetcher.meth_names = None
    return space.call_function(space.w_frozenset, space.newlist(meth_names))

class W_Hash(W_Root):
    NULL_CTX = lltype.nullptr(ropenssl.EVP_MD_CTX.TO)
    ctx = NULL_CTX

    def __init__(self, space, name, copy_from=NULL_CTX):
        self.name = name
        digest_type = self.digest_type_by_name(space)
        self.digest_size = rffi.getintfield(digest_type, 'c_md_size')

        # Allocate a lock for each HASH object.
        # An optimization would be to not release the GIL on small requests,
        # and use a custom lock only when needed.
        self.lock = Lock(space)

        ctx = lltype.malloc(ropenssl.EVP_MD_CTX.TO, flavor='raw')
        rgc.add_memory_pressure(ropenssl.HASH_MALLOC_SIZE + self.digest_size)
        try:
            if copy_from:
                ropenssl.EVP_MD_CTX_copy(ctx, copy_from)
            else:
                ropenssl.EVP_DigestInit(ctx, digest_type)
            self.ctx = ctx
        except:
            lltype.free(ctx, flavor='raw')
            raise
        self.register_finalizer(space)

    def _finalize_(self):
        ctx = self.ctx
        if ctx:
            self.ctx = lltype.nullptr(ropenssl.EVP_MD_CTX.TO)
            ropenssl.EVP_MD_CTX_cleanup(ctx)
            lltype.free(ctx, flavor='raw')

    def digest_type_by_name(self, space):
        digest_type = ropenssl.EVP_get_digestbyname(self.name)
        if not digest_type:
            raise oefmt(space.w_ValueError, "unknown hash function")
        return digest_type

    def descr_repr(self, space):
        addrstring = self.getaddrstring(space)
        return space.wrap("<%s HASH object at 0x%s>" % (
            self.name, addrstring))

    @unwrap_spec(string='bufferstr')
    def update(self, space, string):
        with rffi.scoped_nonmovingbuffer(string) as buf:
            with self.lock:
                # XXX try to not release the GIL for small requests
                ropenssl.EVP_DigestUpdate(self.ctx, buf, len(string))

    def copy(self, space):
        "Return a copy of the hash object."
        with self.lock:
            return W_Hash(space, self.name, copy_from=self.ctx)

    def digest(self, space):
        "Return the digest value as a string of binary data."
        digest = self._digest(space)
        return space.newbytes(digest)

    def hexdigest(self, space):
        "Return the digest value as a string of hexadecimal digits."
        digest = self._digest(space)
        hexdigits = '0123456789abcdef'
        result = StringBuilder(self.digest_size * 2)
        for c in digest:
            result.append(hexdigits[(ord(c) >> 4) & 0xf])
            result.append(hexdigits[ ord(c)       & 0xf])
        return space.wrap(result.build())

    def get_digest_size(self, space):
        return space.wrap(self.digest_size)

    def get_block_size(self, space):
        digest_type = self.digest_type_by_name(space)
        block_size = rffi.getintfield(digest_type, 'c_block_size')
        return space.wrap(block_size)

    def get_name(self, space):
        return space.wrap(self.name)

    def _digest(self, space):
        with lltype.scoped_alloc(ropenssl.EVP_MD_CTX.TO) as ctx:
            with self.lock:
                ropenssl.EVP_MD_CTX_copy(ctx, self.ctx)
            digest_size = self.digest_size
            with rffi.scoped_alloc_buffer(digest_size) as buf:
                ropenssl.EVP_DigestFinal(ctx, buf.raw, None)
                ropenssl.EVP_MD_CTX_cleanup(ctx)
                return buf.str(digest_size)


W_Hash.typedef = TypeDef(
    'HASH',
    __repr__=interp2app(W_Hash.descr_repr),
    update=interp2app(W_Hash.update),
    copy=interp2app(W_Hash.copy),
    digest=interp2app(W_Hash.digest),
    hexdigest=interp2app(W_Hash.hexdigest),
    #
    digest_size=GetSetProperty(W_Hash.get_digest_size),
    digestsize=GetSetProperty(W_Hash.get_digest_size),
    block_size=GetSetProperty(W_Hash.get_block_size),
    name=GetSetProperty(W_Hash.get_name),
)
W_Hash.typedef.acceptable_as_base_class = False

@unwrap_spec(name=str, string='bufferstr')
def new(space, name, string=''):
    w_hash = W_Hash(space, name)
    w_hash.update(space, string)
    return space.wrap(w_hash)

# shortcut functions
def make_new_hash(name, funcname):
    @func_renamer(funcname)
    @unwrap_spec(string='bufferstr')
    def new_hash(space, string=''):
        return new(space, name, string)
    return new_hash

for _name in algorithms:
    _newname = 'new_%s' % (_name,)
    globals()[_newname] = make_new_hash(_name, _newname)
Back to Top