/* SPDX-License-Identifier: BSD-3-Clause */
#include "files.h"
#include "log.h"
#include "object.h"
#include "tpm2.h"
#include "tpm2_tool.h"
#include "tpm2_auth_util.h"
#include "tpm2_alg_util.h"
#include "tpm2_options.h"

typedef struct tpm_ecdhzgen_ctx tpm_ecdhzgen_ctx;
struct tpm_ecdhzgen_ctx {

    struct {
        const char *ctx_path;
        const char *auth_str;
        tpm2_loaded_object object;
    } ecc_key;

    const char *output_z1_path;
    const char *output_z2_path;
    TPM2B_ECC_POINT *Z1;
    TPM2B_ECC_POINT *Z2;

    const char *static_public_path;
    const char *ephemeral_public_path;
    TPM2B_ECC_POINT Q1;
    TPM2B_ECC_POINT Q2;

    UINT16 commit_counter;
    TPMI_ECC_KEY_EXCHANGE keyexchange_scheme;
};

static tpm_ecdhzgen_ctx ctx = {
    .keyexchange_scheme = TPM2_ALG_ECDH,
};

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

    bool result = true;
    switch (key) {
        case 'c':
            ctx.ecc_key.ctx_path = value;
            break;
        case 'p':
            ctx.ecc_key.auth_str = value;
            break;
        case 's':
            ctx.keyexchange_scheme = tpm2_alg_util_from_optarg(value,
                tpm2_alg_util_flags_sig);
            break;
        case 't':
            result = tpm2_util_string_to_uint16(value, &ctx.commit_counter);
            if (!result) {
                LOG_ERR("Could not convert commit counter to number, got: \"%s\"",
                        value);
                return false;
            }
            break;
        case 0:
            ctx.static_public_path = value;
            break;
        case 1:
            ctx.ephemeral_public_path = value;
            break;
        case 2:
            ctx.output_z1_path = value;
            break;
        case 3:
            ctx.output_z2_path = value;
            break;
    };

    return true;
}

static bool tpm2_tool_onstart(tpm2_options **opts) {

    static struct option topts[] = {
      { "key-context",      required_argument, NULL, 'c' },
      { "key-auth",         required_argument, NULL, 'p' },
      { "scheme",           required_argument, NULL, 's' },
      { "counter",          required_argument, NULL, 't' },
      { "static-public",    required_argument, NULL,  0  },
      { "ephemeral-public", required_argument, NULL,  1  },
      { "output-Z1",        required_argument, NULL,  2  },
      { "output-Z2",        required_argument, NULL,  3  },
    };

    *opts = tpm2_options_new("c:p:s:t:", ARRAY_LEN(topts), topts,
            on_option, NULL, 0);

    return *opts != NULL;
}

static tool_rc check_options(void) {

    if (ctx.keyexchange_scheme != TPM2_ALG_ECDH &&
        ctx.keyexchange_scheme != TPM2_ALG_ECMQV &&
        ctx.keyexchange_scheme != TPM2_ALG_SM2) {
            LOG_ERR("Unknown signing scheme");
            return tool_rc_general_error;
    }

    if (!ctx.ecc_key.ctx_path) {
        LOG_ERR("Specify an ecc key handle for context");
        return tool_rc_option_error;
    }

    if (!ctx.static_public_path) {
        LOG_ERR("Specify path to read the static public data from.");
        return tool_rc_option_error;
    }

    if (!ctx.ephemeral_public_path) {
        LOG_ERR("Specify path to read the ephemeral public data from.");
        return tool_rc_option_error;
    }

    if (!ctx.output_z1_path) {
        LOG_ERR("Specify path to save the Z1 data.");
        return tool_rc_option_error;
    }

    if (!ctx.output_z2_path) {
        LOG_ERR("Specify path to save the Z2 data.");
        return tool_rc_option_error;
    }

    return tool_rc_success;
}

static tool_rc process_inputs(ESYS_CONTEXT *ectx, tpm2_option_flags flags) {

    tool_rc rc = tpm2_util_object_load_auth(ectx, ctx.ecc_key.ctx_path,
        ctx.ecc_key.auth_str, &ctx.ecc_key.object, flags.restricted_pwd_session,
        TPM2_HANDLES_FLAGS_TRANSIENT|TPM2_HANDLES_FLAGS_PERSISTENT);
    if (rc != tool_rc_success) {
        return rc;
    }

    bool result = true;
    result = files_load_ecc_point(ctx.static_public_path, &ctx.Q1);
    if (!result) {
        LOG_ERR("Failed to load static public input ECC point Q1");
        return tool_rc_general_error;
    }

    result = files_load_ecc_point(ctx.ephemeral_public_path, &ctx.Q2);
    if (!result) {
        LOG_ERR("Failed to load static public input ECC point Q2");
        return tool_rc_general_error;
    }

    return tool_rc_success;
}

static tool_rc process_outputs(void) {

    bool result = files_save_ecc_point(ctx.Z1, ctx.output_z1_path);
    if (!result) {
        LOG_ERR("Failed to write out the ECC point Z1");
        return tool_rc_general_error;
    }

    result = files_save_ecc_point(ctx.Z2, ctx.output_z2_path);
    if (!result) {
        LOG_ERR("Failed to write out the ECC point Z2");
        return tool_rc_general_error;
    }

    return tool_rc_success;
}

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

    UNUSED(flags);

    // Check input options and arguments
    tool_rc rc = check_options();
    if (rc != tool_rc_success) {
        return rc;
    }

    // Process inputs
    rc = process_inputs(ectx, flags);
    if (rc != tool_rc_success) {
        return rc;
    }

    // ESAPI call
    rc = tpm2_zgen2phase(ectx, &ctx.ecc_key.object, &ctx.Q1, &ctx.Q2, &ctx.Z1,
    &ctx.Z2, ctx.keyexchange_scheme, ctx.commit_counter);
    if (rc != tool_rc_success) {
        return rc;
    }

    // Process ouputs
    rc = process_outputs();

    return rc;
}

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