/*
 * SPDX-License-Identifier: BSD-2-Clause
 * Copyright (c) 2019, Intel Corporation
 */

#ifdef HAVE_CONFIG_H
#include "config.h" // IWYU pragma: keep
#endif

#include <string.h> // for memcmp

#include "sys-util.h"        // for GetDigestSize, TSS2_RETRY_EXP
#include "tss2_common.h"     // for TSS2_RC, TSS2_RC_SUCCESS
#include "tss2_sys.h"        // for TSS2_SYS_CONTEXT, TSS2L_SYS_AUTH_RESPONSE
#include "tss2_tpm2_types.h" // for TPM2B_MAX_NV_BUFFER, TPM2B_DIGEST, TPM2...

#define LOGMODULE test
#include "util/log.h" // for return_if_error, LOG_ERROR, LOGBLOB_DEBUG

#define NV_PS_INDEX_SIZE     34
#define INDEX_LCP_OWN        0x01400001
#define INDEX_LCP_SUP        0x01800001

#define TPM2B_SIZE_MAX(type) (sizeof(type) - 2)

const TSS2L_SYS_AUTH_COMMAND auth_cmd_null_pwd = {
    .count = 1,
    .auths = {
        {
            .sessionHandle = TPM2_RH_PW,
        },
    },
};

static TSS2_RC
create_policy_session(TSS2_SYS_CONTEXT *sys_ctx, TPMI_SH_AUTH_SESSION *handle) {
    TSS2_RC                rc;
    TPM2B_ENCRYPTED_SECRET salt = { 0 };
    TPM2B_NONCE            nonce = {
                   .size = GetDigestSize(TPM2_ALG_SHA256),
    };
    TPM2B_NONCE nonce_tpm = {
        0,
    };
    TPMT_SYM_DEF symmetric = {
        .algorithm = TPM2_ALG_NULL,
    };

    rc = Tss2_Sys_StartAuthSession(sys_ctx, TPM2_RH_NULL, TPM2_RH_NULL, 0, &nonce, &salt,
                                   TPM2_SE_POLICY, &symmetric, TPM2_ALG_SHA256, handle, &nonce_tpm,
                                   0);
    return_if_error(rc, "Tss2_Sys_StartAuthSession");
    return TSS2_RC_SUCCESS;
}

static TSS2_RC
setup_nv(TSS2_SYS_CONTEXT *sys_ctx, TPMI_RH_NV_INDEX index) {
    TSS2_RC              rc;
    TPMI_SH_AUTH_SESSION auth_handle;
    TPM2B_DIGEST         policy_hash = {
                .size = TPM2B_SIZE_MAX(policy_hash),
    };
    TPM2B_AUTH nv_auth = {
        0,
    };
    TSS2L_SYS_AUTH_RESPONSE auth_rsp;
    TPM2B_NV_PUBLIC public_info = {
        .nvPublic = {
            .nameAlg = TPM2_ALG_SHA256,
            .attributes = TPMA_NV_AUTHREAD | TPMA_NV_AUTHWRITE |
                TPMA_NV_PLATFORMCREATE | TPMA_NV_WRITEDEFINE | TPMA_NV_ORDERLY,
            .dataSize = NV_PS_INDEX_SIZE,
            .nvIndex = index,
        },
    };

    rc = create_policy_session(sys_ctx, &auth_handle);
    return_if_error(rc, "create_policy_session");

    rc = Tss2_Sys_PolicyGetDigest(sys_ctx, auth_handle, 0, &policy_hash, 0);
    return_if_error(rc, "Tss2_Sys_PolicyGetDigest");
    LOGBLOB_INFO(policy_hash.buffer, policy_hash.size, "policy_hash");

    rc = Tss2_Sys_NV_DefineSpace(sys_ctx, TPM2_RH_PLATFORM, &auth_cmd_null_pwd, &nv_auth,
                                 &public_info, &auth_rsp);
    return_if_error(rc, "Tss2_Sys_NV_DefineSpace");

    rc = Tss2_Sys_FlushContext(sys_ctx, auth_handle);
    return_if_error(rc, "Tss2_Sys_FlushContext");

    return TSS2_RC_SUCCESS;
}

static TSS2_RC
nv_write_read_test(TSS2_SYS_CONTEXT *sys_ctx, TPMI_RH_NV_INDEX index) {
    TSS2_RC             rc;
    TPM2B_MAX_NV_BUFFER write_data = {
        .size = 4,
        .buffer = { 0xde, 0xad, 0xbe, 0xef },
    };
    TPM2B_MAX_NV_BUFFER nv_buf = {
        0,
    };
    TSS2L_SYS_AUTH_RESPONSE auth_resp = {
        0,
    };

    rc = TSS2_RETRY_EXP(
        Tss2_Sys_NV_Write(sys_ctx, index, index, &auth_cmd_null_pwd, &write_data, 0, &auth_resp));
    return_if_error(rc, "Tss2_Sys_NV_Write");

    rc = Tss2_Sys_NV_Read(sys_ctx, index, index, &auth_cmd_null_pwd, 4, 0, &nv_buf, &auth_resp);
    return_if_error(rc, "Tss2_Sys_NV_Read");

    if (memcmp(nv_buf.buffer, write_data.buffer, write_data.size) != 0) {
        LOG_ERROR("%s: data read from NV is different from data written", __func__);
        LOGBLOB_DEBUG(write_data.buffer, write_data.size, "write_data");
        LOGBLOB_DEBUG(nv_buf.buffer, nv_buf.size, "nv_buf");
        return 1;
    }

    return TSS2_RC_SUCCESS;
}

static TSS2_RC
teardown_nv(TSS2_SYS_CONTEXT *sys_ctx, TPMI_RH_NV_INDEX index) {
    TSS2_RC                 rc;
    TSS2L_SYS_AUTH_RESPONSE auth_resp = {
        0,
    };

    rc = Tss2_Sys_NV_UndefineSpace(sys_ctx, TPM2_RH_PLATFORM, index, &auth_cmd_null_pwd,
                                   &auth_resp);
    return_if_error(rc, "Tss2_Sys_NV_UndefineSpace");

    return TSS2_RC_SUCCESS;
}

int
test_invoke(TSS2_SYS_CONTEXT *sys_ctx) {
    TSS2_RC rc, rc_teardown;

    rc = setup_nv(sys_ctx, INDEX_LCP_OWN);
    return_if_error(rc, "setup_nv for INDEX_LCP_OWN");
    rc = nv_write_read_test(sys_ctx, INDEX_LCP_OWN);
    LOG_ERROR("nv_write_read_test for INDEX_LCP_OWN");
    rc_teardown = teardown_nv(sys_ctx, INDEX_LCP_OWN);
    return_if_error(rc, "INDEX_LCP_OWN test");
    return_if_error(rc_teardown, "teardown_nv for INDEX_LCP_OWN");

    rc = setup_nv(sys_ctx, INDEX_LCP_SUP);
    return_if_error(rc, "setup_nv for INDEX_LCP_SUP");
    rc = nv_write_read_test(sys_ctx, INDEX_LCP_SUP);
    LOG_ERROR("nv_write_read_test for INDEX_LCP_SUP");
    rc_teardown = teardown_nv(sys_ctx, INDEX_LCP_SUP);
    return_if_error(rc, "INDEX_LCP_SUP test");
    return_if_error(rc_teardown, "teardown_nv for INDEX_LCP_SUP");

    return 0;
}
