/* SPDX-License-Identifier: BSD-3-Clause */

#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "log.h"
#include "pcr.h"
#include "tpm2_alg_util.h"
#include "tpm2_capability.h"
#include "tpm2_cc_util.h"
#include "tpm2_tool.h"

/*
 * Older versions of tpm2-tss misspelled these constants' names.
 * See https://github.com/tpm2-software/tpm2-tss/issues/1500.
 */
#ifndef TPM2_PT_HR_TRANSIENT_MIN
#define TPM2_PT_HR_TRANSIENT_MIN    ((TPM2_PT) (TPM2_PT_FIXED + 14))
#define TPM2_PT_HR_PERSISTENT_MIN   ((TPM2_PT) (TPM2_PT_FIXED + 15))
#define TPM2_PT_HR_NV_INDEX         ((TPM2_PT) (TPM2_PT_VAR + 2))
#define TPM2_PT_HR_TRANSIENT_AVAIL  ((TPM2_PT) (TPM2_PT_VAR + 7))
#define TPM2_PT_HR_PERSISTENT       ((TPM2_PT) (TPM2_PT_VAR + 8))
#define TPM2_PT_HR_PERSISTENT_AVAIL ((TPM2_PT) (TPM2_PT_VAR + 9))
#endif

/* convenience macro to convert flags into "1" / "0" strings */
#define prop_str(val) val ? "1" : "0"

/* number of elements in the capability_map array */
#define CAPABILITY_MAP_COUNT \
    (sizeof (capability_map) / sizeof (capability_map_entry_t))

/* Structure to map a string to the appropriate TPM2_CAP / TPM2_PT pair */
typedef struct capability_map_entry {
    char *capability_string;
    TPM2_CAP capability;
    UINT32 property;
    UINT32 count;
} capability_map_entry_t;
/*
 * Array of structures for use as a lookup table to map string representation
 * of a capability to the proper TPM2_CAP / TPM2_PT pair.
 */
capability_map_entry_t capability_map[] = {
    {
        .capability_string = "algorithms",
        .capability        = TPM2_CAP_ALGS,
        .property          = TPM2_ALG_FIRST,
        .count             = TPM2_MAX_CAP_ALGS,
    },
    {
        .capability_string = "commands",
        .capability        = TPM2_CAP_COMMANDS,
        .property          = TPM2_CC_FIRST,
        .count             = TPM2_MAX_CAP_CC,
    },
    {
        .capability_string = "pcrs",
        .capability        = TPM2_CAP_PCRS,
        .property          = 0,
        .count             = TPM2_MAX_TPM_PROPERTIES,
    },
    {
        .capability_string = "properties-fixed",
        .capability        = TPM2_CAP_TPM_PROPERTIES,
        .property          = TPM2_PT_FIXED,
        .count             = TPM2_MAX_TPM_PROPERTIES,
    },
    {
        .capability_string = "properties-variable",
        .capability        = TPM2_CAP_TPM_PROPERTIES,
        .property          = TPM2_PT_VAR,
        .count             = TPM2_MAX_TPM_PROPERTIES,
    },
    {
        .capability_string = "ecc-curves",
        .capability        = TPM2_CAP_ECC_CURVES,
        .property          = TPM2_ECC_NIST_P192,
        .count             = TPM2_MAX_ECC_CURVES,
    },
    {
        .capability_string = "handles-transient",
        .capability        = TPM2_CAP_HANDLES,
        .property          = TPM2_TRANSIENT_FIRST,
        .count             = TPM2_MAX_CAP_HANDLES,
    },
    {
        .capability_string = "handles-persistent",
        .capability        = TPM2_CAP_HANDLES,
        .property          = TPM2_PERSISTENT_FIRST,
        .count             = TPM2_MAX_CAP_HANDLES,
    },
    {
        .capability_string = "handles-permanent",
        .capability        = TPM2_CAP_HANDLES,
        .property          = TPM2_PERMANENT_FIRST,
        .count             = TPM2_MAX_CAP_HANDLES,
    },
    {
        .capability_string = "handles-pcr",
        .capability        = TPM2_CAP_HANDLES,
        .property          = TPM2_PCR_FIRST,
        .count             = TPM2_MAX_CAP_HANDLES,
    },
    {
        .capability_string = "handles-nv-index",
        .capability        = TPM2_CAP_HANDLES,
        .property          = TPM2_NV_INDEX_FIRST,
        .count             = TPM2_MAX_CAP_HANDLES,
    },
    {
        .capability_string = "handles-loaded-session",
        .capability        = TPM2_CAP_HANDLES,
        .property          = TPM2_LOADED_SESSION_FIRST,
        .count             = TPM2_MAX_CAP_HANDLES,
    },
    {
        .capability_string = "handles-saved-session",
        .capability        = TPM2_CAP_HANDLES,
        .property          = TPM2_ACTIVE_SESSION_FIRST,
        .count             = TPM2_MAX_CAP_HANDLES,
    },
};
/*
 * Structure to hold options for this tool.
 */
