text: """passlib.handlers.argon2 -- argon2 password hash wrapper
  
  References
  ==========
  * argon2
      - home: https://github.com/P-H-C/phc-winner-argon2
      - whitepaper: https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
  * argon2 cffi wrapper
      - pypi: https://pypi.python.org/pypi/argon2_cffi
      - home: https://github.com/hynek/argon2_cffi
  * argon2 pure python
      - pypi: https://pypi.python.org/pypi/argon2pure
      - home: https://github.com/bwesterb/argon2pure
  """
  #=============================================================================
  # imports
  #=============================================================================
  from __future__ import with_statement, absolute_import
  # core
  import logging
  log = logging.getLogger(__name__)
  import re
  import types
  from warnings import warn
  # site
  _argon2_cffi = None  # loaded below
  _argon2pure = None  # dynamically imported by _load_backend_argon2pure()
  # pkg
  from passlib import exc
  from passlib.crypto.digest import MAX_UINT32
  from passlib.utils import to_bytes
  from passlib.utils.binary import b64s_encode, b64s_decode
  from passlib.utils.compat import u, unicode, bascii_to_str
  import passlib.utils.handlers as uh
  # local
  __all__ = [
      "argon2",
  ]
  
  #=============================================================================
  # import argon2 package (https://pypi.python.org/pypi/argon2_cffi)
  #=============================================================================
  
  # import package
  try:
      import argon2 as _argon2_cffi
  except ImportError:
      _argon2_cffi = None
  
  # get default settings for hasher
  _PasswordHasher = getattr(_argon2_cffi, "PasswordHasher", None)
  if _PasswordHasher:
      # we have argon2_cffi >= 16.0, use their default hasher settings
      _default_settings = _PasswordHasher()
      _default_version = _argon2_cffi.low_level.ARGON2_VERSION
  else:
      # use these as our fallback settings (for no backend, or argon2pure)
      class _default_settings:
          """
          dummy object to use as source of defaults when argon2 mod not present.
          synced w/ argon2 16.1 as of 2016-6-16
          """
          time_cost = 2
          memory_cost = 512
          parallelism = 2
          salt_len = 16
          hash_len = 16
      _default_version = 0x13
  
  #=============================================================================
  # handler
  #=============================================================================
  class _Argon2Common(uh.SubclassBackendMixin, uh.ParallelismMixin,
                      uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum,
                      uh.GenericHandler):
      """
      Base class which implements brunt of Argon2 code.
      This is then subclassed by the various backends,
      to override w/ backend-specific methods.
  
      When a backend is loaded, the bases of the 'argon2' class proper
      are modified to prepend the correct backend-specific subclass.
      """
      #===================================================================
      # class attrs
      #===================================================================
  
      #------------------------
      # PasswordHash
      #------------------------
  
      name = "argon2"
      setting_kwds = ("salt",
                      "salt_size",
                      "salt_len",  # 'salt_size' alias for compat w/ argon2 package
                      "rounds",
                      "time_cost",  # 'rounds' alias for compat w/ argon2 package
                      "memory_cost",
                      "parallelism",
                      "digest_size",
                      "hash_len",  # 'digest_size' alias for compat w/ argon2 package
                      )
  
      # TODO: could support the optional 'data' parameter,
      #       but need to research the uses, what a more descriptive name would be,
      #       and deal w/ fact that argon2_cffi 16.1 doesn't currently support it.
      #       (argon2_pure does though)
  
      #------------------------
      # GenericHandler
      #------------------------
      ident = u("$argon2i")
      checksum_size = _default_settings.hash_len
  
      # NOTE: from_string() relies on the ordering of these...
      ident_values = (u("$argon2i$"), u("$argon2d$"))
  
      #------------------------
      # HasSalt
      #------------------------
      default_salt_size = _default_settings.salt_len
      min_salt_size = 8
      max_salt_size = MAX_UINT32
  
      #------------------------
      # HasRounds
      # TODO: once rounds limit logic is factored out,
      #       make 'rounds' and 'cost' an alias for 'time_cost'
      #------------------------
      default_rounds = _default_settings.time_cost
      min_rounds = 1
      max_rounds = MAX_UINT32
      rounds_cost = "linear"
  
      #------------------------
      # ParalleismMixin
      #------------------------
      max_parallelism = (1 << 24) - 1  # from argon2.h / ARGON2_MAX_LANES
  
      #------------------------
      # custom
      #------------------------
  
      #: max version support
      #: NOTE: this is dependant on the backend, and initialized/modified by set_backend()
      max_version = _default_version
  
      #: minimum version before needs_update() marks the hash; if None, defaults to max_version
      min_desired_version = None
  
      #: minimum valid memory_cost
      min_memory_cost = 8  # from argon2.h / ARGON2_MIN_MEMORY
  
      #: maximum number of threads (-1=unlimited);
      #: number of threads used by .hash() will be min(parallelism, max_threads)
      max_threads = -1
  
      #: global flag signalling argon2pure backend to use threads
      #: rather than subprocesses.
      pure_use_threads = False
  
      #===================================================================
      # instance attrs
      #===================================================================
  
      #: parallelism setting -- class value controls the default
      parallelism = _default_settings.parallelism
  
      #: hash version (int)
      #: NOTE: this is modified by set_backend()
      version = _default_version
  
      #: memory cost -- class value controls the default
      memory_cost = _default_settings.memory_cost
  
      #: flag indicating a Type D hash
      type_d = False
  
      #: optional secret data
      data = None
  
      #===================================================================
      # variant constructor
      #===================================================================
  
      @classmethod
      def using(cls, memory_cost=None, salt_len=None, time_cost=None, digest_size=None,
                checksum_size=None, hash_len=None, max_threads=None, **kwds):
          # support aliases which match argon2 naming convention
          if time_cost is not None:
              if "rounds" in kwds:
                  raise TypeError("'time_cost' and 'rounds' are mutually exclusive")
              kwds['rounds'] = time_cost
  
          if salt_len is not None:
              if "salt_size" in kwds:
                  raise TypeError("'salt_len' and 'salt_size' are mutually exclusive")
              kwds['salt_size'] = salt_len
  
          if hash_len is not None:
              if digest_size is not None:
                  raise TypeError("'hash_len' and 'digest_size' are mutually exclusive")
              digest_size = hash_len
  
          if checksum_size is not None:
              if digest_size is not None:
                  raise TypeError("'checksum_size' and 'digest_size' are mutually exclusive")
              digest_size = checksum_size
  
          # create variant
          subcls = super(_Argon2Common, cls).using(**kwds)
  
          # set checksum size
          relaxed = kwds.get("relaxed")
          if digest_size is not None:
              if isinstance(digest_size, uh.native_string_types):
                  digest_size = int(digest_size)
              # NOTE: this isn't *really* digest size minimum, but want to enforce secure minimum.
              subcls.checksum_size = uh.norm_integer(subcls, digest_size, min=16, max=MAX_UINT32,
                                                     param="digest_size", relaxed=relaxed)
  
          # set memory cost
          if memory_cost is not None:
              if isinstance(memory_cost, uh.native_string_types):
                  memory_cost = int(memory_cost)
              subcls.memory_cost = subcls._norm_memory_cost(memory_cost, relaxed=relaxed)
  
          # validate constraints
          subcls._validate_constraints(subcls.memory_cost, subcls.parallelism)
  
          # set max threads
          if max_threads is not None:
              if isinstance(max_threads, uh.native_string_types):
                  max_threads = int(max_threads)
              if max_threads < 1 and max_threads != -1:
                  raise ValueError("max_threads (%d) must be -1 (unlimited), or at least 1." %
                                   (max_threads,))
              subcls.max_threads = max_threads
  
          return subcls
  
      @classmethod
      def _validate_constraints(cls, memory_cost, parallelism):
          # NOTE: this is used by class & instance, hence passing in via arguments.
          #       could switch and make this a hybrid method.
          min_memory_cost = 8 * parallelism
          if memory_cost < min_memory_cost:
              raise ValueError("%s: memory_cost (%d) is too low, must be at least "
                               "8 * parallelism (8 * %d = %d)" %
                               (cls.name, memory_cost,
                                parallelism, min_memory_cost))
  
      #===================================================================
      # public api
      #===================================================================
  
      @classmethod
      def identify(cls, hash):
          hash = uh.to_unicode_for_identify(hash)
          return hash.startswith(cls.ident_values)
  
      # hash(), verify(), genhash() -- implemented by backend subclass
  
      #===================================================================
      # hash parsing / rendering
      #===================================================================
  
      # info taken from source of decode_string() function in
      # <https://github.com/P-H-C/phc-winner-argon2/blob/master/src/encoding.c>
      #
      # hash format:
      #   $argon2<T>[$v=<num>]$m=<num>,t=<num>,p=<num>[,keyid=<bin>][,data=<bin>][$<bin>[$<bin>]]
      #
      # NOTE: as of 2016-6-17, the official source (above) lists the "keyid" param in the comments,
      #       but the actual source of decode_string & encode_string don't mention it at all.
      #       we're supporting parsing it, but throw NotImplementedError if encountered.
      #
      # sample hashes:
      #    v1.0: '$argon2i$m=512,t=2,p=2$5VtWOO3cGWYQHEMaYGbsfQ$AcmqasQgW/wI6wAHAMk4aQ'
      #    v1.3: '$argon2i$v=19$m=512,t=2,p=2$5VtWOO3cGWYQHEMaYGbsfQ$AcmqasQgW/wI6wAHAMk4aQ'
  
      #: regex to parse argon hash
      _hash_regex = re.compile(br"""
          ^
          \$argon2(?P<type>[id])\$
          (?:
              v=(?P<version>\d+)
              \$
          )?
          m=(?P<memory_cost>\d+)
          ,
          t=(?P<time_cost>\d+)
          ,
          p=(?P<parallelism>\d+)
          (?:
              ,keyid=(?P<keyid>[^,$]+)
          )?
          (?:
              ,data=(?P<data>[^,$]+)
          )?
          (?:
              \$
              (?P<salt>[^$]+)
              (?:
                  \$
                  (?P<digest>.+)
              )?
          )?
          $
      """, re.X)
  
      @classmethod
      def from_string(cls, hash):
          # NOTE: assuming hash will be unicode, or use ascii-compatible encoding.
          if isinstance(hash, unicode):
              hash = hash.encode("utf-8")
          if not isinstance(hash, bytes):
              raise exc.ExpectedStringError(hash, "hash")
          m = cls._hash_regex.match(hash)
          if not m:
              raise exc.MalformedHashError(cls)
          type, version, memory_cost, time_cost, parallelism, keyid, data, salt, digest = \
              m.group("type", "version", "memory_cost", "time_cost", "parallelism",
                      "keyid", "data", "salt", "digest")
          assert type in [b"i", b"d"], "unexpected type code: %r" % (type,)
          if keyid:
              raise NotImplementedError("argon2 'keyid' parameter not supported")
          return cls(
              type_d=(type == b"d"),
              version=int(version) if version else 0x10,
              memory_cost=int(memory_cost),
              rounds=int(time_cost),
              parallelism=int(parallelism),
              salt=b64s_decode(salt) if salt else None,
              data=b64s_decode(data) if data else None,
              checksum=b64s_decode(digest) if digest else None,
          )
  
      def to_string(self):
          ident = str(self.ident_values[self.type_d])
          version = self.version
          if version == 0x10:
              vstr = ""
          else:
              vstr = "v=%d$" % version
          data = self.data
          if data:
              kdstr = ",data=" + bascii_to_str(b64s_encode(self.data))
          else:
              kdstr = ""
          # NOTE: 'keyid' param currently not supported
          return "%s%sm=%d,t=%d,p=%d%s$%s$%s" % (ident, vstr, self.memory_cost,
                                                 self.rounds, self.parallelism,
                                                 kdstr,
                                                 bascii_to_str(b64s_encode(self.salt)),
                                                 bascii_to_str(b64s_encode(self.checksum)))
  
      #===================================================================
      # init
      #===================================================================
      def __init__(self, type_d=False, version=None, memory_cost=None, data=None, **kwds):
  
          # TODO: factor out variable checksum size support into a mixin.
          # set checksum size to specific value before _norm_checksum() is called
          checksum = kwds.get("checksum")
          if checksum is not None:
              self.checksum_size = len(checksum)
  
          # call parent
          super(_Argon2Common, self).__init__(**kwds)
  
          # init type
          # NOTE: we don't support *generating* type I hashes, but do support verifying them.
          self.type_d = type_d
  
          # init version
          if version is None:
              assert uh.validate_default_value(self, self.version, self._norm_version,
                                               param="version")
          else:
              self.version = self._norm_version(version)
  
          # init memory cost
          if memory_cost is None:
              assert uh.validate_default_value(self, self.memory_cost, self._norm_memory_cost,
                                               param="memory_cost")
          else:
              self.memory_cost = self._norm_memory_cost(memory_cost)
  
          # init data
          if data is None:
              assert self.data is None
          else:
              if not isinstance(data, bytes):
                  raise uh.exc.ExpectedTypeError(data, "bytes", "data")
              self.data = data
  
      #-------------------------------------------------------------------
      # parameter guards
      #-------------------------------------------------------------------
  
      @classmethod
      def _norm_version(cls, version):
          if not isinstance(version, uh.int_types):
              raise uh.exc.ExpectedTypeError(version, "integer", "version")
  
          # minimum valid version
          if version < 0x13 and version != 0x10:
              raise ValueError("invalid argon2 hash version: %d" % (version,))
  
          # check this isn't past backend's max version
          backend = cls.get_backend()
          if version > cls.max_version:
              raise ValueError("%s: hash version 0x%X not supported by %r backend "
                               "(max version is 0x%X); try updating or switching backends" %
                               (cls.name, version, backend, cls.max_version))
          return version
  
      @classmethod
      def _norm_memory_cost(cls, memory_cost, relaxed=False):
          return uh.norm_integer(cls, memory_cost, min=cls.min_memory_cost,
                                 param="memory_cost", relaxed=relaxed)
  
      #===================================================================
      # digest calculation
      #===================================================================
  
      # NOTE: _calc_checksum implemented by backend subclass
  
      #===================================================================
      # hash migration
      #===================================================================
  
      def _calc_needs_update(self, **kwds):
          cls = type(self)
          if self.type_d:
              # type 'd' hashes shouldn't be used for passwords.
              return True
          minver = cls.min_desired_version
          if minver is None or minver > cls.max_version:
              minver = cls.max_version
          if self.version < minver:
              # version is too old.
              return True
          if self.memory_cost != cls.memory_cost:
              return True
          if self.checksum_size != cls.checksum_size:
              return True
          return super(_Argon2Common, self)._calc_needs_update(**kwds)
      
      #===================================================================
      # backend loading
      #===================================================================
  
      _no_backend_suggestion = " -- recommend you install one (e.g. 'pip install argon2_cffi')"
  
      @classmethod
      def _finalize_backend_mixin(mixin_cls, name, dryrun):
          """
          helper called by from backend mixin classes' _load_backend_mixin() --
          invoked after backend imports have been loaded, and performs
          feature detection & testing common to all backends.
          """
          max_version = mixin_cls.max_version
          assert isinstance(max_version, int) and max_version >= 0x10
          if max_version < 0x13:
              warn("%r doesn't support argon2 v1.3, and should be upgraded" % name,
                   uh.exc.PasslibSecurityWarning)
          return True
  
      @classmethod
      def _adapt_backend_error(cls, err, hash=None, self=None):
          """
          internal helper invoked when backend has hash/verification error;
          used to adapt to passlib message.
          """
          backend = cls.get_backend()
  
          # parse hash to throw error if format was invalid, parameter out of range, etc.
          if self is None and hash is not None:
              self = cls.from_string(hash)
  
          # check constraints on parsed object
          # XXX: could move this to __init__, but not needed by needs_update calls
          if self is not None:
              self._validate_constraints(self.memory_cost, self.parallelism)
  
              # as of cffi 16.1, lacks support in hash_secret(), so genhash() will get here.
              # as of cffi 16.2, support removed from verify_secret() as well.
              if backend == "argon2_cffi" and self.data is not None:
                  raise NotImplementedError("argon2_cffi backend doesn't support the 'data' parameter")
  
          # fallback to reporting a malformed hash
          text = str(err)
          if text not in [
              "Decoding failed"  # argon2_cffi's default message
              ]:
              reason = "%s reported: %s: hash=%r" % (backend, text, hash)
          else:
              reason = repr(hash)
          raise exc.MalformedHashError(cls, reason=reason)
  
      #===================================================================
      # eoc
      #===================================================================
  
  #-----------------------------------------------------------------------
  # stub backend
  #-----------------------------------------------------------------------
  class _NoBackend(_Argon2Common):
      """
      mixin used before any backend has been loaded.
      contains stubs that force loading of one of the available backends.
      """
      #===================================================================
      # primary methods
      #===================================================================
      @classmethod
      def hash(cls, secret):
          cls._stub_requires_backend()
          return cls.hash(secret)
  
      @classmethod
      def verify(cls, secret, hash):
          cls._stub_requires_backend()
          return cls.verify(secret, hash)
  
      @uh.deprecated_method(deprecated="1.7", removed="2.0")
      @classmethod
      def genhash(cls, secret, config):
          cls._stub_requires_backend()
          return cls.genhash(secret, config)
  
      #===================================================================
      # digest calculation
      #===================================================================
      def _calc_checksum(self, secret):
          # NOTE: since argon2_cffi takes care of rendering hash,
          #       _calc_checksum() is only used by the argon2pure backend.
          self._stub_requires_backend()
          # NOTE: have to use super() here so that we don't recursively
          #       call subclass's wrapped _calc_checksum
          return super(argon2, self)._calc_checksum(secret)
  
      #===================================================================
      # eoc
      #===================================================================
  
  #-----------------------------------------------------------------------
  # argon2_cffi backend
  #-----------------------------------------------------------------------
  class _CffiBackend(_Argon2Common):
      """
      argon2_cffi backend
      """
      #===================================================================
      # backend loading
      #===================================================================
  
      @classmethod
      def _load_backend_mixin(mixin_cls, name, dryrun):
          # we automatically import this at top, so just grab info
          if _argon2_cffi is None:
              return False
          max_version = _argon2_cffi.low_level.ARGON2_VERSION
          log.debug("detected 'argon2_cffi' backend, version %r, with support for 0x%x argon2 hashes",
                    _argon2_cffi.__version__, max_version)
          mixin_cls.version = mixin_cls.max_version = max_version
          return mixin_cls._finalize_backend_mixin(name, dryrun)
  
      #===================================================================
      # primary methods
      #===================================================================
      @classmethod
      def hash(cls, secret):
          # TODO: add in 'encoding' support once that's finalized in 1.8 / 1.9.
          uh.validate_secret(secret)
          secret = to_bytes(secret, "utf-8")
          # XXX: doesn't seem to be a way to make this honor max_threads
          try:
              return bascii_to_str(_argon2_cffi.low_level.hash_secret(
                  type=_argon2_cffi.low_level.Type.I,
                  memory_cost=cls.memory_cost,
                  time_cost=cls.default_rounds,
                  parallelism=cls.parallelism,
                  salt=to_bytes(cls._generate_salt()),
                  hash_len=cls.checksum_size,
                  secret=secret,
              ))
          except _argon2_cffi.exceptions.HashingError as err:
              raise cls._adapt_backend_error(err)
  
      @classmethod
      def verify(cls, secret, hash):
          # TODO: add in 'encoding' support once that's finalized in 1.8 / 1.9.
          uh.validate_secret(secret)
          secret = to_bytes(secret, "utf-8")
          hash = to_bytes(hash, "ascii")
          if hash.startswith(b"$argon2d$"):
              type = _argon2_cffi.low_level.Type.D
          else:
              type = _argon2_cffi.low_level.Type.I
          # XXX: doesn't seem to be a way to make this honor max_threads
          try:
              result = _argon2_cffi.low_level.verify_secret(hash, secret, type)
              assert result is True
              return True
          except _argon2_cffi.exceptions.VerifyMismatchError:
              return False
          except _argon2_cffi.exceptions.VerificationError as err:
              raise cls._adapt_backend_error(err, hash=hash)
  
      # NOTE: deprecated, will be removed in 2.0
      @classmethod
      def genhash(cls, secret, config):
          # TODO: add in 'encoding' support once that's finalized in 1.8 / 1.9.
          uh.validate_secret(secret)
          secret = to_bytes(secret, "utf-8")
          self = cls.from_string(config)
          if self.type_d:
              type = _argon2_cffi.low_level.Type.D
          else:
              type = _argon2_cffi.low_level.Type.I
          # XXX: doesn't seem to be a way to make this honor max_threads
          try:
              result = bascii_to_str(_argon2_cffi.low_level.hash_secret(
                  type=type,
                  memory_cost=self.memory_cost,
                  time_cost=self.rounds,
                  parallelism=self.parallelism,
                  salt=to_bytes(self.salt),
                  hash_len=self.checksum_size,
                  secret=secret,
                  version=self.version,
              ))
          except _argon2_cffi.exceptions.HashingError as err:
              raise cls._adapt_backend_error(err, hash=config)
          if self.version == 0x10:
              # workaround: argon2 0x13 always returns "v=" segment, even for 0x10 hashes
              result = result.replace("$v=16$", "$")
          return result
  
      #===================================================================
      # digest calculation
      #===================================================================
      def _calc_checksum(self, secret):
          raise AssertionError("shouldn't be called under argon2_cffi backend")
  
      #===================================================================
      # eoc
      #===================================================================
  
  #-----------------------------------------------------------------------
  # argon2pure backend
  #-----------------------------------------------------------------------
  class _PureBackend(_Argon2Common):
      """
      argon2pure backend
      """
      #===================================================================
      # backend loading
      #===================================================================
  
      @classmethod
      def _load_backend_mixin(mixin_cls, name, dryrun):
          # import argon2pure
          global _argon2pure
          try:
              import argon2pure as _argon2pure
          except ImportError:
              return False
  
          # get default / max supported version -- added in v1.2.2
          try:
              from argon2pure import ARGON2_DEFAULT_VERSION as max_version
          except ImportError:
              log.warning("detected 'argon2pure' backend, but package is too old "
                          "(passlib requires argon2pure >= 1.2.3)")
              return False
  
          log.debug("detected 'argon2pure' backend, with support for 0x%x argon2 hashes",
                    max_version)
  
          if not dryrun:
              warn("Using argon2pure backend, which is 100x+ slower than is required "
                   "for adequate security. Installing argon2_cffi (via 'pip install argon2_cffi') "
                   "is strongly recommended", exc.PasslibSecurityWarning)
  
          mixin_cls.version = mixin_cls.max_version = max_version
          return mixin_cls._finalize_backend_mixin(name, dryrun)
  
      #===================================================================
      # primary methods
      #===================================================================
  
      # NOTE: this backend uses default .hash() & .verify() implementations.
  
      #===================================================================
      # digest calculation
      #===================================================================
      def _calc_checksum(self, secret):
          # TODO: add in 'encoding' support once that's finalized in 1.8 / 1.9.
          uh.validate_secret(secret)
          secret = to_bytes(secret, "utf-8")
          if self.type_d:
              type = _argon2pure.ARGON2D
          else:
              type = _argon2pure.ARGON2I
          kwds = dict(
              password=secret,
              salt=self.salt,
              time_cost=self.rounds,
              memory_cost=self.memory_cost,
              parallelism=self.parallelism,
              tag_length=self.checksum_size,
              type_code=type,
              version=self.version,
          )
          if self.max_threads > 0:
              kwds['threads'] = self.max_threads
          if self.pure_use_threads:
              kwds['use_threads'] = True
          if self.data:
              kwds['associated_data'] = self.data
          # NOTE: should return raw bytes
          # NOTE: this may raise _argon2pure.Argon2ParameterError,
          #       but it if does that, there's a bug in our own parameter checking code.
          try:
              return _argon2pure.argon2(**kwds)
          except _argon2pure.Argon2Error as err:
              raise self._adapt_backend_error(err, self=self)
  
      #===================================================================
      # eoc
      #===================================================================
  
  class argon2(_NoBackend, _Argon2Common):
      """
      This class implements the Argon2 password hash [#argon2-home]_, and follows the :ref:`password-hash-api`.
      (This class only supports generating "Type I" argon2 hashes).
  
      Argon2 supports a variable-length salt, and variable time & memory cost,
      and a number of other configurable parameters.
  
      The :meth:`~passlib.ifc.PasswordHash.replace` method accepts the following optional keywords:
  
      :type salt: str
      :param salt:
          Optional salt string.
          If specified, the length must be between 0-1024 bytes.
          If not specified, one will be auto-generated (this is recommended).
  
      :type salt_size: int
      :param salt_size:
          Optional number of bytes to use when autogenerating new salts.
  
      :type rounds: int
      :param rounds:
          Optional number of rounds to use.
          This corresponds linearly to the amount of time hashing will take.
  
      :type time_cost: int
      :param time_cost:
          An alias for **rounds**, for compatibility with underlying argon2 library.
  
      :param int memory_cost:
          Defines the memory usage in kibibytes.
          This corresponds linearly to the amount of memory hashing will take.
  
      :param int parallelism:
          Defines the parallelization factor.
          *NOTE: this will affect the resulting hash value.*
  
      :param int digest_size:
          Length of the digest in bytes.
  
      :param int max_threads:
          Maximum number of threads that will be used.
          -1 means unlimited; otherwise hashing will use ``min(parallelism, max_threads)`` threads.
  
          .. note::
  
              This option is currently only honored by the argon2pure backend.
  
      :type relaxed: bool
      :param relaxed:
          By default, providing an invalid value for one of the other
          keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
          and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
          will be issued instead. Correctable errors include ``rounds``
          that are too small or too large, and ``salt`` strings that are too long.
  
      .. todo::
  
          * Support configurable threading limits.
      """
      #=============================================================================
      # backend
      #=============================================================================
  
      # NOTE: the brunt of the argon2 class is implemented in _Argon2Common.
      #       there are then subclass for each backend (e.g. _PureBackend),
      #       these are dynamically prepended to this class's bases
      #       in order to load the appropriate backend.
  
      #: list of potential backends
      backends = ("argon2_cffi", "argon2pure")
  
      #: flag that this class's bases should be modified by SubclassBackendMixin
      _backend_mixin_target = True
  
      #: map of backend -> mixin class, used by _get_backend_loader()
      _backend_mixin_map = {
          None: _NoBackend,
          "argon2_cffi": _CffiBackend,
          "argon2pure": _PureBackend,
      }
  
      #=============================================================================
      #
      #=============================================================================
  
  #=============================================================================
  # eof
  #=============================================================================
  