#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: MIT
# Copyright (c) 2020-2025 Nicolas Iooss
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""Get various information from a TPM (Trusted Platform Module)

This is like tools such as "tpm2_getcap properties-fixed", but in one command.

A TPM can be accessed using two devices on Linux:
* /dev/tpm0
* /dev/tpmrm0 (for "Trusted Platform Module Resource Manager")

The second device has been introduced in Linux 4.12 with commit fdc915f7f719
("tpm: expose spaces via a device link /dev/tpmrm<n>")
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=fdc915f7f71939ad5a3dda3389b8d2d7a7c5ee66 :

    Currently the tpm spaces are not exposed to userspace.  Make this
    exposure via a separate device, which can now be opened multiple times
    because each read/write transaction goes separately via the space.

    Concurrency is protected by the chip->tpm_mutex for each read/write
    transaction separately.  The TPM is cleared of all transient objects
    by the time the mutex is dropped, so there should be no interference
    between the kernel and userspace.

So use this device by default and fall back to /dev/tpm0 if it does not exist.

Both devices use an API specified in "TCG TSS 2.0 TPM Command Transmission
Interface (TCTI) API Specification". When using tpm2-tools programs, it is
possible to use a parameter such as "--tcti device:/dev/tpmrm0" or an
environment variable such as "TPM2TOOLS_TCTI=device:/dev/tpmrm0". Other TCTI
settings include:

* swtpm:host=127.0.0.1,port=2321 for "swtpm socket --tpm2 --server port=2321"
* mssim:host=127.0.0.1,port=2321 for "tpm_server -port 2321" (from ibm-sw-tpm2)
* tabrmd:bus_type=system for "tpm2-abrmd" (a userspace resource manager)

This script does not use TCTI libraries but it implements their protocols:

* To connect to a swtpm, use option "--host 127.0.0.1 --port 2321" (the protocol
  simply consists in transmitting TPM packets over TCP).
* To connect to a tpm_server, use option "--host 127.0.0.1 --port 2321 --mssim"
  (there is a small encapsulation of TPM packets, over TCP).
* Connecting to tpm2-abrmd's D-Bus service is not supported.

On Windows, the TPM can be accessed using TPM Base Services (TBS):

* https://trustedcomputinggroup.org/resource/microsoft-tpm-base-services/
* https://learn.microsoft.com/en-us/windows/win32/api/_tbs/
* https://github.com/microsoft/TSS.MSR
* https://github.com/tpm2-software/tpm2-tss/blob/3.1.0/src/tss2-tcti/tcti-tbs.c
* C:/Windows/System32/tbs.dll (device \\??\\TPM)

------------------------------- IMPORTANT NOTICE -------------------------------
This script is a Proof of Concept that should not be ever used in production.
Use it at your own risks!
If you want to query a TPM, please use supported software such as
https://tpm2-software.github.io/ that provides a library with Python bindings
that implements the needed abstractions layers to handle sensitive data
(such as passwords when importing data into a TPM) in a secure way.
--------------------------------------------------------------------------------

References and documentation:

* https://tpm2-software.github.io/
  tpm2-software community page, with diagram of the stack
  (https://tpm2-software.github.io/tpm2_stack.svg)

* https://trustedcomputinggroup.org/resource/tpm-library-specification/
  TPM 2.0 Library Specification
* https://trustedcomputinggroup.org/resource/tss-tcti-specification/
  TCG TSS 2.0 TPM Command Transmission Interface (TCTI) API Specification
* https://trustedcomputinggroup.org/resource/tcg-tpm-v2-0-provisioning-guidance/
  https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf
  TCG TPM v2.0 Provisioning Guidance Version 1.0 Revision 1.0

* https://github.com/tianocore/edk2/blob/edk2-stable202002/MdePkg/Include/IndustryStandard/Tpm12.h
  TPM Specification data structures (TCG TPM Specification Version 1.2 Revision 103)
* https://github.com/tianocore/edk2/blob/edk2-stable202002/MdePkg/Include/IndustryStandard/Tpm20.h
  TPM 2.0 Specification data structures
  (Trusted Platform Module Library Specification, Family "2.0", Level 00, Revision 00.96)

Other initiative to collect data about TPM:
* https://twitter.com/CRoCS_MUNI/status/1337445979813982210 (2020-12-11)
  https://crocs.fi.muni.cz/public/research/tpm_live
  https://github.com/danzatt/tpm2-algtest/tree/7c03e5d20216a80ae633da72b8ae9729e6000e16 (to measure perf)
"""
import argparse
import ctypes
from dataclasses import dataclass
import enum
import functools
import hashlib
import hmac
import itertools
import os
from pathlib import Path
import secrets
import struct
import socket
import sys
from typing import Any, BinaryIO, Generator, List, Mapping, Optional, Sequence, Set, Tuple, TYPE_CHECKING, Union


# Use cached_property if it is available
if sys.version_info >= (3, 8):
    cached_property = functools.cached_property
else:
    cached_property = property


def hexdump(data: bytes, indent: str = '') -> None:
    """Show a hexadecimal dump of binary data"""
    for iline in range(0, len(data), 16):
        hexline = ''
        ascline = ''
        for i in range(16):
            if iline + i >= len(data):
                hexline += '  '
            else:
                # pylint: disable=invalid-name
                cur_byte = data[iline + i]
                hexline += f'{cur_byte:02x}'
                ascline += chr(cur_byte) if 32 <= cur_byte < 127 else '.'
            if i % 2:
                hexline += ' '
        print("{}{:06x}:  {} {}".format(indent, iline, hexline, ascline))


# TCG TPM Vendor Registry from https://trustedcomputinggroup.org/resource/vendor-id-registry/
TPM_VENDOR_BY_ID: Mapping[int, str] = {
    0x100B: "National Semiconductor",
    0x1011: "Qualcomm",
    0x1014: "IBM",  # International Business Machines
    0x1022: "AMD",  # Advanced Micro Devices
    0x104A: "STMicroelectronics",
    0x104C: "Texas Instruments",
    0x1050: "Nuvoton Technology",  # Formerly Winbond
    0x1055: "SMSC",  # Standard Microsystems Corporation
    0x1114: "Atmel",
    0x1414: "Microsoft",
    0x144D: "Samsung",
    0x14E4: "Broadcom",
    0x1590: "HPE",  # Hewlett Packard Enterprise
    0x15D1: "Infineon",
    0x17AA: "Lenovo",
    0x19FA: "Sinosun",
    0x1B4E: "Nationz",
    0x232A: "Fuzhou Rockchip",
    0x232B: "FlySlice Technologies",
    0x6666: "Google",
    0x8086: "Intel",
    0x8888: "Huawei",
    0xC5C0: "Cisco",
}

# From https://trustedcomputinggroup.org/wp-content/uploads/TCG_TPM_VendorIDRegistry_v1p06_r0p91_11july2021.pdf
TPM_MANUFACTURERS: Mapping[bytes, str] = {
    b'AMD\0': "AMD",  # Advanced Micro Devices
    b'ATML': "Atmel",
    b'BRCM': "Broadcom",
    b'CSCO': "Cisco",
    b'FLYS': "Flyslice Technologies",
    b'GOOG': "Google",
    b'HISI': "Huawei",
    b'HPE\0': "HPE",  # Hewlett Packard Enterprise
    b'IBM\0': "IBM",  # International Business Machines
    b'IFX\0': "Infineon",
    b'INTC': "Intel",
    b'LEN\0': "Lenovo",
    b'MSFT': "Microsoft",
    b'NSM ': "National Semiconductor",
    b'NTC\0': "Nuvoton Technology",  # Formerly Winbond
    b'NTZ\0': "Nationz",
    b'QCOM': "Qualcomm",
    b'ROCC': "Fuzhou Rockchip",
    b'SMSC': "SMSC",  # Standard Microsystems Corporation
    b'SMSN': "Samsung",
    b'SNS\0': "Sinosun",
    b'STM ': "STMicroelectronics",
    b'TXN\0': "Texas Instruments",
    b'WEC\0': "Winbond",
}


@enum.unique
class Tpm12Tag(enum.IntEnum):
    """TPM_STRUCTURE_TAG: tags in structures and in command/response header in TPM 1.2"""
    TPM_TAG_CONTEXTBLOB = 0x0001
    TPM_TAG_CONTEXT_SENSITIVE = 0x0002
    TPM_TAG_CONTEXTPOINTER = 0x0003
    TPM_TAG_CONTEXTLIST = 0x0004
    TPM_TAG_SIGNINFO = 0x0005
    TPM_TAG_PCR_INFO_LONG = 0x0006
    TPM_TAG_PERSISTENT_FLAGS = 0x0007
    TPM_TAG_VOLATILE_FLAGS = 0x0008
    TPM_TAG_PERSISTENT_DATA = 0x0009
    TPM_TAG_VOLATILE_DATA = 0x000a
    TPM_TAG_SV_DATA = 0x000b
    TPM_TAG_EK_BLOB = 0x000c
    TPM_TAG_EK_BLOB_AUTH = 0x000d
    TPM_TAG_COUNTER_VALUE = 0x000e
    TPM_TAG_TRANSPORT_INTERNAL = 0x000f
    TPM_TAG_TRANSPORT_LOG_IN = 0x0010
    TPM_TAG_TRANSPORT_LOG_OUT = 0x0011
    TPM_TAG_AUDIT_EVENT_IN = 0x0012
    TPM_TAG_AUDIT_EVENT_OUT = 0x0013
    TPM_TAG_CURRENT_TICKS = 0x0014
    TPM_TAG_KEY = 0x0015
    TPM_TAG_STORED_DATA12 = 0x0016
    TPM_TAG_NV_ATTRIBUTES = 0x0017
    TPM_TAG_NV_DATA_PUBLIC = 0x0018
    TPM_TAG_NV_DATA_SENSITIVE = 0x0019
    TPM_TAG_DELEGATIONS = 0x001a
    TPM_TAG_DELEGATE_PUBLIC = 0x001b
    TPM_TAG_DELEGATE_TABLE_ROW = 0x001c
    TPM_TAG_TRANSPORT_AUTH = 0x001d
    TPM_TAG_TRANSPORT_PUBLIC = 0x001e
    TPM_TAG_PERMANENT_FLAGS = 0x001f
    TPM_TAG_STCLEAR_FLAGS = 0x0020
    TPM_TAG_STANY_FLAGS = 0x0021
    TPM_TAG_PERMANENT_DATA = 0x0022
    TPM_TAG_STCLEAR_DATA = 0x0023
    TPM_TAG_STANY_DATA = 0x0024
    TPM_TAG_FAMILY_TABLE_ENTRY = 0x0025
    TPM_TAG_DELEGATE_SENSITIVE = 0x0026
    TPM_TAG_DELG_KEY_BLOB = 0x0027
    TPM_TAG_KEY12 = 0x0028
    TPM_TAG_CERTIFY_INFO2 = 0x0029
    TPM_TAG_DELEGATE_OWNER_BLOB = 0x002a
    TPM_TAG_EK_BLOB_ACTIVATE = 0x002b
    TPM_TAG_DAA_BLOB = 0x002c
    TPM_TAG_DAA_CONTEXT = 0x002d
    TPM_TAG_DAA_ENFORCE = 0x002e
    TPM_TAG_DAA_ISSUER = 0x002f
    TPM_TAG_CAP_VERSION_INFO = 0x0030
    TPM_TAG_DAA_SENSITIVE = 0x0031
    TPM_TAG_DAA_TPM = 0x0032
    TPM_TAG_CMK_MIGAUTH = 0x0033
    TPM_TAG_CMK_SIGTICKET = 0x0034
    TPM_TAG_CMK_MA_APPROVAL = 0x0035
    TPM_TAG_QUOTE_INFO2 = 0x0036
    TPM_TAG_DA_INFO = 0x0037
    TPM_TAG_DA_LIMITED = 0x0038
    TPM_TAG_DA_ACTION_TYPE = 0x0039
    TPM_TAG_RQU_COMMAND = 0x00c1
    TPM_TAG_RQU_AUTH1_COMMAND = 0x00c2
    TPM_TAG_RQU_AUTH2_COMMAND = 0x00c3
    TPM_TAG_RSP_COMMAND = 0x00c4
    TPM_TAG_RSP_AUTH1_COMMAND = 0x00c5
    TPM_TAG_RSP_AUTH2_COMMAND = 0x00c6

    def __repr__(self) -> str:
        """Represent the value in hexadecimal"""
        return f"<{self.name}: {self.value:#06x}>"


@enum.unique
class Tpm12CommandCode(enum.IntEnum):
    """TPM_COMMAND_CODE: command codes in TPM 1.2"""
    TPM_ORD_ActivateIdentity = 0x0000007a
    TPM_ORD_AuthorizeMigrationKey = 0x0000002b
    TPM_ORD_CertifyKey = 0x00000032
    TPM_ORD_CertifyKey2 = 0x00000033
    TPM_ORD_CertifySelfTest = 0x00000052
    TPM_ORD_ChangeAuth = 0x0000000c
    TPM_ORD_ChangeAuthAsymFinish = 0x0000000f
    TPM_ORD_ChangeAuthAsymStart = 0x0000000e
    TPM_ORD_ChangeAuthOwner = 0x00000010
    TPM_ORD_CMK_ApproveMA = 0x0000001d
    TPM_ORD_CMK_ConvertMigration = 0x00000024
    TPM_ORD_CMK_CreateBlob = 0x0000001b
    TPM_ORD_CMK_CreateKey = 0x00000013
    TPM_ORD_CMK_CreateTicket = 0x00000012
    TPM_ORD_CMK_SetRestrictions = 0x0000001c
    TPM_ORD_ContinueSelfTest = 0x00000053
    TPM_ORD_ConvertMigrationBlob = 0x0000002a
    TPM_ORD_CreateCounter = 0x000000dc
    TPM_ORD_CreateEndorsementKeyPair = 0x00000078
    TPM_ORD_CreateMaintenanceArchive = 0x0000002c
    TPM_ORD_CreateMigrationBlob = 0x00000028
    TPM_ORD_CreateRevocableEK = 0x0000007f
    TPM_ORD_CreateWrapKey = 0x0000001f
    TPM_ORD_DAA_JOIN = 0x00000029
    TPM_ORD_DAA_SIGN = 0x00000031
    TPM_ORD_Delegate_CreateKeyDelegation = 0x000000d4
    TPM_ORD_Delegate_CreateOwnerDelegation = 0x000000d5
    TPM_ORD_Delegate_LoadOwnerDelegation = 0x000000d8
    TPM_ORD_Delegate_Manage = 0x000000d2
    TPM_ORD_Delegate_ReadTable = 0x000000db
    TPM_ORD_Delegate_UpdateVerification = 0x000000d1
    TPM_ORD_Delegate_VerifyDelegation = 0x000000d6
    TPM_ORD_DirRead = 0x0000001a
    TPM_ORD_DirWriteAuth = 0x00000019
    TPM_ORD_DisableForceClear = 0x0000005e
    TPM_ORD_DisableOwnerClear = 0x0000005c
    TPM_ORD_DisablePubekRead = 0x0000007e
    TPM_ORD_DSAP = 0x00000011
    TPM_ORD_EstablishTransport = 0x000000e6
    TPM_ORD_EvictKey = 0x00000022
    TPM_ORD_ExecuteTransport = 0x000000e7
    TPM_ORD_Extend = 0x00000014
    TPM_ORD_FieldUpgrade = 0x000000aa
    TPM_ORD_FlushSpecific = 0x000000ba
    TPM_ORD_ForceClear = 0x0000005d
    TPM_ORD_GetAuditDigest = 0x00000085
    TPM_ORD_GetAuditDigestSigned = 0x00000086
    TPM_ORD_GetAuditEvent = 0x00000082
    TPM_ORD_GetAuditEventSigned = 0x00000083
    TPM_ORD_GetCapability = 0x00000065
    TPM_ORD_GetCapabilityOwner = 0x00000066
    TPM_ORD_GetCapabilitySigned = 0x00000064
    TPM_ORD_GetOrdinalAuditStatus = 0x0000008c
    TPM_ORD_GetPubKey = 0x00000021
    TPM_ORD_GetRandom = 0x00000046
    TPM_ORD_GetTestResult = 0x00000054
    TPM_ORD_GetTicks = 0x000000f1
    TPM_ORD_IncrementCounter = 0x000000dd
    TPM_ORD_Init = 0x00000097
    TPM_ORD_KeyControlOwner = 0x00000023
    TPM_ORD_KillMaintenanceFeature = 0x0000002e
    TPM_ORD_LoadAuthContext = 0x000000b7
    TPM_ORD_LoadContext = 0x000000b9
    TPM_ORD_LoadKey = 0x00000020
    TPM_ORD_LoadKey2 = 0x00000041
    TPM_ORD_LoadKeyContext = 0x000000b5
    TPM_ORD_LoadMaintenanceArchive = 0x0000002d
    TPM_ORD_LoadManuMaintPub = 0x0000002f
    TPM_ORD_MakeIdentity = 0x00000079
    TPM_ORD_MigrateKey = 0x00000025
    TPM_ORD_NV_DefineSpace = 0x000000cc
    TPM_ORD_NV_ReadValue = 0x000000cf
    TPM_ORD_NV_ReadValueAuth = 0x000000d0
    TPM_ORD_NV_WriteValue = 0x000000cd
    TPM_ORD_NV_WriteValueAuth = 0x000000ce
    TPM_ORD_OIAP = 0x0000000a
    TPM_ORD_OSAP = 0x0000000b
    TPM_ORD_OwnerClear = 0x0000005b
    TPM_ORD_OwnerReadInternalPub = 0x00000081
    TPM_ORD_OwnerReadPubek = 0x0000007d
    TPM_ORD_OwnerSetDisable = 0x0000006e
    TPM_ORD_PCR_Reset = 0x000000c8
    TPM_ORD_PcrRead = 0x00000015
    TPM_ORD_PhysicalDisable = 0x00000070
    TPM_ORD_PhysicalEnable = 0x0000006f
    TPM_ORD_PhysicalSetDeactivated = 0x00000072
    TPM_ORD_Quote = 0x00000016
    TPM_ORD_Quote2 = 0x0000003e
    TPM_ORD_ReadCounter = 0x000000de
    TPM_ORD_ReadManuMaintPub = 0x00000030
    TPM_ORD_ReadPubek = 0x0000007c
    TPM_ORD_ReleaseCounter = 0x000000df
    TPM_ORD_ReleaseCounterOwner = 0x000000e0
    TPM_ORD_ReleaseTransportSigned = 0x000000e8
    TPM_ORD_Reset = 0x0000005a
    TPM_ORD_ResetLockValue = 0x00000040
    TPM_ORD_RevokeTrust = 0x00000080
    TPM_ORD_SaveAuthContext = 0x000000b6
    TPM_ORD_SaveContext = 0x000000b8
    TPM_ORD_SaveKeyContext = 0x000000b4
    TPM_ORD_SaveState = 0x00000098
    TPM_ORD_Seal = 0x00000017
    TPM_ORD_Sealx = 0x0000003d
    TPM_ORD_SelfTestFull = 0x00000050
    TPM_ORD_SetCapability = 0x0000003f
    TPM_ORD_SetOperatorAuth = 0x00000074
    TPM_ORD_SetOrdinalAuditStatus = 0x0000008d
    TPM_ORD_SetOwnerInstall = 0x00000071
    TPM_ORD_SetOwnerPointer = 0x00000075
    TPM_ORD_SetRedirection = 0x0000009a
    TPM_ORD_SetTempDeactivated = 0x00000073
    TPM_ORD_SHA1Complete = 0x000000a2
    TPM_ORD_SHA1CompleteExtend = 0x000000a3
    TPM_ORD_SHA1Start = 0x000000a0
    TPM_ORD_SHA1Update = 0x000000a1
    TPM_ORD_Sign = 0x0000003c
    TPM_ORD_Startup = 0x00000099
    TPM_ORD_StirRandom = 0x00000047
    TPM_ORD_TakeOwnership = 0x0000000d
    TPM_ORD_Terminate_Handle = 0x00000096
    TPM_ORD_TickStampBlob = 0x000000f2
    TPM_ORD_UnBind = 0x0000001e
    TPM_ORD_Unseal = 0x00000018
    # TPM_CONNECTION_COMMAND = 0x40000000
    TSC_ORD_PhysicalPresence = 0x4000000a
    TSC_ORD_ResetEstablishmentBit = 0x4000000b

    def __repr__(self) -> str:
        """Represent the value in hexadecimal"""
        return f"<{self.name}: {self.value:#010x}>"


@enum.unique
class Tpm12ResponseCode(enum.IntEnum):
    """TPM_RESULT: command response codes in TPM 1.2"""
    TPM_SUCCESS = 0
    TPM_AUTHFAIL = 1
    TPM_BADINDEX = 2
    TPM_BAD_PARAMETER = 3
    TPM_AUDITFAILURE = 4
    TPM_CLEAR_DISABLED = 5
    TPM_DEACTIVATED = 6
    TPM_DISABLED = 7
    TPM_DISABLED_CMD = 8
    TPM_FAIL = 9
    TPM_BAD_ORDINAL = 10
    TPM_INSTALL_DISABLED = 11
    TPM_INVALID_KEYHANDLE = 12
    TPM_KEYNOTFOUND = 13
    TPM_INAPPROPRIATE_ENC = 14
    TPM_MIGRATEFAIL = 15
    TPM_INVALID_PCR_INFO = 16
    TPM_NOSPACE = 17
    TPM_NOSRK = 18
    TPM_NOTSEALED_BLOB = 19
    TPM_OWNER_SET = 20
    TPM_RESOURCES = 21
    TPM_SHORTRANDOM = 22
    TPM_SIZE = 23
    TPM_WRONGPCRVAL = 24
    TPM_BAD_PARAM_SIZE = 25
    TPM_SHA_THREAD = 26
    TPM_SHA_ERROR = 27
    TPM_FAILEDSELFTEST = 28
    TPM_AUTH2FAIL = 29
    TPM_BADTAG = 30
    TPM_IOERROR = 31
    TPM_ENCRYPT_ERROR = 32
    TPM_DECRYPT_ERROR = 33
    TPM_INVALID_AUTHHANDLE = 34
    TPM_NO_ENDORSEMENT = 35
    TPM_INVALID_KEYUSAGE = 36
    TPM_WRONG_ENTITYTYPE = 37
    TPM_INVALID_POSTINIT = 38
    TPM_INAPPROPRIATE_SIG = 39
    TPM_BAD_KEY_PROPERTY = 40
    TPM_BAD_MIGRATION = 41
    TPM_BAD_SCHEME = 42
    TPM_BAD_DATASIZE = 43
    TPM_BAD_MODE = 44
    TPM_BAD_PRESENCE = 45
    TPM_BAD_VERSION = 46
    TPM_NO_WRAP_TRANSPORT = 47
    TPM_AUDITFAIL_UNSUCCESSFUL = 48
    TPM_AUDITFAIL_SUCCESSFUL = 49
    TPM_NOTRESETABLE = 50
    TPM_NOTLOCAL = 51
    TPM_BAD_TYPE = 52
    TPM_INVALID_RESOURCE = 53
    TPM_NOTFIPS = 54
    TPM_INVALID_FAMILY = 55
    TPM_NO_NV_PERMISSION = 56
    TPM_REQUIRES_SIGN = 57
    TPM_KEY_NOTSUPPORTED = 58
    TPM_AUTH_CONFLICT = 59
    TPM_AREA_LOCKED = 60
    TPM_BAD_LOCALITY = 61
    TPM_READ_ONLY = 62
    TPM_PER_NOWRITE = 63
    TPM_FAMILYCOUNT = 64
    TPM_WRITE_LOCKED = 65
    TPM_BAD_ATTRIBUTES = 66
    TPM_INVALID_STRUCTURE = 67
    TPM_KEY_OWNER_CONTROL = 68
    TPM_BAD_COUNTER = 69
    TPM_NOT_FULLWRITE = 70
    TPM_CONTEXT_GAP = 71
    TPM_MAXNVWRITES = 72
    TPM_NOOPERATOR = 73
    TPM_RESOURCEMISSING = 74
    TPM_DELEGATE_LOCK = 75
    TPM_DELEGATE_FAMILY = 76
    TPM_DELEGATE_ADMIN = 77
    TPM_TRANSPORT_NOTEXCLUSIVE = 78
    TPM_OWNER_CONTROL = 79
    TPM_DAA_RESOURCES = 80
    TPM_DAA_INPUT_DATA0 = 81
    TPM_DAA_INPUT_DATA1 = 82
    TPM_DAA_ISSUER_SETTINGS = 83
    TPM_DAA_TPM_SETTINGS = 84
    TPM_DAA_STAGE = 85
    TPM_DAA_ISSUER_VALIDITY = 86
    TPM_DAA_WRONG_W = 87
    TPM_BAD_HANDLE = 88
    TPM_BAD_DELEGATE = 89
    TPM_BADCONTEXT = 90
    TPM_TOOMANYCONTEXTS = 91
    TPM_MA_TICKET_SIGNATURE = 92
    TPM_MA_DESTINATION = 93
    TPM_MA_SOURCE = 94
    TPM_MA_AUTHORITY = 95
    TPM_PERMANENTEK = 97
    TPM_BAD_SIGNATURE = 98
    TPM_NOCONTEXTSPACE = 99
    TPM_RC_VER1 = 0x100
    TPM_RC_VENDOR_ERROR = 0x400
    TPM_RETRY = 0x00000800  # TPM_NON_FATAL
    TPM_NEEDS_SELFTEST = 0x00000801
    TPM_DOING_SELFTEST = 0x00000802
    TPM_DEFEND_LOCK_RUNNING = 0x00000803


class Tpm12Failure(Exception):
    """Failure while executing a TPM 1.2 command"""
    def __init__(self, cmd_code: Tpm12CommandCode, resp_code: int):
        super(Tpm12Failure, self).__init__()
        self.cmd_code = cmd_code
        self.code = resp_code
        try:
            self.code_desc: Optional[Tpm12ResponseCode] = Tpm12ResponseCode(resp_code)
        except ValueError:
            print(f"Warning: unknown TPM 1.2 response code {self.code:#x}", file=sys.stderr)
            self.code_desc = None

    def __str__(self) -> str:
        if self.code_desc:
            return f"{self.cmd_code.name}: {self.code_desc.name}={self.code_desc:#x}"
        return f"{self.cmd_code.name}: {self.code:#x}"


@enum.unique
class Tpm12AlgId(enum.IntEnum):
    """TPM_ALGORITHM_ID: algorithm ID in TPM 1.2"""
    TPM_ALG_RSA = 0x00000001
    TPM_ALG_DES = 0x00000002
    TPM_ALG_3DES = 0x00000003  # 3DES in EDE mode
    TPM_ALG_SHA = 0x00000004  # SHA1
    TPM_ALG_HMAC = 0x00000005  # RFC 2104 HMAC
    TPM_ALG_AES128 = 0x00000006  # AES, key size 128
    TPM_ALG_MGF1 = 0x00000007  # XOR algorithm using MGF1 to create a string the size of the encrypted block
    TPM_ALG_AES192 = 0x00000008  # AES, key size 192
    TPM_ALG_AES256 = 0x00000009  # AES, key size 256
    TPM_ALG_XOR = 0x0000000a  # XOR using the rolling nonces

    def __repr__(self) -> str:
        """Represent the value in hexadecimal"""
        return f"<{self.name}: {self.value:#x}>"


@enum.unique
class Tpm12SymMode(enum.IntEnum):
    """TPM_SYM_MODE: mode of a symmetric encryption in TPM 1.2"""
    TPM_SYM_MODE_ECB = 0x00000001
    TPM_SYM_MODE_CBC = 0x00000002
    TPM_SYM_MODE_CFB = 0x00000003

    def __repr__(self) -> str:
        """Represent the value in hexadecimal"""
        return f"<{self.name}: {self.value:#x}>"


@enum.unique
class Tpm12ProtocolId(enum.IntEnum):
    """TPM_PROTOCOL_ID: protocol ID in TPM 1.2"""
    TPM_PID_OIAP = 0x0001  # Object-Independent Authorization Protocol
    TPM_PID_OSAP = 0x0002  # Object-Specific Authorization Protocol
    TPM_PID_ADIP = 0x0003  # AuthData Insertion Protocol
    TPM_PID_ADCP = 0x0004  # AuthData Change Protocol
    TPM_PID_OWNER = 0x0005
    TPM_PID_DSAP = 0x0006  # Delegate-Specific Authorization Protocol
    TPM_PID_TRANSPORT = 0x0007


@enum.unique
class Tpm12CapabilityArea(enum.IntEnum):
    """TPM_CAPABILITY_AREA: capability areas in TPM 1.2"""
    TPM_CAP_ORD = 0x00000001
    TPM_CAP_ALG = 0x00000002
    TPM_CAP_PID = 0x00000003
    TPM_CAP_FLAG = 0x00000004
    TPM_CAP_PROPERTY = 0x00000005
    TPM_CAP_VERSION = 0x00000006
    TPM_CAP_KEY_HANDLE = 0x00000007
    TPM_CAP_CHECK_LOADED = 0x00000008
    TPM_CAP_SYM_MODE = 0x00000009
    TPM_CAP_KEY_STATUS = 0x0000000C
    TPM_CAP_NV_LIST = 0x0000000D
    TPM_CAP_MFR = 0x00000010
    TPM_CAP_NV_INDEX = 0x00000011
    TPM_CAP_TRANS_ALG = 0x00000012
    TPM_CAP_HANDLE = 0x00000014
    TPM_CAP_TRANS_ES = 0x00000015
    TPM_CAP_AUTH_ENCRYPT = 0x00000017
    TPM_CAP_SELECT_SIZE = 0x00000018
    TPM_CAP_VERSION_VAL = 0x0000001A
    # Flags
    TPM_CAP_FLAG_PERMANENT = 0x00000108
    TPM_CAP_FLAG_VOLATILE = 0x00000109
    # CAP_PROPERTY Subcap values for GetCapability
    TPM_CAP_PROP_PCR = 0x00000101
    TPM_CAP_PROP_DIR = 0x00000102
    TPM_CAP_PROP_MANUFACTURER = 0x00000103
    TPM_CAP_PROP_KEYS = 0x00000104
    TPM_CAP_PROP_MIN_COUNTER = 0x00000107
    TPM_CAP_PROP_AUTHSESS = 0x0000010a
    TPM_CAP_PROP_TRANSESS = 0x0000010b
    TPM_CAP_PROP_COUNTERS = 0x0000010c
    TPM_CAP_PROP_MAX_AUTHSESS = 0x0000010d
    TPM_CAP_PROP_MAX_TRANSESS = 0x0000010e
    TPM_CAP_PROP_MAX_COUNTERS = 0x0000010f
    TPM_CAP_PROP_MAX_KEYS = 0x00000110
    TPM_CAP_PROP_OWNER = 0x00000111
    TPM_CAP_PROP_CONTEXT = 0x00000112
    TPM_CAP_PROP_MAX_CONTEXT = 0x00000113
    TPM_CAP_PROP_FAMILYROWS = 0x00000114
    TPM_CAP_PROP_TIS_TIMEOUT = 0x00000115
    TPM_CAP_PROP_STARTUP_EFFECT = 0x00000116
    TPM_CAP_PROP_DELEGATE_ROW = 0x00000117
    TPM_CAP_PROP_DAA_MAX = 0x00000119
    TPM_CAP_PROP_DAASESS = 0x0000011a
    TPM_CAP_PROP_CONTEXT_DIST = 0x0000011b
    TPM_CAP_PROP_DAA_INTERRUPT = 0x0000011c
    TPM_CAP_PROP_SESSIONS = 0x0000011d
    TPM_CAP_PROP_MAX_SESSIONS = 0x0000011e
    TPM_CAP_PROP_CMK_RESTRICTION = 0x0000011f
    TPM_CAP_PROP_DURATION = 0x00000120
    TPM_CAP_PROP_ACTIVE_COUNTER = 0x00000122
    TPM_CAP_PROP_MAX_NV_AVAILABLE = 0x00000123
    TPM_CAP_PROP_INPUT_BUFFER = 0x00000124

    def __repr__(self) -> str:
        """Represent the value in hexadecimal"""
        return f"<{self.name}: {self.value:#x}>"


# Description of fields of structure TPM_PERMANENT_FLAGS
TPM12_PERMANENT_FLAGS_DESC: Tuple[str, ...] = (
    'disable',
    'ownership',
    'deactivated',
    'readPubek',
    'disableOwnerClear',
    'allowMaintenance',
    'physicalPresenceLifetimeLock',
    'physicalPresenceHWEnable',
    'physicalPresenceCMDEnable',
    'CEKPUsed',
    'TPMpost',
    'TPMpostLock',
    'FIPS',
    'operator',
    'enableRevokeEK',
    'nvLocked',
    'readSRKPub',
    'tpmEstablished',
    'maintenanceDone',
    'disableFullDALogicInfo',
)


# Description of fields of structure TPM_STCLEAR_FLAGS
TPM12_STCLEAR_FLAGS_DESC: Tuple[str, ...] = (
    'deactivated',
    'disableForceClear',
    'physicalPresence',
    'physicalPresenceLock',
    'bGlobalLock',
)


# Description of the TPM_NV_ATTRIBUTES bits
TPM12_NV_ATTRIBUTES_DESC: Tuple[Tuple[int, str], ...] = (
    (0x80000000, 'readStClear'),
    (0x00040000, 'authRead'),
    (0x00020000, 'ownerRead'),
    (0x00010000, 'ppRead'),
    (0x00008000, 'GlobalLock'),
    (0x00004000, 'writeStClear'),
    (0x00002000, 'writeDefine'),
    (0x00001000, 'writeAll'),
    (0x00000004, 'authWrite'),
    (0x00000002, 'ownerWrite'),
    (0x00000001, 'ppWrite'),
)


# Reserved NV indexes in TPM 1.2
# and in Intel TXT (Trusted Execution Technology) Appendix I
# (https://www.intel.com/content/www/us/en/software-developers/intel-txt-software-development-guide.html)
# and Intel TXT Revision 013 Appendix J
# (https://usermanual.wiki/Document/inteltxtsoftwaredevelopmentguide.1721028921/view)
# and tboot (http://hg.code.sf.net/p/tboot/code/file/v2.0.0/tboot/common/tpm_12.c#l1828)
TPM12_RESERVED_NV_INDEXES_DESC: Mapping[int, str] = {
    0x0000f000: 'EK Certificate',
    0x0000f001: 'TPM CC',
    0x0000f002: 'Platform Certificate',
    0x0000f003: 'Platform CC',
    0x00010000: 'Group Resv Base',
    0x00011100: 'TSS Base',
    0x00011200: 'PC Base',
    0x00011300: 'Server Base',
    0x00011400: 'Mobile Base',
    0x00011500: 'Peripheral Base',
    # Section 28.2 Use of NV storage during manufacturing of
    # https://trustedcomputinggroup.org/wp-content/uploads/TPM-Main-Part-1-Design-Principles_v1.2_rev116_01032011.pdf
    # defines bit 0x10000000 as the 'D' bit: these NV indexes can be defined
    # during manufacturing and then be locked (by setting nvLocked to TRUE)
    0x10000001: 'DIR[0]',  # Compatibility with older TPM versions
    0x1000f000: 'D bit + EK Certificate',
    0x1000f002: 'D bit + Platform Certificate',
    0x20000001: 'Policy for Verified Launch (Intel TXT)',
    0x20000002: 'Launch error (Intel TXT)',
    0x40000001: 'LCP Structure for Platform Owner (Intel TXT "PO")',  # LCP = Launch Control Policies
    0x50000001: 'LCP Platform Supplier (Intel TXT "PS")',
    0x50000002: 'Launch Auxiliary during IVB and before platforms (Intel TXT "AUX")',  # IVB = Ivy Bridge
    0x50000003: 'new Launch Auxiliary (Intel TXT "AUX")',
    0x50000004: 'SGX Software Version Number (Intel TXT "SGX SVN")',  # SGX = Software Guard Extensions
    0xffffffff: 'Lock',
}


@enum.unique
class Tpm20Tag(enum.IntEnum):
    """TPM_ST: tags in command/response header in TPM 2.0"""
    TPM2_ST_RSP_COMMAND = 0x00c4
    TPM2_ST_NULL = 0x8000
    TPM2_ST_NO_SESSIONS = 0x8001
    TPM2_ST_SESSIONS = 0x8002
    TPM2_ST_ATTEST_NV = 0x8014
    TPM2_ST_ATTEST_COMMAND_AUDIT = 0x8015
    TPM2_ST_ATTEST_SESSION_AUDIT = 0x8016
    TPM2_ST_ATTEST_CERTIFY = 0x8017
    TPM2_ST_ATTEST_QUOTE = 0x8018
    TPM2_ST_ATTEST_TIME = 0x8019
    TPM2_ST_ATTEST_CREATION = 0x801a
    TPM2_ST_CREATION = 0x8021
    TPM2_ST_VERIFIED = 0x8022
    TPM2_ST_AUTH_SECRET = 0x8023
    TPM2_ST_HASHCHECK = 0x8024
    TPM2_ST_AUTH_SIGNED = 0x8025
    TPM2_ST_FU_MANIFEST = 0x8029

    def __repr__(self) -> str:
        """Represent the value in hexadecimal"""
        return f"<{self.name}: {self.value:#06x}>"


@enum.unique
class Tpm20CommandCode(enum.IntEnum):
    """TPM_CC: command codes in TPM 2.0

    From https://github.com/microsoft/ms-tpm-20-ref/blob/main/TPMCmd/tpm/include/TpmTypes.h
    """
    TPM2_CC_ACT_SetTimeout = 0x00000198
    TPM2_CC_AC_GetCapability = 0x00000194
    TPM2_CC_AC_Send = 0x00000195
    TPM2_CC_ActivateCredential = 0x00000147
    TPM2_CC_Certify = 0x00000148
    TPM2_CC_CertifyCreation = 0x0000014a
    TPM2_CC_CertifyX509 = 0x00000197
    TPM2_CC_ChangeEPS = 0x00000124
    TPM2_CC_ChangePPS = 0x00000125
    TPM2_CC_Clear = 0x00000126
    TPM2_CC_ClearControl = 0x00000127
    TPM2_CC_ClockRateAdjust = 0x00000130
    TPM2_CC_ClockSet = 0x00000128
    TPM2_CC_Commit = 0x0000018b
    TPM2_CC_ContextLoad = 0x00000161
    TPM2_CC_ContextSave = 0x00000162
    TPM2_CC_Create = 0x00000153
    TPM2_CC_CreateLoaded = 0x00000191
    TPM2_CC_CreatePrimary = 0x00000131
    TPM2_CC_DictionaryAttackLockReset = 0x00000139
    TPM2_CC_DictionaryAttackParameters = 0x0000013a
    TPM2_CC_Duplicate = 0x0000014b
    TPM2_CC_ECC_Decrypt = 0x0000019a
    TPM2_CC_ECC_Encrypt = 0x00000199
    TPM2_CC_ECC_Parameters = 0x00000178
    TPM2_CC_ECDH_KeyGen = 0x00000163
    TPM2_CC_ECDH_ZGen = 0x00000154
    TPM2_CC_EC_Ephemeral = 0x0000018e
    TPM2_CC_EncryptDecrypt = 0x00000164
    TPM2_CC_EncryptDecrypt2 = 0x00000193
    TPM2_CC_EventSequenceComplete = 0x00000185
    TPM2_CC_EvictControl = 0x00000120
    TPM2_CC_FieldUpgradeData = 0x00000141
    TPM2_CC_FieldUpgradeStart = 0x0000012f
    TPM2_CC_FirmwareRead = 0x00000179
    TPM2_CC_FlushContext = 0x00000165
    TPM2_CC_GetCapability = 0x0000017a
    TPM2_CC_GetCommandAuditDigest = 0x00000133
    TPM2_CC_GetRandom = 0x0000017b
    TPM2_CC_GetSessionAuditDigest = 0x0000014d
    TPM2_CC_GetTestResult = 0x0000017c
    TPM2_CC_GetTime = 0x0000014c
    TPM2_CC_HMAC = 0x00000155
    TPM2_CC_HMAC_Start = 0x0000015b
    TPM2_CC_Hash = 0x0000017d
    TPM2_CC_HashSequenceStart = 0x00000186
    TPM2_CC_HierarchyChangeAuth = 0x00000129
    TPM2_CC_HierarchyControl = 0x00000121
    TPM2_CC_Import = 0x00000156
    TPM2_CC_IncrementalSelfTest = 0x00000142
    TPM2_CC_Load = 0x00000157
    TPM2_CC_LoadExternal = 0x00000167
    TPM2_CC_MakeCredential = 0x00000168
    TPM2_CC_NV_Certify = 0x00000184
    TPM2_CC_NV_ChangeAuth = 0x0000013b
    TPM2_CC_NV_DefineSpace = 0x0000012a
    TPM2_CC_NV_Extend = 0x00000136
    TPM2_CC_NV_GlobalWriteLock = 0x00000132
    TPM2_CC_NV_Increment = 0x00000134
    TPM2_CC_NV_Read = 0x0000014e
    TPM2_CC_NV_ReadLock = 0x0000014f
    TPM2_CC_NV_ReadPublic = 0x00000169
    TPM2_CC_NV_SetBits = 0x00000135
    TPM2_CC_NV_UndefineSpace = 0x00000122
    TPM2_CC_NV_UndefineSpaceSpecial = 0x0000011f
    TPM2_CC_NV_Write = 0x00000137
    TPM2_CC_NV_WriteLock = 0x00000138
    TPM2_CC_ObjectChangeAuth = 0x00000150
    TPM2_CC_PCR_Allocate = 0x0000012b
    TPM2_CC_PCR_Event = 0x0000013c
    TPM2_CC_PCR_Extend = 0x00000182
    TPM2_CC_PCR_Read = 0x0000017e
    TPM2_CC_PCR_Reset = 0x0000013d
    TPM2_CC_PCR_SetAuthPolicy = 0x0000012c
    TPM2_CC_PCR_SetAuthValue = 0x00000183
    TPM2_CC_PP_Commands = 0x0000012d
    TPM2_CC_PolicyAuthValue = 0x0000016b
    TPM2_CC_PolicyAuthorize = 0x0000016a
    TPM2_CC_PolicyAuthorizeNV = 0x00000192
    TPM2_CC_PolicyCommandCode = 0x0000016c
    TPM2_CC_PolicyCounterTimer = 0x0000016d
    TPM2_CC_PolicyCpHash = 0x0000016e
    TPM2_CC_PolicyDuplicationSelect = 0x00000188
    TPM2_CC_PolicyGetDigest = 0x00000189
    TPM2_CC_PolicyLocality = 0x0000016f
    TPM2_CC_PolicyNV = 0x00000149
    TPM2_CC_PolicyNameHash = 0x00000170
    TPM2_CC_PolicyNvWritten = 0x0000018f
    TPM2_CC_PolicyOR = 0x00000171
    TPM2_CC_PolicyPCR = 0x0000017f
    TPM2_CC_PolicyPassword = 0x0000018c
    TPM2_CC_PolicyPhysicalPresence = 0x00000187
    TPM2_CC_PolicyRestart = 0x00000180
    TPM2_CC_PolicySecret = 0x00000151
    TPM2_CC_PolicySigned = 0x00000160
    TPM2_CC_PolicyTemplate = 0x00000190
    TPM2_CC_PolicyTicket = 0x00000172
    TPM2_CC_Policy_AC_SendSelect = 0x00000196
    TPM2_CC_Quote = 0x00000158
    TPM2_CC_RSA_Decrypt = 0x00000159
    TPM2_CC_RSA_Encrypt = 0x00000174
    TPM2_CC_ReadClock = 0x00000181
    TPM2_CC_ReadPublic = 0x00000173
    TPM2_CC_Rewrap = 0x00000152
    TPM2_CC_SelfTest = 0x00000143
    TPM2_CC_SequenceComplete = 0x0000013e
    TPM2_CC_SequenceUpdate = 0x0000015c
    TPM2_CC_SetAlgorithmSet = 0x0000013f
    TPM2_CC_SetCommandCodeAuditStatus = 0x00000140
    TPM2_CC_SetPrimaryPolicy = 0x0000012e
    TPM2_CC_Shutdown = 0x00000145
    TPM2_CC_Sign = 0x0000015d
    TPM2_CC_StartAuthSession = 0x00000176
    TPM2_CC_Startup = 0x00000144
    TPM2_CC_StirRandom = 0x00000146
    TPM2_CC_TestParms = 0x0000018a
    TPM2_CC_Unseal = 0x0000015e
    TPM2_CC_Vendor_TCG_Test = 0x20000000
    TPM2_CC_VerifySignature = 0x00000177
    TPM2_CC_ZGen_2Phase = 0x0000018d

    def __repr__(self) -> str:
        """Represent the value in hexadecimal"""
        return f"<{self.name}: {self.value:#010x}>"


@enum.unique
class Tpm20ResponseCode(enum.IntEnum):
    """TPM_RC: command response codes in TPM 2.0

    Documentation:
    https://github.com/tpm2-software/tpm2-tss/blob/3.1.0/include/tss2/tss2_tpm2_types.h
    """
    TPM2_RC_SUCCESS = 0x000
    TPM2_RC_BAD_TAG = 0x01e  # Same as Tpm12ResponseCode.TPM_BADTAG

    RC_FMT1 = 0x080
    TPM2_RC_ASYMMETRIC = 0x081
    TPM2_RC_ATTRIBUTES = 0x082
    TPM2_RC_HASH = 0x083
    TPM2_RC_VALUE = 0x084
    TPM2_RC_HIERARCHY = 0x085
    TPM2_RC_KEY_SIZE = 0x087
    TPM2_RC_MGF = 0x088
    TPM2_RC_MODE = 0x089
    TPM2_RC_TYPE = 0x08a
    TPM2_RC_HANDLE = 0x08b
    TPM2_RC_KDF = 0x08c
    TPM2_RC_RANGE = 0x08d
    TPM2_RC_AUTH_FAIL = 0x08e
    TPM2_RC_NONCE = 0x08f
    TPM2_RC_PP = 0x090
    TPM2_RC_SCHEME = 0x092
    TPM2_RC_SIZE = 0x095
    TPM2_RC_SYMMETRIC = 0x096
    TPM2_RC_TAG = 0x097
    TPM2_RC_SELECTOR = 0x098
    TPM2_RC_INSUFFICIENT = 0x09a
    TPM2_RC_SIGNATURE = 0x09b
    TPM2_RC_KEY = 0x09c
    TPM2_RC_POLICY_FAIL = 0x09d
    TPM2_RC_INTEGRITY = 0x09f
    TPM2_RC_TICKET = 0x0a0
    TPM2_RC_RESERVED_BITS = 0x0a1
    TPM2_RC_BAD_AUTH = 0x0a2
    TPM2_RC_EXPIRED = 0x0a3
    TPM2_RC_POLICY_CC = 0x0a4
    TPM2_RC_BINDING = 0x0a5
    TPM2_RC_CURVE = 0x0a6
    TPM2_RC_ECC_POINT = 0x0a7

    # RC_VER1 = 0x100
    TPM2_RC_INITIALIZE = 0x100
    TPM2_RC_FAILURE = 0x101
    TPM2_RC_SEQUENCE = 0x103
    TPM2_RC_PRIVATE = 0x10b
    TPM2_RC_HMAC = 0x119
    TPM2_RC_DISABLED = 0x120
    TPM2_RC_EXCLUSIVE = 0x121
    TPM2_RC_AUTH_TYPE = 0x124
    TPM2_RC_AUTH_MISSING = 0x125
    TPM2_RC_POLICY = 0x126
    TPM2_RC_PCR = 0x127
    TPM2_RC_PCR_CHANGED = 0x128
    TPM2_RC_UPGRADE = 0x12d
    TPM2_RC_TOO_MANY_CONTEXTS = 0x12e
    TPM2_RC_AUTH_UNAVAILABLE = 0x12f
    TPM2_RC_REBOOT = 0x130
    TPM2_RC_UNBALANCED = 0x131
    TPM2_RC_COMMAND_SIZE = 0x142
    TPM2_RC_COMMAND_CODE = 0x143
    TPM2_RC_AUTHSIZE = 0x144
    TPM2_RC_AUTH_CONTEXT = 0x145
    TPM2_RC_NV_RANGE = 0x146
    TPM2_RC_NV_SIZE = 0x147
    TPM2_RC_NV_LOCKED = 0x148
    TPM2_RC_NV_AUTHORIZATION = 0x149
    TPM2_RC_NV_UNINITIALIZED = 0x14a
    TPM2_RC_NV_SPACE = 0x14b
    TPM2_RC_NV_DEFINED = 0x14c
    TPM2_RC_BAD_CONTEXT = 0x150
    TPM2_RC_CPHASH = 0x151
    TPM2_RC_PARENT = 0x152
    TPM2_RC_NEEDS_TEST = 0x153
    TPM2_RC_NO_RESULT = 0x154
    TPM2_RC_SENSITIVE = 0x155
    RC_MAX_FM0 = 0x17f

    # TPM2_RC_H=0x000 + TPM2_RC_1=0x100: error related to handle 1
    TPM2_RC_HANDLE1_ASYMMETRIC = 0x181
    TPM2_RC_HANDLE1_ATTRIBUTES = 0x182
    TPM2_RC_HANDLE1_HASH = 0x183
    TPM2_RC_HANDLE1_VALUE = 0x184  # For example from command Public_Read
    TPM2_RC_HANDLE1_HIERARCHY = 0x185
    TPM2_RC_HANDLE1_KEY_SIZE = 0x187
    TPM2_RC_HANDLE1_MGF = 0x188
    TPM2_RC_HANDLE1_MODE = 0x189
    TPM2_RC_HANDLE1_TYPE = 0x18a
    TPM2_RC_HANDLE1_HANDLE = 0x18b
    TPM2_RC_HANDLE1_KDF = 0x18c
    TPM2_RC_HANDLE1_RANGE = 0x18d
    TPM2_RC_HANDLE1_AUTH_FAIL = 0x18e
    TPM2_RC_HANDLE1_NONCE = 0x18f
    TPM2_RC_HANDLE1_PP = 0x190
    TPM2_RC_HANDLE1_SCHEME = 0x192
    TPM2_RC_HANDLE1_SIZE = 0x195
    TPM2_RC_HANDLE1_SYMMETRIC = 0x196
    TPM2_RC_HANDLE1_TAG = 0x197
    TPM2_RC_HANDLE1_SELECTOR = 0x198
    TPM2_RC_HANDLE1_INSUFFICIENT = 0x19a
    TPM2_RC_HANDLE1_SIGNATURE = 0x19b
    TPM2_RC_HANDLE1_KEY = 0x19c
    TPM2_RC_HANDLE1_POLICY_FAIL = 0x19d
    TPM2_RC_HANDLE1_INTEGRITY = 0x19f
    TPM2_RC_HANDLE1_TICKET = 0x1a0
    TPM2_RC_HANDLE1_RESERVED_BITS = 0x1a1
    TPM2_RC_HANDLE1_BAD_AUTH = 0x1a2
    TPM2_RC_HANDLE1_EXPIRED = 0x1a3
    TPM2_RC_HANDLE1_POLICY_CC = 0x1a4
    TPM2_RC_HANDLE1_BINDING = 0x1a5
    TPM2_RC_HANDLE1_CURVE = 0x1a6
    TPM2_RC_HANDLE1_ECC_POINT = 0x1a7

    # TPM2_RC_P=0x040 + TPM2_RC_1=0x100: error related to parameter 1
    TPM2_RC_PARAM1_ASYMMETRIC = 0x1c1
    TPM2_RC_PARAM1_ATTRIBUTES = 0x1c2
    TPM2_RC_PARAM1_HASH = 0x1c3
    TPM2_RC_PARAM1_VALUE = 0x1c4
    TPM2_RC_PARAM1_HIERARCHY = 0x1c5
    TPM2_RC_PARAM1_KEY_SIZE = 0x1c7
    TPM2_RC_PARAM1_MGF = 0x1c8
    TPM2_RC_PARAM1_MODE = 0x1c9
    TPM2_RC_PARAM1_TYPE = 0x1ca
    TPM2_RC_PARAM1_HANDLE = 0x1cb
    TPM2_RC_PARAM1_KDF = 0x1cc
    TPM2_RC_PARAM1_RANGE = 0x1cd
    TPM2_RC_PARAM1_AUTH_FAIL = 0x1ce
    TPM2_RC_PARAM1_NONCE = 0x1cf
    TPM2_RC_PARAM1_PP = 0x1d0
    TPM2_RC_PARAM1_SCHEME = 0x1d2
    TPM2_RC_PARAM1_SIZE = 0x1d5
    TPM2_RC_PARAM1_SYMMETRIC = 0x1d6
    TPM2_RC_PARAM1_TAG = 0x1d7
    TPM2_RC_PARAM1_SELECTOR = 0x1d8
    TPM2_RC_PARAM1_INSUFFICIENT = 0x1da
    TPM2_RC_PARAM1_SIGNATURE = 0x1db
    TPM2_RC_PARAM1_KEY = 0x1dc
    TPM2_RC_PARAM1_POLICY_FAIL = 0x1dd
    TPM2_RC_PARAM1_INTEGRITY = 0x1df
    TPM2_RC_PARAM1_TICKET = 0x1e0
    TPM2_RC_PARAM1_RESERVED_BITS = 0x1e1
    TPM2_RC_PARAM1_BAD_AUTH = 0x1e2
    TPM2_RC_PARAM1_EXPIRED = 0x1e3
    TPM2_RC_PARAM1_POLICY_CC = 0x1e4
    TPM2_RC_PARAM1_BINDING = 0x1e5
    TPM2_RC_PARAM1_CURVE = 0x1e6
    TPM2_RC_PARAM1_ECC_POINT = 0x1e7

    TPM2_RC_PARAM2_HANDLE = 0x2cb  # For example from command GetCapability

    RC_WARN = 0x900
    TPM2_RC_CONTEXT_GAP = 0x901
    TPM2_RC_OBJECT_MEMORY = 0x902
    TPM2_RC_SESSION_MEMORY = 0x903
    TPM2_RC_MEMORY = 0x904
    TPM2_RC_SESSION_HANDLES = 0x905
    TPM2_RC_OBJECT_HANDLES = 0x906
    TPM2_RC_LOCALITY = 0x907
    TPM2_RC_YIELDED = 0x908
    TPM2_RC_CANCELED = 0x909
    TPM2_RC_TESTING = 0x90a
    TPM2_RC_REFERENCE_H0 = 0x910
    TPM2_RC_REFERENCE_H1 = 0x911
    TPM2_RC_REFERENCE_H2 = 0x912
    TPM2_RC_REFERENCE_H3 = 0x913
    TPM2_RC_REFERENCE_H4 = 0x914
    TPM2_RC_REFERENCE_H5 = 0x915
    TPM2_RC_REFERENCE_H6 = 0x916
    TPM2_RC_REFERENCE_S0 = 0x918
    TPM2_RC_REFERENCE_S1 = 0x919
    TPM2_RC_REFERENCE_S2 = 0x91a
    TPM2_RC_REFERENCE_S3 = 0x91b
    TPM2_RC_REFERENCE_S4 = 0x91c
    TPM2_RC_REFERENCE_S5 = 0x91d
    TPM2_RC_REFERENCE_S6 = 0x91e
    TPM2_RC_NV_RATE = 0x920
    TPM2_RC_LOCKOUT = 0x921
    TPM2_RC_RETRY = 0x922
    TPM2_RC_NV_UNAVAILABLE = 0x923
    TPM2_RC_NOT_USED = 0x97f

    # TPM2_RC_S=0x800 + TPM2_RC_1=0x100: error related to session 1
    TPM2_RC_SESS1_ASYMMETRIC = 0x981
    TPM2_RC_SESS1_ATTRIBUTES = 0x982
    TPM2_RC_SESS1_HASH = 0x983
    TPM2_RC_SESS1_VALUE = 0x984
    TPM2_RC_SESS1_HIERARCHY = 0x985
    TPM2_RC_SESS1_KEY_SIZE = 0x987
    TPM2_RC_SESS1_MGF = 0x988
    TPM2_RC_SESS1_MODE = 0x989
    TPM2_RC_SESS1_TYPE = 0x98a
    TPM2_RC_SESS1_HANDLE = 0x98b
    TPM2_RC_SESS1_KDF = 0x98c
    TPM2_RC_SESS1_RANGE = 0x98d
    TPM2_RC_SESS1_AUTH_FAIL = 0x98e
    TPM2_RC_SESS1_NONCE = 0x98f
    TPM2_RC_SESS1_PP = 0x990
    TPM2_RC_SESS1_SCHEME = 0x992
    TPM2_RC_SESS1_SIZE = 0x995
    TPM2_RC_SESS1_SYMMETRIC = 0x996
    TPM2_RC_SESS1_TAG = 0x997
    TPM2_RC_SESS1_SELECTOR = 0x998
    TPM2_RC_SESS1_INSUFFICIENT = 0x99a
    TPM2_RC_SESS1_SIGNATURE = 0x99b
    TPM2_RC_SESS1_KEY = 0x99c
    TPM2_RC_SESS1_POLICY_FAIL = 0x99d
    TPM2_RC_SESS1_INTEGRITY = 0x99f
    TPM2_RC_SESS1_TICKET = 0x9a0
    TPM2_RC_SESS1_RESERVED_BITS = 0x9a1
    TPM2_RC_SESS1_BAD_AUTH = 0x9a2  # For example from command NV_Read
    TPM2_RC_SESS1_EXPIRED = 0x9a3
    TPM2_RC_SESS1_POLICY_CC = 0x9a4
    TPM2_RC_SESS1_BINDING = 0x9a5
    TPM2_RC_SESS1_CURVE = 0x9a6
    TPM2_RC_SESS1_ECC_POINT = 0x9a7

    # TPM_RC_H = 0x000  # add to a handle-related error
    TPM_RC_P = 0x040  # add to a parameter-related error
    TPM_RC_S = 0x800  # add to a session-related error
    # TPM_RC_1 = 0x100  # add to a parameter-, handle-, or session-related error
    TPM_RC_2 = 0x200
    TPM_RC_3 = 0x300
    TPM_RC_4 = 0x400
    TPM_RC_5 = 0x500
    TPM_RC_6 = 0x600
    TPM_RC_7 = 0x700

    def __repr__(self) -> str:
        """Represent the value in hexadecimal"""
        return f"<{self.name}: {self.value:#06x}>"


class Tpm20Failure(Exception):
    """Failure while executing a TPM 2.0 command"""
    def __init__(self, cmd_code: Tpm20CommandCode, code: int):
        super(Tpm20Failure, self).__init__()
        self.cmd_code = cmd_code
        self.code = code
        try:
            self.code_desc: Optional[Tpm20ResponseCode] = Tpm20ResponseCode(code)
        except ValueError:
            print(f"Warning: unknown TPM 2.0 response code {self.code:#x}", file=sys.stderr)
            self.code_desc = None

    def __str__(self) -> str:
        if self.code_desc:
            return f"{self.cmd_code.name}: {self.code_desc.name}={self.code_desc:#x}"
        return f"{self.cmd_code.name}: {self.code:#x}"


@enum.unique
class TpmAlgId(enum.IntEnum):
    """TPM_ALG_ID constants for TPM 2.0

    Reference: "TCG Algorithm Registry"
    https://trustedcomputinggroup.org/resource/tcg-algorithm-registry/
    https://trustedcomputinggroup.org/wp-content/uploads/TCG-_Algorithm_Registry_r1p32_pub.pdf
    """
    TPM_ALG_ERROR = 0x0000
    TPM_ALG_RSA = 0x0001
    TPM_ALG_TDES = 0x0003  # Triple Data Encryption Standard
    TPM_ALG_SHA1 = 0x0004  # Secure Hash Algorithm
    TPM_ALG_HMAC = 0x0005  # Hash Message Authentication Code
    TPM_ALG_AES = 0x0006  # Advanced Encryption Standard
    TPM_ALG_MGF1 = 0x0007  # hash-based mask-generation function
    TPM_ALG_KEYEDHASH = 0x0008
    TPM_ALG_XOR = 0x000a
    TPM_ALG_SHA256 = 0x000b
    TPM_ALG_SHA384 = 0x000c
    TPM_ALG_SHA512 = 0x000d
    TPM_ALG_NULL = 0x0010
    TPM_ALG_SM3_256 = 0x0012
    TPM_ALG_SM4 = 0x0013
    TPM_ALG_RSASSA = 0x0014  # RSA Signature Scheme with Appendix (PKCS#1 v1.5)
    TPM_ALG_RSAES = 0x0015  # RSA Encryption/decryption Scheme (PKCS#1 v1.5)
    TPM_ALG_RSAPSS = 0x0016  # RSA Probabilistic Signature Scheme
    TPM_ALG_OAEP = 0x0017  # RSA Optimal Asymmetric Encryption Padding
    TPM_ALG_ECDSA = 0x0018  # signature algorithm using elliptic curve cryptography
    TPM_ALG_ECDH = 0x0019  # secret sharing using ECC
    TPM_ALG_ECDAA = 0x001a  # elliptic-curve based, anonymous signing scheme
    TPM_ALG_SM2 = 0x001b
    TPM_ALG_ECSCHNORR = 0x001c  # elliptic-curve based Schnorr signature
    TPM_ALG_ECMQV = 0x001d  # two-phase elliptic-curve key exchange (NIST SP800-56A)
    TPM_ALG_KDF1_SP800_56a = 0x0020  # concatenation key derivation function (NIST SP800-56A)
    TPM_ALG_KDF2 = 0x0021  # key derivation function KDF2 (IEEE Std 1363a-2004)
    TPM_ALG_KDF1_SP800_108 = 0x0022  # key derivation method (NIST SP800-108)
    TPM_ALG_ECC = 0x0023
    TPM_ALG_SYMCIPHER = 0x0025  # symmetric block cipher
    TPM_ALG_CAMELLIA = 0x0026
    TPM_ALG_SHA3_256 = 0x0027
    TPM_ALG_SHA3_384 = 0x0028
    TPM_ALG_SHA3_512 = 0x0029
    TPM_ALG_CMAC = 0x003f  # block Cipher-based Message Authentication Code
    TPM_ALG_CTR = 0x0040  # Counter mode
    TPM_ALG_OFB = 0x0041  # Output Feedback mode
    TPM_ALG_CBC = 0x0042  # Cipher Block Chaining mode
    TPM_ALG_CFB = 0x0043  # Cipher Feedback mode
    TPM_ALG_ECB = 0x0044  # Electronic Codebook mode
    TPM_ALG_CCM = 0x0050  # Counter with Cipher Block Chaining-Message Authentication Code
    TPM_ALG_GCM = 0x0051  # Galois/Counter Mode
    TPM_ALG_KW = 0x0052  # AES Key Wrap (NIST SP800-38F)
    TPM_ALG_KWP = 0x0053  # AES Key Wrap with Padding (NIST SP800-38F)
    TPM_ALG_EAX = 0x0054  # Authenticated-Encryption Mode
    TPM_ALG_EDDSA = 0x0060  # Edwards-curve Digital Signature Algorithm (PureEdDSA)

    def __repr__(self) -> str:
        """Represent the value in hexadecimal"""
        return f"<{self.name}: {self.value:#06x}>"

    def __str__(self) -> str:
        return f"{self.name} ({self.value:#06x})"

    def compute_hash(self, data: bytes) -> bytes:
        """Compute the hash digest according to the algorithm"""
        if self == TpmAlgId.TPM_ALG_SHA256:
            return hashlib.sha256(data).digest()
        if self == TpmAlgId.TPM_ALG_SHA384:
            return hashlib.sha384(data).digest()
        raise NotImplementedError(f"Unimplemented hash {self.name}")

    def compute_hmac(self, key: bytes, data: bytes) -> bytes:
        if self == TpmAlgId.TPM_ALG_SHA256:
            return hmac.new(key, data, 'sha256').digest()
        raise NotImplementedError(f"Unimplemented hash {self.name}")


@enum.unique
class Tpm20EccCurve(enum.IntEnum):
    """TPM_ECC_CURVE: Elliptic-Curve Cryptography curves in TPM 2.0"""
    TPM_ECC_NONE = 0x0000
    TPM_ECC_NIST_P192 = 0x0001
    TPM_ECC_NIST_P224 = 0x0002
    TPM_ECC_NIST_P256 = 0x0003
    TPM_ECC_NIST_P384 = 0x0004
    TPM_ECC_NIST_P521 = 0x0005
    TPM_ECC_BN_P256 = 0x0010  # curve to support ECDAA
    TPM_ECC_BN_P638 = 0x0011  # curve to support ECDAA
    TPM_ECC_SM2_P256 = 0x0020
    TPM_ECC_BP_P256_R1 = 0x0030  # Brainpool
    TPM_ECC_BP_P384_R1 = 0x0031  # Brainpool
    TPM_ECC_BP_P512_R1 = 0x0032  # Brainpool
    TPM_ECC_CURVE_25519 = 0x0040  # curve to support EdDSA

    def __repr__(self) -> str:
        """Represent the value in hexadecimal"""
        return f"<{self.name}: {self.value:#06x}>"

    def __str__(self) -> str:
        return f"{self.name} ({self.value:#06x})"


@enum.unique
class Tpm20SessionType(enum.IntEnum):
    """TPM_SE: authorization session types"""
    TPM_SE_HMAC = 0x00
    TPM_SE_POLICY = 0x01
    TPM_SE_TRIAL = 0x03

    def __str__(self) -> str:
        return f"{self.name} ({self.value})"


@enum.unique
class Tpm20Capability(enum.IntEnum):
    """TPM_CAP: capabilities in TPM 2.0"""
    TPM_CAP_ALGS = 0x00000000
    TPM_CAP_HANDLES = 0x00000001
    TPM_CAP_COMMANDS = 0x00000002
    TPM_CAP_PP_COMMANDS = 0x00000003
    TPM_CAP_AUDIT_COMMANDS = 0x00000004
    TPM_CAP_PCRS = 0x00000005
    TPM_CAP_TPM_PROPERTIES = 0x00000006
    TPM_CAP_PCR_PROPERTIES = 0x00000007
    TPM_CAP_ECC_CURVES = 0x00000008


@enum.unique
class Tpm20Property(enum.IntEnum):
    """TPM2_PT: TPM properties in TPM 2.0"""
    TPM2_PT_NONE = 0x00000000
    # TPM2_PT_FIXED = 0x00000100
    TPM2_PT_FAMILY_INDICATOR = 0x00000100
    TPM2_PT_LEVEL = 0x00000101
    TPM2_PT_REVISION = 0x00000102
    TPM2_PT_DAY_OF_YEAR = 0x00000103
    TPM2_PT_YEAR = 0x00000104
    TPM2_PT_MANUFACTURER = 0x00000105
    TPM2_PT_VENDOR_STRING_1 = 0x00000106
    TPM2_PT_VENDOR_STRING_2 = 0x00000107
    TPM2_PT_VENDOR_STRING_3 = 0x00000108
    TPM2_PT_VENDOR_STRING_4 = 0x00000109
    TPM2_PT_VENDOR_TPM_TYPE = 0x0000010a
    TPM2_PT_FIRMWARE_VERSION_1 = 0x0000010b
    TPM2_PT_FIRMWARE_VERSION_2 = 0x0000010c
    TPM2_PT_INPUT_BUFFER = 0x0000010d
    TPM2_PT_HR_TRANSIENT_MIN = 0x0000010e
    TPM2_PT_HR_PERSISTENT_MIN = 0x0000010f
    TPM2_PT_HR_LOADED_MIN = 0x00000110
    TPM2_PT_ACTIVE_SESSIONS_MAX = 0x00000111
    TPM2_PT_PCR_COUNT = 0x00000112
    TPM2_PT_PCR_SELECT_MIN = 0x00000113
    TPM2_PT_CONTEXT_GAP_MAX = 0x00000114
    TPM2_PT_NV_COUNTERS_MAX = 0x00000116
    TPM2_PT_NV_INDEX_MAX = 0x00000117
    TPM2_PT_MEMORY = 0x00000118
    TPM2_PT_CLOCK_UPDATE = 0x00000119
    TPM2_PT_CONTEXT_HASH = 0x0000011a
    TPM2_PT_CONTEXT_SYM = 0x0000011b
    TPM2_PT_CONTEXT_SYM_SIZE = 0x0000011c
    TPM2_PT_ORDERLY_COUNT = 0x0000011d
    TPM2_PT_MAX_COMMAND_SIZE = 0x0000011e
    TPM2_PT_MAX_RESPONSE_SIZE = 0x0000011f
    TPM2_PT_MAX_DIGEST = 0x00000120
    TPM2_PT_MAX_OBJECT_CONTEXT = 0x00000121
    TPM2_PT_MAX_SESSION_CONTEXT = 0x00000122
    TPM2_PT_PS_FAMILY_INDICATOR = 0x00000123
    TPM2_PT_PS_LEVEL = 0x00000124
    TPM2_PT_PS_REVISION = 0x00000125
    TPM2_PT_PS_DAY_OF_YEAR = 0x00000126
    TPM2_PT_PS_YEAR = 0x00000127
    TPM2_PT_SPLIT_MAX = 0x00000128
    TPM2_PT_TOTAL_COMMANDS = 0x00000129
    TPM2_PT_LIBRARY_COMMANDS = 0x0000012a
    TPM2_PT_VENDOR_COMMANDS = 0x0000012b
    TPM2_PT_NV_BUFFER_MAX = 0x0000012c
    TPM2_PT_MODES = 0x0000012d
    TPM2_PT_MAX_CAP_BUFFER = 0x0000012e
    # TPM2_PT_VAR = 0x00000200
    TPM2_PT_PERMANENT = 0x00000200
    TPM2_PT_STARTUP_CLEAR = 0x00000201
    TPM2_PT_HR_NV_INDEX = 0x00000202
    TPM2_PT_HR_LOADED = 0x00000203
    TPM2_PT_HR_LOADED_AVAIL = 0x00000204
    TPM2_PT_HR_ACTIVE = 0x00000205
    TPM2_PT_HR_ACTIVE_AVAIL = 0x00000206
    TPM2_PT_HR_TRANSIENT_AVAIL = 0x00000207
    TPM2_PT_HR_PERSISTENT = 0x00000208
    TPM2_PT_HR_PERSISTENT_AVAIL = 0x00000209
    TPM2_PT_NV_COUNTERS = 0x0000020a
    TPM2_PT_NV_COUNTERS_AVAIL = 0x0000020b
    TPM2_PT_ALGORITHM_SET = 0x0000020c
    TPM2_PT_LOADED_CURVES = 0x0000020d
    TPM2_PT_LOCKOUT_COUNTER = 0x0000020e
    TPM2_PT_MAX_AUTH_FAIL = 0x0000020f
    TPM2_PT_LOCKOUT_INTERVAL = 0x00000210
    TPM2_PT_LOCKOUT_RECOVERY = 0x00000211
    TPM2_PT_NV_WRITE_RECOVERY = 0x00000212
    TPM2_PT_AUDIT_COUNTER_0 = 0x00000213
    TPM2_PT_AUDIT_COUNTER_1 = 0x00000214

    def __repr__(self) -> str:
        """Represent the value in hexadecimal"""
        return f"<{self.name}: {self.value:#06x}>"


@enum.unique
class Tpm20PcrProperty(enum.IntEnum):
    """TPM2_PT_PCR: PCR properties in TPM 2.0"""
    TPM_PT_PCR_SAVE = 0x00000000
    TPM_PT_PCR_EXTEND_L0 = 0x00000001
    TPM_PT_PCR_RESET_L0 = 0x00000002
    TPM_PT_PCR_EXTEND_L1 = 0x00000003
    TPM_PT_PCR_RESET_L1 = 0x00000004
    TPM_PT_PCR_EXTEND_L2 = 0x00000005
    TPM_PT_PCR_RESET_L2 = 0x00000006
    TPM_PT_PCR_EXTEND_L3 = 0x00000007
    TPM_PT_PCR_RESET_L3 = 0x00000008
    TPM_PT_PCR_EXTEND_L4 = 0x00000009
    TPM_PT_PCR_RESET_L4 = 0x0000000a
    TPM_PT_PCR_NO_INCREMENT = 0x00000011
    TPM_PT_PCR_DRTM_RESET = 0x00000012
    TPM_PT_PCR_POLICY = 0x00000013
    TPM_PT_PCR_AUTH = 0x00000014

    def __repr__(self) -> str:
        """Represent the value in hexadecimal"""
        return f"<{self.name}: {self.value:#x}>"


@enum.unique
class Tpm20HandleType(enum.IntEnum):
    """TPM_HT: Handle Types in TPM 2.0 (high byte of handles)"""
    TPM_HT_PCR = 0x00
    TPM_HT_NV_INDEX = 0x01
    TPM_HT_HMAC_SESSION = 0x02
    # TPM_HT_LOADED_SESSION = 0x02  # Only in TPM2_GetCapability
    TPM_HT_POLICY_SESSION = 0x03
    # TPM_HT_SAVED_SESSION = 0x03  # Only in TPM2_GetCapability
    TPM_HT_PERMANENT = 0x40
    TPM_HT_TRANSIENT = 0x80
    TPM_HT_PERSISTENT = 0x81
    TPM_HT_AC = 0x90  # Attached Component

    def __repr__(self) -> str:
        """Represent the value in hexadecimal"""
        return f"<{self.name}: {self.value:#04x}>"


@enum.unique
class Tpm20Handle(enum.IntEnum):
    """TPM_HANDLE / TPM_HC: Handles Values Constants in TPM 2.0

    Some reserved handles are defined by TCG in
    "Registry of reserved TPM 2.0 handles and localities"
    https://trustedcomputinggroup.org/wp-content/uploads/131011-Registry-of-reserved-TPM2-handles-and-localities.pdf
    https://trustedcomputinggroup.org/wp-content/uploads/RegistryOfReservedTPM2HandlesAndLocalities_v1p1_pub.pdf

    For the Endorsement: "TCG EK Credential Profile For TPM Family 2.0"
    https://trustedcomputinggroup.org/wp-content/uploads/TCG_IWG_EKCredentialProfile_v2p3_r2_pub.pdf

    For Intel TXT: "Intel Trusted Execution Technology", Appendix I
    https://www.intel.com/content/www/us/en/software-developers/intel-txt-software-development-guide.html
    and Intel TXT Version 013, Appendix J
    https://usermanual.wiki/Document/inteltxtsoftwaredevelopmentguide.1721028921/view
    (which explains that:
        Initially TXT used index handles selected from 0x180_xxxx and 0x140_xxxx ranges based on early version of TCG
        "Registry of reserved TPM 2.0 handles and localities" and TXT legacy. Later revision of TCG registry repurposed
        the above ranges creating mismatch between TXT practice and normative TCG document. Moreover, selection of TXT
        related NV index handles from the ranges assigned to OEMs and Users does not guarantee its conflict free usage.
        In order to address both issues Intel requested TCG to assign "Intel Reserved" range of indices 0x1C1_0100 -
        0x1C1_013F for TXT and other purposes out of range 0x1C1_xxxx reserved for component vendors.
    )
    and tboot: http://hg.code.sf.net/p/tboot/code/file/v2.0.0/tboot/common/tpm_20.c#l2722
    """
    # 0x00000000-0x00ffffff: Platform Control Register indices
    # PCR_FIRST = 0
    TPM_HC_PCR0 = 0x00000000
    TPM_HC_PCR1 = 0x00000001
    TPM_HC_PCR2 = 0x00000002
    TPM_HC_PCR3 = 0x00000003
    TPM_HC_PCR4 = 0x00000004
    TPM_HC_PCR5 = 0x00000005
    TPM_HC_PCR6 = 0x00000006
    TPM_HC_PCR7 = 0x00000007
    TPM_HC_PCR8 = 0x00000008
    TPM_HC_PCR9 = 0x00000009
    TPM_HC_PCR10 = 0x0000000a
    TPM_HC_PCR11 = 0x0000000b
    TPM_HC_PCR12 = 0x0000000c
    TPM_HC_PCR13 = 0x0000000d
    TPM_HC_PCR14 = 0x0000000e
    TPM_HC_PCR15 = 0x0000000f
    TPM_HC_PCR16 = 0x00000010
    TPM_HC_PCR17 = 0x00000011
    TPM_HC_PCR18 = 0x00000012
    TPM_HC_PCR19 = 0x00000013
    TPM_HC_PCR20 = 0x00000014
    TPM_HC_PCR21 = 0x00000015
    TPM_HC_PCR22 = 0x00000016
    TPM_HC_PCR23 = 0x00000017
    # Reserved Handles in Non-Volatile storage index
    # 0x01000000-0x013fffff: NV indexes defined by TPM Manufacturer
    # 0x01400000-0x017fffff: NV indexes defined by Platform Manufacturer
    # 0x01800000-0x01bfffff: NV indexes defined by Owner
    # 0x01c00000-0x01ffffff: NV indexes defined by TCG
    #   0x01c00000-0x01c07fff: Endorsement certificates, key templates, etc.
    #     0x01c00000-0x01c000ff: EK Certificates
    #       0x0x01c00002-0x01c00011: Low Range
    #       0x0x01c00012-0x01c000ff: High Range
    #     0x01c00100-0x01c001ff: EK Certificate Chains
    #     0x01c07f00-0x01c07fff: EK Policies (cf. TCG EK Credential Profile)
    #   0x01c08000-0x01c0ffff: Platform certificates, key templates, etc.
    #   0x01c10000-0x01c1ffff: Component OEM
    #     0x01c10100-0x01c1013f: Registered to Intel (and used by Intel TXT)
    #   0x01c20000-0x01c2ffff: TPM OEM
    #   0x01c30000-0x01c3ffff: Platform OEM
    #   0x01c40000-0x01c4ffff: PC-Client workgroup
    #   0x01c50000-0x01c5ffff: Server workgroup
    #   0x01c60000-0x01c6ffff: Virtualized Platform workgroup
    #   0x01c70000-0x01c7ffff: MPWG
    #   0x01c80000-0x01c8ffff: Embedded workgroup
    TPM_OLD_NV_INTEL_TXT_LAUNCH_POLICY = 0x01200001  # Policy for Verified Launch (Intel TXT, old)
    TPM_OLD_NV_INTEL_TXT_LAUNCH_ERROR = 0x01200002  # Launch error (Intel TXT, old)
    TPM_OLD_NV_INTEL_TXT_PLATFORM_OWNER = 0x01400001  # LCP Platform Owner (Intel TXT "PO", old)
    # Microsoft uses NV index 0x01410004:
    # - in C:\Windows\System32\TpmCoreProvisioning.dll:
    #   - Tpm2NVDefineIndex creates the index:
    #     - attributes=0x2020002 (ownerwrite|ownerread|no_da)
    #     - auth policy empty
    #     - dataSize=8
    #     => name 000b631f619fb359741cb0122e145448c1606e90d57b44e45cd3843da80c6e27566c
    #   - after the index is written, attributes=0x22020002
    #     => name 000b866c4dee2765bcf46fd56dc1f297b1d4e39e3e3bfd8d6731c62d4e447ce87b4b
    #   - Tpm2NVReadPublic reads the public part of the index
    #   - Tpm2NVWriteToIndex writes the content
    #   - CTpmCoreClass20::WriteFirmwareVersionToNV write 8 bytes to the content,
    #     matching the firmware version with properties TPM2_PT_FIRMWARE_VERSION_{1,2}
    #   - Tpm2NVReadIndex reads the index
    #   - CTpmCoreClass20::CopyFirmwareVersionFromNvToRegistry copies the value in registry key
    #     HKEY_LOCAL_MACHINE System\CurrentControlSet\Services\TPM\WMI\FirmwareVersionAtLastProvision
    #     (if TpmRegDriverPersistedData tells to write such a key)
    TPM_NV_WINDOWS_NV_INDEX = 0x01410004
    # Intel defines some indexes in Kernelflinger:
    # https://github.com/intel/kernelflinger/blob/9a84515a0f2b6b5697c49c770513b8c432b6f9d4/libkernelflinger/tpm2_security.c  # noqa
    TPM_NV_INTEL_KERNELFLINGER_TRUSTYOS_SEED = 0x01500047
    TPM_NV_INTEL_KERNELFLINGER_BOOTLOADER = 0x01500048
    TPM_OLD_NV_INTEL_TXT_PLATFORM_SUPPLIER = 0x01800001  # LCP Platform Supplier (Intel TXT "PS", old)
    TPM_OLD_NV_INTEL_TXT_LAUNCH_AUXILIARY = 0x01800003  # Launch Auxiliary (Intel TXT "AUX", old)
    TPM_OLD_NV_INTEL_TXT_SGX_SVN = 0x01800004  # SGX Software Version Number (Intel TXT "SGX", old)
    # Microsoft uses NV index 0x01880001:
    # - in C:\Windows\System32\TpmCoreProvisioning.dll:
    #   - TpmCore20::CreateWindowsNvBits2 -> TpmW8ApiCreateOrVerifyWindowsNvBits20 creates the index:
    #     - attributes=0x61028 (policywrite|bits|writeall|ownerread|authread)
    #     - auth policy=0c8df0cf0169c38828c8fa4c0ff37a548c23c041aeecd2a12ca740d501d620b7
    #     - dataSize=8
    #     => name 000bc8305aa4aa4cc6c279b5c104bbcd212c02602b604b9a749f6ef6830feea58fbe
    #   - after the index is written, attributes=0x20061028
    #     => name 000bd5e41629f4d1ee7b318d4a3e7eae93b0d11e72ffe71e5478f11461c49fa2e6f2
    #   - NvbpCheckWindowsNvBitsState -> ScpGetTpmNvIndexState checks that the index exists
    #   - TpmCore20::ReadWindowsNvBit2 -> TpmW8ApiReadWindowsNvBit20 reads a bit of the content (big endian)
    #     => bit 0 is '00 00 00 00 00 00 00 01'
    #   - TpmCore20::SetWindowsNvBit2 -> TpmW8ApiSetWindowsNvBit20 sets a bit of the content
    #   - bit 0 is the "Legacy DA Parameters bit"
    # - in C:\Windows\System32\tcblaunch.exe:
    #   - TpmW8ApiCreateOrVerifyWindowsNvBits20
    #   - bit 1 is to lock index 0x01880002 (for DRTM SVN)
    TPM_NV_WINDOWS_NV_BITS = 0x01880001
    # Microsoft uses NV index 0x01880002:
    # - in C:\Windows\System32\tcblaunch.exe:
    #   - SvnpCheckCreateDrtmSvnIndex creates the index:
    #     - attributes=0x2061028 (policywrite|bits|writeall|ownerread|authread|no_da)
    #     - auth policy=fb204f312abaaac0980ce9fbbf5260788c7c7b6d4b68b6ce0845750c761511ed
    #     - dataSize=8
    #     => name 000b92862e1e0748f9d25fea6f651bca5e92a83a342ee88a21f85fb0ba49ca7d98cd
    #   - after the index is written, attributes=0x22061028
    #     => name 000b56094638c94535195b5f577a5c007401de262ca8b90aeaa0433f8471ae5829f6
    #   - TpmApiReadDrtmSvnCounter20 reads the content (using owner authorization) and count its bits
    #   - SvnpSetDrtmSvnValue writes the content, setting it to 1
    #   - BlSiPerformDrtmSvnCheck calls the previous functions
    #   - Tpm20pExecDirectPolicyAuthorization performs Policy commands according to TPM_API_PA_BLOB information:
    #     - validates PCR values (TPM2_CC_PolicyPCR)
    #     - ensures that the SVN is lower than a threshold (TPM2_CC_PolicyNV with TPM_EO_UNSIGNED_LE = 0x0009)
    #     - in order to use an object
    #   - Tpm20pExecPolicyAuthorize uses TPM2_CC_PolicyAuthorize to authenticate a new policy,
    #     for example to unseal data
    TPM_NV_WINDOWS_DRTM_SVN = 0x01880002
    # Microsoft uses NV index 0x01880011:
    # - in C:\Windows\System32\TpmCoreProvisioning.dll:
    #   - Tpm2SetLockoutPolicy -> CreateLockoutPolicyNVHash creates the index:
    #     - attributes=0x60006 (ownerwrite|authwrite|ownerread|authread)
    #     - auth policy empty
    #     - dataSize=32
    #     => name 000bff5823fccc7b8df28fd0e8f94318a3c21bf18d7c4d5eeec121562a103d4456fb
    #   - after the index is written, attributes=0x20060006
    #     => name 000b51a55522eb40aa7f66605334f0814377c886126edb72391675ec8132258398ad
    #   - content: 89d26cf200e9169047cfcb7a597b23e647f336a9a45c2aa09068cc370d2606f5
    #     which is a policyOR between TPM2_CC_Clear and a succession of policies
    #     that check that bit 0 of NV Index 0x01880001 becomes set and that
    #     TPM2_CC_DictionaryAttackParameters(RH_LOCKOUT, newMaxTries=32, newRecoveryTime=7200, lockoutRecovery=86400)
    #     is executed.
    #   - Tpm2SetLockoutPolicy runs SetPrimaryPolicy(TPM_RH_LOCKOUT, policy)
    #   - TpmCore20::CanClearUsingAuthPolicy20 checks that the content is the expected policy
    #   - TpmCore20::ClearUsingAuthPolicy20 clears the TPM using the policy for TPM2_CC_Clear
    TPM_NV_WINDOWS_LOCKOUT_POLICY = 0x01880011
    TPM_RESERVED_HANDLE_RSA2048_EK_CERT = 0x01c00002  # RSA 2048 EK Certificate
    TPM_RESERVED_HANDLE_RSA2048_EK_NONCE = 0x01c00003  # RSA 2048 EK Nonce
    TPM_RESERVED_HANDLE_RSA2048_EK_TEMPLATE = 0x01c00004  # RSA 2048 EK Template
    TPM_RESERVED_HANDLE_ECCP256_EK_CERT = 0x01c0000a  # ECC NIST P256 EK Certificate
    TPM_RESERVED_HANDLE_ECCP256_EK_NONCE = 0x01c0000b  # ECC NIST P256 EK Nonce
    TPM_RESERVED_HANDLE_ECCP256_EK_TEMPLATE = 0x01c0000c  # ECC NIST P256 EK Template
    TPM_RESERVED_HANDLE_HI_RSA2048_EK_CERT = 0x01c00012  # RSA 2048 EK Certificate in High Range
    TPM_RESERVED_HANDLE_HI_ECCP256_EK_CERT = 0x01c00014  # ECC NIST P256 EK Certificate in High Range
    TPM_RESERVED_HANDLE_HI_ECCP384_EK_CERT = 0x01c00016  # ECC NIST P384 EK Certificate in High Range
    TPM_RESERVED_HANDLE_HI_ECCP384_EK_TEMPLATE = 0x01c00017  # ECC NIST P384 EK Template in High Range
    TPM_RESERVED_HANDLE_HI_ECCP521_EK_CERT = 0x01c00018  # ECC NIST P521 EK Certificate in High Range
    TPM_RESERVED_HANDLE_HI_ECCSM2P256_EK_CERT = 0x01c0001a  # ECC SM2 P256 EK Certificate in High Range
    TPM_RESERVED_HANDLE_HI_RSA3072_EK_CERT = 0x01c0001c  # RSA 3072 EK Certificate in High Range
    TPM_RESERVED_HANDLE_HI_RSA4096_EK_CERT = 0x01c0001e  # RSA 4096 EK Certificate in High Range
    TPM_RESERVED_HANDLE_EK_NV_POLICY_SHA256 = 0x01c07f01  # Policy Index I-1 with nameAlg = SHA256 in https://trustedcomputinggroup.org/wp-content/uploads/TCG_IWG_Credential_Profile_EK_V2.1_R13.pdf  # noqa
    TPM_RESERVED_HANDLE_EK_NV_POLICY_SHA384 = 0x01c07f02  # Policy Index I-2 with nameAlg = SHA384
    TPM_RESERVED_HANDLE_EK_NV_POLICY_SHA512 = 0x01c07f03  # Policy Index I-3 with nameAlg = SHA512
    TPM_RESERVED_HANDLE_EK_NV_POLICY_SM3_256 = 0x01c07f04  # Policy Index I-4 with nameAlg = SM3_256
    TPM_RESERVED_HANDLE_PLATFORM_CERTIFICATE = 0x01c08000  # Platform certificate
    # Intel uses 0x01c10100 for SGX "EPID (Enhanced Privacy ID) Membership Credential":
    # https://github.com/intel/linux-sgx/blob/sgx_2.15/external/epid-sdk/epid/member/src/startup.c#L43
    # https://github.com/intel/linux-sgx/blob/sgx_2.15/external/epid-sdk/epid/member/src/provision_join.c#L36
    # https://github.com/intel/linux-sgx/blob/sgx_2.15/external/epid-sdk/epid/member/src/provision_bulk.c#L36
    # https://github.com/intel/linux-sgx/blob/sgx_2.15/external/epid-sdk/epid/member/src/storage.c
    TPM_NV_INTEL_SGX_EPID_MEMBERSIP_CREDENTIAL = 0x01c10100  # SGX EPID Membership Credential
    TPM_NV_INTEL_TXT_LAUNCH_AUXILIARY = 0x01c10102  # Launch Auxiliary (Intel TXT "AUX")
    TPM_NV_INTEL_TXT_PLATFORM_SUPPLIER = 0x01c10103  # LCP Structure for Platform Supplier (Intel TXT "PS")
    TPM_NV_INTEL_TXT_SGX_SVN = 0x01c10104  # SGX Software Version Number (Intel TXT "SGX")
    TPM_NV_INTEL_TXT_PLATFORM_OWNER = 0x01c10106  # LCP Platform Owner (Intel TXT "PO")
    TPM_NV_INTEL_TXT_LAUNCH_POLICY = 0x01c10131  # Policy for Verified Launch (Intel TXT)
    TPM_NV_INTEL_TXT_LAUNCH_ERROR = 0x01c10132  # Launch error (Intel TXT)
    # https://learn.microsoft.com/en-us/windows/security/threat-protection/windows-defender-system-guard/how-hardware-based-root-of-trust-helps-protect-windows  # noqa
    # states that "Platform firmware must set up a TPM NV index for use by the OS with: Handle: 0x01C101C0"
    TPM_NV_MICROSOFT = 0x01c101c0  # TPM NV Index for use by Microsoft
    TPM_RESERVED_HANDLE_IDEVID_CERT = 0x01c90000  # IDevID Certificate (Initial Device Identifier)
    # 0x02000000-0x02ffffff: HMAC sessions
    TPM_HMAC_SESSION_0 = 0x02000000
    # TPM_RH: Resource Handle
    TPM_RH_SRK = 0x40000000  # SRK = Storage Root Key
    TPM_RH_OWNER = 0x40000001  # handle references the Storage Primary Seed (SPS), the ownerAuth, and the ownerPolicy
    TPM_RH_REVOKE = 0x40000002
    TPM_RH_TRANSPORT = 0x40000003
    TPM_RH_OPERATOR = 0x40000004
    TPM_RH_ADMIN = 0x40000005
    TPM_RH_EK = 0x40000006  # EK = Endorsement Key (Root Trust for Reporting, RTR)
    TPM_RH_NULL = 0x40000007  # a handle associated with the null hierarchy, an EmptyAuth authValue, and an Empty Policy authPolicy  # noqa
    TPM_RH_UNASSIGNED = 0x40000008  # value reserved to the TPM to indicate a handle location that has not been initialized or assigned  # noqa
    TPM_RS_PW = 0x40000009  # authorization value used to indicate a password authorization session
    TPM_RH_LOCKOUT = 0x4000000a  # references the authorization associated with the dictionary attack lockout reset  # noqa
    TPM_RH_ENDORSEMENT = 0x4000000b  # references the Endorsement Primary Seed (EPS), endorsementAuth, and endorsementPolicy  # noqa
    TPM_RH_PLATFORM = 0x4000000c  # references the Platform Primary Seed (PPS), platformAuth, and platformPolicy
    TPM_RH_PLATFORM_NV = 0x4000000d  # for phEnableNV
    #   0x40000010-0x4000010f: range of authorization values that are vendor specific.
    #     A TPM may support any of the values in this range as are needed for vendor-specific purposes.
    #     For example used in tboot: http://hg.code.sf.net/p/tboot/code/file/v2.0.0/tboot/common/policy.c#l160
    TPM_RH_AUTH_00 = 0x40000010
    TPM_RH_AUTH_FF = 0x4000010f
    #   0x40000110-0x4000011f: range of authenticated timers
    TPM_RH_ACT_0 = 0x40000110
    TPM_RH_ACT_1 = 0x40000111
    TPM_RH_ACT_2 = 0x40000112
    TPM_RH_ACT_3 = 0x40000113
    TPM_RH_ACT_4 = 0x40000114
    TPM_RH_ACT_5 = 0x40000115
    TPM_RH_ACT_6 = 0x40000116
    TPM_RH_ACT_7 = 0x40000117
    TPM_RH_ACT_8 = 0x40000118
    TPM_RH_ACT_9 = 0x40000119
    TPM_RH_ACT_A = 0x4000011a
    TPM_RH_ACT_B = 0x4000011b
    TPM_RH_ACT_C = 0x4000011c
    TPM_RH_ACT_D = 0x4000011d
    TPM_RH_ACT_E = 0x4000011e
    TPM_RH_ACT_F = 0x4000011f
    # 0x80000000-0x80ffffff: Transient handles
    TPM_TRANSIENT_HANDLE_0 = 0x80000000
    # Reserved Handles in persistent objects
    # 0x81000000-0x817fffff: persistent objects with auth TPM_RH_OWNER
    #   0x81000000-0x8100ffff: Storage primary keys
    #     0x81000001: RSA SRK
    #     0x81000002: Maybe AK (Attestation Key) as it uses signOrEncrypt attribute
    #       instead of decrypt (compared to SRK) and as some code refers to AIK, such as:
    #       https://github.com/ms-iot/security/blob/cd5d215d335771dac83dfbb81c359b42eeaecb5a/Urchin/T2T/T2T.cpp#L1024
    #       Older TCG guidance suggests that 0x81000002 was an EC SRK,
    #       and that AK uses handle 0x81010002 (in Endorsement primary keys)
    #       Microsoft uses 0x81000002 to store a "WindowsAIK"
    TPM_RESERVED_HANDLE_SRK_0 = 0x81000000
    TPM_RESERVED_HANDLE_SRK_1 = 0x81000001
    TPM_RESERVED_HANDLE_SRK_2 = 0x81000002
    TPM_RESERVED_HANDLE_SRK_3 = 0x81000003
    TPM_RESERVED_HANDLE_SRK_4 = 0x81000004
    #   0x81010000-0x8101ffff: Endorsement primary keys
    TPM_RESERVED_HANDLE_EK = 0x81010001
    TPM_RESERVED_HANDLE_EK_ECC_SECP384R1 = 0x81010016  # ECC NIST P384 public key, cf. https://github.com/stefanberger/swtpm/commit/d65f5ae13be66d1de5030004b5ca70e7835d23da  # noqa
    TPM_RESERVED_HANDLE_IDEVID_KEY = 0x81020000  # IDevID Key (Initial Device Identifier)
    # 0x81800000-0x81ffffff: persistent objects with auth TPM_RH_PLATFORM
    #   0x81800000-0x8180ffff: Platforms primary keys
    TPM_RESERVED_HANDLE_PLATFORM_KEY_0 = 0x81800000
    TPM_RESERVED_HANDLE_PLATFORM_KEY_1 = 0x81800001

    def __repr__(self) -> str:
        """Represent the value in hexadecimal"""
        return f"<{self.name}: {self.value:#010x}>"

    def __str__(self) -> str:
        return f"{self.name} ({self.value:#010x})"


# Well-known Enhanced Authorization policy digests in TPM 2.0
# From https://github.com/fishilico/shared/blob/master/python/crypto/tpm_ea_policy.py
WELL_KNOWN_EA_POLICIES: Mapping[str, str] = {
    '8fcd2169ab92694e0c633f1ab772842b8241bbc20288981fc7ac1eddc1fddb0e': 'PolicyAuthValue()',
    '1d2dc485e177ddd0a40a344913ceeb420caa093c42587d2e1b132b157ccb5db0': 'PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial)',  # noqa
    'ddee6af14bf3c4e8127ced87bcf9a57e1c0c8ddb5e67735c8505f96f07b8dbb8': 'PolicyLocality(ONE)',
    '07039b45baf2cc169b0d84af7c53fd1622b033df0a5dcda66360aa99e54947cd': 'PolicyLocality(THREE, FOUR)',
    '3c326323670e28ad37bd57f63b4cc34d26ab205ef22f275c58d47fab2485466e': 'PolicyNvWritten(NO)',
    'f7887d158ae8d38be0ac5319f37a9e07618bf54885453c7a54ddb0c6a6193beb': 'PolicyNvWritten(YES)',
    '84b506c91f205e06abd6f83f269d8d8011d495e09214a40fe32b4660301dda09': 'PolicyPCR(0,1,2,3 ZEROS)',
    '0d7c6747b1b9facbba03492097aa9d5af792e5efc07346e05f9daa8b3d9e13b5': 'PolicyPhysicalPresence()',
    '0d84f55daf6e43ac97966e62c9bb989d3397777d25c5f749868055d65394f952': 'PolicySecret(RH_OWNER)',
    'a0cab3762662675a14347a87504584a08e1002525d91371c3289224bea3ff4af': 'PolicySecret(RH_LOCKOUT)',
    '837197674484b3f81a90cc8d46a5d724fd52d76e06520b64f2a1da1b331469aa': 'PolicySecret(RH_ENDORSEMENT)',
    '8bbf2266537c171cb56e403c4dc1d4b64f432611dc386e6f532050c3278c930e143e8bb1133824ccb431053871c6db53': 'PolicySecret_SHA384(RH_ENDORSEMENT)',  # noqa
    '1e3b76502c8a1425aa0b7b3fc646a1b0fae063b03b5368f9c4cddecaff0891dd682bac1a85d4d832b781ea451915de5fc5bf0dc4a1917cd42fa041e3f998e0ee': 'PolicySecret_SHA512(RH_ENDORSEMENT)',  # noqa
    'c8b1292eff2ce7a3fa0fb1aed9ad254fb03fc01c9abc2dd1985161ba6811bdc7': 'PolicySecret(RH_PLATFORM)',
    '0c8df0cf0169c38828c8fa4c0ff37a548c23c041aeecd2a12ca740d501d620b7': 'PolicySecret(RH_LOCKOUT) OR PolicyNvWritten(YES)',  # noqa
    '3767e2edd43ff45a3a7e1eaefcef78643dca964632e7aad82c673a30d8633fde': 'PolicyAuthorizeNV(0x01c07f01)',
    'd6032ce61f2fb3c240eb3cf6a33237ef2b6a16f4293c22b455e261cffd217ad5b4947c2d73e63005eed2dc2b3593d165': 'PolicyAuthorizeNV_SHA384(0x01c07f02)',  # noqa
    '589ee1e146544716e8deafe6db247b01b81e9f9c7dd16b814aa159138749105fba5388dd1dea702f35240c184933121e2c61b8f50d3ef91393a49a38c3f73fc8': 'PolicyAuthorizeNV_SHA512(0x01c07f03)',  # noqa
    'ca3d0a99a2b93906f7a3342414efcfb3a385d44cd1fd459089d19b5071c0b7a0': 'PolicySecret(RH_ENDORSEMENT) OR PolicyAuthorizeNV(0x01c07f01)',  # noqa
    'b26e7d28d11a50bc53d882bcf5fd3a1a074148bb35d3b4e4cb1c0ad9bde419cacb47ba09699646150f9fc000f3f80e12': 'PolicySecret_SHA384(RH_ENDORSEMENT) OR PolicyAuthorizeNV_SHA384(0x01c07f02)',  # noqa
    'b8221ca69e8550a4914de3faa6a18c072cc01208073a928d5d66d59ef79e49a429c41a6b269571d57edb25fbdb1838425608b413cd616a5f6db5b6071af99bea': 'PolicySecret_SHA512(RH_ENDORSEMENT) OR PolicyAuthorizeNV_SHA512(0x01c07f03)',  # noqa
    '06c7d805ad3bec1106502a44c6b2e3b36d157750e8efca1fff998c874a7664c5': 'PolicyLocality(THREE, FOUR) AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial)',  # noqa
    'ef9a26fc22d1ae8cecff59e9481ac1ec533dbe228bec6d17930f4cb2cc5b9724': 'PolicyLocality(THREE, FOUR) OR (PolicyLocality(THREE, FOUR) AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial))',  # noqa
    'dffdb6c8eafcbe691e358882b18703121eab40de2386f7a8e7b4a06591e1f0ee': '(PolicyLocality(THREE, FOUR) AND PolicyCommandCode(TPM2_CC_NV_Write)) OR (PolicyLocality(THREE, FOUR) AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial))',  # noqa
    'cb45c81ff34bcf0afb9e1a8029fa231c8727303c0922dcce684be3db817c20e1': 'PolicyAuthorize(MSFT_DRTM_AUTH_BLOB_SigningKey) OR (PolicyAuthorize(MSFT_DRTM_AUTH_BLOB_SigningKey) AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial))',  # noqa
    '98cbaba62e682c148a202acad533d9d63f85a68392fe1b66648d1c491aef1b3a': 'PolicyAuthValue() AND PolicyCommandCode(TPM2_CC_ObjectChangeAuth)',  # noqa
    '363ac945b6457c47c31f3355dba0db27de8db213d6250c6bf79685003f9fe7ab': 'PolicyAuthValue() AND PolicyCommandCode(TPM2_CC_NV_ChangeAuth)',  # noqa
    'a13eb52d274deb81431f5adc2e40f7e7c4094abba5d325da741e7afc4117901f': 'PolicyCommandCode(TPM2_CC_NV_Read) OR PolicyNvWritten(NO)',  # noqa
    '3355408f64a7ebe10ac90dab8a4405eef7c8f164eaa9034220c961edf1dbb680': 'PolicyCommandCode(TPM2_CC_NV_Write) AND PolicyAuthValue()',  # noqa
    'c1ef6962e6e15b2fda5026efca791ae9272bd87338c6fcacdf2ccca45d03d7be': 'PolicyCommandCode(TPM2_CC_NV_Read) OR (PolicyCommandCode(TPM2_CC_NV_Write) AND PolicyAuthValue())',  # noqa
    '310a0eb2a2c3ebd96c39d954d2865a80c7925ab8996c5d73d0bb723756ec42bf': 'PolicyCounterTimer(safe=YES)',
    '7f48cceb9fae31e1662d7f8306fdd1c4f81d2b8d3b0e9d82fdec42949ad5257e': 'PolicyCounterTimer(time<60000)',
    '47a3a4e8c7567b07e33aad03b2adca52b02c2f96cd0ea41073d67f3e3f80eaf8': 'PolicyCounterTimer(clock<60000)',
    'cd397212fec5f9c77c0f9eff5d6878d7d5d43fe0f0ef4bfd9c9edf2adc7ab30f': 'PolicyCounterTimer(resets<=42)',
    '12d20ca971bf0eaafae3c58f4666013cab78654330ef8c95a3e7fc9d87c9658d': 'PolicyCounterTimer(restarts<=42)',
    'e529f5d6112872954e8ed6605117b757e237c6e19513a949fee1f204c458023a': 'PolicyCommandCode(TPM2_CC_ObjectChangeAuth) AND PolicyAuthValue()',  # noqa
    'af2ca569699c436a21006f1cb8a2756c98bc1c765a3559c5fe1c3f5e7228a7e7': 'PolicyCommandCode(TPM2_CC_Certify) AND PolicyAuthValue()',  # noqa
    'c413a847b11112b1cbddd4eca4daaa15a1852c1c3bba57461d257605f3d5af53': 'PolicyCommandCode(TPM2_CC_ActivateCredential) AND PolicyAuthValue()',  # noqa
    '048e9a3ace08583f79f344ff785bbea9f07ac7fa3325b3d49a21dd5194c65850': 'PolicyCommandCode(TPM2_CC_Certify)',
    '9dffcbf36c383ae699fb9868dc6dcb89d7153884be2803922c124158bfad22ae': 'PolicyAuthValue() OR (PolicyCommandCode(TPM2_CC_ObjectChangeAuth) AND PolicyAuthValue()) OR (PolicyCommandCode(TPM2_CC_Certify) AND PolicyAuthValue()) OR (PolicyCommandCode(TPM2_CC_ActivateCredential) AND PolicyAuthValue()) OR PolicyCommandCode(TPM2_CC_Certify)',  # noqa
    'c4dfabceda8de836c95661952892b1def7203afb46fefec43ffcfc93be540730': 'PolicyCommandCode(TPM2_CC_Clear)',
    'fca06036f1972d4d6069c625455d5c9f0d413f6c6b3bf5fd85314809dea99e8e': 'PolicyNV(windows_nvbits_0x01880001: bit 0 clear)',  # noqa
    '9711091a3aa56173f59b73b6d27d446fea52fd6fcefbc51bfa9271b09a206a87': 'PolicyNV(windows_nvbits_0x01880001: bit 0 clear->set)',  # noqa
    '268b6bac0debb1e5a1659d35f0d28421c9f62b8ea1a3326e9b71dd5ba295214a': 'PolicyNV(windows_nvbits_0x01880001: bit 0 clear->set) AND PolicyCpHash(TPM2_CC_DictionaryAttackParameters(RH_LOCKOUT,32,7200,86400))',  # noqa
    '89d26cf200e9169047cfcb7a597b23e647f336a9a45c2aa09068cc370d2606f5': 'PolicyCommandCode(TPM2_CC_Clear) OR (PolicyNV(windows_nvbits_0x01880001: bit 0 clear->set) AND PolicyCpHash(TPM2_CC_DictionaryAttackParameters(RH_LOCKOUT,32,7200,86400)))',  # noqa
    '21784fe1fc7d5496c488e4fa33dd95f82fce48f440a75d882f8c8a44bc12018a': 'PolicyNV(windows_nvbits_0x01880001: bit 1 clear)',  # noqa
    '983d228d2827649da8e461587538d741991aef5cd1b5ceae869242f537535ce1': 'PolicyNvWritten(YES) AND PolicyLocality(TWO, THREE, FOUR)',  # noqa
    'fb204f312abaaac0980ce9fbbf5260788c7c7b6d4b68b6ce0845750c761511ed': 'PolicyNV(windows_nvbits_0x01880001: bit 1 clear) OR (PolicyNvWritten(YES) AND PolicyLocality(TWO, THREE, FOUR))',  # noqa
    '093ceb41181d47808862d7946268ee6a17a10e3d1b79b32351bc56e4beaceff0': 'PolicyPCR(0 is ZEROS)',
    '4b44fc4192db5ad7167e0135708fd374890a06bfb56317df01f24f2226542a3f': 'PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial) AND PolicyPCR(0 is ZEROS)',  # noqa
    'cb5c8014e27a5f7586aae42db4f9776a977bcbc952ca61e33609da2b2c329418': 'PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial) AND PolicyPCR(0 is extended from SHA1(00,01,00))',  # noqa
    'e6ef4f0296ac3ef0f53906480985b1be8058e0e517e5f74a5b8a415efe339d87': 'PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial) AND PolicyPCR(0 is extended from SHA1(01,01,00))',  # noqa
    '44447900cbb83f5b15765650ef96980a2b966ea909044a01b85fa54a96fc5984': '(PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial) AND PolicyPCR(0 is ZEROS)) OR (PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial) AND PolicyPCR(0 is extended from SHA1(00,01,00))) OR (PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial) AND PolicyPCR(0 is extended from SHA1(01,01,00)))',  # noqa
    '22030b7e0bb1f9d50657571ee2f7fce1eb91990c8b8ae977fcb3f158b03eba96': 'zeros OR 771CEB9D52438BB72009A316750DEA301A6A62ED3835A18ED9AF89F9EF36EBE4',  # noqa
    'b75ce1946f78df8baa426918db09318017e6b38d048c954e05c2c4f34bd44060': '(zeros OR 771CEB9D52438BB72009A316750DEA301A6A62ED3835A18ED9AF89F9EF36EBE4) AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial)',  # noqa
    'c001c8000210d0faa4f4f4f8a78ef4f8264e6f8555340d2f04180f8cf110ffdd': '((zeros OR 771CEB9D52438BB72009A316750DEA301A6A62ED3835A18ED9AF89F9EF36EBE4) AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial)) OR (zeros OR 771CEB9D52438BB72009A316750DEA301A6A62ED3835A18ED9AF89F9EF36EBE4) OR PolicyNvWritten(NO)',  # noqa
    'a4469af0287113e5d5eb95287d94bab42bd166a42dfa89fe91866e7034420805': 'FD516FA72051D00FA032B98DF1E2110A20C2766E49B5FB417621D5572601743A AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial)',  # noqa
    '9f970e88340b836b768e682db1be76ec3f4284282fddf64b05acf8fd2699a71c': '(FD516FA72051D00FA032B98DF1E2110A20C2766E49B5FB417621D5572601743A AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial)) OR FD516FA72051D00FA032B98DF1E2110A20C2766E49B5FB417621D5572601743A OR PolicyNvWritten(NO)',  # noqa
    'a8652ea8a9787937bc33c4164b58f07f8378bbae396875293a626048b5955c4c': '061408869C564D49F631C981EA9C303AA0B126671532CBA86ABBEDC73B8A5692 AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial)',  # noqa
    '6ce3379d380001bd45b1f6b3dffff39a03ca61bffb13feafc2f257196df49fe4': '(061408869C564D49F631C981EA9C303AA0B126671532CBA86ABBEDC73B8A5692 AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial)) OR 061408869C564D49F631C981EA9C303AA0B126671532CBA86ABBEDC73B8A5692 OR PolicyNvWritten(NO)',  # noqa
    '1169a46a813a8ccdd0f3066785207bb9b67afd3a6cd6dfe5c5aee120867a96df': 'Unknown (used by Intel TXT for LCP Platform Supplier and SGX SVN)',  # noqa
}


class ParsedBuffer:
    """A buffer being parsed, for example to unmarshal data"""
    def __init__(self, buffer: bytes, name: Optional[str] = None, ensure_end_on_exit: bool = True) -> None:
        self.buffer = buffer
        self.pos = 0
        self.name = name
        self.ensure_end_on_exit = ensure_end_on_exit

    def remaining_size(self) -> int:
        return len(self.buffer) - self.pos

    def remaining(self) -> bytes:
        return self.buffer[self.pos:]

    def read_remaining(self) -> bytes:
        value = self.buffer[self.pos:]
        self.pos = len(self.buffer)
        return value

    def assert_end(self) -> None:
        if self.pos != len(self.buffer):
            if self.name:
                raise RuntimeError(f"while parsing {self.name}, remaining {self.remaining_size()} bytes: {self.remaining().hex()}")  # noqa
            raise RuntimeError(f"remaining {self.remaining_size()} bytes: {self.remaining().hex()}")

    def __enter__(self) -> 'ParsedBuffer':
        return self

    def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
        if self.ensure_end_on_exit:
            self.assert_end()

    def read_bytes(self, size: int) -> bytes:
        assert size >= 0
        assert self.pos + size <= len(self.buffer)
        value = self.buffer[self.pos:self.pos + size]
        self.pos += size
        return value

    def read_bool(self) -> bool:
        value = self.read_bytes(1)
        if value == b"\x00":
            return False
        if value == b"\x01":
            return True
        raise ValueError(f"Unexpected serialized boolean {value!r}")

    def read_u8(self) -> int:
        return int.from_bytes(self.read_bytes(1), "big")

    def read_u16(self) -> int:
        return int.from_bytes(self.read_bytes(2), "big")

    def read_u32(self) -> int:
        return int.from_bytes(self.read_bytes(4), "big")

    def read_u64(self) -> int:
        return int.from_bytes(self.read_bytes(8), "big")

    def read_tpm2b(self) -> bytes:
        """Decode a TPM_2B structure, which includes a 16-bit size"""
        size = self.read_u16()
        return self.read_bytes(size)

    def read_tpm_algid(self) -> TpmAlgId:
        return TpmAlgId(self.read_u16())

    def read_tpm20_handle(self) -> Tpm20Handle:
        return Tpm20Handle(self.read_u32())


class OutputBuffer:
    """A buffer being written to"""
    def __init__(self) -> None:
        self.chunks = [b""]

    def finalize(self) -> bytes:
        return b"".join(self.chunks)

    def write_bytes(self, data: bytes) -> None:
        self.chunks.append(data)

    def write_u8(self, value: int) -> None:
        self.chunks.append(value.to_bytes(1, "big"))

    def write_u16(self, value: int) -> None:
        self.chunks.append(value.to_bytes(2, "big"))

    def write_u32(self, value: int) -> None:
        self.chunks.append(value.to_bytes(4, "big"))

    def write_u64(self, value: int) -> None:
        self.chunks.append(value.to_bytes(8, "big"))

    def write_tpm2b(self, buffer: bytes) -> None:
        self.write_u16(len(buffer))
        self.write_bytes(buffer)


@dataclass
class Tpm20BufferStruct:
    """Represent any TPM2B_... in TPM 2.0"""
    data: bytes

    @classmethod
    def from_buffer(cls, buf: ParsedBuffer) -> 'Tpm20BufferStruct':
        return cls(buf.read_tpm2b())

    def to_buffer(self, buf: OutputBuffer) -> None:
        buf.write_tpm2b(self.data)

    def print(self, indent: str = "") -> None:
        hexdump(self.data, indent=f"{indent}")


@dataclass
class Tpm20SymDefObjectStruct:
    """Represent a TPMT_SYM_DEF_OBJECT in TPM 2.0

    struct TPMT_SYM_DEF_OBJECT {
        TPMI_ALG_SYM_OBJECT algorithm; // TPM_ALG_ID = UINT16
        TPMU_SYM_KEY_BITS keyBits; // TPM_KEY_BITS = UINT16
        TPMU_SYM_MODE mode; // TPMI_ALG_SYM_MODE = TPM_ALG_ID = UINT16
    }
    """
    alg: TpmAlgId
    key_bits: Optional[int]
    mode: Optional[TpmAlgId]

    @classmethod
    def from_buffer(cls, buf: ParsedBuffer) -> 'Tpm20SymDefObjectStruct':
        alg = buf.read_tpm_algid()
        if alg == TpmAlgId.TPM_ALG_NULL:
            return cls(alg, None, None)
        if alg == TpmAlgId.TPM_ALG_AES:
            key_bits = buf.read_u16()
            mode = buf.read_tpm_algid()
            return cls(alg, key_bits, mode)
        raise NotImplementedError(f"Unimplemented parameters for symmetric algorithm {alg!r}")

    def to_buffer(self, buf: OutputBuffer) -> None:
        buf.write_u16(self.alg)
        if self.key_bits is not None:
            buf.write_u16(self.key_bits)
            assert self.mode is not None
            buf.write_u16(self.mode)

    def __str__(self) -> str:
        if self.alg == TpmAlgId.TPM_ALG_NULL:
            return f"{self.alg}"
        return f"{self.alg} with key_bits={self.key_bits}, mode={self.mode}"

    def print(self, indent: str = "") -> None:
        print(f"{indent}- symmetric: {self}")


@dataclass
class Tpm20KdfSchemeStruct:
    """Represent a TPMT_KDF_SCHEME in TPM 2.0

    struct TPMT_KDF_SCHEME {
        TPMI_ALG_KDF scheme; // TPM_ALG_ID = UINT16
        TPMU_KDF_SCHEME details; // TPMS_SCHEME_HASH = TPMI_ALG_HASH = TPM_ALG_ID = UINT16
    }
    """
    scheme: TpmAlgId
    details: Optional[TpmAlgId]

    @classmethod
    def from_buffer(cls, buf: ParsedBuffer) -> 'Tpm20KdfSchemeStruct':
        scheme = buf.read_tpm_algid()
        if scheme == TpmAlgId.TPM_ALG_NULL:
            return cls(scheme, None)
        raise NotImplementedError(f"Unimplemented parameters for KDF scheme {scheme!r}")

    def to_buffer(self, buf: OutputBuffer) -> None:
        buf.write_u16(self.scheme)
        if self.details is not None:
            buf.write_u16(self.details)

    def __str__(self) -> str:
        if self.scheme == TpmAlgId.TPM_ALG_NULL:
            return f"{self.scheme}"
        return f"{self.scheme} with details={self.details}"


@dataclass
class Tpm20KeyedHashSchemeStruct:
    """Represent a TPMT_KEYEDHASH_SCHEME in TPM 2.0

    struct TPMT_KEYEDHASH_SCHEME {
        TPMI_ALG_KEYEDHASH_SCHEME scheme; // TPM_ALG_ID = UINT16
        TPMU_SCHEME_KEYEDHASH details;
    }
    union TPMU_SCHEME_KEYEDHASH {
        TPMS_SCHEME_HMAC hmac; // TPMS_SCHEME_HASH = TPMI_ALG_HASH = TPM_ALG_ID = UINT16
        TPMS_SCHEME_XOR exclusiveOr;
    }
    struct TPMS_SCHEME_XOR {
        TPMI_ALG_HASH hashAlg; // TPM_ALG_ID = UINT16
        TPMI_ALG_KDF kdf; // TPM_ALG_ID = UINT16
    };
    """
    scheme: TpmAlgId
    hash_alg: Optional[TpmAlgId]
    kdf: Optional[TpmAlgId]

    @classmethod
    def from_buffer(cls, buf: ParsedBuffer) -> 'Tpm20KeyedHashSchemeStruct':
        scheme = buf.read_tpm_algid()
        if scheme == TpmAlgId.TPM_ALG_NULL:
            return cls(scheme, None, None)
        if scheme == TpmAlgId.TPM_ALG_HMAC:
            hash_alg = buf.read_tpm_algid()
            return cls(scheme, hash_alg, None)
        if scheme == TpmAlgId.TPM_ALG_XOR:
            hash_alg = buf.read_tpm_algid()
            kdf = buf.read_tpm_algid()
            return cls(scheme, hash_alg, kdf)
        raise NotImplementedError(f"Unimplemented parameters for Keyed Hash scheme {scheme!r}")

    def to_buffer(self, buf: OutputBuffer) -> None:
        buf.write_u16(self.scheme)
        if self.hash_alg is not None:
            buf.write_u16(self.hash_alg)
            if self.kdf is not None:
                buf.write_u16(self.kdf)
        else:
            assert self.kdf is None

    def __str__(self) -> str:
        if self.scheme == TpmAlgId.TPM_ALG_NULL:
            return f"{self.scheme}"
        if self.scheme == TpmAlgId.TPM_ALG_HMAC:
            return f"{self.scheme} with hash_alg={self.hash_alg}"
        return f"{self.scheme} with hash_alg={self.hash_alg}, kdf={self.kdf}"


@dataclass
class Tpm20RsaSchemeStruct:
    """Represent a TPMT_RSA_SCHEME in TPM 2.0

    struct TPMT_RSA_SCHEME {
        TPMI_ALG_RSA_SCHEME scheme; // TPM_ALG_ID = UINT16
        TPMU_ASYM_SCHEME details; // TPMS_SCHEME_SIGHASH = TPMI_ALG_HASH = TPM_ALG_ID = UINT16
    }
    """
    scheme: TpmAlgId
    details: Optional[TpmAlgId]

    @classmethod
    def from_buffer(cls, buf: ParsedBuffer) -> 'Tpm20RsaSchemeStruct':
        scheme = buf.read_tpm_algid()
        if scheme == TpmAlgId.TPM_ALG_NULL:
            return cls(scheme, None)
        if scheme == TpmAlgId.TPM_ALG_RSASSA:
            details = buf.read_tpm_algid()
            return cls(scheme, details)
        raise NotImplementedError(f"Unimplemented parameters for RSA scheme {scheme!r}")

    def to_buffer(self, buf: OutputBuffer) -> None:
        buf.write_u16(self.scheme)
        if self.details is not None:
            buf.write_u16(self.details)

    def __str__(self) -> str:
        if self.scheme == TpmAlgId.TPM_ALG_NULL:
            return f"{self.scheme}"
        if self.scheme == TpmAlgId.TPM_ALG_RSASSA:
            return f"{self.scheme} with halg={self.details}"
        return f"{self.scheme} with details={self.details}"


@dataclass
class Tpm20EccSchemeStruct:
    """Represent a TPMT_ECC_SCHEME in TPM 2.0

    struct TPMT_ECC_SCHEME {
        TPMI_ALG_ECC_SCHEME scheme; // TPM_ALG_ID = UINT16
        TPMU_SIG_SCHEME details; // TPMS_SCHEME_SIGHASH = TPMI_ALG_HASH = TPM_ALG_ID = UINT16
    }
    """
    scheme: TpmAlgId
    details: Optional[TpmAlgId]

    @classmethod
    def from_buffer(cls, buf: ParsedBuffer) -> 'Tpm20EccSchemeStruct':
        scheme = buf.read_tpm_algid()
        if scheme == TpmAlgId.TPM_ALG_NULL:
            return cls(scheme, None)
        if scheme == TpmAlgId.TPM_ALG_ECDSA:
            details = buf.read_tpm_algid()
            return cls(scheme, details)
        raise NotImplementedError(f"Unimplemented parameters for ECC scheme {scheme!r}")

    def to_buffer(self, buf: OutputBuffer) -> None:
        buf.write_u16(self.scheme)
        if self.details is not None:
            buf.write_u16(self.details)

    def __str__(self) -> str:
        if self.scheme == TpmAlgId.TPM_ALG_NULL:
            return f"{self.scheme}"
        if self.scheme == TpmAlgId.TPM_ALG_ECDSA:
            return f"{self.scheme} with halg={self.details}"
        return f"{self.scheme} with details={self.details}"


@dataclass
class Tpm20KeyedHashParmsStruct:
    """Represent a TPMS_KEYEDHASH_PARMS in TPM 2.0

    struct TPMS_KEYEDHASH_PARMS {
        TPMT_KEYEDHASH_SCHEME scheme;
    };
    """
    scheme: Tpm20KeyedHashSchemeStruct

    @classmethod
    def from_buffer(cls, buf: ParsedBuffer) -> 'Tpm20KeyedHashParmsStruct':
        scheme = Tpm20KeyedHashSchemeStruct.from_buffer(buf)
        return cls(scheme)

    def to_buffer(self, buf: OutputBuffer) -> None:
        self.scheme.to_buffer(buf)

    def print(self, indent: str = "") -> None:
        print(f"{indent}- scheme: {self.scheme}")


@dataclass
class Tpm20RsaParmsStruct:
    """Represent a TPMS_RSA_PARMS in TPM 2.0

    struct TPMS_RSA_PARMS {
        TPMT_SYM_DEF_OBJECT symmetric;
        TPMT_RSA_SCHEME scheme;
        TPMI_RSA_KEY_BITS keyBits; // TPM_KEY_BITS = UINT16
        UINT32 exponent;
    }

    Examples:
    * For a signature key:
        0010  symmetric.algorithm = TPM_ALG_NULL
        0014  scheme.scheme = TPM_ALG_RSASSA
        0004  scheme.details = TPM_ALG_SHA1
        0800  keyBits = 2048
        0000 0000  exponent = 0 (default exponent is 0x10001)
        0100  unique.TPM2B_PUBLIC_KEY_RSA.size = 256 bytes
        ...   modulus
    * For an encryption key:
        0006  symmetric.algorithm = TPM_ALG_AES
        0080  symmetric.keyBits = 128
        0043  symmetric.mode = TPM_ALG_CFB
        0010  scheme.scheme = TPM_ALG_NULL
        0800  keyBits = 2048
        0000 0000  exponent = 0 (default exponent is 0x10001)
        0100  unique.TPM2B_PUBLIC_KEY_RSA.size = 256 bytes
        ...   modulus
    """
    symmetric: Tpm20SymDefObjectStruct
    scheme: Tpm20RsaSchemeStruct
    key_bits: int
    exponent: int

    @classmethod
    def from_buffer(cls, buf: ParsedBuffer) -> 'Tpm20RsaParmsStruct':
        symmetric = Tpm20SymDefObjectStruct.from_buffer(buf)
        scheme = Tpm20RsaSchemeStruct.from_buffer(buf)
        key_bits = buf.read_u16()
        exponent = buf.read_u32()
        return cls(symmetric, scheme, key_bits, exponent)

    def to_buffer(self, buf: OutputBuffer) -> None:
        self.symmetric.to_buffer(buf)
        self.scheme.to_buffer(buf)
        buf.write_u16(self.key_bits)
        buf.write_u32(self.exponent)

    def print(self, indent: str = "") -> None:
        print(f"{indent}- symmetric: {self.symmetric}")
        print(f"{indent}- scheme: {self.scheme}")
        print(f"{indent}- key_bits: {self.key_bits}")
        if self.exponent == 0:
            print(f"{indent}- exponent: {self.exponent:#x} (default is 0x10001)")
        else:
            print(f"{indent}- exponent: {self.exponent:#x}")


@dataclass
class Tpm20EccParmsStruct:
    """Represent a TPMS_ECC_PARMS in TPM 2.0

    struct TPMS_ECC_PARMS {
        TPMT_SYM_DEF_OBJECT symmetric;
        TPMT_ECC_SCHEME scheme;
        TPMI_ECC_CURVE curveID; // TPM_ECC_CURVE = UINT16
        TPMT_KDF_SCHEME kdf;
    }

    Examples:
    * For a signature key:
        0010  symmetric.algorithm = TPM_ALG_NULL
        0018  scheme.scheme = TPM_ALG_ECDSA
        000b  scheme.details = TPM_ALG_SHA256
        0003  curve = TPM_ECC_NIST_P256
        0010  kdf.scheme = TPM_ALG_NULL
        0020  unique.TPMS_ECC_POINT.x.size = 32 bytes
        ...   point.x
        0020  unique.TPMS_ECC_POINT.y.size = 32 bytes
        ...   point.y
    * For an encryption key:
        0006  symmetric.algorithm = TPM_ALG_AES
        0080  symmetric.keyBits = 128
        0043  symmetric.mode = TPM_ALG_CFB
        0010  scheme.scheme = TPM_ALG_NULL
        0003  curve = TPM_ECC_NIST_P256
        0010  kdf.scheme = TPM_ALG_NULL
        0020  unique.TPMS_ECC_POINT.x.size = 32 bytes
        ...   point.x
        0020  unique.TPMS_ECC_POINT.y.size = 32 bytes
        ...   point.y
    """
    symmetric: Tpm20SymDefObjectStruct
    scheme: Tpm20EccSchemeStruct
    curve: Tpm20EccCurve
    kdf: Tpm20KdfSchemeStruct

    @classmethod
    def from_buffer(cls, buf: ParsedBuffer) -> 'Tpm20EccParmsStruct':
        symmetric = Tpm20SymDefObjectStruct.from_buffer(buf)
        scheme = Tpm20EccSchemeStruct.from_buffer(buf)
        curve = Tpm20EccCurve(buf.read_u16())
        kdf = Tpm20KdfSchemeStruct.from_buffer(buf)
        return cls(symmetric, scheme, curve, kdf)

    def to_buffer(self, buf: OutputBuffer) -> None:
        self.symmetric.to_buffer(buf)
        self.scheme.to_buffer(buf)
        buf.write_u16(self.curve)
        self.kdf.to_buffer(buf)

    def print(self, indent: str = "") -> None:
        print(f"{indent}- symmetric: {self.symmetric}")
        print(f"{indent}- scheme: {self.scheme}")
        print(f"{indent}- curve: {self.curve}")
        print(f"{indent}- KDF scheme: {self.kdf}")


@dataclass
class Tpm20EccPointStruct:
    """Represent a TPMS_ECC_POINT in TPM 2.0

    struct TPMS_ECC_POINT {
        TPM2B_ECC_PARAMETER x; // X coordinate
        TPM2B_ECC_PARAMETER y; // Y coordinate
    };
    """
    x: bytes
    y: bytes

    @classmethod
    def from_buffer(cls, buf: ParsedBuffer) -> 'Tpm20EccPointStruct':
        x = buf.read_tpm2b()
        y = buf.read_tpm2b()
        return cls(x, y)

    def to_buffer(self, buf: OutputBuffer) -> None:
        buf.write_tpm2b(self.x)
        buf.write_tpm2b(self.y)

    def print(self, indent: str = "") -> None:
        print(f"{indent}- x ({len(self.x)} bytes):")
        hexdump(self.x, indent=f"{indent}  ")
        print(f"{indent}- y ({len(self.y)} bytes):")
        hexdump(self.y, indent=f"{indent}  ")


@dataclass
class Tpm20PublicStruct:
    """Represent a TPMT_PUBLIC in TPM 2.0

    struct TPMT_PUBLIC {
        TPMI_ALG_PUBLIC type; // TPM_ALG_ID = UINT16
        TPMI_ALG_HASH nameAlg; // TPM_ALG_ID = UINT16
        TPMA_OBJECT objectAttributes;
        TPM2B_DIGEST authPolicy;
        TPMU_PUBLIC_PARMS parameters;
        TPMU_PUBLIC_ID unique;
    }
    As "parameters" and "unique" are variable-length and depends on the type,
    store them in the same field.
    struct TPMA_OBJECT {
        UINT32 reserved1            : 1;
        UINT32 fixedTPM             : 1;
        UINT32 stClear              : 1;
        UINT32 reserved4            : 1;
        UINT32 fixedParent          : 1;
        UINT32 sensitiveDataOrigin  : 1;
        UINT32 userWithAuth         : 1;
        UINT32 adminWithPolicy      : 1;
        UINT32 reserved8_9          : 2;
        UINT32 noDA                 : 1;
        UINT32 encryptedDuplication : 1;
        UINT32 reserved12_15        : 4;
        UINT32 restricted           : 1;
        UINT32 decrypt              : 1;
        UINT32 sign                 : 1;
        UINT32 reserved19_31        : 13;
    }
    union TPMU_PUBLIC_PARMS {
        TPMS_KEYEDHASH_PARMS keyedHashDetail;
        TPMT_SYM_DEF_OBJECT symDetail;
        TPMS_RSA_PARMS rsaDetail;
        TPMS_ECC_PARMS eccDetail;
        TPMS_ASYM_PARMS asymDetail;
    }
    union TPMU_PUBLIC_ID {
        TPM2B_DIGEST keyedHash;
        TPM2B_DIGEST sym;
        TPM2B_PUBLIC_KEY_RSA rsa;
        TPMS_ECC_POINT ecc;
    }
    """
    type_alg: TpmAlgId
    name_alg: TpmAlgId
    attributes: int
    auth_policy: bytes
    parameters: Union[Tpm20KeyedHashParmsStruct, Tpm20SymDefObjectStruct, Tpm20RsaParmsStruct, Tpm20EccParmsStruct]
    unique: Union[Tpm20BufferStruct, Tpm20EccPointStruct]

    @classmethod
    def from_buffer(cls, buf: ParsedBuffer) -> 'Tpm20PublicStruct':
        """Decode a TPMT_PUBLIC from a buffer"""
        type_alg = buf.read_tpm_algid()
        name_alg = buf.read_tpm_algid()
        attributes = buf.read_u32()
        auth_policy = buf.read_tpm2b()

        if type_alg == TpmAlgId.TPM_ALG_KEYEDHASH:
            keyedhash_parameters = Tpm20KeyedHashParmsStruct.from_buffer(buf)
            keyedhash_unique = Tpm20BufferStruct.from_buffer(buf)
            return cls(type_alg, name_alg, attributes, auth_policy, keyedhash_parameters, keyedhash_unique)

        if type_alg == TpmAlgId.TPM_ALG_SYMCIPHER:
            sym_parameters = Tpm20SymDefObjectStruct.from_buffer(buf)
            sym_unique = Tpm20BufferStruct.from_buffer(buf)
            return cls(type_alg, name_alg, attributes, auth_policy, sym_parameters, sym_unique)

        if type_alg == TpmAlgId.TPM_ALG_RSA:
            rsa_parameters = Tpm20RsaParmsStruct.from_buffer(buf)
            rsa_unique = Tpm20BufferStruct.from_buffer(buf)  # RSA modulus
            if rsa_parameters.key_bits != len(rsa_unique.data) * 8:
                raise ValueError(f"Unexpected RSA-{rsa_parameters.key_bits} modulus size: {len(rsa_unique.data)} bytes")
            return cls(type_alg, name_alg, attributes, auth_policy, rsa_parameters, rsa_unique)

        if type_alg == TpmAlgId.TPM_ALG_ECC:
            ecc_parameters = Tpm20EccParmsStruct.from_buffer(buf)
            ecc_unique = Tpm20EccPointStruct.from_buffer(buf)
            return cls(type_alg, name_alg, attributes, auth_policy, ecc_parameters, ecc_unique)

        raise NotImplementedError(f"Unimplemented object public parameters for algorithm {type_alg!r}")

    @classmethod
    def from_buffer_2b(cls, buf: ParsedBuffer) -> 'Tpm20PublicStruct':
        """Decode a TPM2B_PUBLIC from a buffer"""
        data = buf.read_tpm2b()
        with ParsedBuffer(data) as buf:
            return cls.from_buffer(buf)

    def to_buffer(self, buf: OutputBuffer) -> None:
        buf.write_u16(self.type_alg)
        buf.write_u16(self.name_alg)
        buf.write_u32(self.attributes)
        buf.write_tpm2b(self.auth_policy)
        self.parameters.to_buffer(buf)
        self.unique.to_buffer(buf)

    def compute_name(self) -> bytes:
        """Get the name of the Non-Volatile Index

        cf. clause 16 of Trusted Platform Module Library Specification,
        Family "2.0", Level 00, Revision 01.59 - November 2019
        Part 1: Architecture
        """
        buf = OutputBuffer()
        self.to_buffer(buf)
        name_hash = self.name_alg.compute_hash(buf.finalize())
        return struct.pack('>H', self.name_alg.value) + name_hash

    def attr_desc(self) -> str:
        """Get the description of the attribute"""
        remaining = self.attributes
        if not remaining:
            return ''
        all_desc: List[str] = []
        for bitmask, bit_desc in TPM20_OBJECT_ATTR_DESC:
            if remaining & bitmask:
                all_desc.append(bit_desc)
                remaining &= ~bitmask
                if not remaining:
                    return '|'.join(all_desc)
            assert remaining
        all_desc.append(hex(remaining))
        return '|'.join(all_desc)

    def print(self, indent: str = "") -> None:
        print(f"{indent}- type alg: {self.type_alg}")
        print(f"{indent}- name alg: {self.name_alg}")
        print(f"{indent}- attributes: {self.attr_desc()} ({self.attributes:#010x})")
        if self.auth_policy:
            hex_auth_policy = self.auth_policy.hex()
            desc_auth_policy = WELL_KNOWN_EA_POLICIES.get(hex_auth_policy, 'unknown')
            print(f"{indent}- auth policy: {hex_auth_policy} = {desc_auth_policy}")
        else:
            print(f"{indent}- auth policy empty")
        print(f"{indent}- parameters:")
        self.parameters.print(indent=f"{indent}  ")
        print(f"{indent}- unique:")
        self.unique.print(indent=f"{indent}  ")


# Description of the TPMA_OBJECT bits
TPM20_OBJECT_ATTR_DESC: Tuple[Tuple[int, str], ...] = (
    # bit 0: reserved
    (0x00000002, 'fixedTPM'),  # the object cannot be duplicated for use on a different TPM
    (0x00000004, 'stClear'),  # saved contexts of this object will be invalidated on TPM2_Startup(TPM_SU_CLEAR)
    # bit 3: reserved
    (0x00000010, 'fixedParent'),  # the object's parent may not be changed ; this object may not be the object of a TPM2_Duplicate()  # noqa
    (0x00000020, 'sensitiveDataOrigin'),  # the key was generated by TPM.
    (0x00000040, 'userWithAuth'),  # authorization for operations that require USER role authorization may be given if the caller provides proof of knowledge of the authValue of the object with an HMAC authorization session or a password  # noqa
    (0x00000080, 'adminWithPolicy'),  # HMAC or password authorizations may not be used for ADMIN role authorizations.
    # bits 8-9: reserved
    (0x00000400, 'noDA'),  # authorization failures for the object do not affect the dictionary attack protection logic and authorization of the object is not blocked if the TPM is in lockout.  # noqa
    (0x00000800, 'encryptedDuplication'),  # when the object is duplicated, the sensitive portion of the object is required to be encrypted with an inner wrapper and the new parent shall be an asymmetric key and not TPM_RH_NULL.  # noqa
    # bits 12-15: reserved
    (0x00010000, 'restricted'),  # this attribute modifies the decrypt and sign attributes of an object.
    (0x00020000, 'decrypt'),  # the private portion of this key can be used to decrypt an external blob.
    (0x00040000, 'signOrEncrypt'),  # the private portion of this key may be used to sign a digest if the key is an asymmetric key or to encrypt a block of data if the key is a symmetric key.  # noqa
    # bits 19-31: reserved
)


@dataclass
class Tpm20SensitiveStruct:
    """Represent a TPMT_SENSITIVE in TPM 2.0

    struct TPMT_SENSITIVE {
        TPMI_ALG_PUBLIC sensitiveType; // TPM_ALG_ID = UINT16
        TPM2B_AUTH authValue;
        TPM2B_DIGEST seedValue;
        TPMU_SENSITIVE_COMPOSITE sensitive; // TPM2B
    }
    """
    type_alg: TpmAlgId
    auth_value: bytes
    seed_value: bytes
    sensitive: bytes

    @classmethod
    def from_buffer(cls, buf: ParsedBuffer) -> 'Tpm20SensitiveStruct':
        """Decode a TPMT_PUBLIC from a buffer"""
        type_alg = buf.read_tpm_algid()
        auth_value = buf.read_tpm2b()
        seed_value = buf.read_tpm2b()
        sensitive = buf.read_tpm2b()
        return cls(type_alg, auth_value, seed_value, sensitive)

    @classmethod
    def from_buffer_2b(cls, buf: ParsedBuffer) -> 'Tpm20SensitiveStruct':
        """Decode a TPM2B_SENSITIVE from a buffer"""
        data = buf.read_tpm2b()
        with ParsedBuffer(data) as buf:
            return cls.from_buffer(buf)

    def print(self, indent: str = "") -> None:
        print(f"{indent}- type alg: {self.type_alg}")
        print(f"{indent}- auth value: {self.auth_value.hex()}")
        print(f"{indent}- seed value: {self.seed_value.hex()}")
        if self.type_alg == TpmAlgId.TPM_ALG_KEYEDHASH:
            print(f"{indent}- sensitive (Keyed hash secret, {len(self.sensitive)} bytes):")
        elif self.type_alg == TpmAlgId.TPM_ALG_RSA:
            print(f"{indent}- sensitive (RSA private factor, {len(self.sensitive)} bytes):")
        elif self.type_alg == TpmAlgId.TPM_ALG_ECC:
            print(f"{indent}- sensitive (ECC private key, {len(self.sensitive)} bytes):")
        else:
            print(f"{indent}- sensitive (???, {len(self.sensitive)} bytes):")
        hexdump(self.sensitive, indent=f"{indent}  ")


@dataclass
class Tpm20NvPublicStruct:
    """Represent a TPMS_NV_PUBLIC in TPM 2.0

    struct TPMS_NV_PUBLIC {
        TPMI_RH_NV_INDEX nvIndex; // TPM_HANDLE = UINT32
        TPMI_ALG_HASH nameAlg; // TPM_ALG_ID = UINT16
        TPMA_NV attributes; // UINT32
        TPM2B_DIGEST authPolicy;
        UINT16 dataSize;
    }
    struct TPMA_NV {
        UINT32 TPMA_NV_PPWRITE        : 1;
        UINT32 TPMA_NV_OWNERWRITE     : 1;
        UINT32 TPMA_NV_AUTHWRITE      : 1;
        UINT32 TPMA_NV_POLICYWRITE    : 1;
        UINT32 TPMA_NV_COUNTER        : 1;
        UINT32 TPMA_NV_BITS           : 1;
        UINT32 TPMA_NV_EXTEND         : 1;
        UINT32 reserved7_9            : 3;
        UINT32 TPMA_NV_POLICY_DELETE  : 1;
        UINT32 TPMA_NV_WRITELOCKED    : 1;
        UINT32 TPMA_NV_WRITEALL       : 1;
        UINT32 TPMA_NV_WRITEDEFINE    : 1;
        UINT32 TPMA_NV_WRITE_STCLEAR  : 1;
        UINT32 TPMA_NV_GLOBALLOCK     : 1;
        UINT32 TPMA_NV_PPREAD         : 1;
        UINT32 TPMA_NV_OWNERREAD      : 1;
        UINT32 TPMA_NV_AUTHREAD       : 1;
        UINT32 TPMA_NV_POLICYREAD     : 1;
        UINT32 reserved20_24          : 5;
        UINT32 TPMA_NV_NO_DA          : 1;
        UINT32 TPMA_NV_ORDERLY        : 1;
        UINT32 TPMA_NV_CLEAR_STCLEAR  : 1;
        UINT32 TPMA_NV_READLOCKED     : 1;
        UINT32 TPMA_NV_WRITTEN        : 1;
        UINT32 TPMA_NV_PLATFORMCREATE : 1;
        UINT32 TPMA_NV_READ_STCLEAR   : 1;
    }
    struct TPM2B_DIGEST {
        UINT16 size;
        BYTE buffer[sizeof(TPMU_HA)];
    }
    """
    nv_index: int
    name_alg: TpmAlgId
    attributes: int
    auth_policy: bytes
    data_size: int

    @classmethod
    def from_buffer(cls, buf: ParsedBuffer) -> 'Tpm20NvPublicStruct':
        """Decode a TPMS_NV_PUBLIC from a buffer"""
        nv_index = buf.read_u32()
        name_alg = buf.read_tpm_algid()
        attributes = buf.read_u32()
        auth_policy = buf.read_tpm2b()
        data_size = buf.read_u16()
        return cls(nv_index, name_alg, attributes, auth_policy, data_size)

    @classmethod
    def from_buffer_2b(cls, buf: ParsedBuffer) -> 'Tpm20NvPublicStruct':
        """Decode a TPM2B_NV_PUBLIC from a buffer"""
        data = buf.read_tpm2b()
        with ParsedBuffer(data) as buf:
            return cls.from_buffer(buf)

    def to_buffer(self, buf: OutputBuffer) -> None:
        buf.write_u32(self.nv_index)
        buf.write_u16(self.name_alg)
        buf.write_u32(self.attributes)
        buf.write_tpm2b(self.auth_policy)
        buf.write_u16(self.data_size)

    def compute_name(self) -> bytes:
        """Get the name of the Non-Volatile Index

        cf. clause 16 of Trusted Platform Module Library Specification,
        Family "2.0", Level 00, Revision 01.59 - November 2019
        Part 1: Architecture
        """
        buf = OutputBuffer()
        self.to_buffer(buf)
        name_hash = self.name_alg.compute_hash(buf.finalize())
        return struct.pack('>H', self.name_alg.value) + name_hash

    def attr_desc(self) -> str:
        """Get the description of the attribute, using a syntax from
        https://github.com/tpm2-software/tpm2-tools/blob/master/man/common/nv-attrs.md
        """
        remaining = self.attributes
        if not remaining:
            return ''
        all_desc: List[str] = []
        for bitmask, bit_desc in TPM20_NV_ATTR_DESC:
            if remaining & bitmask:
                all_desc.append(bit_desc)
                remaining &= ~bitmask
                if not remaining:
                    return '|'.join(all_desc)
            assert remaining
        all_desc.append(hex(remaining))
        return '|'.join(all_desc)

    def print(self, expected_handle: Optional[int] = None, indent: str = "") -> None:
        if expected_handle is None:
            print(f"{indent}- NV index: {self.nv_index:#010x}")
        elif self.nv_index != expected_handle:
            raise ValueError(f"Unexpected NV Index in public data: {self.nv_index:#010x}")
        print(f"{indent}- name alg: {self.name_alg}")
        print(f"{indent}- attributes: {self.attr_desc()} ({self.attributes:#010x})")
        if self.auth_policy:
            hex_auth_policy = self.auth_policy.hex()
            desc_auth_policy = WELL_KNOWN_EA_POLICIES.get(hex_auth_policy, 'unknown')
            print(f"{indent}- auth policy: {hex_auth_policy} = {desc_auth_policy}")
        else:
            print(f"{indent}- auth policy empty")
        if self.data_size <= 9:
            print(f"{indent}- data size: {self.data_size}")
        else:
            print(f"{indent}- data size: {self.data_size:#x} = {self.data_size}")


# Description of the TPMA_NV bits
TPM20_NV_ATTR_DESC: Tuple[Tuple[int, str], ...] = (
    (0x00000001, 'ppwrite'),
    (0x00000002, 'ownerwrite'),
    (0x00000004, 'authwrite'),
    (0x00000008, 'policywrite'),
    (0x00000010, 'counter'),
    (0x00000020, 'bits'),
    (0x00000040, 'extend'),
    # bits 7-9: reserved
    (0x00000400, 'policy_delete'),
    (0x00000800, 'writelocked'),
    (0x00001000, 'writeall'),
    (0x00002000, 'writedefine'),
    (0x00004000, 'write_stclear'),
    (0x00008000, 'globallock'),
    (0x00010000, 'ppread'),
    (0x00020000, 'ownerread'),
    (0x00040000, 'authread'),
    (0x00080000, 'policyread'),
    # bits 20-24: reserved
    (0x02000000, 'no_da'),
    (0x04000000, 'orderly'),
    (0x08000000, 'clear_stclear'),
    (0x10000000, 'readlocked'),
    (0x20000000, 'written'),
    (0x40000000, 'platformcreate'),
    (0x80000000, 'read_stclear'),
)


@dataclass
class Tpm20StartAuthSessionCmdParams:
    """Parameters of a TPM2_CC_StartAuthSession command"""
    tpm_key: Tpm20Handle
    bind: Tpm20Handle
    nonce_caller: bytes
    encrypted_salt: bytes
    session_type: Tpm20SessionType
    symmetric: Tpm20SymDefObjectStruct
    auth_hash: TpmAlgId

    @classmethod
    def new_with_hmac(cls, nonce_caller: bytes, auth_hash: TpmAlgId) -> 'Tpm20StartAuthSessionCmdParams':
        """Starts an unbound and unsalted HMAC session"""
        return cls(
            tpm_key=Tpm20Handle.TPM_RH_NULL,
            bind=Tpm20Handle.TPM_RH_NULL,
            nonce_caller=nonce_caller,
            encrypted_salt=b"",
            session_type=Tpm20SessionType.TPM_SE_HMAC,
            symmetric=Tpm20SymDefObjectStruct(TpmAlgId.TPM_ALG_NULL, None, None),
            auth_hash=auth_hash,
        )

    @classmethod
    def from_buffer(cls, buf: ParsedBuffer) -> 'Tpm20StartAuthSessionCmdParams':
        tpm_key = buf.read_tpm20_handle()
        bind = buf.read_tpm20_handle()
        nonce_caller = buf.read_tpm2b()
        encrypted_salt = buf.read_tpm2b()
        session_type = Tpm20SessionType(buf.read_u8())
        symmetric = Tpm20SymDefObjectStruct.from_buffer(buf)
        auth_hash = buf.read_tpm_algid()
        return cls(tpm_key, bind, nonce_caller, encrypted_salt, session_type, symmetric, auth_hash)

    def to_buffer(self, buf: OutputBuffer) -> None:
        buf.write_u32(self.tpm_key)
        buf.write_u32(self.bind)
        buf.write_tpm2b(self.nonce_caller)
        buf.write_tpm2b(self.encrypted_salt)
        buf.write_u8(self.session_type)
        self.symmetric.to_buffer(buf)
        buf.write_u16(self.auth_hash)

    def to_bytes(self) -> bytes:
        buf = OutputBuffer()
        self.to_buffer(buf)
        return buf.finalize()

    def print(self, indent: str = "") -> None:
        print(f"{indent}- tpm key: {self.tpm_key}")
        print(f"{indent}- bind: {self.bind}")
        print(f"{indent}- nonce caller: {self.nonce_caller.hex()}")
        print(f"{indent}- encrypted salt: {self.encrypted_salt.hex()}")
        print(f"{indent}- session type: {self.session_type}")
        print(f"{indent}- symmetric: {self.symmetric}")
        print(f"{indent}- auth hash: {self.auth_hash}")


@dataclass
class Tpm20StartAuthSessionResponse:
    """Response of a TPM2_CC_StartAuthSession command"""
    session_handle: int
    nonce_tpm: bytes

    @classmethod
    def from_buffer(cls, buf: ParsedBuffer) -> 'Tpm20StartAuthSessionResponse':
        session_handle = buf.read_u32()
        nonce_tpm = buf.read_tpm2b()
        return cls(session_handle, nonce_tpm)

    def print(self, indent: str = "") -> None:
        try:
            session_handle = Tpm20Handle(self.session_handle)
        except ValueError:
            print(f"{indent}- session handle: {self.session_handle:#010x} (unknown)")
        else:
            print(f"{indent}- session handle: {session_handle}")
        print(f"{indent}- nonce tpm: {self.nonce_tpm.hex()}")


@dataclass
class Tpm20AuthSessionContext:
    """Context of a TPM 2.0 authorization session"""
    session_handle: int
    hash_alg: TpmAlgId
    nonce_caller: bytes
    nonce_tpm: bytes
    session_key: bytes


@dataclass
class Tpm20CmdAuthArea:
    """Authorization area for a TPM 2.0 command"""
    session_handle: int
    nonce_caller: bytes
    session_attributes: int
    cphash: bytes  # Command HMAC

    @classmethod
    def from_buffer(cls, buf: ParsedBuffer) -> 'Tpm20CmdAuthArea':
        session_handle = buf.read_u32()
        nonce_caller = buf.read_tpm2b()
        session_attributes = buf.read_u8()
        cphash = buf.read_tpm2b()
        return cls(session_handle, nonce_caller, session_attributes, cphash)

    @classmethod
    def from_buffer_with_size(cls, buf: ParsedBuffer) -> 'Tpm20CmdAuthArea':
        auth_area_size = buf.read_u32()
        with ParsedBuffer(buf.read_bytes(auth_area_size)) as auth_area_buf:
            return cls.from_buffer(auth_area_buf)

    @classmethod
    def from_buffer_2b(cls, buf: ParsedBuffer) -> 'Tpm20CmdAuthArea':
        data = buf.read_tpm2b()
        with ParsedBuffer(data) as buf:
            return cls.from_buffer(buf)

    def to_buffer_with_size(self, buf: OutputBuffer) -> None:
        # NB. the size of the Authorization Area is 32-bit wide, not 16-bit like TPM2B
        buf.write_u32(9 + len(self.nonce_caller) + len(self.cphash))
        buf.write_u32(self.session_handle)
        buf.write_tpm2b(self.nonce_caller)
        buf.write_u8(self.session_attributes)
        buf.write_tpm2b(self.cphash)

    def to_bytes_with_size(self) -> bytes:
        buf = OutputBuffer()
        self.to_buffer_with_size(buf)
        return buf.finalize()

    def print(self, indent: str = "") -> None:
        try:
            session_handle = Tpm20Handle(self.session_handle)
        except ValueError:
            print(f"{indent}- session handle: {self.session_handle:#010x} (unknown)")
        else:
            print(f"{indent}- session handle: {session_handle}")
        print(f"{indent}- nonce caller: {self.nonce_caller.hex()}")
        print(f"{indent}- session attributes: {self.session_attributes:#04x}")
        print(f"{indent}- command hmac: {self.cphash.hex()}")


@dataclass
class Tpm20RespAuth:
    """Authorization response for a TPM 2.0 command"""
    data: bytes
    nonce_tpm: bytes
    session_attributes: int
    resp_hmac: bytes
    buffer_name: Optional[str]

    @classmethod
    def from_buffer(cls, buf: ParsedBuffer) -> 'Tpm20RespAuth':
        data_size = buf.read_u32()
        data = buf.read_bytes(data_size)
        nonce_tpm = buf.read_tpm2b()
        session_attributes = buf.read_u8()
        rphash = buf.read_tpm2b()
        return cls(data, nonce_tpm, session_attributes, rphash, buf.name)

    def print(self, indent: str = "") -> None:
        print(f"{indent}- data (response parameters, {len(self.data)} bytes):")
        hexdump(self.data, indent=f"{indent}  ")
        print(f"{indent}- nonce tpm: {self.nonce_tpm.hex()}")
        print(f"{indent}- session attributes: {self.session_attributes:#04x}")
        print(f"{indent}- response hmac: {self.resp_hmac.hex()}")

    def into_buffer(self, name: Optional[str] = None, ensure_end_on_exit: bool = True) -> ParsedBuffer:
        if name is None and self.buffer_name:
            name = f"Decapsulated {self.buffer_name}"
        return ParsedBuffer(self.data, name=name, ensure_end_on_exit=ensure_end_on_exit)


@dataclass
class Tpm20ReadPublicResponse:
    """Response of a TPM2_CC_ReadPublic command"""
    public: Tpm20PublicStruct
    name: bytes
    qualified_name: bytes

    @classmethod
    def from_buffer(cls, buf: ParsedBuffer) -> 'Tpm20ReadPublicResponse':
        public = Tpm20PublicStruct.from_buffer_2b(buf)
        name = buf.read_tpm2b()
        qualified_name = buf.read_tpm2b()
        return cls(public, name, qualified_name)

    def print(self, indent: str = "") -> None:
        print(f"{indent}- object name: {self.name.hex()}")
        print(f"{indent}- object qualified name: {self.qualified_name.hex()}")
        print(f"{indent}- object public area:")
        self.public.print(indent=f"{indent}  ")


@dataclass
class Tpm20NvReadPublicResponse:
    """Response of a TPM2_CC_NV_ReadPublic command"""
    public: Tpm20NvPublicStruct
    name: bytes

    @classmethod
    def from_buffer(cls, buf: ParsedBuffer) -> 'Tpm20NvReadPublicResponse':
        public = Tpm20NvPublicStruct.from_buffer_2b(buf)
        name = buf.read_tpm2b()
        return cls(public, name)

    def print(self, expected_handle: Optional[int] = None, indent: str = "") -> None:
        print(f"{indent}- NV name: {self.name.hex()}")
        print(f"{indent}- NV public area:")
        self.public.print(expected_handle=expected_handle, indent=f"{indent}  ")


def print_file(file_type: str, in_file: Optional[Path]) -> None:
    """Deserialize a file and dump its content"""
    if in_file:
        with in_file.open("rb") as f_in:
            data = f_in.read()
    elif sys.stdin.isatty():
        print("Error: --type expects data either from a file or from stdin", file=sys.stderr)
        sys.exit(1)
    else:
        data = sys.stdin.buffer.read()

    file_type = file_type.upper()
    with ParsedBuffer(data) as buf:
        if file_type == "TPM2B_PUBLIC":
            pub_obj = Tpm20PublicStruct.from_buffer_2b(buf)
            pub_obj.print()
        elif file_type == "TPMT_PUBLIC":
            pub_obj = Tpm20PublicStruct.from_buffer(buf)
            pub_obj.print()
        elif file_type == "TPM2B_SENSITIVE":
            sens_obj = Tpm20SensitiveStruct.from_buffer_2b(buf)
            sens_obj.print()
        elif file_type == "TPMT_SENSITIVE":
            sens_obj = Tpm20SensitiveStruct.from_buffer(buf)
            sens_obj.print()
        elif file_type == "TPM2B_NV_PUBLIC":
            nvpub_obj = Tpm20NvPublicStruct.from_buffer_2b(buf)
            nvpub_obj.print()
        elif file_type == "TPMS_NV_PUBLIC":
            nvpub_obj = Tpm20NvPublicStruct.from_buffer(buf)
            nvpub_obj.print()
        else:
            print(f"Error: unknown type {file_type}", file=sys.stderr)
            sys.exit(1)


TPM2_MAX_CAP_BUFFER = 1024
TPM2_MAX_CAP_CC = 256
TPM2_MAX_PCRS = 32
TPM2_PCR_SELECT_MAX = (TPM2_MAX_PCRS + 7) // 8
SIZEOF_TPM2_CAP = 4
SIZEOF_TPM2_HANDLE = 4
SIZEOF_TPM2_ALG_ID = 2
SIZEOF_TPMA_ALGORITHM = 4
SIZEOF_TPMS_ALG_PROPERTY = SIZEOF_TPM2_ALG_ID + SIZEOF_TPMA_ALGORITHM
SIZEOF_TPM2_PT = 4
SIZEOF_TPMS_TAGGED_PROPERTY = SIZEOF_TPM2_PT + 4
SIZEOF_TPM2_PT_PCR = 4
SIZEOF_TPMS_TAGGED_PCR_SELECT = SIZEOF_TPM2_PT_PCR + 1 + TPM2_PCR_SELECT_MAX
SIZEOF_TPM2_ECC_CURVE = 2
SIZEOF_TPMI_ALG_HASH = SIZEOF_TPM2_ALG_ID
SIZEOF_TPMU_HA = 0x40  # SHA512 is maximum
SIZEOF_TPMT_HA = SIZEOF_TPMI_ALG_HASH + SIZEOF_TPMU_HA
SIZEOF_TPMS_TAGGED_POLICY = SIZEOF_TPM2_HANDLE + SIZEOF_TPMT_HA
TPM2_MAX_CAP_HANDLES = (TPM2_MAX_CAP_BUFFER - SIZEOF_TPM2_CAP - 4) // SIZEOF_TPM2_HANDLE
TPM2_MAX_CAP_ALGS = (TPM2_MAX_CAP_BUFFER - SIZEOF_TPM2_CAP - 4) // SIZEOF_TPMS_ALG_PROPERTY
TPM2_MAX_TPM_PROPERTIES = (TPM2_MAX_CAP_BUFFER - SIZEOF_TPM2_CAP - 4) // SIZEOF_TPMS_TAGGED_PROPERTY
TPM2_MAX_PCR_PROPERTIES = (TPM2_MAX_CAP_BUFFER - SIZEOF_TPM2_CAP - 4) // SIZEOF_TPMS_TAGGED_PCR_SELECT
TPM2_MAX_ECC_CURVES = (TPM2_MAX_CAP_BUFFER - SIZEOF_TPM2_CAP - 4) // SIZEOF_TPM2_ECC_CURVE
TPM2_MAX_TAGGED_POLICIES = (TPM2_MAX_CAP_BUFFER - SIZEOF_TPM2_CAP - 4) // SIZEOF_TPMS_TAGGED_POLICY


Tpm20CapAlgs = List[Tuple[TpmAlgId, int]]
Tpm20CapCommands = List[Tuple[int, int]]
Tpm20CapEccCurves = List[Tpm20EccCurve]
Tpm20CapHandles = List[int]
Tpm20CapPcrProperties = List[Tuple[int, Sequence[int]]]
Tpm20CapPcrSelection = List[Tuple[TpmAlgId, Sequence[int]]]
Tpm20CapTpmProperties = List[Tuple[int, int]]


class IsNotTpm12Error(Exception):
    """Identify when a TPM 1.2 command is attempted on a TPM 2.0 device"""


class IsNotTpm20Error(Exception):
    """Identify when a TPM 2.0 command is attempted on a TPM 1.2 device"""


class TpmDevice:
    def __init__(self, name: str):
        self.name = name

    def submit_command_raw(self, cmd: bytes) -> bytes:
        """Transmit an encoded command to the TPM device and receive a response from it"""
        raise NotImplementedError(f"submit_command_raw needs to be implemented for {self!r}")

    def submit_command(self, tag: int, code: int, params: Optional[bytes] = None) -> Tuple[int, int, bytes]:
        """Transmit a command to the TPM device and receive a response from it

        Command header:
        * For TPM 1.2
            struct TPM_RQU_COMMAND_HDR {
                TPM_STRUCTURE_TAG tag; // UINT16
                UINT32 paramSize;
                TPM_COMMAND_CODE ordinal; // UINT32
            }
        * For TPM 2.0
            struct TPM2_COMMAND_HEADER {
                TPM_ST tag; // UINT16
                UINT32 paramSize;
                TPM_CC commandCode; // UINT32
            }

        Response header:
        * For TPM 1.2
            struct TPM_RSP_COMMAND_HDR {
                TPM_STRUCTURE_TAG tag; // UINT16
                UINT32 paramSize;
                TPM_RESULT returnCode; // UINT32
            }
        * For TPM 2.0
            struct TPM2_RESPONSE_HEADER {
                TPM_ST tag; // UINT16
                UINT32 paramSize;
                TPM_RC responseCode; // UINT32
            }
        """
        if params:
            cmd = struct.pack('>HII', tag, 10 + len(params), code) + params
        else:
            cmd = struct.pack('>HII', tag, 10, code)

        response = self.submit_command_raw(cmd)
        if len(response) < 10:
            raise RuntimeError(f"Received response too short: {response!r}")
        tag, param_size, code = struct.unpack('>HII', response[:10])
        if len(response) != param_size:
            raise RuntimeError(
                f"Unexpected param size {param_size} in {len(response)}-byte long response: {response!r}")
        return tag, code, response[10:]

    def tpm12_run(self, tag: Tpm12Tag, code: Tpm12CommandCode, params: Optional[bytes] = None) -> bytes:
        """Run a TPM 1.2 command"""
        resp_tag, resp_code, response = self.submit_command(tag.value, code.value, params)
        if resp_tag == 0x8001 and resp_code in (0x84, 0xb0143, 0x80280400) and response == b'':
            # Linux driver forges a TPM2 response when the command is not implemented by the TPM:
            #     tag = TPM2_ST_NO_SESSIONS
            #     code = TPM2_RC_COMMAND_CODE | TSS2_RESMGR_TPM_RC_LAYER = 0xb0143
            #       (with TSS2_RESMGR_TPM_RC_LAYER = 11 << 16)
            # swtpm returns code = TPM2_RC_VALUE = 0x84
            # Microsoft Windows TBS returns code 0x80280400
            raise IsNotTpm12Error()

        if resp_tag == Tpm12Tag.TPM_TAG_RSP_COMMAND.value and resp_code == Tpm12ResponseCode.TPM_BADTAG:
            # Using /dev/tpm0 directly when it is a TPM 2.0 returns a TPM_BADTAG error
            raise IsNotTpm12Error()
        try:
            tpm12_tag = Tpm12Tag(resp_tag)
        except ValueError:
            raise NotImplementedError(f"Unknown tag {resp_tag:#x} for TPM 1.2 response code")
        if resp_code != 0:
            # Failure
            raise Tpm12Failure(code, resp_code)

        if (tag == Tpm12Tag.TPM_TAG_RQU_COMMAND and tpm12_tag == Tpm12Tag.TPM_TAG_RSP_COMMAND) or \
                (tag == Tpm12Tag.TPM_TAG_RQU_AUTH1_COMMAND and tpm12_tag == Tpm12Tag.TPM_TAG_RSP_AUTH1_COMMAND) or \
                (tag == Tpm12Tag.TPM_TAG_RQU_AUTH2_COMMAND and tpm12_tag == Tpm12Tag.TPM_TAG_RSP_AUTH2_COMMAND):
            return response
        raise RuntimeError(f"Unexpected response tag {tpm12_tag!r} from request {tag!r}")

    def tpm20_run(self, tag: Tpm20Tag, code: Tpm20CommandCode, params: Optional[bytes] = None) -> bytes:
        """Run a TPM 2.0 command"""
        resp_tag, resp_code, response = self.submit_command(tag.value, code.value, params)
        if resp_tag == 0x00c4 and resp_code in (0x0009, 0x000a, 0x001e) and response == b'':
            # Received a TPM1 response (tag=TPM_RSP_COMMAND) with
            # * resp_code=TPM_FAIL=0x0009 (from swtpm)
            # * resp_code=TPM_BAD_ORDINAL=0x000a (from a real TPM)
            # * resp_code=TPM_BADTAG=0x001e (from Windows with swtpm simulating TPM 1.2)
            raise IsNotTpm20Error()
        try:
            tpm20_tag = Tpm20Tag(resp_tag)
        except ValueError:
            raise NotImplementedError(f"Unknown tag {resp_tag:#x} for TPM 2.0 response code")
        if resp_code != 0:
            # Failure
            raise Tpm20Failure(code, resp_code)

        if (tag == Tpm20Tag.TPM2_ST_NO_SESSIONS and tpm20_tag == Tpm20Tag.TPM2_ST_NO_SESSIONS) or \
                (tag == Tpm20Tag.TPM2_ST_SESSIONS and tpm20_tag == Tpm20Tag.TPM2_ST_SESSIONS):
            return response
        raise RuntimeError(f"Unexpected response tag {tpm20_tag!r} from request {tag!r}")

    @cached_property
    def is_tpm12(self) -> bool:
        """Is the device a TPM 1.2?"""
        # Try reading PCR 0
        try:
            self.tpm12_pcr_read(0)
        except IsNotTpm12Error:
            return False
        return True

    @cached_property
    def is_tpm20(self) -> bool:
        """Is the device a TPM 2.0?"""
        # Try reading PCRs capability
        try:
            self.tpm20_get_capability(Tpm20Capability.TPM_CAP_PCRS, 0)
        except IsNotTpm20Error:
            return False
        return True

    def tpm_version(self) -> str:
        if self.is_tpm20:
            return '2.0'
        if self.is_tpm12:
            return '1.2'
        raise NotImplementedError("Unable to guess the TPM version")

    def tpm12_pcr_read(self, pcr_idx: int) -> bytes:
        """Read a PCR (Platform Control Register) using TPM 1.2 command"""
        resp = self.tpm12_run(
            Tpm12Tag.TPM_TAG_RQU_COMMAND,
            Tpm12CommandCode.TPM_ORD_PcrRead,
            struct.pack('>I', pcr_idx))
        if len(resp) != 20:
            raise RuntimeError(f"Unexpected size of TPM 1.2 PCR (SHA1): {len(resp)}, {resp!r})")
        return resp

    def tpm12_get_capability_with_bytes(self, cap_area: Tpm12CapabilityArea, subcap: bytes) -> bytes:
        """Get a TPM 1.2 capability, with sub capability given as bytes"""
        resp = self.tpm12_run(
            Tpm12Tag.TPM_TAG_RQU_COMMAND,
            Tpm12CommandCode.TPM_ORD_GetCapability,
            struct.pack('>II', cap_area.value, len(subcap)) + subcap)
        resp_size, = struct.unpack('>I', resp[:4])
        if 4 + resp_size != len(resp):
            raise RuntimeError(f"Unexpected response format for TPM_ORD_GetCapability: {resp!r}")
        return resp[4:]

    def tpm12_get_capability_nosub(self, cap_area: Tpm12CapabilityArea) -> bytes:
        """Get a TPM 1.2 capability with no sub capability"""
        return self.tpm12_get_capability_with_bytes(cap_area, b'')

    def tpm12_get_capability_prop(self, subcap: Tpm12CapabilityArea) -> Union[bool, int, Tuple[int, ...]]:
        """Get a TPM 1.2 capability TPM_CAP_PROPERTY with a sub-capability"""
        resp = self.tpm12_get_capability_with_bytes(
            Tpm12CapabilityArea.TPM_CAP_PROPERTY,
            struct.pack('>I', subcap.value))
        if subcap == Tpm12CapabilityArea.TPM_CAP_PROP_TIS_TIMEOUT and len(resp) == 0x10:
            if TYPE_CHECKING:
                return (0, 0, 0, 0)  # Make mypy happy, as struct.unpack returns Tuple[Any, ...]
            return struct.unpack('>IIII', resp)
        if subcap == Tpm12CapabilityArea.TPM_CAP_PROP_DURATION and len(resp) == 0xc:
            if TYPE_CHECKING:
                return (0, 0, 0)
            return struct.unpack('>III', resp)
        if resp in (b'\x00', b'\x01'):
            return resp != b'\x00'  # boolean
        if len(resp) == 4:
            if TYPE_CHECKING:
                return 0
            return struct.unpack('>I', resp)[0]
        raise RuntimeError(f"Unexpected response format for TPM_CAP_PROPERTY: {resp!r}")

    def tpm12_get_cap_ordinal(self, ordinal: int) -> bool:
        """Get a TPM 1.2 capability TPM_CAP_ORD (true if the associated command code is available)"""
        resp = self.tpm12_get_capability_with_bytes(
            Tpm12CapabilityArea.TPM_CAP_ORD,
            struct.pack('>I', ordinal))
        if resp not in (b'\x00', b'\x01'):
            raise RuntimeError(f"Unexpected response format for cap TPM_CAP_ORD: {resp!r}")
        return resp != b'\x00'

    def tpm12_get_cap_algorithm(self, algorithm: int) -> bool:
        """Get a TPM 1.2 capability TPM_CAP_ALG (true if the associated algorithm is available)"""
        resp = self.tpm12_get_capability_with_bytes(
            Tpm12CapabilityArea.TPM_CAP_ALG,
            struct.pack('>I', algorithm))
        if resp not in (b'\x00', b'\x01'):
            raise RuntimeError(f"Unexpected response format for cap TPM_CAP_ALG: {resp!r}")
        return resp != b'\x00'

    def tpm12_get_cap_sym_mode(self, sym_mode: int) -> bool:
        """Get a TPM 1.2 capability TPM_CAP_SYM_MODE (true if the associated sym_mode is available)"""
        resp = self.tpm12_get_capability_with_bytes(
            Tpm12CapabilityArea.TPM_CAP_SYM_MODE,
            struct.pack('>I', sym_mode))
        if resp not in (b'\x00', b'\x01'):
            raise RuntimeError(f"Unexpected response format for cap TPM_CAP_SYM_MODE: {resp!r}")
        return resp != b'\x00'

    def tpm12_get_cap_protocol(self, protocol: int) -> bool:
        """Get a TPM 1.2 capability TPM_CAP_PID (true if the associated protocol is available)"""
        resp = self.tpm12_get_capability_with_bytes(
            Tpm12CapabilityArea.TPM_CAP_PID,
            struct.pack('>H', protocol))
        if resp not in (b'\x00', b'\x01'):
            raise RuntimeError(f"Unexpected response format for cap TPM_CAP_PID: {resp!r}")
        return resp != b'\x00'

    def tpm12_get_cap_flag(self, flag: Tpm12CapabilityArea) -> Tuple[bool, ...]:
        """Get a TPM 1.2 capability TPM_CAP_FLAG"""
        resp = self.tpm12_get_capability_with_bytes(
            Tpm12CapabilityArea.TPM_CAP_FLAG,
            struct.pack('>I', flag.value))
        tag, = struct.unpack('>H', resp[:2])
        if flag == Tpm12CapabilityArea.TPM_CAP_FLAG_PERMANENT:
            if tag != Tpm12Tag.TPM_TAG_PERMANENT_FLAGS:
                raise RuntimeError(f"Unexpected response format for cap TPM_CAP_FLAG: {resp!r}")
        elif flag == Tpm12CapabilityArea.TPM_CAP_FLAG_VOLATILE:
            if tag != Tpm12Tag.TPM_TAG_STCLEAR_FLAGS:
                raise RuntimeError(f"Unexpected response format for cap TPM_CAP_FLAG: {resp!r}")
        else:
            raise ValueError("Unsupported TPM_CAP_FLAG {flag!r}")
        if any(x not in (0, 1) for x in resp[2:]):
            raise RuntimeError(f"Unexpected response format for cap TPM_CAP_FLAG: {resp!r}")
        return tuple(x != 0 for x in resp[2:])

    def tpm12_get_cap_nv_list(self) -> Tuple[int, ...]:
        """Get a TPM 1.2 capability TPM_CAP_NV_LIST"""
        resp = self.tpm12_get_capability_nosub(Tpm12CapabilityArea.TPM_CAP_NV_LIST)
        return struct.unpack('>' + 'I' * (len(resp) // 4), resp)

    def tpm12_print_cap_version(self, indent: str = '') -> None:
        """Print TPM 1.2 capability TPM_CAP_VERSION

        Response: struct TPM_STRUCT_VER which is always 1, 1, 0, 0
        """
        resp = self.tpm12_get_capability_nosub(Tpm12CapabilityArea.TPM_CAP_VERSION)
        v_maj, v_min, v_rmaj, v_rmin = struct.unpack('4B', resp)
        print(f"{indent}- Version (deprecated): {v_maj}.{v_min}.{v_rmaj}.{v_rmin}")
        if resp != b'\x01\x01\x00\x00':
            print(f"Warning: unexpected TPM_CAP_VERSION {resp!r}", file=sys.stderr)

    def tpm12_print_cap_version_info(self, indent: str = '') -> None:
        """Print TPM 1.2 capability TPM_CAP_VERSION_VAL

        Response:
            struct TPM_CAP_VERSION_INFO {
                TPM_STRUCTURE_TAG tag; // UINT16
                TPM_VERSION version;
                UINT16 specLevel;
                UINT8 errataRev;
                UINT8 tpmVendorID[4];
                UINT16 vendorSpecificSize;
                UINT8 *vendorSpecific;
            }
            struct TPM_VERSION {
                TPM_VERSION_BYTE major;
                TPM_VERSION_BYTE minor;
                UINT8 revMajor;
                UINT8 revMinor;
           }
        """
        resp = self.tpm12_get_capability_nosub(Tpm12CapabilityArea.TPM_CAP_VERSION_VAL)
        tag, v_maj, v_min, v_rmaj, v_rmin, spc, erra, vendor, vend_spec_size = struct.unpack('>HBBBBHBIH', resp[:0xf])
        if tag != Tpm12Tag.TPM_TAG_CAP_VERSION_INFO.value:
            raise RuntimeError(f"Unexpected TPM_CAP_VERSION_INFO tag: {tag:#x}")
        if 0xf + vend_spec_size != len(resp):
            raise RuntimeError(f"Unexpected response format for cap TPM_CAP_VERSION_VAL: {resp!r}")
        print(f"{indent}- Version Info:")
        # Split version field like /sys/class/tpm/tpm0/caps
        print(f"{indent}  - TCG version: {v_maj}.{v_min}")
        print(f"{indent}  - Firmware version: {v_rmaj}.{v_rmin}")
        print(f"{indent}  - spec level: {spc}")
        print(f"{indent}  - errata revision: {erra}")
        bytes_vendor = struct.pack('>I', vendor).rstrip(b'\0')
        try:
            repr_vendor = repr(bytes_vendor.decode('ascii'))
        except UnicodeDecodeError:
            repr_vendor = repr(bytes_vendor)
        print(f"{indent}  - TPM vendor ID: {repr_vendor} ({vendor:#x})")
        if vend_spec_size:
            print(f"{indent}  - vendor specific information: {resp[0xf:].hex()}")

    def tpm12_print_cap_nv_index(self, nv_idx: int, indent: str = '') -> None:
        """Print TPM 1.2 capability TPM_CAP_NV_INDEX

        struct TPM_NV_DATA_PUBLIC {
            TPM_STRUCTURE_TAG tag;
            TPM_NV_INDEX nvIndex;
            TPM_PCR_INFO_SHORT pcrInfoRead;
            TPM_PCR_INFO_SHORT pcrInfoWrite;
            TPM_NV_ATTRIBUTES permission;
            BOOLEAN bReadSTClear;
            BOOLEAN bWriteSTClear;
            BOOLEAN bWriteDefine;
            UINT32 dataSize;
        }
        struct TPM_PCR_INFO_SHORT {
            TPM_PCR_SELECTION pcrSelection;
            TPM_LOCALITY_SELECTION localityAtRelease; // UINT8
            TPM_COMPOSITE_HASH digestAtRelease; // TPM_DIGEST = UINT8[20]
        }
        struct TPM_PCR_SELECTION {
            UINT16 sizeOfSelect;
            UINT8 pcrSelect[];
        }
        struct TPM_NV_ATTRIBUTES {
            TPM_STRUCTURE_TAG tag;
            UINT32 attributes;
        }
        """
        resp = self.tpm12_get_capability_with_bytes(
            Tpm12CapabilityArea.TPM_CAP_NV_INDEX,
            struct.pack('>I', nv_idx))
        tag, nv_index = struct.unpack('>HI', resp[:6])
        if tag != Tpm12Tag.TPM_TAG_NV_DATA_PUBLIC or nv_index != nv_idx:
            raise RuntimeError(f"Unexpected response format for cap TPM_CAP_NV_INDEX: {resp!r}")
        offset = 6
        for read_write in range(2):
            pcr_select_size, = struct.unpack('>H', resp[offset:offset + 2])
            offset += 2
            pcr_select = []
            for pcr_idx_bytepos in range(pcr_select_size):
                pcrsel_byte = resp[offset + pcr_idx_bytepos]
                pcr_idx = pcr_idx_bytepos * 8
                while pcrsel_byte:
                    if pcrsel_byte & 1:
                        pcr_select.append(pcr_idx)
                    pcrsel_byte >>= 1
                    pcr_idx += 1
            offset += pcr_select_size
            pcr_loc_at_release, = struct.unpack('B', resp[offset:offset + 1])
            offset += 1
            pcr_digest_at_release_hex = resp[offset:offset + 20].hex()
            offset += 20
            print(f"{indent}- PCR info {['read', 'write'][read_write]}: {pcr_select}")
            print(f"{indent}  - localities at release: {pcr_loc_at_release:#x}")  # 0x1f is "ALL" in tpm_nvinfo
            if pcr_digest_at_release_hex != '0' * 40:
                print(f"{indent}  - digest at release: {pcr_digest_at_release_hex}")
        perm_tag, attr, r_st_clear, w_st_clear, w_define, data_size = struct.unpack('>HIBBBI', resp[offset:])
        if perm_tag != Tpm12Tag.TPM_TAG_NV_ATTRIBUTES:
            raise RuntimeError(f"Unexpected response format for cap TPM_CAP_NV_INDEX (perm tag): {resp!r}")
        perm_desc: List[str] = []
        remaining = attr
        for bitmask, bit_desc in TPM12_NV_ATTRIBUTES_DESC:
            if remaining & bitmask:
                perm_desc.append(bit_desc)
                remaining &= ~bitmask
            if not remaining:
                break
        if remaining:
            perm_desc.append(hex(remaining))
        if not perm_desc:
            perm_desc = ['none']
        print(f"{indent}- Permission attributes: {'|'.join(perm_desc)} ({attr:#x})")
        bool_xlate: Mapping[int, bool] = {0: False, 1: True}
        print(f"{indent}- Read ST Clear: {bool_xlate.get(r_st_clear, r_st_clear)}")
        print(f"{indent}- Write ST Clear: {bool_xlate.get(w_st_clear, w_st_clear)}")
        print(f"{indent}- Write Define: {bool_xlate.get(w_define, w_define)}")
        print(f"{indent}- Data Size: {data_size:#x}")
        if data_size:
            try:
                data = self.tpm12_nv_read_all(nv_idx, data_size)
            except Tpm12Failure as exc:
                if exc.code == Tpm12ResponseCode.TPM_AUTH_CONFLICT:
                    print(f"{indent}- NV data: error TPM_AUTH_CONFLICT")
                elif exc.code == Tpm12ResponseCode.TPM_NOSPACE:
                    print(f"{indent}- NV data: error TPM_NOSPACE")
                else:
                    raise
            else:
                print(f"{indent}- NV data:")
                hexdump(data, indent=indent + '  ')

    def tpm12_nv_read(self, nv_idx: int, offset: int, size: int) -> bytes:
        """Read a non-volatile variable, using TPM_NV_ReadValue"""
        resp = self.tpm12_run(
            Tpm12Tag.TPM_TAG_RQU_COMMAND,
            Tpm12CommandCode.TPM_ORD_NV_ReadValue,
            struct.pack('>III', nv_idx, offset, size))
        resp_size, = struct.unpack('>I', resp[:4])
        if 4 + resp_size != len(resp):
            raise RuntimeError(f"Unexpected response format for TPM_ORD_NV_ReadValue: {resp!r}")
        return resp[4:]

    def tpm12_nv_read_all(self, nv_idx: int, size: int) -> bytes:
        """Read a non-volatile variable, using repeated TPM2_NV_Read"""
        nv_buffer_size = 0x400
        data = b''
        while len(data) < size:
            requested_size = min(size - len(data), nv_buffer_size)
            resp = self.tpm12_nv_read(nv_idx, len(data), requested_size)
            if not resp:
                print(f"Warning: unable to get whole data for {nv_idx:#x}: {len(data):#x}/{size:#x}",
                      file=sys.stderr)
                break
            data += resp
        return data

    def tpm12_read_pubek(self) -> bytes:
        """Read the Public EK, using TPM_ReadPubek

        Response:
            TPM_PUBKEY pubEndorsementKey
            TPM_DIGEST checksum

        struct TPM_PUBKEY {
          TPM_KEY_PARMS    algorithmParms;
          TPM_STORE_PUBKEY pubKey;
        }
        struct TPM_KEY_PARMS {
          TPM_ALGORITHM_ID algorithmID;
          TPM_ENC_SCHEME   encScheme;
          TPM_SIG_SCHEME   sigScheme;
          UINT32           parmSize;
          UINT8            parms[parmSize];
        }
        struct TPM_RSA_KEY_PARMS {
            UINT32 keyLength;
            UINT32 numPrimes;
            UINT32 exponentSize;
            UINT8  exponent[exponentSize];
        }
        struct TPM_STORE_PUBKEY {
          UINT32 keyLength;
          UINT8  key[keyLength];
        }
        struct TPM_DIGEST {
          UINT8 digest[TPM_SHA1_160_HASH_LEN];
        }
        TPM_ALGORITHM_ID is UINT32
        TPM_ENC_SCHEME is UINT16
        TPM_SIG_SCHEME is UINT16

        Example with swtpm (the TPM_KEY_PARMS structure is directly an argument
        of command TPM_ORD_CreateEndorsementKeyPair), with follows
        recommandations from section "14.1 TPM_CreateEndorsementKeyPair" of
        https://trustedcomputinggroup.org/wp-content/uploads/TPM-Main-Part-3-Commands_v1.2_rev116_01032011.pdf
        pubEndorsementKey:
            000000:  0000 0001 0003 0001 0000 000c 0000 0800  ................
            000010:  0000 0002 0000 0000 0000 0100 bbf7 4455  ..............DU
            ...
            000110:  dd2c 4712 e6e7 b14f 6b63 8933
        => algorithmParms.algorithmID = 1 = TPM_ALG_RSA
           algorithmParms.encScheme = 3 = TPM_ES_RSAESOAEP_SHA1_MGF1
           algorithmParms.sigScheme = 1 = TPM_SS_NONE
           algorithmParms.parms.keyLength = 0x800 = 2048 bits
           algorithmParms.parms.numPrimes = 2
           algorithmParms.parms.exponent[0] = {}
           pubKey.key[0x100] = RSA2048 modulus
        """
        nonce = secrets.token_bytes(0x14)
        resp = self.tpm12_run(
            Tpm12Tag.TPM_TAG_RQU_COMMAND,
            Tpm12CommandCode.TPM_ORD_ReadPubek,
            nonce)
        if len(resp) <= 16 + 20:
            raise RuntimeError(f"ReadPubek returned too few bytes: {len(resp)}, {resp.hex()}")
        # Verify that checksum == SHA1(TPM_PUBKEY || nonce)
        pubek = resp[:-20]
        checksum = resp[-20:]
        computed_checksum = hashlib.sha1(pubek + nonce).digest()
        if checksum != computed_checksum:
            print(f"Error: ReadPubek returned invalid checksum {checksum.hex()}", file=sys.stderr)
            print(f"Error: ... ReadPubek nonce was {nonce.hex()}", file=sys.stderr)
            print(f"Error: ... ReadPubek response was {resp.hex()}", file=sys.stderr)
            raise RuntimeError("ReadPubek returned an invalid checksum")
        return pubek

    def tpm20_pcr_read(self, pcr_idx: int, alg_id: TpmAlgId) -> Tuple[bytes, int]:
        """Read a PCR (Platform Control Register) using TPM 2.0 command

        Parameters:
            TPML_PCR_SELECTION pcrSelectionIn (the selection of PCR to read)
        Response:
            UINT32 pcrUpdateCounter (the current value of the PCR update counter)
            TPML_PCR_SELECTION pcrSelectionOut (the PCR in the returned list)
            TPML_DIGEST pcrValues (the contents of the PCR, as tagged digests)
        """
        # Build a TPML_PCR_SELECTION list with one item
        # pcrsel is at least 3, because minimum 24 PCR, 8 bit each => TPM2_PCR_SELECT_MIN = 3 bytes
        pcrsel_size = max((pcr_idx >> 3) + 1, 3)
        pcrsel = bytearray(pcrsel_size)
        pcrsel[pcr_idx >> 3] = 1 << (pcr_idx & 7)
        resp = self.tpm20_run(
            Tpm20Tag.TPM2_ST_NO_SESSIONS,
            Tpm20CommandCode.TPM2_CC_PCR_Read,
            struct.pack('>IHB', 1, alg_id.value, pcrsel_size) + pcrsel)

        with ParsedBuffer(resp, "TPM2_CC_PCR_Read Response") as buf:
            pcr_update_counter = buf.read_u32()
            selection_count = buf.read_u32()
            sel_algid = buf.read_tpm_algid()
            sel_size = buf.read_u8()
            sel_out = buf.read_bytes(sel_size)
            if selection_count != 1:
                raise RuntimeError(f"Unexpected selection count {selection_count} != 1")
            if sel_algid != alg_id:
                raise RuntimeError(f"Unexpected selection alg ID {sel_algid} != {alg_id}")
            if sel_out != pcrsel:
                raise RuntimeError(f"Unexpected selected PCR in {resp!r}: {sel_out.hex()} != {pcrsel.hex()}")
            pcr_values_count = buf.read_u32()
            if pcr_values_count != 1:
                raise RuntimeError(f"Unexpected PCR values count {pcr_values_count} != 1")
            pcr_value = buf.read_tpm2b()
            return pcr_value, pcr_update_counter

    def tpm20_get_capability(self, capability: Tpm20Capability, prop: int, prop_count: int = 1
                             ) -> Tuple[bool, bytes]:
        """Get a TPM 2.0 capability

        Parameters:
            TPM_CAP capability (group selection; determines the format of the response)
            UINT32 property (further definition of information)
            UINT32 propertyCount (number of properties of the indicated type to return)
        Response:
            TPMI_YES_NO moreData (flag to indicate if there are more values of this type
            TPMS_CAPABILITY_DATA capability data:
                TPM_CAP capability
                TPMU_CAPABILITIES data

        (TPML for List, TPMS for Structure, TPMU for Union)
        """
        resp = self.tpm20_run(
            Tpm20Tag.TPM2_ST_NO_SESSIONS,
            Tpm20CommandCode.TPM2_CC_GetCapability,
            struct.pack('>III', capability.value, prop, prop_count))
        resp_more_data, resp_capability = struct.unpack('>BI', resp[:5])
        if resp_capability != capability.value:
            raise RuntimeError(f"Unexpected response capability {resp_capability:#x} for requested {capability!r}")
        return resp_more_data != 0, resp[5:]

    def tpm20_get_capability_vec_gen(self, capability: Tpm20Capability, prop_start: int, page_count: int,
                                     item_size_opt: Optional[int]) -> Generator[bytes, None, None]:
        """Get a TPM 2.0 capability with all its properties as a generator

        Suppose that the returned value contains the count and that all items
        share the same size (so it is a real "vector").
        """
        while True:
            more_data, resp_data = self.tpm20_get_capability(capability, prop_start, page_count)
            items_count, = struct.unpack('>I', resp_data[:4])
            if items_count:
                if not item_size_opt:
                    item_size = (len(resp_data) - 4) // items_count
                else:
                    item_size = item_size_opt
                if len(resp_data) != 4 + item_size * items_count:
                    raise RuntimeError(
                        f"Unexpected unaligned response for capability {capability.name}: " +
                        f"{len(resp_data)} != 4 + {item_size} * {items_count}")
                for offset in range(4, len(resp_data), item_size):
                    yield resp_data[offset:offset + item_size]
            if not more_data:
                return
            prop_start += page_count

    def tpm20_get_cap_algs(self) -> Tpm20CapAlgs:
        """Get TPM 2.0 capability TPM_CAP_ALGS

        "prop" is the starting algorithm ID.

        struct TPML_ALG_PROPERTY {
            UINT32 count;
            TPMS_ALG_PROPERTY algProperties[MAX_CAP_ALGS];
        }
        struct TPMS_ALG_PROPERTY {
            TPM_ALG_ID alg;
            TPMA_ALGORITHM algProperties;
        }
        struct TPMA_ALGORITHM {
            UINT32 asymmetric    : 1;
            UINT32 symmetric     : 1;
            UINT32 hash          : 1;
            UINT32 object        : 1;
            UINT32 reserved4_7   : 4;
            UINT32 signing       : 1;
            UINT32 encrypting    : 1;
            UINT32 method        : 1;
            UINT32 reserved11_31 : 21;
        }
        """
        algs: Tpm20CapAlgs = []
        for resp_data in self.tpm20_get_capability_vec_gen(Tpm20Capability.TPM_CAP_ALGS, 0, TPM2_MAX_CAP_ALGS, 6):
            alg_id, alg_property = struct.unpack('>HI', resp_data)
            algs.append((TpmAlgId(alg_id), alg_property))
        return algs

    def tpm20_get_cap_ecc_curves(self) -> Tpm20CapEccCurves:
        """Get TPM 2.0 capability TPM_CAP_ECC_CURVES

        "prop" is the starting ECC curve algorithm ID.

        struct TPML_ECC_CURVE {
            UINT32 count;
            TPM_ECC_CURVE eccCurves[MAX_ECC_CURVES];
        }
        TPM_ECC_CURVE is UINT16
        """
        curves: Tpm20CapEccCurves = []
        for resp_data in self.tpm20_get_capability_vec_gen(
                Tpm20Capability.TPM_CAP_ECC_CURVES,
                0,
                TPM2_MAX_ECC_CURVES,
                2):
            ecc_curve_id, = struct.unpack('>H', resp_data)
            curves.append(Tpm20EccCurve(ecc_curve_id))
        return curves

    def tpm20_get_cap_handles(self, bruteforce: bool = False) -> Tpm20CapHandles:
        """Get TPM 2.0 capability TPM_CAP_HANDLES

        "prop" is the starting handle.

        struct TPML_HANDLE {
            UINT32 count;
            TPM2_HANDLE tpmProperty[TPM2_MAX_CAP_HANDLES];
        }
        TPM2_HANDLE is UINT32
        """
        handles: Tpm20CapHandles = []
        for starting_handle in range(0, 0x100000000, 0x1000000):
            if not bruteforce:
                # Limit to the known handle types
                try:
                    Tpm20HandleType(starting_handle >> 24)
                except ValueError:
                    continue
            try:
                for resp_data in self.tpm20_get_capability_vec_gen(
                        Tpm20Capability.TPM_CAP_HANDLES,
                        starting_handle,
                        TPM2_MAX_CAP_HANDLES,
                        4):
                    handle, = struct.unpack('>I', resp_data)
                    handles.append(handle)
            except Tpm20Failure as exc:
                if exc.code == Tpm20ResponseCode.TPM2_RC_HANDLE.value:
                    # On Windows, GetCapability returns response code 0x8b for unsupported input handle types
                    continue
                if exc.code == Tpm20ResponseCode.TPM2_RC_PARAM2_HANDLE.value:
                    # GetCapability returns response code 0x2cb for unsupported input handle types
                    # (because param 1 is capability, 2 is property and 3 is propertyCount)
                    continue
                raise
        return handles

    def tpm20_get_cap_commands(self, cap: Tpm20Capability) -> Tpm20CapCommands:
        """Get TPM 2.0 capability TPM_CAP_COMMANDS, TPM_CAP_PP_COMMANDS or TPM_CAP_AUDIT_COMMAND

        "prop" is the starting command code.

        struct TPML_CCA {
            UINT32 count;
            TPMA_CC commandAttributes[MAX_CAP_CC];
        }
        struct TPMA_CC {
            UINT32 commandIndex  : 16;
            UINT32 reserved16_21 : 6;
            UINT32 nv            : 1;
            UINT32 extensive     : 1;
            UINT32 flushed       : 1;
            UINT32 cHandles      : 3;
            UINT32 rHandle       : 1;
            UINT32 V             : 1;
            UINT32 Res           : 2;
        }
        """
        comms: Tpm20CapCommands = []
        for starting_command in range(0, 0x10000, 0x1000):
            for resp_data in self.tpm20_get_capability_vec_gen(
                    cap,
                    starting_command,
                    TPM2_MAX_CAP_CC,
                    4):
                comm_attr, comm_code = struct.unpack('>HH', resp_data)
                if comm_code >= starting_command:
                    comms.append((comm_code, comm_attr))
        return comms

    def tpm20_get_cap_pcrs(self) -> Tpm20CapPcrSelection:
        """Get TPM 2.0 capability TPM_CAP_PCRS

        struct TPML_PCR_SELECTION {
            UINT32 count;
            TPMS_PCR_SELECTION pcrSelections[HASH_COUNT];
        }
        struct TPMS_PCR_SELECTION {
            TPMI_ALG_HASH hash;
            UINT8 sizeofSelect;
            BYTE pcrSelect[PCR_SELECT_MAX];
        }
        """
        # Do not use tpm20_get_capability_vec_gen as the size of TPMS_PCR_SELECTION
        # depends on the implementation
        more_data, resp_data = self.tpm20_get_capability(Tpm20Capability.TPM_CAP_PCRS, 0)
        if more_data:
            raise RuntimeError("Unexpected more data for TPM_CAP_PCRS")

        with ParsedBuffer(resp_data, "TPM_CAP_PCRS") as buf:
            nr_banks = buf.read_u32()
            pcr_select: Tpm20CapPcrSelection = []
            for _ in range(nr_banks):
                bank_alg_hash = buf.read_u16()
                select_size = buf.read_u8()
                bank_pcr_select = []
                for pcr_idx_bytepos in range(select_size):
                    pcrsel_byte = buf.read_u8()
                    pcr_idx = pcr_idx_bytepos * 8
                    while pcrsel_byte:
                        if pcrsel_byte & 1:
                            bank_pcr_select.append(pcr_idx)
                        pcrsel_byte >>= 1
                        pcr_idx += 1
                pcr_select.append((TpmAlgId(bank_alg_hash), bank_pcr_select))
            return pcr_select

    def tpm20_get_cap_tpm_properties(self) -> Tpm20CapTpmProperties:
        """Get TPM 2.0 capability TPM_CAP_TPM_PROPERTIES

        "prop" is the starting property.

        struct TPML_TAGGED_TPM_PROPERTY {
            UINT32 count;
            TPMS_TAGGED_PROPERTY tpmProperty[MAX_TPM_PROPERTIES];
        }
        struct TPMS_TAGGED_PROPERTY {
            TPM_PT property; // UINT32
            UINT32 value;
        }
        """
        properties: Tpm20CapTpmProperties = []
        max_prop_id = -1
        # TPM2_MAX_TPM_PROPERTIES = 0x7f so request the largest power of 2
        # below, which is 0x40
        for prop_start in range(0, 0x1000, 0x40):
            for resp_data in self.tpm20_get_capability_vec_gen(
                    Tpm20Capability.TPM_CAP_TPM_PROPERTIES,
                    prop_start,
                    TPM2_MAX_TPM_PROPERTIES,
                    8):
                with ParsedBuffer(resp_data, "TPM_CAP_TPM_PROPERTIES") as buf:
                    prop_id = buf.read_u32()
                    value = buf.read_u32()
                if prop_id > max_prop_id:
                    properties.append((prop_id, value))
                    max_prop_id = prop_id
                else:
                    # Got overlapping data: ensure it is already present
                    if (prop_id, value) not in properties:
                        raise RuntimeError(
                            f"Error: property {prop_id:#x} with value {value:#x} is not present in previous properties")
        return properties

    def tpm20_get_cap_tpm_property(self, prop: Tpm20Property) -> Optional[int]:
        """Get a TPM 2.0 capability from TPM_CAP_TPM_PROPERTIES"""
        resp_data = self.tpm20_get_capability(Tpm20Capability.TPM_CAP_TPM_PROPERTIES, prop.value, 1)[1]
        with ParsedBuffer(resp_data, "TPM_CAP_TPM_PROPERTIES") as buf:
            items_count = buf.read_u32()
            if items_count == 0:
                # The property does not exist
                return None
            if items_count != 1 or len(resp_data) != 0xc:
                raise RuntimeError(f"Unexpected response format for cap TPM_CAP_TPM_PROPERTIES: {resp_data!r}")
            prop_id = buf.read_u32()
            value = buf.read_u32()
        if prop_id != prop.value:
            # Another property is returned when the property does not exist
            return None
        return value

    def tpm20_get_cap_pcr_properties(self) -> Tpm20CapPcrProperties:
        """Get TPM 2.0 capability TPM_CAP_PCR_PROPERTIES

        "prop" is the starting property.

        struct TPMS_TAGGED_PCR_SELECT {
            TPM_PT tag; // UINT32
            UINT8 sizeofSelect;
            BYTE pcrSelect[PCR_SELECT_MAX];
        }
        struct TPMS_PCR_SELECT {
            UINT8 sizeofSelect;
            BYTE pcrSelect[PCR_SELECT_MAX];
        }
        """
        properties: Tpm20CapPcrProperties = []
        for resp_data in self.tpm20_get_capability_vec_gen(
                Tpm20Capability.TPM_CAP_PCR_PROPERTIES,
                0,
                TPM2_MAX_PCR_PROPERTIES,
                None):
            with ParsedBuffer(resp_data, "TPM_CAP_PCR_PROPERTIES") as buf:
                prop = buf.read_u32()
                select_size = buf.read_u8()
                pcr_select = []
                for pcr_idx_bytepos in range(select_size):
                    pcrsel_byte = buf.read_u8()
                    pcr_idx = pcr_idx_bytepos * 8
                    while pcrsel_byte:
                        if pcrsel_byte & 1:
                            pcr_select.append(pcr_idx)
                        pcrsel_byte >>= 1
                        pcr_idx += 1
                properties.append((prop, pcr_select))
        return properties

    def tpm20_testparms_rsa(self, key_bits: int) -> None:
        """Check if RSA with the specified key size is implemented, using TPM2_TestParms
        Parameters:
            TPMT_PUBLIC_PARMS parameters;

            struct TPMT_PUBLIC_PARMS {
                TPMI_ALG_PUBLIC type;
                TPMU_PUBLIC_PARMS parameters;
            }
            union TPMU_PUBLIC_PARMS {
                TPMS_RSA_PARMS rsaDetail;
                ...
            }
        """
        parameters = struct.pack(
            '>HHHHI',
            TpmAlgId.TPM_ALG_RSA,  # TPMT_PUBLIC_PARMS.type
            TpmAlgId.TPM_ALG_NULL,  # TPMS_RSA_PARMS.symmetric.algorithm
            TpmAlgId.TPM_ALG_NULL,  # TPMS_RSA_PARMS.scheme.scheme
            key_bits,  # TPMS_RSA_PARMS.keyBits
            0,  # TPMS_RSA_PARMS.exponent
        )
        resp = self.tpm20_run(
            Tpm20Tag.TPM2_ST_NO_SESSIONS,
            Tpm20CommandCode.TPM2_CC_TestParms,
            parameters)
        if resp:
            raise RuntimeError(f"Unexpected response content for TPM2_CC_TestParms: {resp!r}")

    def tpm20_testparms_symcipher(self, alg: TpmAlgId, key_bits: int, mode: TpmAlgId) -> None:
        """Check if the symmetric cipher with the specified parameters is implemented, using TPM2_TestParms
        Parameters:
            TPMT_PUBLIC_PARMS parameters;

            struct TPMT_PUBLIC_PARMS {
                TPMI_ALG_PUBLIC type;
                TPMU_PUBLIC_PARMS parameters;
            }
            union TPMU_PUBLIC_PARMS {
                TPMT_SYM_DEF_OBJECT symDetail;
                ...
            }
        """
        parameters = struct.pack(
            '>HHHH',
            TpmAlgId.TPM_ALG_SYMCIPHER,  # TPMT_PUBLIC_PARMS.type
            alg,  # TPMT_SYM_DEF_OBJECT.algorithm
            key_bits,  # TPMT_SYM_DEF_OBJECT.keyBits
            mode,  # TPMT_SYM_DEF_OBJECT.mode
        )
        resp = self.tpm20_run(
            Tpm20Tag.TPM2_ST_NO_SESSIONS,
            Tpm20CommandCode.TPM2_CC_TestParms,
            parameters)
        if resp:
            raise RuntimeError(f"Unexpected response content for TPM2_CC_TestParms: {resp!r}")

    def tpm20_public_read(self, handle: int) -> Tpm20ReadPublicResponse:
        """Read the public area of a loaded object, without authorization, using TPM2_ReadPublic

        Response:
            TPM2B_PUBLIC outPublic : structure containing the public area of an object
            TPM2B_NAME name : name of the object
            TPM2B_NAME qualifiedName : the Qualified Name of the object

        struct {
            UINT16 size;
            TPMT_PUBLIC publicArea;
        } TPM2B_PUBLIC;
        struct TPM2B_NAME {
            UINT16 size;
            BYTE name[sizeof(TPMU_NAME)];
        }
        """
        resp = self.tpm20_run(
            Tpm20Tag.TPM2_ST_NO_SESSIONS,
            Tpm20CommandCode.TPM2_CC_ReadPublic,
            struct.pack('>I', handle))
        with ParsedBuffer(resp, "TPM2_CC_ReadPublic response") as buf:
            obj = Tpm20ReadPublicResponse.from_buffer(buf)
        computed_name = obj.public.compute_name()
        if computed_name != obj.name:
            raise RuntimeError(
                f"Name of object {handle:#x} does not match its public area: {computed_name.hex()} != {obj.name.hex()}")  # noqa
        return obj

    def tpm20_nv_public_read(self, nv_index: int) -> Tpm20NvReadPublicResponse:
        """Read the public area and name of an Non-Volatile Index, using TPM2_NV_ReadPublic

        Response:
            TPM2B_NV_PUBLIC nvPublic : the public area of the NV Index
            TPM2B_NAME nvName : the Name of the NV Index

        struct TPM2B_NV_PUBLIC {
            UINT16 size;
            TPMS_NV_PUBLIC nvPublic;
        }
        struct TPM2B_NAME {
            UINT16 size;
            BYTE name[sizeof(TPMU_NAME)];
        }
        """
        resp = self.tpm20_run(
            Tpm20Tag.TPM2_ST_NO_SESSIONS,
            Tpm20CommandCode.TPM2_CC_NV_ReadPublic,
            struct.pack('>I', nv_index))
        with ParsedBuffer(resp, "TPM2_CC_NV_ReadPublic response") as buf:
            obj = Tpm20NvReadPublicResponse.from_buffer(buf)
        computed_name = obj.public.compute_name()
        if computed_name != obj.name:
            raise RuntimeError(
                f"Name of NV index {nv_index:#x} does not match its public area: {computed_name.hex()} != {obj.name.hex()}")  # noqa
        return obj

    def tpm20_start_auth_session_hmac(self) -> Tpm20AuthSessionContext:
        """Start an authorization session with HMAC-SHA256, using TPM2_StartAuthSession

        The session is "Unbound and Unsalted", as it does not use tpmKey nor bind parameter.

        Parameters:
            TPMI_DH_OBJECT tpmKey: handle of a loaded decrypt key used to encrypt salt
            TPMI_DH_ENTITY bind: entity providing the authValue
            TPM2B_NONCE nonceCaller
            TPM2B_ENCRYPTED_SECRET encryptedSalt
            TPM_SE sessionType
            TPMT_SYM_DEF+ symmetric: the algorithm and key size for parameter encryption
            TPMI_ALG_HASH authHash

        Response:
            TPMI_SH_AUTH_SESSION sessionHandle
            TPM2B_NONCE nonceTPM

        TPM2B_NONCE is TPM2B_DIGEST (UINT16 size followed by bytes)
        TPM2B_ENCRYPTED_SECRET is also struct {UINT16 size; BYTE secret[sizeof(TPMU_ENCRYPTED_SECRET)];}
        """
        nonce_caller: bytes = secrets.token_bytes(0x20)
        params = Tpm20StartAuthSessionCmdParams.new_with_hmac(nonce_caller, TpmAlgId.TPM_ALG_SHA256)
        resp = self.tpm20_run(
            Tpm20Tag.TPM2_ST_NO_SESSIONS,
            Tpm20CommandCode.TPM2_CC_StartAuthSession,
            params.to_bytes())
        with ParsedBuffer(resp, "TPM2_CC_StartAuthSession response") as buf:
            obj = Tpm20StartAuthSessionResponse.from_buffer(buf)
        return Tpm20AuthSessionContext(obj.session_handle, TpmAlgId.TPM_ALG_SHA256, nonce_caller, obj.nonce_tpm, b"")

    def tpm20_flush_context(self, handle: int) -> None:
        """Remove from the TPM memory the context associated with a loaded
        object, sequence object, or session, using TPM2_FlushContext.
        """
        resp = self.tpm20_run(
            Tpm20Tag.TPM2_ST_NO_SESSIONS,
            Tpm20CommandCode.TPM2_CC_FlushContext,
            struct.pack('>I', handle))
        if resp:
            raise RuntimeError(f"Unexpected response content for TPM2_CC_FlushContext: {resp!r}")

    def tpm20_nv_read(self, handle: int, auth_session: Tpm20AuthSessionContext,
                      handle_name: bytes, offset: int, size: int) -> bytes:
        """Read a non-volatile variable, using the given auth_session, using TPM2_NV_Read"""
        parameters = struct.pack('>HH', size, offset)

        # Define session attributes to 1 for "continueSession"
        session_attributes = b'\x01'

        # Compute the cpHash (Command Parameter Hash)
        cphash = auth_session.hash_alg.compute_hash(
            struct.pack('>I', Tpm20CommandCode.TPM2_CC_NV_Read) +
            handle_name +
            handle_name +
            parameters)

        # Generate a new caller nonce
        auth_session.nonce_caller = secrets.token_bytes(len(auth_session.nonce_caller))

        # Compute the HMAC
        hmac_command = auth_session.hash_alg.compute_hmac(
            auth_session.session_key,
            cphash + auth_session.nonce_caller + auth_session.nonce_tpm + session_attributes)

        auth_area = Tpm20CmdAuthArea(
            session_handle=auth_session.session_handle,
            nonce_caller=auth_session.nonce_caller,
            session_attributes=1,  # 1 means "continueSession"
            cphash=hmac_command,
        )

        # Transmit an authenticated command
        resp = self.tpm20_run(
            Tpm20Tag.TPM2_ST_SESSIONS,
            Tpm20CommandCode.TPM2_CC_NV_Read,
            struct.pack(
                '>II',
                handle,  # parameter @authHandle
                handle,  # parameter nvIndex
            ) +
            auth_area.to_bytes_with_size() +
            parameters)
        with ParsedBuffer(resp, "TPM2_CC_NV_Read response") as buf:
            auth_resp = Tpm20RespAuth.from_buffer(buf)
        auth_session.nonce_tpm = auth_resp.nonce_tpm
        if auth_resp.session_attributes != 1:
            raise RuntimeError(f"Unexpected response session attributes for TPM2_CC_NV_Read: {auth_resp.session_attributes:#x}")  # noqa

        # Compute the rpHash (Response Parameter Hash)
        rphash = auth_session.hash_alg.compute_hash(
            struct.pack('>II', Tpm20ResponseCode.TPM2_RC_SUCCESS, Tpm20CommandCode.TPM2_CC_NV_Read) +
            auth_resp.data)

        # Verify the HMAC
        sess_attr_byte = struct.pack('B', auth_resp.session_attributes)
        hmac_response = auth_session.hash_alg.compute_hmac(
            auth_session.session_key,
            rphash + auth_session.nonce_tpm + auth_session.nonce_caller + sess_attr_byte)
        if not hmac.compare_digest(hmac_response, auth_resp.resp_hmac):
            raise RuntimeError("Invalid HMAC in response")

        with auth_resp.into_buffer() as buf:
            return buf.read_tpm2b()

    def tpm20_nv_read_all(self, handle: int, auth_session: Tpm20AuthSessionContext,
                          handle_name: bytes, size: int) -> bytes:
        """Read a non-volatile variable, using the given auth_session, using repeated TPM2_NV_Read"""
        prop_nv_buffer_max = self.tpm20_get_cap_tpm_property(Tpm20Property.TPM2_PT_NV_BUFFER_MAX)
        if not prop_nv_buffer_max:
            print("Warning: unable to get TPM2_PT_NV_BUFFER_MAX, using default value", file=sys.stderr)
            nv_buffer_size = 0x400
        else:
            nv_buffer_size = prop_nv_buffer_max

        # Read parts
        data = b''
        while len(data) < size:
            requested_size = min(size - len(data), nv_buffer_size)
            resp = self.tpm20_nv_read(handle, auth_session, handle_name, len(data), requested_size)
            if not resp:
                print(f"Warning: unable to get whole data for {handle:#x}: {len(data):#x}/{size:#x}",
                      file=sys.stderr)
                break
            data += resp
        return data


def show_tpm_dev(dev: TpmDevice) -> None:
    print(f"Device {dev.name} is TPM {dev.tpm_version()}")

    if dev.is_tpm12:
        print("TPM Capabilities:")
        dev.tpm12_print_cap_version("  ")
        dev.tpm12_print_cap_version_info("  ")
        print("  - Properties:")
        prop_pcr = dev.tpm12_get_capability_prop(Tpm12CapabilityArea.TPM_CAP_PROP_PCR)
        assert isinstance(prop_pcr, int)
        print(f"    - Number of PCR: {prop_pcr}")
        print(f"    - Number of DIR: {dev.tpm12_get_capability_prop(Tpm12CapabilityArea.TPM_CAP_PROP_DIR)}")
        manuf = dev.tpm12_get_capability_prop(Tpm12CapabilityArea.TPM_CAP_PROP_MANUFACTURER)
        bytes_manuf = struct.pack('>I', manuf)
        manuf_name = TPM_MANUFACTURERS.get(bytes_manuf, "?")
        bytes_manuf = bytes_manuf.rstrip(b'\0')
        try:
            repr_manuf = repr(bytes_manuf.decode('ascii'))
        except UnicodeDecodeError:
            repr_manuf = repr(bytes_manuf)
        print(f"    - Manufacturer: {repr_manuf} ({manuf:#x}, {manuf_name})")
        for prop, desc in (
                (Tpm12CapabilityArea.TPM_CAP_PROP_KEYS, 'Number of loadable RSA-2048 keys'),
                (Tpm12CapabilityArea.TPM_CAP_PROP_MIN_COUNTER, "Minimum 10ths of seconds between monotonic counter increments"),  # noqa
                (Tpm12CapabilityArea.TPM_CAP_PROP_AUTHSESS, "Available authorization sessions"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_TRANSESS, "Available transport sessions"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_COUNTERS, "Available monotonic counters"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_MAX_AUTHSESS, "Maximum authorization sessions"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_MAX_TRANSESS, "Maximum transport sessions"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_MAX_COUNTERS, "Maximum monotonic counters"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_MAX_KEYS, "Maximum RSA-2048 keys"),  # without EK or SRK
                (Tpm12CapabilityArea.TPM_CAP_PROP_OWNER, "Is an owner installed"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_CONTEXT, "Available saved session slots"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_MAX_CONTEXT, "Maximum saved session slots"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_FAMILYROWS, "Maximum rows in family table"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_TIS_TIMEOUT, "TIS timeouts"),  # timeouts for platform specific TPM Interface Specification.  # noqa
                (Tpm12CapabilityArea.TPM_CAP_PROP_STARTUP_EFFECT, "Startup Effect"),  # TPM_STARTUP_EFFECTS = UINT32
                (Tpm12CapabilityArea.TPM_CAP_PROP_DELEGATE_ROW, "Maximum size of the delegate table in rows"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_DAA_MAX, "Maximum DAA sessions"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_DAASESS, "Available DAA sessions"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_CONTEXT_DIST, "Maximum distance between count values"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_DAA_INTERRUPT, "Does accept any command while DAA Join or Sign"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_SESSIONS, "Available auth. and transp. sessions"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_MAX_SESSIONS, "Maximum sessions"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_CMK_RESTRICTION, "CMK restriction"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_DURATION, "Small, medium and long durations (µs)"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_ACTIVE_COUNTER, "Active counter ID"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_MAX_NV_AVAILABLE, "Maximum NV space"),
                (Tpm12CapabilityArea.TPM_CAP_PROP_INPUT_BUFFER, "Maximum size of input/output buffers")):
            prop_value = dev.tpm12_get_capability_prop(prop)
            if prop == Tpm12CapabilityArea.TPM_CAP_PROP_ACTIVE_COUNTER:
                print(f"    - {desc}: {prop_value:#x}")
            else:
                print(f"    - {desc}: {prop_value}")
        print("")

        print("  - Supported commands:")
        # TODO when encountering a TPM 1.2, bruteforce all the possible commands
        for ordinal in itertools.chain(range(0x100), range(0x20000000, 0x20000300), range(0x40000000, 0x40000100)):
            if dev.tpm12_get_cap_ordinal(ordinal):
                try:
                    command_name = Tpm12CommandCode(ordinal).name
                except ValueError:
                    # If this is not a vendor command, this is fatal
                    if ordinal < 0x10000:
                        raise
                    # Otherwise, it is a vendor command
                    command_name = f"VendorCommand{ordinal}"
                print(f"    [{ordinal:#10x}] {command_name}")
        print("")

        print("  - Supported algorithms:")
        for algorithm in range(0x10):
            if dev.tpm12_get_cap_algorithm(algorithm):
                print(f"    [{algorithm:#4x}] {Tpm12AlgId(algorithm).name}")
        print("")

        print("  - Supported symmetric modes:")
        for sym_mode in range(4):
            if dev.tpm12_get_cap_sym_mode(sym_mode):
                print(f"    [{sym_mode:#4x}] {Tpm12SymMode(sym_mode).name}")
        print("")

        print("  - Supported protocols:")
        for protocol in range(0x10):
            if dev.tpm12_get_cap_protocol(protocol):
                print(f"    [{protocol:#4x}] {Tpm12ProtocolId(protocol).name}")
        print("")

        print("  - Permanent flags:")
        permanent_flags = dev.tpm12_get_cap_flag(Tpm12CapabilityArea.TPM_CAP_FLAG_PERMANENT)
        for flag_idx, flag_value in enumerate(permanent_flags):
            if flag_idx < len(TPM12_PERMANENT_FLAGS_DESC):
                print(f"    [{flag_idx:2d}] {TPM12_PERMANENT_FLAGS_DESC[flag_idx]} = {flag_value}")
            else:
                print(f"    [{flag_idx:2d}] Unknown? = {flag_value}")
        print("")

        print("  - Volatile (Startup Clear) flags:")
        for flag_idx, flag_value in enumerate(dev.tpm12_get_cap_flag(Tpm12CapabilityArea.TPM_CAP_FLAG_VOLATILE)):
            if flag_idx < len(TPM12_STCLEAR_FLAGS_DESC):
                print(f"    [{flag_idx:2d}] {TPM12_STCLEAR_FLAGS_DESC[flag_idx]} = {flag_value}")
            else:
                print(f"    [{flag_idx:2d}] Unknown? = {flag_value}")
        print("")

        print("PCR:")
        for pcr_idx in range(prop_pcr):
            pcr_digest = dev.tpm12_pcr_read(pcr_idx)
            print(f"  {pcr_idx:2d}: {pcr_digest.hex()}")
        print("")

        print("Non Volatile memory:")
        for nv_idx in dev.tpm12_get_cap_nv_list():
            nv_desc = TPM12_RESERVED_NV_INDEXES_DESC.get(nv_idx)
            if nv_desc:
                print(f"  [{nv_idx:#010x}] {nv_desc}")
            else:
                print(f"  [{nv_idx:#010x}]")
            dev.tpm12_print_cap_nv_index(nv_idx, indent="    ")
        print("")

        print("PubEK (Public Endorsement Key):")  # This is a privacy sentitive value, according to the spec.
        try:
            pubek = dev.tpm12_read_pubek()
        except Tpm12Failure as exc:
            if exc.code == Tpm12ResponseCode.TPM_DISABLED_CMD:
                if not permanent_flags[3]:
                    print("  Error TPM_DISABLED_CMD as expected (readPubek is false, an owner may exist)")
                else:
                    print("  Error TPM_DISABLED_CMD but readPubek is true, raise the exception")
                    raise
            elif exc.code == Tpm12ResponseCode.TPM_RC_VENDOR_ERROR:
                # On Windows 10 1809, non-administrator users can open the TPM 1.2
                # but cannot read the pub EK
                if not permanent_flags[3]:
                    print("  Error TPM_RC_VENDOR_ERROR, missing Administrator privileges? (and readPubek is false, an owner may exist)")  # noqa
                else:
                    print("  Error TPM_RC_VENDOR_ERROR, missing Administrator privileges?")
                    raise
            else:
                raise
        else:
            hexdump(pubek, indent="  ")
            # Check that the PubEK is a RSA2048 key with the standard parameters
            ek_rsa2048_prefix = bytes.fromhex("00000001 0003 0001 0000000c 00000800 00000002 00000000 00000100")
            if len(pubek) == 0x11c and pubek[:0x1c] == ek_rsa2048_prefix:
                print("  => RSA2048 key")
            else:
                raise RuntimeError("Unexpected PubEK parameters")

    if dev.is_tpm20:
        tpm_prop_group = None
        for prop_id, prop_value in dev.tpm20_get_cap_tpm_properties():
            if tpm_prop_group is None or prop_id >= tpm_prop_group + 0x100:
                # Next group
                tpm_prop_group = prop_id & ~0xff
                if tpm_prop_group == 0x100:
                    print("TPM fixed properties:")
                elif tpm_prop_group == 0x200:
                    print("TPM variable properties:")
                else:
                    print(f"TPM properties group {tpm_prop_group:#x} (UNKNOWN):")
            try:
                prop_name = Tpm20Property(prop_id).name
            except ValueError:
                prop_name = f"<UNKNOWN property {prop_id}>"
            repr_prop_value = hex(prop_value)
            if prop_id in (
                    Tpm20Property.TPM2_PT_FAMILY_INDICATOR.value,
                    Tpm20Property.TPM2_PT_VENDOR_STRING_1.value,
                    Tpm20Property.TPM2_PT_VENDOR_STRING_2.value,
                    Tpm20Property.TPM2_PT_VENDOR_STRING_3.value,
                    Tpm20Property.TPM2_PT_VENDOR_STRING_4.value):
                # String property
                bytes_value = struct.pack('>I', prop_value).rstrip(b'\0')
                try:
                    repr_prop_value = repr(bytes_value.decode('ascii'))
                except UnicodeDecodeError:
                    repr_prop_value = repr(bytes_value)
            elif prop_id == Tpm20Property.TPM2_PT_MANUFACTURER.value:
                # Manufacturer property
                bytes_value = struct.pack('>I', prop_value)
                manuf_name = TPM_MANUFACTURERS.get(bytes_value, "?")
                bytes_value = bytes_value.rstrip(b'\0')
                try:
                    repr_prop_value = repr(bytes_value.decode('ascii'))
                except UnicodeDecodeError:
                    repr_prop_value = repr(bytes_value)
                repr_prop_value += f" ({manuf_name})"
            elif prop_id == Tpm20Property.TPM2_PT_REVISION.value:
                # Version property
                repr_prop_value = f"{prop_value // 100}.{prop_value % 100}"
            elif prop_id in (
                    Tpm20Property.TPM2_PT_FIRMWARE_VERSION_1,
                    Tpm20Property.TPM2_PT_FIRMWARE_VERSION_2):
                version_major = (prop_value >> 16) & 0xffff
                version_minor = prop_value & 0xffff
                repr_prop_value = f"{version_major}.{version_minor} ({prop_value:#x})"
            elif prop_id == Tpm20Property.TPM2_PT_CLOCK_UPDATE.value:
                # Clock update property, at least 2**22 milliseconds (~69.9 minutes)
                val_mins, val_remaining = divmod(prop_value, 60000)
                val_secs, val_millis = divmod(val_remaining, 1000)
                repr_prop_value = f"{val_mins}m {val_secs:02d}s {val_millis:03d}ms ({prop_value:#x})"
            elif prop_id in (
                    Tpm20Property.TPM2_PT_DAY_OF_YEAR.value,
                    Tpm20Property.TPM2_PT_YEAR.value,
                    Tpm20Property.TPM2_PT_PCR_COUNT.value,
                    Tpm20Property.TPM2_PT_LOCKOUT_INTERVAL.value,
                    Tpm20Property.TPM2_PT_LOCKOUT_RECOVERY.value):
                # Decimal property
                repr_prop_value = str(prop_value)
            print(f"  [{prop_id:#6x}] {prop_name} = {repr_prop_value}")

        print("")

        print("Supported algorithms:")
        for alg_id, alg_properties in dev.tpm20_get_cap_algs():
            desc_props = ""
            if alg_properties & 1:
                desc_props += " asymmetric"
            if alg_properties & 2:
                desc_props += " symmetric"
            if alg_properties & 4:
                desc_props += " hash"
            if alg_properties & 8:
                desc_props += " object"
            if alg_properties & 0x100:
                desc_props += " signing"
            if alg_properties & 0x200:
                desc_props += " encrypting"
            if alg_properties & 0x400:
                desc_props += " method"
            remaining = alg_properties & ~0x70f
            if remaining:
                desc_props += f" ???({remaining:#x})"
            print(f"  [{alg_id.value:#4x}] {alg_id.name}:{desc_props}")

        print("")

        print("Supported ECC curves:")
        for ecc_curve in dev.tpm20_get_cap_ecc_curves():
            print(f"  [{ecc_curve.value:#4x}] {ecc_curve.name}")

        print("")

        print("Supported RSA key sizes:")
        for key_bits in (512, 1024, 2048, 3072, 4096, 8192):
            try:
                dev.tpm20_testparms_rsa(key_bits)
            except Tpm20Failure as exc:
                if exc.code_desc is not None:
                    desc_result = f"{exc.code_desc.name}={exc.code_desc:#x}"
                else:
                    desc_result = f"unknown error {exc}"

                if exc.code == Tpm20ResponseCode.TPM2_RC_PARAM1_VALUE:
                    desc_result = f"unsupported algorithm specification ({desc_result})"
            else:
                desc_result = "OK"
            print(f"  RSA {key_bits:4}: {desc_result}")

        print("")

        print("Supported AES key sizes and modes:")
        symcipher_modes: Tuple[Tuple[str, TpmAlgId], ...] = (
            ("", TpmAlgId.TPM_ALG_NULL),
            ("CTR", TpmAlgId.TPM_ALG_CTR),
            ("OFB", TpmAlgId.TPM_ALG_OFB),
            ("CBC", TpmAlgId.TPM_ALG_CBC),
            ("CFB", TpmAlgId.TPM_ALG_CFB),
            ("ECB", TpmAlgId.TPM_ALG_ECB),
            ("CCM", TpmAlgId.TPM_ALG_CCM),
            ("GCM", TpmAlgId.TPM_ALG_GCM),
            ("KW", TpmAlgId.TPM_ALG_KW),
            ("KWP", TpmAlgId.TPM_ALG_KWP),
        )
        for key_bits in (128, 192, 256):
            for mode_name, mode in symcipher_modes:
                try:
                    dev.tpm20_testparms_symcipher(TpmAlgId.TPM_ALG_AES, key_bits, mode)
                except Tpm20Failure as exc:
                    if exc.code_desc is not None:
                        desc_result = f"{exc.code_desc.name}={exc.code_desc:#x}"
                    else:
                        desc_result = f"unknown error {exc}"

                    if exc.code == Tpm20ResponseCode.TPM2_RC_PARAM1_VALUE:
                        desc_result = f"unsupported algorithm specification ({desc_result})"
                    elif exc.code == Tpm20ResponseCode.TPM2_RC_PARAM1_MODE:
                        desc_result = f"unsupported symmetric mode ({desc_result})"
                else:
                    desc_result = "OK"
                print(f"  AES {key_bits:3} {mode_name:3}: {desc_result}")

        print("")

        all_update_counters: Set[int] = set()
        for bank_alg_id, bank_pcr_select in dev.tpm20_get_cap_pcrs():
            print(f"PCR for bank {bank_alg_id.name}:")
            for pcr_idx in bank_pcr_select:
                pcr_digest, pcr_update_counter = dev.tpm20_pcr_read(pcr_idx, bank_alg_id)
                all_update_counters.add(pcr_update_counter)
                print(f"  {pcr_idx:2d}: {pcr_digest.hex()}")
        print(f"PCR update counter: {','.join(str(c) for c in sorted(all_update_counters))}")

        print("")

        print("PCR properties:")
        for pcr_prop_id, pcr_select in dev.tpm20_get_cap_pcr_properties():
            prop_name = Tpm20PcrProperty(pcr_prop_id).name
            print(f"  [{pcr_prop_id:#4x}] {prop_name}: {','.join(str(pcr) for pcr in pcr_select)}")

        print("")

        cmd_list: Sequence[Tuple[str, Tpm20Capability]] = (
            ("Normal commands", Tpm20Capability.TPM_CAP_COMMANDS),
            # "... for confirmation of platform authorization"
            ("Commands requiring Physical Presence", Tpm20Capability.TPM_CAP_PP_COMMANDS),
            ("Audit commands", Tpm20Capability.TPM_CAP_AUDIT_COMMANDS),
        )
        for comm_category, comm_cap in cmd_list:
            print(f"{comm_category}:")
            for command_code, comm_attr in dev.tpm20_get_cap_commands(comm_cap):
                try:
                    command_name = Tpm20CommandCode(command_code).name
                except ValueError:
                    # If this is not a vendor command, this is fatal
                    if (comm_attr & 0x2000) == 0:
                        raise
                    # Otherwise, it is a vendor command
                    command_name = f"VendorCommand{command_code}"
                desc_attr = ""
                if comm_attr & 0x40:
                    desc_attr += " nv"
                if comm_attr & 0x80:
                    desc_attr += " extensive"
                if comm_attr & 0x100:
                    desc_attr += " flushed"
                if comm_attr & 0xe00:
                    desc_attr += f" cHandles<{(comm_attr >> 9) & 7}>"
                if comm_attr & 0x1000:
                    desc_attr += " rHandle"
                if comm_attr & 0x2000:
                    desc_attr += " Vendor"
                remaining = comm_attr & ~0x3fc0
                if remaining:
                    desc_attr += f" ???({remaining:#x})"
                if desc_attr:
                    print(f"  [{command_code:#6x}] {command_name}:{desc_attr}")
                else:
                    print(f"  [{command_code:#6x}] {command_name}")

                if command_code == Tpm20CommandCode.TPM2_CC_FirmwareRead.value:
                    # Terminate abruptely in order to investigate
                    print("/!\\ The TPM firmware can be read /!\\", file=sys.stderr)
                    sys.exit(1)

        print("")

        # Try opening a session with the TPM
        auth_session = dev.tpm20_start_auth_session_hmac()
        print(f"Started session with handle {auth_session.session_handle:#x}")
        try:
            print("Handles:")
            previous_handle_type: Optional[int] = None
            for handle in dev.tpm20_get_cap_handles():
                handle_type = handle >> 24
                if previous_handle_type != handle_type:
                    handle_type_name = Tpm20HandleType(handle_type).name
                    print(f"  [{handle_type:#04x}] Handle type {handle_type_name}")
                    previous_handle_type = handle_type
                try:
                    handle_name = Tpm20Handle(handle).name
                except ValueError:
                    if 0x01000000 <= handle <= 0x013fffff:
                        handle_name = f"<unknown handle {handle:#x} for NV index defined by TPM Manufacturer>"
                    elif 0x01400000 <= handle <= 0x017fffff:
                        handle_name = f"<unknown handle {handle:#x} for NV index defined by Platform Manufacturer>"
                    elif 0x01800000 <= handle <= 0x01bfffff:
                        handle_name = f"<unknown handle {handle:#x} for NV index defined by Owner>"
                    elif 0x01c00000 <= handle <= 0x01ffffff:
                        handle_name = f"<unknown handle {handle:#x} for NV index defined by TCG>"
                    else:
                        handle_name = f"<unknown handle {handle:#x}>"
                if handle == auth_session.session_handle:
                    handle_name += " (handle to the active session)"
                print(f"    [{handle:#010x}] {handle_name}")

                # Read the public area on objects
                if handle_type not in (
                        Tpm20HandleType.TPM_HT_PCR.value,
                        Tpm20HandleType.TPM_HT_NV_INDEX.value,
                        Tpm20HandleType.TPM_HT_HMAC_SESSION.value,
                        Tpm20HandleType.TPM_HT_PERMANENT.value):
                    try:
                        obj_resp = dev.tpm20_public_read(handle)
                    except Tpm20Failure as exc:
                        if exc.code == Tpm20ResponseCode.TPM2_RC_HANDLE1_VALUE:
                            # Error TPM2_RC_VALUE on Handle 1 (parameter) means
                            # "value is out of range or is not correct for the context"
                            print(f"      - Public object read: error {exc.code:#x} (handle value is not correct for the context)")  # noqa
                        else:
                            raise
                    else:
                        obj_resp.print(indent="      ")

                # Read non-volatile indexes
                if handle_type == Tpm20HandleType.TPM_HT_NV_INDEX.value:
                    nv_resp = dev.tpm20_nv_public_read(handle)
                    nv_resp.print(expected_handle=handle, indent="      ")

                    try:
                        # Try reading the handle, using the session
                        handle_data = dev.tpm20_nv_read_all(
                            handle, auth_session, nv_resp.name, nv_resp.public.data_size)
                    except Tpm20Failure as exc:
                        if exc.code == Tpm20ResponseCode.TPM2_RC_AUTH_UNAVAILABLE:
                            print("      - NV data: error TPM2_RC_AUTH_UNAVAILABLE")
                        elif exc.code == Tpm20ResponseCode.TPM2_RC_NV_UNINITIALIZED:
                            print("      - NV data: error TPM2_RC_NV_UNINITIALIZED")
                        elif exc.code == Tpm20ResponseCode.TPM2_RC_SESS1_BAD_AUTH:
                            print("      - NV data: error TPM2_RC_S+TPM2_RC_1+TPM2_RC_BAD_AUTH")
                        else:
                            raise
                    else:
                        print("      - NV data:")
                        hexdump(handle_data, indent='        ')
        finally:
            dev.tpm20_flush_context(auth_session.session_handle)


class LinuxTpmDevice(TpmDevice):
    """Access the TPM from Linux device /dev/tpm0 or /dev/tpmrm0, or from a socket"""
    def __init__(self, dev_path: Optional[Path], tpm_host: Optional[str],
                 tpm_port: Optional[int], use_mssim: bool = False):
        self.sock: Optional[socket.socket] = None
        self.dev: Optional[BinaryIO] = None
        if dev_path is not None:
            name = str(dev_path)
            self.dev = dev_path.open('r+b', buffering=0)
        elif tpm_port is not None:
            host = '127.0.0.1' if tpm_host is None else tpm_host
            proto = 'tcp+mssim' if use_mssim else 'tcp'
            name = f"{proto}://{host}:{tpm_port}"
            self.sock = socket.create_connection((host, tpm_port))
            self.dev = self.sock.makefile(mode='rwb', buffering=0)
        else:
            raise ValueError("No device path nor TCP connection info provided")

        super(LinuxTpmDevice, self).__init__(name)
        self.use_mssim = use_mssim

    def __del__(self) -> None:
        self.close()

    def close(self) -> None:
        if self.dev is not None:
            self.dev.close()
            self.dev = None
        if self.sock is not None:
            self.sock.close()
            self.sock = None

    def _read_exact(self, size: int) -> bytes:
        """Read exactly size bytes from the device"""
        assert self.dev is not None
        chunks = []
        while size > 0:
            new_data = self.dev.read(size)
            if not new_data:
                raise RuntimeError(f"Reached EOF while expecting {size} bytes")
            chunks.append(new_data)
            size -= len(new_data)
        return b''.join(chunks)

    def submit_command_raw(self, cmd: bytes) -> bytes:
        assert self.dev is not None
        if self.use_mssim:
            # Add a 9-byte command header:
            # - uint32 MS_SIM_TPM_SEND_COMMAND = 8
            # - uint8 locality = 0
            # - uint32 size
            cmd = struct.pack('>IBI', 8, 0, len(cmd)) + cmd

        written = self.dev.write(cmd)
        if written != len(cmd):
            raise RuntimeError(
                f"Fail to write the whole command to TPM device: {written} != {len(cmd)}")

        if self.use_mssim:
            # Receive a 4-byte response size
            response_size, = struct.unpack('>I', self._read_exact(4))
            if response_size == 0:
                raise RuntimeError("Received zero as response size")
            if response_size > 4096:
                raise RuntimeError(f"Received response size too large: {response_size!r}")
            response = self._read_exact(response_size)
            # Receive the appended four bytes of 0's
            last_zeros = self._read_exact(4)
            if last_zeros != b'\0\0\0\0':
                raise RuntimeError(f"Unexpected MSSIM trailer: {last_zeros.hex()}")
            return response

        # On Linux, TPM_BUFSIZE = 4096
        return self.dev.read(4096)


@enum.unique
class TbsResult(enum.IntEnum):
    """TBS_RESULT: result from TBS commands

    https://learn.microsoft.com/en-us/windows/win32/tbs/tbs-return-codes
    """
    TBS_SUCCESS = 0x0
    TBS_E_INTERNAL_ERROR = 0x80284001
    TBS_E_BAD_PARAMETER = 0x80284002
    TBS_E_INVALID_OUTPUT_POINTER = 0x80284003
    TBS_E_INVALID_CONTEXT = 0x80284004
    TBS_E_INSUFFICIENT_BUFFER = 0x80284005
    TBS_E_IOERROR = 0x80284006
    TBS_E_INVALID_CONTEXT_PARAM = 0x80284007
    TBS_E_SERVICE_NOT_RUNNING = 0x80284008
    TBS_E_TOO_MANY_TBS_CONTEXTS = 0x80284009
    TBS_E_TOO_MANY_RESOURCES = 0x8028400a
    TBS_E_SERVICE_START_PENDING = 0x8028400b
    TBS_E_PPI_NOT_SUPPORTED = 0x8028400c
    TBS_E_COMMAND_CANCELED = 0x8028400d
    TBS_E_BUFFER_TOO_LARGE = 0x8028400e
    TBS_E_TPM_NOT_FOUND = 0x8028400f
    TBS_E_SERVICE_DISABLED = 0x80284010
    TBS_E_NO_EVENT_LOG = 0x80284011
    TBS_E_ACCESS_DENIED = 0x80284012
    TBS_E_PROVISIONING_NOT_ALLOWED = 0x80284013
    TBS_E_PPI_FUNCTION_UNSUPPORTED = 0x80284014
    TBS_E_OWNERAUTH_NOT_FOUND = 0x80284015  # also TBS_E_PROVISIONING_INCOMPLETE


class TbsFailure(Exception):
    """Failure while executing a Windows TBS function"""
    def __init__(self, func_name: str, result: int):
        super(TbsFailure, self).__init__()
        self.func_name = func_name
        self.result = result
        try:
            self.result_enum: Optional[TbsResult] = TbsResult(result)
        except ValueError:
            print(f"Warning: unknown TBS result {self.result:#x}", file=sys.stderr)
            self.result_enum = None

    def __str__(self) -> str:
        if self.result_enum:
            return f"{self.func_name}: {self.result_enum.name}={self.result:#x}"
        return f"{self.func_name}: {self.result:#x}"


class WindowsTbsDevice(TpmDevice):
    """Access the TPM from Microsoft Windows using TPM Base Services"""
    def __init__(self: 'WindowsTbsDevice'):
        super(WindowsTbsDevice, self).__init__("TBS")
        self.tbsctx = None
        if TYPE_CHECKING:
            self.tbslib = ctypes.CDLL("Tbs")  # Make type-checking work on Linux
        else:
            self.tbslib = ctypes.WinDLL('Tbs')

        # https://learn.microsoft.com/en-us/windows/win32/api/tbs/nf-tbs-tbsi_context_create
        self.tbslib.Tbsi_Context_Create.argtypes = (
            ctypes.POINTER(ctypes.c_uint),  # PCTBS_CONTEXT_PARAMS pContextParams
            ctypes.POINTER(ctypes.c_void_p),  # PTBS_HCONTEXT phContext
        )
        self.tbslib.Tbsi_Context_Create.restype = ctypes.c_uint  # TBS_RESULT

        # https://learn.microsoft.com/en-us/windows/win32/api/tbs/nf-tbs-tbsip_context_close
        self.tbslib.Tbsip_Context_Close.argtypes = (
            ctypes.c_void_p,  # TBS_HCONTEXT hContext
        )
        self.tbslib.Tbsip_Context_Close.restype = ctypes.c_uint  # TBS_RESULT

        # https://learn.microsoft.com/en-us/windows/win32/api/tbs/nf-tbs-tbsip_submit_command
        self.tbslib.Tbsip_Submit_Command.argtypes = (
            ctypes.c_void_p,  # TBS_HCONTEXT hContext
            ctypes.c_uint,  # TBS_COMMAND_LOCALITY Locality
            ctypes.c_uint,  # TBS_COMMAND_PRIORITY Priority
            ctypes.POINTER(ctypes.c_ubyte),  # PCBYTE pabCommand
            ctypes.c_uint,  # UINT32 cbCommand
            ctypes.POINTER(ctypes.c_ubyte),  # PBYTE pabResult
            ctypes.POINTER(ctypes.c_uint),  # PUINT32 pcbResult
        )
        self.tbslib.Tbsip_Submit_Command.restype = ctypes.c_uint  # TBS_RESULT

        # Create a struct TBS_CONTEXT_PARAMS2,
        # cf. https://learn.microsoft.com/en-us/windows/win32/api/tbs/ns-tbs-tbs_context_params2
        #     typedef struct tdTBS_CONTEXT_PARAMS2 {
        #       UINT32 version;
        #       union {
        #         struct {
        #           UINT32 requestRaw : 1;
        #           UINT32 includeTpm12 : 1;
        #           UINT32 includeTpm20 : 1;
        #         };
        #         UINT32 asUINT32;
        #       };
        #     } TBS_CONTEXT_PARAMS2, *PTBS_CONTEXT_PARAMS2;
        self.tbsctx = ctypes.c_void_p()
        tbs_context_params = (ctypes.c_uint * 2)(2, 6)
        result = self.tbslib.Tbsi_Context_Create(tbs_context_params, ctypes.byref(self.tbsctx))
        if result == TbsResult.TBS_E_INVALID_CONTEXT_PARAM:
            # Try again with a version-1 TBS_CONTEXT_PARAMS structure
            print("Warning: Tbsi_Context_Create does not support params v2. Trying v1...", file=sys.stderr)
            tbs_context_params = (ctypes.c_uint * 2)(1, 0)
            result = self.tbslib.Tbsi_Context_Create(tbs_context_params, ctypes.byref(self.tbsctx))

        if result == TbsResult.TBS_E_INTERNAL_ERROR:
            # This happens on Windows 7 when Python is not executed as Administrator
            print("Tbsi_Context_Create failed with error TBS_E_INTERNAL_ERROR, are you admin?", file=sys.stderr)
            raise TbsFailure("Tbsi_Context_Create", result)
        if result != 0:
            raise TbsFailure("Tbsi_Context_Create", result)

    def __del__(self) -> None:
        self.close()

    def close(self) -> None:
        if self.tbsctx is not None:
            result = self.tbslib.Tbsip_Context_Close(self.tbsctx)
            self.tbsctx = None
            if result != 0:
                raise TbsFailure("Tbsip_Context_Close", result)

    def submit_command_raw(self, cmd: bytes) -> bytes:
        if self.tbsctx is None:
            raise ValueError("The WindowsTbsDevice was closed")
        cmd_buf = (ctypes.c_ubyte * len(cmd))()
        assert len(cmd) == ctypes.sizeof(cmd_buf)
        ctypes.memmove(cmd_buf, cmd, len(cmd))
        resp_len = ctypes.c_uint(4096)
        resp_buf = (ctypes.c_ubyte * resp_len.value)()
        result = self.tbslib.Tbsip_Submit_Command(
            self.tbsctx,
            0,  # TBS_COMMAND_LOCALITY_ZERO
            100,  # TBS_COMMAND_PRIORITY_LOW
            cmd_buf, len(cmd),
            resp_buf, ctypes.byref(resp_len))
        if result != 0:
            raise TbsFailure("Tbsip_Submit_Command", result)
        return bytes(resp_buf[:resp_len.value])


def main(argv: Optional[Sequence[str]] = None) -> None:
    parser = argparse.ArgumentParser(description="Get various information from a TPM")
    parser.add_argument('device', nargs="?", type=Path,
                        help="TPM device to use (/dev/tpmrm0 by default on Linux)")
    parser.add_argument('-H', '--host', type=int,
                        help="Connect to a TPM through a TCP host and port (127.0.0.1 by default)")
    parser.add_argument('-p', '--port', type=int,
                        help="Connect to a TPM through a TCP port (for example to use swtpm)")
    parser.add_argument('--mssim', action='store_true',
                        help="Use Microsoft Simulator protocol (used by mssim and ibm-sw-tpm2's tpm_server)")
    parser.add_argument('--tbs', action='store_true',
                        help="Use Microsoft Windows TPM Base Services (default on Windows)")
    parser.add_argument('--quiet-if-not-found', action='store_true',
                        help="Do not show a message if no TPM is found")
    parser.add_argument('-t', '--type', type=str,
                        help="Deserialize a file (or stdin) instead of querying a TPM (like tpm2_print)")
    args = parser.parse_args(argv)

    file_type: Optional[str] = args.type
    if file_type:
        print_file(file_type, args.device)
        return

    dev_path: Optional[Path] = args.device
    tpm_host: Optional[str] = args.host
    tpm_port: Optional[int] = args.port
    use_tbs: bool = args.tbs
    if tpm_port is None and not use_tbs:
        if os.name == 'nt':
            use_tbs = True
        else:
            if dev_path is None:
                for tried_dev in ('/dev/tpmrm0', '/dev/tpm0'):
                    tried_path = Path(tried_dev)
                    if tried_path.exists():
                        dev_path = tried_path
                        break
            if dev_path is None:
                if not args.quiet_if_not_found:
                    print("No TPM device found, exiting.")
                return

    if use_tbs:
        try:
            dev: Union[WindowsTbsDevice, LinuxTpmDevice] = WindowsTbsDevice()
        except TbsFailure as exc:
            if exc.result == TbsResult.TBS_E_TPM_NOT_FOUND:
                # Exit cleanly if no TPM was found
                if not args.quiet_if_not_found:
                    print("No TPM device found using TPM Base Services, exiting.")
                return
            raise
    else:
        dev = LinuxTpmDevice(dev_path=dev_path, tpm_host=tpm_host, tpm_port=tpm_port, use_mssim=args.mssim)
    try:
        show_tpm_dev(dev)
    finally:
        dev.close()


if __name__ == '__main__':
    main()