typedef struct capability_opts {
    char *capability_string;
    TPM2_CAP capability;
    UINT32 property;
    UINT32 count;
    bool list;
} capability_opts_t;

static capability_opts_t options;

/*
 * This function uses the 'capability_string' field in the capabilities_opts
 * structure to locate the same string in the capability_map array and then
 * populates the 'capability' and 'property' fields of the capability_opts_t
 * structure with the appropriate values from the capability_map.
 * Return values:
 * true - the function executed normally.
 * false - no matching entry found in capability_map.
 */
bool sanity_check_capability_opts(void) {

    if (options.capability_string == NULL) {
        LOG_ERR("missing capability string, see --help");
        return false;
    }

    size_t i;
    for (i = 0; i < CAPABILITY_MAP_COUNT; ++i) {
        int cmp = strcmp(capability_map[i].capability_string,
                options.capability_string);
        if (cmp == 0) {
            options.capability = capability_map[i].capability;
            options.property = capability_map[i].property;
            options.count = capability_map[i].count;
            return true;
        }
    }

    LOG_ERR("invalid capability string: %s, see --help",
            options.capability_string);

    return false;
}

static void print_cap_map() {

    size_t i;
    for (i = 0; i < CAPABILITY_MAP_COUNT; ++i) {
        const char *capstr = capability_map[i].capability_string;
        tpm2_tool_output("- %s\n", capstr);
    }
}

/*
 * There are a number of fixed TPM properties (tagged properties) that are
 * characters (8bit chars) packed into 32bit integers, trim leading and trailing
 * spaces.
 * Also add the escape characters when necessary.
 */
static char *
get_uint32_as_chars(UINT32 value) {

    value = tpm2_util_ntoh_32(value);
    UINT8 *bytes = (UINT8 *) &value;

    /*
     * move the start of the string to the beginning
     * first non space character
     * Record the number of skips in i.
     */
    unsigned i;
    for (i = 0; i < sizeof(value); i++) {
        UINT8 b = *bytes;
        if (!isspace(b)) {
            break;
        }
        bytes++;
    }

    /* record the number of trailing spaces in j */
    unsigned j;
    for (j = sizeof(value) - i; j > i; j--) {
        UINT8 b = bytes[j - 1];
        /* NULL bytes count as space */
        if (b && !isspace(b)) {
            break;
        }
    }

    static char buf[10] = { 0 };
    unsigned k = 0;
    for(i = 0; i < j; i++) {
        if (bytes[i] == '"') {  // Characters that must be escaped
            buf[k] = '\\';      // The escape character
            k+=1;
        }
        buf[k] = bytes[i];
        k++;
    }
    buf[k] = '\0';

    return buf;
}
/*
 * Print string representations of the TPMA_MODES.
 */
static void tpm2_tool_output_tpma_modes(TPMA_MODES modes) {
    tpm2_tool_output("TPM2_PT_MODES:\n"
            "  raw: 0x%X\n", modes);
    if (modes & TPMA_MODES_FIPS_140_2)
        tpm2_tool_output("  value: TPMA_MODES_FIPS_140_2\n");
    if (modes & TPMA_MODES_RESERVED1_MASK)
        tpm2_tool_output(
                "  value: TPMA_MODES_RESERVED1 (these bits shouldn't be set)\n");
}
/*
 * Print string representation of the TPMA_PERMANENT attributes.
 */
