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

#include <stdlib.h>

#include "log.h"
#include "object.h"
#include "tpm2.h"
#include "tpm2_capability.h"
#include "tpm2_options.h"

struct tpm_flush_context_ctx {
    TPM2_HANDLE property;
    char *context_arg;
    unsigned encountered_option;
};

static struct tpm_flush_context_ctx ctx;

static const char *get_property_name(TPM2_HANDLE handle) {

    switch (handle & TPM2_HR_RANGE_MASK) {
    case TPM2_HR_TRANSIENT:
        return "transient";
    case TPM2_HT_LOADED_SESSION << TPM2_HR_SHIFT:
        return "loaded session";
    case TPM2_HT_SAVED_SESSION << TPM2_HR_SHIFT:
        return "saved session";
    }

    return "invalid";
}

static tool_rc flush_contexts_tpm2(ESYS_CONTEXT *ectx, TPM2_HANDLE handles[],
        UINT32 count) {

    UINT32 i;

    for (i = 0; i < count; ++i) {

        ESYS_TR handle;
        tool_rc rc = tpm2_util_sys_handle_to_esys_handle(ectx, handles[i],
                &handle);
        if (rc != tool_rc_success) {
            return rc;
        }

        rc = tpm2_flush_context(ectx, handle);
        if (rc != tool_rc_success) {
            LOG_ERR("Failed Flush Context for %s handle 0x%x",
                    get_property_name(handles[i]), handles[i]);
            return rc;
        }
    }

    return tool_rc_success;
}

static bool flush_contexts_tr(ESYS_CONTEXT *ectx, ESYS_TR handles[],
        UINT32 count) {

    UINT32 i;

    for (i = 0; i < count; ++i) {
        tool_rc rc = tpm2_flush_context(ectx, handles[i]);
        if (rc != tool_rc_success) {
            return rc;
        }
    }

    return tool_rc_success;
}

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

    if (ctx.encountered_option) {
        LOG_ERR("Options -t, -l and -s are mutually exclusive");
        return false;
    }

    ctx.encountered_option = true;

    switch (key) {
    case 't':
        ctx.property = TPM2_TRANSIENT_FIRST;
        break;
    case 'l':
        ctx.property = TPM2_LOADED_SESSION_FIRST;
        break;
    case 's':
        ctx.property = TPM2_ACTIVE_SESSION_FIRST;
        break;
    }

    return true;
}

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

    if (ctx.encountered_option && argc != 0) {
        LOG_ERR("Options are mutually exclusive of an argument");
        return false;
    }

    ctx.context_arg = argv[0];

    return true;
}

bool tpm2_tool_onstart(tpm2_options **opts) {

    static const struct option topts[] = {
        { "transient-object", no_argument, NULL, 't' },
        { "loaded-session",   no_argument, NULL, 'l' },
        { "saved-session",    no_argument, NULL, 's' },
    };

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

    return *opts != NULL;
}

tool_rc tpm2_tool_onrun(ESYS_CONTEXT *ectx, tpm2_option_flags flags) {

    UNUSED(flags);

    if (ctx.property) {
        TPMS_CAPABILITY_DATA *capability_data;
        tool_rc rc = tpm2_capability_get(ectx, TPM2_CAP_HANDLES, ctx.property,
                TPM2_MAX_CAP_HANDLES, &capability_data);
        if (rc != tool_rc_success) {
            return rc;
        }

        TPML_HANDLE *handles = &capability_data->data.handles;
        rc = flush_contexts_tpm2(ectx, handles->handle, handles->count);
        free(capability_data);
        return rc;
    }

    if (!ctx.context_arg) {
        LOG_ERR("Specify options to evict handles or a session context.");
        return tool_rc_option_error;
    }

    TPM2_HANDLE handle;
    bool result = tpm2_util_string_to_uint32(ctx.context_arg, &handle);
    if (!result) {
        /* hmm not a handle, try a session */
        tpm2_session *s = NULL;
        tool_rc rc = tpm2_session_restore(ectx, ctx.context_arg, true, &s);
        if (rc != tool_rc_success) {
            return rc;
        }

        tpm2_session_close(&s);

        return tool_rc_success;
    }

    /* its a handle, call flush */
    ESYS_TR tr_handle = ESYS_TR_NONE;
    tool_rc rc = tpm2_util_sys_handle_to_esys_handle(ectx, handle, &tr_handle);
    if (rc != tool_rc_success) {
        return rc;
    }

    return flush_contexts_tr(ectx, &tr_handle, 1);
}
