text: """This module provides an `ECTester <https://github.com/crocs-muni/ECTester/>`_ target class."""
  from abc import ABC
  from binascii import hexlify
  from enum import IntEnum, IntFlag
  from functools import reduce
  from math import ceil, log
  from operator import or_
  from typing import Optional, Mapping, List, Union
  
  from public import public
  from smartcard.CardConnection import CardConnection
  from smartcard.Exceptions import CardConnectionException
  
  from .ISO7816 import CommandAPDU, ResponseAPDU, ISO7816
  from .PCSC import PCSCTarget
  from ...ec.model import ShortWeierstrassModel
  from ...ec.params import DomainParameters
  from ...ec.point import Point
  
  
  class ShiftableFlag(IntFlag):  # pragma: no cover
      def __lshift__(self, other):
          val = int(self) << other
          for e in self.__class__:
              if val == e.value:
                  return e
          raise ValueError
  
      def __rshift__(self, other):
          val = int(self) >> other
          for e in self.__class__:
              if val == e.value:
                  return e
          raise ValueError
  
      def __iter__(self):
          val = int(self)
          for e in self.__class__:
              i = int(e)
              if i & val == i:
                  while i % 2 == 0 and i != 0:
                      i //= 2
                  if i == 1:
                      yield e
  
  
  @public
  class KeypairEnum(ShiftableFlag):  # pragma: no cover
      """ECTester's KeyPair type."""
  
      KEYPAIR_LOCAL = 0x01
      KEYPAIR_REMOTE = 0x02
      KEYPAIR_BOTH = KEYPAIR_LOCAL | KEYPAIR_REMOTE
  
  
  @public
  class InstructionEnum(IntEnum):  # pragma: no cover
      """ECTester's instruction (INS)."""
  
      INS_ALLOCATE = 0x5A
      INS_CLEAR = 0x5B
      INS_SET = 0x5C
      INS_TRANSFORM = 0x5D
      INS_GENERATE = 0x5E
      INS_EXPORT = 0x5F
      INS_ECDH = 0x70
      INS_ECDH_DIRECT = 0x71
      INS_ECDSA = 0x72
      INS_ECDSA_SIGN = 0x73
      INS_ECDSA_VERIFY = 0x74
      INS_CLEANUP = 0x75
      INS_ALLOCATE_KA = 0x76
      INS_ALLOCATE_SIG = 0x77
      INS_GET_INFO = 0x78
      INS_SET_DRY_RUN_MODE = 0x79
      INS_BUFFER = 0x7A
      INS_PERFORM = 0x7B
  
  
  @public
  class KeyBuildEnum(IntEnum):  # pragma: no cover
      """ECTester's key builder type."""
  
      BUILD_KEYPAIR = 0x01
      BUILD_KEYBUILDER = 0x02
  
  
  @public
  class ExportEnum(IntEnum):  # pragma: no cover
      """ECTester's export boolean."""
  
      EXPORT_TRUE = 0xFF
      EXPORT_FALSE = 0x00
  
      @classmethod
      def from_bool(cls, val: bool):
          return cls.EXPORT_TRUE if val else cls.EXPORT_FALSE
  
  
  @public
  class RunModeEnum(IntEnum):  # pragma: no cover
      """ECTester's run mode."""
  
      MODE_NORMAL = 0xAA
      MODE_DRY_RUN = 0xBB
  
  
  @public
  class KeyEnum(ShiftableFlag):  # pragma: no cover
      """ECTester's key enum."""
  
      PUBLIC = 0x01
      PRIVATE = 0x02
      BOTH = PRIVATE | PUBLIC
  
  
  @public
  class AppletBaseEnum(IntEnum):  # pragma: no cover
      """ECTester's JavaCard applet base version."""
  
      BASE_221 = 0x0221
      BASE_222 = 0x0222
  
  
  @public
  class KeyClassEnum(IntEnum):  # pragma: no cover
      """JavaCard EC-based key class."""
  
      ALG_EC_F2M = 4
      ALG_EC_FP = 5
  
  
  @public
  class KeyAgreementEnum(IntEnum):  # pragma: no cover
      """JavaCard `KeyAgreement` type values."""
  
      ALG_EC_SVDP_DH = 1
      ALG_EC_SVDP_DH_KDF = 1
      ALG_EC_SVDP_DHC = 2
      ALG_EC_SVDP_DHC_KDF = 2
      ALG_EC_SVDP_DH_PLAIN = 3
      ALG_EC_SVDP_DHC_PLAIN = 4
      ALG_EC_PACE_GM = 5
      ALG_EC_SVDP_DH_PLAIN_XY = 6
  
  
  @public
  class SignatureEnum(IntEnum):  # pragma: no cover
      """JavaCard `Signature` type values."""
  
      ALG_ECDSA_SHA = 17
      ALG_ECDSA_SHA_224 = 37
      ALG_ECDSA_SHA_256 = 33
      ALG_ECDSA_SHA_384 = 34
      ALG_ECDSA_SHA_512 = 38
  
  
  @public
  class TransformationEnum(ShiftableFlag):  # pragma: no cover
      """ECTester's point/value transformation types."""
  
      NONE = 0x00
      FIXED = 0x01
      FULLRANDOM = 0x02
      ONEBYTERANDOM = 0x04
      ZERO = 0x08
      ONE = 0x10
      MAX = 0x20
      INCREMENT = 0x40
      INFINITY = 0x80
      COMPRESS = 0x0100
      COMPRESS_HYBRID = 0x0200
      MASK_04 = 0x0400
  
  
  @public
  class FormatEnum(IntEnum):  # pragma: no cover
      """ECTester's point format types."""
  
      UNCOMPRESSED = 0
      COMPRESSED = 1
      HYBRID = 2
  
  
  @public
  class CurveEnum(IntEnum):  # pragma: no cover
      """ECTester's curve constants."""
  
      default = 0x00
      external = 0xFF
      secp112r1 = 0x01
      secp128r1 = 0x02
      secp160r1 = 0x03
      secp192r1 = 0x04
      secp224r1 = 0x05
      secp256r1 = 0x06
      secp384r1 = 0x07
      secp521r1 = 0x08
      sect163r1 = 0x09
      sect233r1 = 0x0A
      sect283r1 = 0x0B
      sect409r1 = 0x0C
      sect571r1 = 0x0D
  
  
  @public
  class ParameterEnum(ShiftableFlag):  # pragma: no cover
      """ECTester's parameter ids."""
  
      NONE = 0x00
      FP = 0x01
      F2M = 0x02
      A = 0x04
      B = 0x08
      G = 0x10
      R = 0x20
      K = 0x40
      W = 0x80
      S = 0x0100
      DOMAIN_FP = FP | A | B | G | R | K
      DOMAIN_F2M = F2M | A | B | G | R | K
      KEYPAIR = W | S
      ALL = FP | F2M | A | B | G | R | K | W | S
  
  
  @public
  class ChunkingException(Exception):  # pragma: no cover
      """An exception that is raised if an error happened during the chunking process of a large APDU."""
  
      pass
  
  
  class Response(ABC):  # pragma: no cover
      """An abstract base class of a response APDU."""
  
      resp: ResponseAPDU
      sws: List[int]
      params: List[bytes]
      success: bool = True
      error: bool = False
  
      def __init__(self, resp: ResponseAPDU, num_sw: int, num_params: int):
          self.resp = resp
          self.sws = [0 for _ in range(num_sw)]
          self.params = [bytes() for _ in range(num_params)]
  
          offset = 0
          for i in range(num_sw):
              if len(resp.data) >= offset + 2:
                  self.sws[i] = int.from_bytes(resp.data[offset : offset + 2], "big")
                  offset += 2
                  if self.sws[i] != ISO7816.SW_NO_ERROR:
                      self.success = False
              else:
                  self.success = False
                  self.error = True
  
          if self.resp.sw != ISO7816.SW_NO_ERROR:
              self.success = False
              self.error = False
  
          for i in range(num_params):
              if len(resp.data) < offset + 2:
                  self.success = False
                  self.error = True
                  break
              param_len = int.from_bytes(resp.data[offset : offset + 2], "big")
              offset += 2
              if len(resp.data) < offset + param_len:
                  self.success = False
                  self.error = True
                  break
              self.params[i] = resp.data[offset : offset + param_len]
              offset += param_len
  
      def __repr__(self):
          return f"{self.__class__.__name__}(sws=[{', '.join(list(map(hex, self.sws)))}], sw={hex(self.resp.sw)}, success={self.success}, error={self.error})"
  
  
  @public
  class AllocateKaResponse(Response):  # pragma: no cover
      """A response to the KeyAgreement allocation command."""
  
      def __init__(self, resp: ResponseAPDU):
          super().__init__(resp, 1, 0)
  
  
  @public
  class AllocateSigResponse(Response):  # pragma: no cover
      """A response to the Signature allocation command."""
  
      def __init__(self, resp: ResponseAPDU):
          super().__init__(resp, 1, 0)
  
  
  @public
  class AllocateResponse(Response):  # pragma: no cover
      """A response to the KeyPair allocation command."""
  
      def __init__(self, resp: ResponseAPDU, keypair: KeypairEnum):
          super().__init__(resp, 2 if keypair == KeypairEnum.KEYPAIR_BOTH else 1, 0)
  
  
  @public
  class ClearResponse(Response):  # pragma: no cover
      """A response to the Clear key command."""
  
      def __init__(self, resp: ResponseAPDU, keypair: KeypairEnum):
          super().__init__(resp, 2 if keypair == KeypairEnum.KEYPAIR_BOTH else 1, 0)
  
  
  @public
  class SetResponse(Response):  # pragma: no cover
      """A response to the Set command."""
  
      def __init__(self, resp: ResponseAPDU, keypair: KeypairEnum):
          super().__init__(resp, 2 if keypair == KeypairEnum.KEYPAIR_BOTH else 1, 0)
  
  
  @public
  class TransformResponse(Response):  # pragma: no cover
      """A response to the Transform command."""
  
      def __init__(self, resp: ResponseAPDU, keypair: KeypairEnum):
          super().__init__(resp, 2 if keypair == KeypairEnum.KEYPAIR_BOTH else 1, 0)
  
  
  @public
  class GenerateResponse(Response):  # pragma: no cover
      """A response to the Generate command."""
  
      def __init__(self, resp: ResponseAPDU, keypair: KeypairEnum):
          super().__init__(resp, 2 if keypair == KeypairEnum.KEYPAIR_BOTH else 1, 0)
  
  
  @public
  class ExportResponse(Response):  # pragma: no cover
      """A response to the Export command, contains the exported parameters/values."""
  
      keypair: KeypairEnum
      key: KeyEnum
      parameters: ParameterEnum
  
      def __init__(
          self,
          resp: ResponseAPDU,
          keypair: KeypairEnum,
          key: KeyEnum,
          params: ParameterEnum,
      ):
          self.keypair = keypair
          self.key = key
          self.parameters = params
          exported = 2 if keypair == KeypairEnum.KEYPAIR_BOTH else 1
          keys = 2 if key == KeyEnum.BOTH else 1
          param_count = 0
          param = ParameterEnum.FP
          while True:
              if param & params:
                  param_count += 1
              if param == ParameterEnum.K:
                  break
              param <<= 1
          other = 0
          other += 1 if key & KeyEnum.PUBLIC and params & ParameterEnum.W else 0
          other += 1 if key & KeyEnum.PRIVATE and params & ParameterEnum.S else 0
          super().__init__(
              resp, exported, exported * keys * param_count + exported * other
          )
  
      def get_index(self, keypair: KeypairEnum, param: ParameterEnum) -> Optional[int]:
          pair = KeypairEnum.KEYPAIR_LOCAL
          index = 0
          while True:
              mask = ParameterEnum.FP
              while True:
                  if pair == keypair and param == mask:
                      return index
                  if self.parameters & mask and self.keypair & pair:
                      if mask == ParameterEnum.W:
                          if self.key & KeyEnum.PUBLIC:
                              index += 1
                      elif mask == ParameterEnum.S:
                          if self.key & KeyEnum.PRIVATE:
                              index += 1
                      else:
                          index += 1
                  if mask == ParameterEnum.S:
                      break
                  mask <<= 1
              if pair == KeypairEnum.KEYPAIR_REMOTE:
                  break
              pair <<= 1
          return None
  
      def get_param(self, keypair: KeypairEnum, param: ParameterEnum) -> Optional[bytes]:
          index = self.get_index(keypair, param)
          if index is not None:
              return self.params[index]
          return None
  
      def __repr__(self):
          return (
              f"{self.__class__.__name__}(sws=[{', '.join(list(map(hex, self.sws)))}], sw={hex(self.resp.sw)}, success={self.success}, error={self.error}, "
              f"keypair={self.keypair.name}, key={self.key.name}, params={self.parameters.name})"
          )
  
  
  @public
  class ECDHResponse(Response):  # pragma: no cover
      """A response to the ECDH and ECDH_direct KeyAgreement commands."""
  
      def __init__(self, resp: ResponseAPDU, export: bool):
          super().__init__(resp, 1, 1 if export else 0)
  
      @property
      def secret(self):
          if len(self.params) != 0:
              return self.params[0]
          return None
  
      def __repr__(self):
          return f"{self.__class__.__name__}(sws=[{', '.join(list(map(hex, self.sws)))}], sw={hex(self.resp.sw)}, success={self.success}, error={self.error}, secret={hexlify(self.secret).decode() if self.secret else ''})"
  
  
  @public
  class ECDSAResponse(Response):  # pragma: no cover
      """A response to the ECDSA and ECDSA sign and ECDSA verify commands."""
  
      def __init__(self, resp: ResponseAPDU, export: bool):
          super().__init__(resp, 1, 1 if export else 0)
  
      @property
      def signature(self):
          if len(self.params) != 0:
              return self.params[0]
          return None
  
      def __repr__(self):
          return f"{self.__class__.__name__}(sws=[{', '.join(list(map(hex, self.sws)))}], sw={hex(self.resp.sw)}, success={self.success}, error={self.error}, sig={hexlify(self.signature).decode() if self.signature else ''})"
  
  
  @public
  class CleanupResponse(Response):  # pragma: no cover
      """A response to the Cleanup command."""
  
      def __init__(self, resp: ResponseAPDU):
          super().__init__(resp, 1, 0)
  
  
  @public
  class RunModeResponse(Response):  # pragma: no cover
      """A response to the Set run mode command."""
  
      def __init__(self, resp: ResponseAPDU):
          super().__init__(resp, 1, 0)
  
  
  class InfoResponse(Response):  # pragma: no cover
      """A response to the Info command, contains all information about the applet version/environment."""
  
      version: str
      base: AppletBaseEnum
      system_version: float
      object_deletion_supported: bool
      buf_len: int
      ram1_len: int
      ram2_len: int
      apdu_len: int
  
      def __init__(self, resp: ResponseAPDU):
          super().__init__(resp, 1, 0)
  
          offset = 2
          version_len = int.from_bytes(resp.data[offset : offset + 2], "big")
          offset += 2
          self.version = resp.data[offset : offset + version_len].decode()
          offset += version_len
          self.base = AppletBaseEnum(
              int.from_bytes(resp.data[offset : offset + 2], "big")
          )
          offset += 2
          system_version = int.from_bytes(resp.data[offset : offset + 2], "big")
          system_major = system_version >> 8
          system_minor = system_version & 0xFF
          minor_size = 1 if system_minor == 0 else ceil(log(system_minor, 10))
          self.system_version = system_major + system_minor / (minor_size * 10)
          offset += 2
          self.object_deletion_supported = (
              int.from_bytes(resp.data[offset : offset + 2], "big") == 1
          )
          offset += 2
          self.buf_len = int.from_bytes(resp.data[offset : offset + 2], "big")
          offset += 2
          self.ram1_len = int.from_bytes(resp.data[offset : offset + 2], "big")
          offset += 2
          self.ram2_len = int.from_bytes(resp.data[offset : offset + 2], "big")
          offset += 2
          self.apdu_len = int.from_bytes(resp.data[offset : offset + 2], "big")
          offset += 2
  
      def __repr__(self):
          return (
              f"{self.__class__.__name__}(sws=[{', '.join(list(map(hex, self.sws)))}], sw={hex(self.resp.sw)}, "
              f"success={self.success}, error={self.error}, version={self.version}, base={self.base.name}, system_version={self.system_version}, "
              f"object_deletion_supported={self.object_deletion_supported}, buf_len={self.buf_len}, ram1_len={self.ram1_len}, ram2_len={self.ram2_len}, apdu_len={self.apdu_len})"
          )
  
  
  @public
  class ECTesterTarget(PCSCTarget):  # pragma: no cover
      """Smartcard target which communicates with the `ECTester <https://github.com/crocs-muni/ECTester>`_ sapplet on smartcards of the JavaCard platform using PCSC."""
  
      CLA_ECTESTER = 0xB0
      AID_PREFIX = bytes([0x45, 0x43, 0x54, 0x65, 0x73, 0x74, 0x65, 0x72])
      AID_CURRENT_VERSION = bytes([0x30, 0x33, 0x33])  # Version v0.3.3
      AID_SUFFIX_221 = bytes([0x62])
      AID_SUFFIX_222 = bytes([0x78])
  
      chunking: bool
  
      def connect(self):
          self.chunking = False
          try:
              self.connection.connect(CardConnection.T1_protocol)
          except CardConnectionException:
              self.connection.connect(CardConnection.T0_protocol)
              self.chunking = True
  
      def send_apdu(self, apdu: CommandAPDU) -> ResponseAPDU:
          if self.chunking:
              data = bytes(apdu)
              num_chunks = (len(data) + 254) // 255
              for i in range(num_chunks):
                  chunk_start = i * 255
                  chunk_length = 255
                  if chunk_start + chunk_length > len(data):
                      chunk_length = len(data) - chunk_start
                  chunk = data[chunk_start : chunk_start + chunk_length]
                  chunk_apdu = CommandAPDU(
                      self.CLA_ECTESTER, InstructionEnum.INS_BUFFER, 0, 0, chunk
                  )
                  resp = super().send_apdu(chunk_apdu)
                  if resp.sw != 0x9000:
                      raise ChunkingException()
              apdu = CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_PERFORM, 0, 0)
          resp = super().send_apdu(apdu)
          if resp.sw & 0xFF00 == ISO7816.SW_BYTES_REMAINING_00:
              resp = super().send_apdu(
                  CommandAPDU(0x00, 0xC0, 0x00, 0x00, None, resp.sw & 0xFF)
              )
          return resp
  
      def select_applet(
          self, latest_version: bytes = AID_CURRENT_VERSION, count_back: int = 10
      ) -> bool:
          """
          Select the *ECTester* applet, with a specified version or older.
  
          :param latest_version: The latest version to try.
          :param count_back: How many versions back to try.
          :return: Whether an applet was successfully selected.
          """
          version_bytes = bytearray(latest_version)
          for _ in range(count_back):
              aid_222 = self.AID_PREFIX + version_bytes + self.AID_SUFFIX_222
              if self.select(aid_222):
                  break
              else:
                  aid_221 = self.AID_PREFIX + version_bytes + self.AID_SUFFIX_221
                  if self.select(aid_221):
                      break
              # Count down by versions
              if version_bytes[2] == 0x30:
                  if version_bytes[1] == 0x30:
                      if version_bytes[0] == 0x30:
                          return False
                      else:
                          version_bytes[0] -= 1
                          version_bytes[1] = 0x39
                          version_bytes[2] = 0x39
                  else:
                      version_bytes[1] -= 1
                      version_bytes[2] = 0x39
              else:
                  version_bytes[2] -= 1
          else:
              return False
          return True
  
      @staticmethod
      def encode_parameters(
          params: ParameterEnum, obj: Union[DomainParameters, Point, int]
      ) -> Mapping[ParameterEnum, bytes]:
          """Encode values from `obj` into the byte parameters that the **ECTester** applet expects."""
  
          def convert_int(obj: int) -> bytes:
              ilen = (obj.bit_length() + 7) // 8
              return obj.to_bytes(ilen, "big")
  
          def convert_point(obj: Point) -> bytes:
              return bytes(obj)
  
          result = {}
          if isinstance(obj, DomainParameters) and isinstance(
              obj.curve.model, ShortWeierstrassModel
          ):
              for param in params & ParameterEnum.DOMAIN_FP:
                  if param == ParameterEnum.G:
                      result[param] = convert_point(obj.generator.to_affine())
                  elif param == ParameterEnum.FP:
                      result[param] = convert_int(obj.curve.prime)
                  elif param == ParameterEnum.A:
                      result[param] = convert_int(obj.curve.parameters["a"].x)
                  elif param == ParameterEnum.B:
                      result[param] = convert_int(obj.curve.parameters["b"].x)
                  elif param == ParameterEnum.R:
                      result[param] = convert_int(obj.order)
                  elif param == ParameterEnum.K:
                      result[param] = convert_int(obj.cofactor)
          elif isinstance(obj, Point):
              for param in params & (ParameterEnum.G | ParameterEnum.W):
                  result[param] = convert_point(obj)
          elif isinstance(obj, int):
              for param in params & (
                  (ParameterEnum.DOMAIN_FP ^ ParameterEnum.G) | ParameterEnum.S
              ):
                  result[param] = convert_int(obj)
          else:
              raise TypeError
          return result
  
      def allocate_ka(self, ka_type: KeyAgreementEnum) -> AllocateKaResponse:
          """
          Send the Allocate KeyAgreement command.
  
          :param ka_type: Which KeyAgreement type to allocate.
          :return: The response.
          """
          resp = self.send_apdu(
              CommandAPDU(
                  self.CLA_ECTESTER,
                  InstructionEnum.INS_ALLOCATE_KA,
                  0,
                  0,
                  bytes([ka_type]),
              )
          )
          return AllocateKaResponse(resp)
  
      def allocate_sig(self, sig_type: SignatureEnum) -> AllocateSigResponse:
          """
          Send the Allocate Signature command.
  
          :param sig_type: Which Signature type to allocate.
          :return: The response.
          """
          resp = self.send_apdu(
              CommandAPDU(
                  self.CLA_ECTESTER,
                  InstructionEnum.INS_ALLOCATE_SIG,
                  0,
                  0,
                  bytes([sig_type]),
              )
          )
          return AllocateSigResponse(resp)
  
      def allocate(
          self,
          keypair: KeypairEnum,
          builder: KeyBuildEnum,
          key_length: int,
          key_class: KeyClassEnum,
      ) -> AllocateResponse:
          """
          Send the Allocate KeyPair command.
  
          :param keypair: Which keypair to allocate.
          :param builder: Which builder to use to allocate the keypair.
          :param key_length: Bit-size of the allocated keypair.
          :param key_class: Type of the allocated keypair.
          :return: The response.
          """
          resp = self.send_apdu(
              CommandAPDU(
                  self.CLA_ECTESTER,
                  InstructionEnum.INS_ALLOCATE,
                  keypair,
                  builder,
                  key_length.to_bytes(2, "big") + bytes([key_class]),
              )
          )
          return AllocateResponse(resp, keypair)
  
      def clear(self, keypair: KeypairEnum) -> ClearResponse:
          """
          Send the Clear key command.
  
          :param keypair: Which keypair to clear.
          :return: The response.
          """
          resp = self.send_apdu(
              CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_CLEAR, keypair, 0, None)
          )
          return ClearResponse(resp, keypair)
  
      def set(
          self,
          keypair: KeypairEnum,
          curve: CurveEnum,
          params: ParameterEnum,
          values: Optional[Mapping[ParameterEnum, bytes]] = None,
      ) -> SetResponse:
          """
          Send the Set command.
  
          :param keypair: Which keypair to set values on.
          :param curve: Which pre-set curve to use to set values, or default or external.
          :param params: Which parameters to set on the keypair.
          :param values: External values to set on the keypair.
          :return: The response.
          """
          if curve == CurveEnum.external and values is not None:
              if params != reduce(or_, values.keys()):
                  raise ValueError("Params and values need to have the same keys.")
              payload = params.to_bytes(2, "big")
              e = ParameterEnum.FP
              while True:
                  if e in values:
                      payload += len(values[e]).to_bytes(2, "big") + values[e]
                  if e == ParameterEnum.S:
                      break
                  e <<= 1
              resp = self.send_apdu(
                  CommandAPDU(
                      self.CLA_ECTESTER, InstructionEnum.INS_SET, keypair, curve, payload
                  )
              )
          elif values is not None:
              raise ValueError("Values should be specified only if curve is external.")
          else:
              resp = self.send_apdu(
                  CommandAPDU(
                      self.CLA_ECTESTER,
                      InstructionEnum.INS_SET,
                      keypair,
                      curve,
                      params.to_bytes(2, "big"),
                  )
              )
          return SetResponse(resp, keypair)
  
      def transform(
          self,
          keypair: KeypairEnum,
          key: KeyEnum,
          params: ParameterEnum,
          transformation: TransformationEnum,
      ) -> TransformResponse:
          """
          Send the Transform command.
  
          :param keypair: Which keypair to transform.
          :param key: Which key to apply the transform to.
          :param params: Which parameters to transform.
          :param transformation: What transformation to apply.
          :return: The response.
          """
          resp = self.send_apdu(
              CommandAPDU(
                  self.CLA_ECTESTER,
                  InstructionEnum.INS_TRANSFORM,
                  keypair,
                  key,
                  params.to_bytes(2, "big") + transformation.to_bytes(2, "big"),
              )
          )
          return TransformResponse(resp, keypair)
  
      def generate(self, keypair: KeypairEnum) -> GenerateResponse:
          """
          Send the Generate command.
  
          :param keypair: Which keypair to generate.
          :return: The response.
          """
          resp = self.send_apdu(
              CommandAPDU(
                  self.CLA_ECTESTER, InstructionEnum.INS_GENERATE, keypair, 0, None
              )
          )
          return GenerateResponse(resp, keypair)
  
      def export(
          self, keypair: KeypairEnum, key: KeyEnum, params: ParameterEnum
      ) -> ExportResponse:
          """
          Send the Export command.
  
          :param keypair: Which keypair to export from.
          :param key: Which key to export from.
          :param params: Which parameters to export.
          :return: The response, containing the exported parameters.
          """
          resp = self.send_apdu(
              CommandAPDU(
                  self.CLA_ECTESTER,
                  InstructionEnum.INS_EXPORT,
                  keypair,
                  key,
                  params.to_bytes(2, "big"),
              )
          )
          return ExportResponse(resp, keypair, key, params)
  
      def ecdh(
          self,
          pubkey: KeypairEnum,
          privkey: KeypairEnum,
          export: bool,
          transformation: TransformationEnum,
          ka_type: KeyAgreementEnum,
      ) -> ECDHResponse:
          """
          Send the ECDH command.
  
          :param pubkey: Which keypair to use the pubkey from, in the key-agreement.
          :param privkey: Which keypair to use the privkey from, in the key-agreement.
          :param export: Whether to export the shared secret.
          :param transformation: The transformation to apply to the pubkey before key-agreement.
          :param ka_type: The key-agreement type to use.
          :return: The response.
          """
          resp = self.send_apdu(
              CommandAPDU(
                  self.CLA_ECTESTER,
                  InstructionEnum.INS_ECDH,
                  pubkey,
                  privkey,
                  bytes([ExportEnum.from_bool(export)])
                  + transformation.to_bytes(2, "big")
                  + bytes([ka_type]),
              )
          )
          return ECDHResponse(resp, export)
  
      def ecdh_direct(
          self,
          privkey: KeypairEnum,
          export: bool,
          transformation: TransformationEnum,
          ka_type: KeyAgreementEnum,
          pubkey: bytes,
      ) -> ECDHResponse:
          """
          Send the ECDH direct command.
  
          :param privkey: Which keypair to use the privkey from, in the key-agreement.
          :param export: Whether to export the shared secret.
          :param transformation: The transformation to apply to the pubkey before key-agreement.
          :param ka_type: The key-agreement type to use.
          :param pubkey: The raw bytes that will be used as a pubkey in the key-agreement.
          :return: The response.
          """
          resp = self.send_apdu(
              CommandAPDU(
                  self.CLA_ECTESTER,
                  InstructionEnum.INS_ECDH_DIRECT,
                  privkey,
                  ExportEnum.from_bool(export),
                  transformation.to_bytes(2, "big")
                  + bytes([ka_type])
                  + len(pubkey).to_bytes(2, "big")
                  + pubkey,
              )
          )
          return ECDHResponse(resp, export)
  
      def ecdsa(
          self, keypair: KeypairEnum, export: bool, sig_type: SignatureEnum, data: bytes
      ) -> ECDSAResponse:
          """
          Send the ECDSA command.
  
          :param keypair: The keypair to use.
          :param export: Whether to export the signature.
          :param sig_type: The Signature type to use.
          :param data: The data to sign and verify.
          :return: The response.
          """
          resp = self.send_apdu(
              CommandAPDU(
                  self.CLA_ECTESTER,
                  InstructionEnum.INS_ECDSA,
                  keypair,
                  ExportEnum.from_bool(export),
                  bytes([sig_type]) + len(data).to_bytes(2, "big") + data,
              )
          )
          return ECDSAResponse(resp, export)
  
      def ecdsa_sign(
          self, keypair: KeypairEnum, export: bool, sig_type: SignatureEnum, data: bytes
      ) -> ECDSAResponse:
          """
          Send the ECDSA sign command.
  
          :param keypair: The keypair to use to sign.
          :param export: Whether to export the signature.
          :param sig_type: The Signature type to use.
          :param data: The data to sign.
          :return: The response.
          """
          resp = self.send_apdu(
              CommandAPDU(
                  self.CLA_ECTESTER,
                  InstructionEnum.INS_ECDSA_SIGN,
                  keypair,
                  ExportEnum.from_bool(export),
                  bytes([sig_type]) + len(data).to_bytes(2, "big") + data,
              )
          )
          return ECDSAResponse(resp, export)
  
      def ecdsa_verify(
          self, keypair: KeypairEnum, sig_type: SignatureEnum, sig: bytes, data: bytes
      ) -> ECDSAResponse:
          """
          Send the ECDSA verify command.
  
          :param keypair: The keypair to use to verify.
          :param sig_type: The Signature type to use.
          :param sig: The signature to verify.
          :param data: The data.
          :return: The response.
          """
          resp = self.send_apdu(
              CommandAPDU(
                  self.CLA_ECTESTER,
                  InstructionEnum.INS_ECDSA_VERIFY,
                  keypair,
                  sig_type,
                  len(data).to_bytes(2, "big") + data + len(sig).to_bytes(2, "big") + sig,
              )
          )
          return ECDSAResponse(resp, False)
  
      def cleanup(self) -> CleanupResponse:
          """
          Send the Cleanup command.
  
          :return: The response.
          """
          resp = self.send_apdu(
              CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_CLEANUP, 0, 0, None)
          )
          return CleanupResponse(resp)
  
      def info(self) -> InfoResponse:
          """
          Send the Info command.
  
          :return: The response.
          """
          resp = self.send_apdu(
              CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_GET_INFO, 0, 0, None)
          )
          return InfoResponse(resp)
  
      def run_mode(self, run_mode: RunModeEnum) -> RunModeResponse:
          """
          Send the Run mode command.
  
          :return: The response.
          """
          resp = self.send_apdu(
              CommandAPDU(
                  self.CLA_ECTESTER,
                  InstructionEnum.INS_SET_DRY_RUN_MODE,
                  run_mode,
                  0,
                  None,
              )
          )
          return RunModeResponse(resp)
  