static void dump_permanent_attrs(TPMA_PERMANENT attrs) {
    tpm2_tool_output("TPM2_PT_PERMANENT:\n");
    tpm2_tool_output("  ownerAuthSet:              %s\n",
            prop_str (attrs & TPMA_PERMANENT_OWNERAUTHSET));
    tpm2_tool_output("  endorsementAuthSet:        %s\n",
            prop_str (attrs & TPMA_PERMANENT_ENDORSEMENTAUTHSET));
    tpm2_tool_output("  lockoutAuthSet:            %s\n",
            prop_str (attrs & TPMA_PERMANENT_LOCKOUTAUTHSET));
    tpm2_tool_output("  reserved1:                 %s\n",
            prop_str (attrs & TPMA_PERMANENT_RESERVED1_MASK));
    tpm2_tool_output("  disableClear:              %s\n",
            prop_str (attrs & TPMA_PERMANENT_DISABLECLEAR));
    tpm2_tool_output("  inLockout:                 %s\n",
            prop_str (attrs & TPMA_PERMANENT_INLOCKOUT));
    tpm2_tool_output("  tpmGeneratedEPS:           %s\n",
            prop_str (attrs & TPMA_PERMANENT_TPMGENERATEDEPS));
    tpm2_tool_output("  reserved2:                 %s\n",
            prop_str (attrs & TPMA_PERMANENT_RESERVED2_MASK));
}
/*
 * Print string representations of the TPMA_STARTUP_CLEAR attributes.
 */
static void dump_startup_clear_attrs(TPMA_STARTUP_CLEAR attrs) {
    tpm2_tool_output("TPM2_PT_STARTUP_CLEAR:\n");
    tpm2_tool_output("  phEnable:                  %s\n",
            prop_str (attrs & TPMA_STARTUP_CLEAR_PHENABLE));
    tpm2_tool_output("  shEnable:                  %s\n",
            prop_str (attrs & TPMA_STARTUP_CLEAR_SHENABLE));
    tpm2_tool_output("  ehEnable:                  %s\n",
            prop_str (attrs & TPMA_STARTUP_CLEAR_EHENABLE));;
    tpm2_tool_output("  phEnableNV:                %s\n",
            prop_str (attrs & TPMA_STARTUP_CLEAR_PHENABLENV));
    tpm2_tool_output("  reserved1:                 %s\n",
            prop_str (attrs & TPMA_STARTUP_CLEAR_RESERVED1_MASK));
    tpm2_tool_output("  orderly:                   %s\n",
            prop_str (attrs & TPMA_STARTUP_CLEAR_ORDERLY));
}
/*
 * Iterate over all fixed properties, call the unique print function for each.
 */
