# Implements the hmac module from the Python standard library.


class HMAC:
    def __init__(self, key, msg=None, digestmod=None):
        if not isinstance(key, (bytes, bytearray)):
            raise TypeError("key: expected bytes/bytearray")

        import hashlib

        if digestmod is None:
            # TODO: Default hash algorithm is now deprecated.
            digestmod = hashlib.md5

        if callable(digestmod):
            # A hashlib constructor returning a new hash object.
            make_hash = digestmod  # A
        elif isinstance(digestmod, str):
            # A hash name suitable for hashlib.new().
            make_hash = lambda d=b"": hashlib.new(digestmod, d)  # B
        else:
            # A module supporting PEP 247.
            make_hash = digestmod.new  # C

        self._outer = make_hash()
        self._inner = make_hash()

        self.digest_size = getattr(self._inner, "digest_size", None)
        # If the provided hash doesn't support block_size (e.g. built-in
        # hashlib), 64 is the correct default for all built-in hash
        # functions (md5, sha1, sha256).
        self.block_size = getattr(self._inner, "block_size", 64)

        # Truncate to digest_size if greater than block_size.
        if len(key) > self.block_size:
            key = make_hash(key).digest()

        # Pad to block size.
        key = key + bytes(self.block_size - len(key))

        self._outer.update(bytes(x ^ 0x5C for x in key))
        self._inner.update(bytes(x ^ 0x36 for x in key))

        if msg is not None:
            self.update(msg)

    @property
    def name(self):
        return "hmac-" + getattr(self._inner, "name", type(self._inner).__name__)

    def update(self, msg):
        self._inner.update(msg)

    def copy(self):
        if not hasattr(self._inner, "copy"):
            # Not supported for built-in hash functions.
            raise NotImplementedError()
        # Call __new__ directly to avoid the expensive __init__.
        other = self.__class__.__new__(self.__class__)
        other.block_size = self.block_size
        other.digest_size = self.digest_size
        other._inner = self._inner.copy()
        other._outer = self._outer.copy()
        return other

    def _current(self):
        h = self._outer
        if hasattr(h, "copy"):
            # built-in hash functions don't support this, and as a result,
            # digest() will finalise the hmac and further calls to
            # update/digest will fail.
            h = h.copy()
        h.update(self._inner.digest())
        return h

    def digest(self):
        h = self._current()
        return h.digest()

    def hexdigest(self):
        import binascii

        return str(binascii.hexlify(self.digest()), "utf-8")


def new(key, msg=None, digestmod=None):
    return HMAC(key, msg, digestmod)