static void dump_tpm_properties_fixed(TPMS_TAGGED_PROPERTY properties[],
        size_t count) {
    size_t i;
    char *buf;

    for (i = 0; i < count; ++i) {
        TPM2_PT property = properties[i].property;
        UINT32 value = properties[i].value;
        switch (property) {
        case TPM2_PT_FAMILY_INDICATOR:
            buf = get_uint32_as_chars(value);
            tpm2_tool_output("TPM2_PT_FAMILY_INDICATOR:\n"
                    "  raw: 0x%X\n"
                    "  value: \"%s\"\n", value, buf);
            break;
        case TPM2_PT_LEVEL:
            tpm2_tool_output("TPM2_PT_LEVEL:\n"
                    "  raw: %d\n", value);
            break;
        case TPM2_PT_REVISION:
            tpm2_tool_output("TPM2_PT_REVISION:\n"
                    "  raw: 0x%X\n"
                    "  value: %.2f\n", value, (float )value / 100);
            break;
        case TPM2_PT_DAY_OF_YEAR:
            tpm2_tool_output("TPM2_PT_DAY_OF_YEAR:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_YEAR:
            tpm2_tool_output("TPM2_PT_YEAR:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_MANUFACTURER: {
            UINT32 he_value = tpm2_util_ntoh_32(value);
            tpm2_tool_output("TPM2_PT_MANUFACTURER:\n"
                    "  raw: 0x%X\n"
                    "  value: \"%.*s\"\n", value, (int )sizeof(value),
                    (char * )&he_value);
        }
            break;
        case TPM2_PT_VENDOR_STRING_1:
            buf = get_uint32_as_chars(value);
            tpm2_tool_output("TPM2_PT_VENDOR_STRING_1:\n"
                    "  raw: 0x%X\n"
                    "  value: \"%s\"\n", value, buf);
            break;
        case TPM2_PT_VENDOR_STRING_2:
            buf = get_uint32_as_chars(value);
            tpm2_tool_output("TPM2_PT_VENDOR_STRING_2:\n"
                    "  raw: 0x%X\n"
                    "  value: \"%s\"\n", value, buf);
            break;
        case TPM2_PT_VENDOR_STRING_3:
            buf = get_uint32_as_chars(value);
            tpm2_tool_output("TPM2_PT_VENDOR_STRING_3:\n"
                    "  raw: 0x%X\n"
                    "  value: \"%s\"\n", value, buf);
            break;
        case TPM2_PT_VENDOR_STRING_4:
            buf = get_uint32_as_chars(value);
            tpm2_tool_output("TPM2_PT_VENDOR_STRING_4:\n"
                    "  raw: 0x%X\n"
                    "  value: \"%s\"\n", value, buf);
            break;
        case TPM2_PT_VENDOR_TPM_TYPE:
            tpm2_tool_output("TPM2_PT_VENDOR_TPM_TYPE:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_FIRMWARE_VERSION_1:
            tpm2_tool_output("TPM2_PT_FIRMWARE_VERSION_1:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_FIRMWARE_VERSION_2:
            tpm2_tool_output("TPM2_PT_FIRMWARE_VERSION_2:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_INPUT_BUFFER:
            tpm2_tool_output("TPM2_PT_INPUT_BUFFER:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_HR_TRANSIENT_MIN:
            tpm2_tool_output("TPM2_PT_HR_TRANSIENT_MIN:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_HR_PERSISTENT_MIN:
            tpm2_tool_output("TPM2_PT_HR_PERSISTENT_MIN:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_HR_LOADED_MIN:
            tpm2_tool_output("TPM2_PT_HR_LOADED_MIN:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_ACTIVE_SESSIONS_MAX:
            tpm2_tool_output("TPM2_PT_ACTIVE_SESSIONS_MAX:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_PCR_COUNT:
            tpm2_tool_output("TPM2_PT_PCR_COUNT:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_PCR_SELECT_MIN:
            tpm2_tool_output("TPM2_PT_PCR_SELECT_MIN:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_CONTEXT_GAP_MAX:
            tpm2_tool_output("TPM2_PT_CONTEXT_GAP_MAX:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_NV_COUNTERS_MAX:
            tpm2_tool_output("TPM2_PT_NV_COUNTERS_MAX:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_NV_INDEX_MAX:
            tpm2_tool_output("TPM2_PT_NV_INDEX_MAX:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_MEMORY:
            tpm2_tool_output("TPM2_PT_MEMORY:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_CLOCK_UPDATE:
            tpm2_tool_output("TPM2_PT_CLOCK_UPDATE:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_CONTEXT_HASH: /* this may be a TPM2_ALG_ID type */
            tpm2_tool_output("TPM2_PT_CONTEXT_HASH:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_CONTEXT_SYM: /* this is a TPM2_ALG_ID type */
            tpm2_tool_output("TPM2_PT_CONTEXT_SYM:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_CONTEXT_SYM_SIZE:
            tpm2_tool_output("TPM2_PT_CONTEXT_SYM_SIZE:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_ORDERLY_COUNT:
            tpm2_tool_output("TPM2_PT_ORDERLY_COUNT:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_MAX_COMMAND_SIZE:
            tpm2_tool_output("TPM2_PT_MAX_COMMAND_SIZE:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_MAX_RESPONSE_SIZE:
            tpm2_tool_output("TPM2_PT_MAX_RESPONSE_SIZE:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_MAX_DIGEST:
            tpm2_tool_output("TPM2_PT_MAX_DIGEST:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_MAX_OBJECT_CONTEXT:
            tpm2_tool_output("TPM2_PT_MAX_OBJECT_CONTEXT:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_MAX_SESSION_CONTEXT:
            tpm2_tool_output("TPM2_PT_MAX_SESSION_CONTEXT:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_PS_FAMILY_INDICATOR:
            tpm2_tool_output("TPM2_PT_PS_FAMILY_INDICATOR:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_PS_LEVEL:
            tpm2_tool_output("TPM2_PT_PS_LEVEL:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_PS_REVISION:
            tpm2_tool_output("TPM2_PT_PS_REVISION:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_PS_DAY_OF_YEAR:
            tpm2_tool_output("TPM2_PT_PS_DAY_OF_YEAR:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_PS_YEAR:
            tpm2_tool_output("TPM2_PT_PS_YEAR:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_SPLIT_MAX:
            tpm2_tool_output("TPM2_PT_SPLIT_MAX:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_TOTAL_COMMANDS:
            tpm2_tool_output("TPM2_PT_TOTAL_COMMANDS:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_LIBRARY_COMMANDS:
            tpm2_tool_output("TPM2_PT_LIBRARY_COMMANDS:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_VENDOR_COMMANDS:
            tpm2_tool_output("TPM2_PT_VENDOR_COMMANDS:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_NV_BUFFER_MAX:
            tpm2_tool_output("TPM2_PT_NV_BUFFER_MAX:\n"
                    "  raw: 0x%X\n", value);
            break;
        case TPM2_PT_MODES:
            tpm2_tool_output_tpma_modes((TPMA_MODES) value);
            break;
        }
    }
}
/*
 * Iterate over all variable properties, call the unique print function for each.
 */
static void dump_tpm_properties_var(TPMS_TAGGED_PROPERTY properties[],
        size_t count) {
    size_t i;

    for (i = 0; i < count; ++i) {
        TPM2_PT property = properties[i].property;
        UINT32 value = properties[i].value;
        switch (property) {
        case TPM2_PT_PERMANENT:
            dump_permanent_attrs((TPMA_PERMANENT) value);
            break;
        case TPM2_PT_STARTUP_CLEAR:
            dump_startup_clear_attrs((TPMA_STARTUP_CLEAR) value);
            break;
        case TPM2_PT_HR_NV_INDEX:
            tpm2_tool_output("TPM2_PT_HR_NV_INDEX: 0x%X\n", value);
            break;
        case TPM2_PT_HR_LOADED:
            tpm2_tool_output("TPM2_PT_HR_LOADED: 0x%X\n", value);
            break;
        case TPM2_PT_HR_LOADED_AVAIL:
            tpm2_tool_output("TPM2_PT_HR_LOADED_AVAIL: 0x%X\n", value);
            break;
        case TPM2_PT_HR_ACTIVE:
            tpm2_tool_output("TPM2_PT_HR_ACTIVE: 0x%X\n", value);
            break;
        case TPM2_PT_HR_ACTIVE_AVAIL:
            tpm2_tool_output("TPM2_PT_HR_ACTIVE_AVAIL: 0x%X\n", value);
            break;
        case TPM2_PT_HR_TRANSIENT_AVAIL:
            tpm2_tool_output("TPM2_PT_HR_TRANSIENT_AVAIL: 0x%X\n", value);
            break;
        case TPM2_PT_HR_PERSISTENT:
            tpm2_tool_output("TPM2_PT_HR_PERSISTENT: 0x%X\n", value);
            break;
        case TPM2_PT_HR_PERSISTENT_AVAIL:
            tpm2_tool_output("TPM2_PT_HR_PERSISTENT_AVAIL: 0x%X\n", value);
            break;
        case TPM2_PT_NV_COUNTERS:
            tpm2_tool_output("TPM2_PT_NV_COUNTERS: 0x%X\n", value);
            break;
        case TPM2_PT_NV_COUNTERS_AVAIL:
            tpm2_tool_output("TPM2_PT_NV_COUNTERS_AVAIL: 0x%X\n", value);
            break;
        case TPM2_PT_ALGORITHM_SET:
            tpm2_tool_output("TPM2_PT_ALGORITHM_SET: 0x%X\n", value);
            break;
        case TPM2_PT_LOADED_CURVES:
            tpm2_tool_output("TPM2_PT_LOADED_CURVES: 0x%X\n", value);
            break;
        case TPM2_PT_LOCKOUT_COUNTER:
            tpm2_tool_output("TPM2_PT_LOCKOUT_COUNTER: 0x%X\n", value);
            break;
        case TPM2_PT_MAX_AUTH_FAIL:
            tpm2_tool_output("TPM2_PT_MAX_AUTH_FAIL: 0x%X\n", value);
            break;
        case TPM2_PT_LOCKOUT_INTERVAL:
            tpm2_tool_output("TPM2_PT_LOCKOUT_INTERVAL: 0x%X\n", value);
            break;
        case TPM2_PT_LOCKOUT_RECOVERY:
            tpm2_tool_output("TPM2_PT_LOCKOUT_RECOVERY: 0x%X\n", value);
            break;
        case TPM2_PT_NV_WRITE_RECOVERY:
            tpm2_tool_output("TPM2_PT_NV_WRITE_RECOVERY: 0x%X\n", value);
            break;
        case TPM2_PT_AUDIT_COUNTER_0:
            tpm2_tool_output("TPM2_PT_AUDIT_COUNTER_0: 0x%X\n", value);
            break;
        case TPM2_PT_AUDIT_COUNTER_1:
            tpm2_tool_output("TPM2_PT_AUDIT_COUNTER_1: 0x%X\n", value);
            break;
        default:
            tpm2_tool_output("unknown%X: 0x%X\n", value, value);
            break;
        }
    }
}
/*
 * Print data about TPM2_ALG_ID in human readable form.
 */
static void dump_algorithm_properties(TPM2_ALG_ID id, TPMA_ALGORITHM alg_attrs) {
    const char *id_name = tpm2_alg_util_algtostr(id, tpm2_alg_util_flags_any);
    bool is_unknown = id_name == NULL;
    id_name = id_name ? id_name : "unknown";

    if (!is_unknown) {
        tpm2_tool_output("%s:\n", id_name);
    } else {
        /* If it's unknown, we don't want N unknowns in the map, so
         * make them unknown42, unknown<alg id> since that's unique.
         * We do it this way, as most folks will want to just look up
         * if a given alg via "friendly" name like rsa is supported.
         */
        tpm2_tool_output("%s%x:\n", id_name, id);
    }
    tpm2_tool_output("  value:      0x%X\n", id);
    tpm2_tool_output("  asymmetric: %s\n",
            prop_str (alg_attrs & TPMA_ALGORITHM_ASYMMETRIC));
    tpm2_tool_output("  symmetric:  %s\n",
            prop_str (alg_attrs & TPMA_ALGORITHM_SYMMETRIC));
    tpm2_tool_output("  hash:       %s\n",
            prop_str (alg_attrs & TPMA_ALGORITHM_HASH));
    tpm2_tool_output("  object:     %s\n",
            prop_str (alg_attrs & TPMA_ALGORITHM_OBJECT));
    tpm2_tool_output("  reserved:   0x%X\n",
            (alg_attrs & TPMA_ALGORITHM_RESERVED1_MASK) >> 4);
    tpm2_tool_output("  signing:    %s\n",
            prop_str (alg_attrs & TPMA_ALGORITHM_SIGNING));
    tpm2_tool_output("  encrypting: %s\n",
            prop_str (alg_attrs & TPMA_ALGORITHM_ENCRYPTING));
    tpm2_tool_output("  method:     %s\n",
            prop_str (alg_attrs & TPMA_ALGORITHM_METHOD));
}

/*
 * Iterate over the count TPMS_ALG_PROPERTY entries and dump the
 * TPMA_ALGORITHM attributes for each.
 */
static void dump_algorithms(TPMS_ALG_PROPERTY alg_properties[], size_t count) {
    size_t i;

    for (i = 0; i < count; ++i)
        dump_algorithm_properties(alg_properties[i].alg,
                alg_properties[i].algProperties);
}

/*
 * Pretty print the bit fields from the TPMA_CC (UINT32)
 */
static bool dump_command_attrs(TPMA_CC tpma_cc) {
    const char *value = tpm2_cc_util_to_str(
            tpma_cc & TPMA_CC_COMMANDINDEX_MASK);
    /* not found, make a hex version of it */
    if (!value) {
        /*
         * big enough for hex representation of
         * saturated U32 + 0x prefix and then some.
         */
        static char _buf[16];
        memset(_buf, 0, sizeof(_buf));
        int rc  = snprintf(_buf, sizeof(_buf), "0x%x", tpma_cc);
        /* ignore bytes, we don't care if it's truncated and it shouldnt happen */
        UNUSED(rc);
        value = _buf;
    }

    tpm2_tool_output("%s:\n", value);
    tpm2_tool_output("  value: 0x%X\n", tpma_cc);
    tpm2_tool_output("  commandIndex: 0x%x\n",
            tpma_cc & TPMA_CC_COMMANDINDEX_MASK);
    tpm2_tool_output("  reserved1:    0x%x\n",
            (tpma_cc & TPMA_CC_RESERVED1_MASK) >> 16);
    tpm2_tool_output("  nv:           %s\n", prop_str (tpma_cc & TPMA_CC_NV));
    tpm2_tool_output("  extensive:    %s\n",
            prop_str (tpma_cc & TPMA_CC_EXTENSIVE));
    tpm2_tool_output("  flushed:      %s\n",
            prop_str (tpma_cc & TPMA_CC_FLUSHED));
    tpm2_tool_output("  cHandles:     0x%x\n",
            (tpma_cc & TPMA_CC_CHANDLES_MASK) >> TPMA_CC_CHANDLES_SHIFT);
    tpm2_tool_output("  rHandle:      %s\n",
            prop_str (tpma_cc & TPMA_CC_RHANDLE));
    tpm2_tool_output("  V:            %s\n", prop_str (tpma_cc & TPMA_CC_V));
    tpm2_tool_output("  Res:          0x%x\n",
            (tpma_cc & TPMA_CC_RES_MASK) >> TPMA_CC_RES_SHIFT);
    return true;
}
/*
 * Iterate over an array of TPM2_ECC_CURVEs and dump out a human readable
 * representation of each array member.
 */
static void dump_ecc_curves(TPM2_ECC_CURVE curve[], UINT32 count) {
    size_t i;

    for (i = 0; i < count; ++i) {
        switch (curve[i]) {
        case TPM2_ECC_NIST_P192:
            tpm2_tool_output("TPM2_ECC_NIST_P192: 0x%X\n", curve[i]);
            break;
        case TPM2_ECC_NIST_P224:
            tpm2_tool_output("TPM2_ECC_NIST_P224: 0x%X\n", curve[i]);
            break;
        case TPM2_ECC_NIST_P256:
            tpm2_tool_output("TPM2_ECC_NIST_P256: 0x%X\n", curve[i]);
            break;
        case TPM2_ECC_NIST_P384:
            tpm2_tool_output("TPM2_ECC_NIST_P384: 0x%X\n", curve[i]);
            break;
        case TPM2_ECC_NIST_P521:
            tpm2_tool_output("TPM2_ECC_NIST_P521: 0x%X\n", curve[i]);
            break;
        case TPM2_ECC_BN_P256:
            tpm2_tool_output("TPM2_ECC_BN_P256: 0x%X\n", curve[i]);
            break;
        case TPM2_ECC_BN_P638:
            tpm2_tool_output("TPM2_ECC_BN_P638: 0x%X\n", curve[i]);
            break;
        case TPM2_ECC_SM2_P256:
            tpm2_tool_output("TPM2_ECC_SM2_P256: 0x%X\n", curve[i]);
            break;
        default:
            tpm2_tool_output("unknown%X: 0x%X\n", curve[i], curve[i]);
            break;
        }
    }
}
/*
 * Iterate over an array of TPMA_CCs and dump out a human readable
 * representation of each array member.
 */
static bool dump_command_attr_array(TPMA_CC command_attributes[], UINT32 count) {
    size_t i;
    bool result = true;
    for (i = 0; i < count; ++i)
        result &= dump_command_attrs(command_attributes[i]);

    return result;
}
/*
 * Iterate over an array of TPML_HANDLEs and dump out the handle
 * values.
 */
static void dump_handles(TPM2_HANDLE handles[], UINT32 count) {
    UINT32 i;

    for (i = 0; i < count; ++i)
        tpm2_tool_output("- 0x%X\n", handles[i]);
}
/*
 * Query the TPM for TPM capabilities.
 */
static tool_rc get_tpm_capability_all(ESYS_CONTEXT *context,
        TPMS_CAPABILITY_DATA **capability_data) {
    return tpm2_capability_get(context, options.capability, options.property,
            options.count, capability_data);
}

/*
 * This function is a glorified switch statement. It uses the 'capability'
 * and 'property' fields from the capability_opts structure to find the right
 * print function for the capabilities in the 'capabilities' parameter.
 * On success it will return true, if it fails (is unable to find an
 * appropriate print function for the provided 'capability' / 'property'
 * pair or the print routine fails)  then it will return false.
 */
static bool dump_tpm_capability(TPMU_CAPABILITIES *capabilities) {

    bool result = true;
    switch (options.capability) {
    case TPM2_CAP_ALGS:
        dump_algorithms(capabilities->algorithms.algProperties,
                capabilities->algorithms.count);
        break;
    case TPM2_CAP_COMMANDS:
        result = dump_command_attr_array(
                capabilities->command.commandAttributes,
                capabilities->command.count);
        break;
    case TPM2_CAP_TPM_PROPERTIES:
        switch (options.property) {
        case TPM2_PT_FIXED:
            dump_tpm_properties_fixed(capabilities->tpmProperties.tpmProperty,
                    capabilities->tpmProperties.count);
            break;
        case TPM2_PT_VAR:
            dump_tpm_properties_var(capabilities->tpmProperties.tpmProperty,
                    capabilities->tpmProperties.count);
            break;
        default:
            return false;
        }
        break;
    case TPM2_CAP_ECC_CURVES:
        dump_ecc_curves(capabilities->eccCurves.eccCurves,
                capabilities->eccCurves.count);
        break;
    case TPM2_CAP_HANDLES:
        switch (options.property & TPM2_HR_RANGE_MASK) {
        case TPM2_HR_TRANSIENT:
        case TPM2_HR_PERSISTENT:
        case TPM2_HR_PERMANENT:
        case TPM2_HR_PCR:
        case TPM2_HR_NV_INDEX:
        case TPM2_HT_LOADED_SESSION << TPM2_HR_SHIFT:
        case TPM2_HT_SAVED_SESSION << TPM2_HR_SHIFT:
            dump_handles(capabilities->handles.handle,
                    capabilities->handles.count);
            break;
        default:
            return false;
        }
        break;
    case TPM2_CAP_PCRS:
        pcr_print_pcr_selections(&capabilities->assignedPCR);
        break;
    default:
        return false;
    }
    return result;
}

static bool on_option(char key, char *value) {

    UNUSED(value);

    switch (key) {
    case 'l':
        options.list = true;
    }

    return true;
}

static bool on_arg(int argc, char *argv[]) {

    if (argc != 1) {
        LOG_ERR("Only supports 1 capability group, got: %d", argc);
        return false;
    }

    options.capability_string = argv[0];

    return true;
}

static bool tpm2_tool_onstart(tpm2_options **opts) {

    const struct option topts[] = { { "list", no_argument, NULL, 'l' },

    };

    *opts = tpm2_options_new("l", ARRAY_LEN(topts), topts, on_option, on_arg,
            0);

    return *opts != NULL;
}

static tool_rc tpm2_tool_onrun(ESYS_CONTEXT *context, tpm2_option_flags flags) {

    UNUSED(flags);

    if (options.list && options.capability_string) {
        LOG_ERR("Cannot specify -l with a capability group.");
        return tool_rc_option_error;
    }

    /* list known capabilities, ie -l option */
    if (options.list) {
        print_cap_map();
        return tool_rc_success;
    }

    /* List a capability, ie <capability group> option */
    TPMS_CAPABILITY_DATA *capability_data = NULL;

    bool ret = sanity_check_capability_opts();
    if (!ret) {
        return tool_rc_option_error;
    }
    /* get requested capability from TPM, dump it to stdout */
    tool_rc rc = get_tpm_capability_all(context, &capability_data);
    if (rc != tool_rc_success) {
        return rc;
    }

    bool result = dump_tpm_capability(&capability_data->data);
    free(capability_data);
    return result ? tool_rc_success : tool_rc_general_error;
}

// Register this tool with tpm2_tool.c
TPM2_TOOL_REGISTER("getcap", tpm2_tool_onstart, tpm2_tool_onrun, NULL, NULL)
