/* SPDX-License-Identifier: BSD-2-Clause */
/*******************************************************************************
 * Copyright 2018-2019, Fraunhofer SIT sponsored by Infineon Technologies AG
 * All rights reserved.
 *******************************************************************************/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>

#include "ifapi_helpers.h"
#include "ifapi_eventlog.h"
#include "ifapi_json_serialize.h"

#define LOGMODULE fapi
#include "util/log.h"
#include "util/aux_util.h"
#include "ifapi_macros.h"

/** Initialize the eventlog module of FAPI.
 *
 * @param[in,out] eventlog The context area for the eventlog.
 * @param[in] log_dir The directory where to put the eventlog data.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_IO_ERROR if creation of log_dir failed or log_dir is not writable.
 * @retval TSS2_FAPI_RC_MEMORY if memory allocation failed.
 * @retval TSS2_FAPI_RC_BAD_REFERENCE a invalid null pointer is passed.
 * @retval TSS2_FAPI_RC_BAD_VALUE if an invalid value was passed into
 *         the function.
 */
TSS2_RC
ifapi_eventlog_initialize(
    IFAPI_EVENTLOG *eventlog,
    const char *log_dir)
{
    check_not_null(eventlog);
    check_not_null(log_dir);

    TSS2_RC r;

    r = ifapi_io_check_create_dir(log_dir);
    return_if_error2(r, "Directory check/creation failed for %s", log_dir);

    eventlog->log_dir = strdup(log_dir);
    return_if_null(eventlog->log_dir, "Out of memory.", TSS2_FAPI_RC_MEMORY);

    return TSS2_RC_SUCCESS;
}

/** Retrieve the eventlog for a given list of pcrs using asynchronous io.
 *
 * Call ifapi_eventlog_get_finish to retrieve the results.
 *
 * @param[in,out] eventlog The context area for the eventlog.
 * @param[in,out] io The context area for the asynchronous io module.
 * @param[in] pcrList The list of PCR indices to retrieve the log for.
 * @param[in] pcrListSize The size of pcrList.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_IO_ERROR if creation of log_dir failed or log_dir is not writable.
 * @retval TSS2_FAPI_RC_MEMORY if memory allocation failed.
 * @retval TSS2_FAPI_RC_BAD_VALUE if an invalid value was passed into
 *         the function.
 * @retval TSS2_FAPI_RC_BAD_REFERENCE a invalid null pointer is passed.
 */
TSS2_RC
ifapi_eventlog_get_async(
    IFAPI_EVENTLOG *eventlog,
    IFAPI_IO *io,
    const TPM2_HANDLE *pcrList,
    size_t pcrListSize)
{
    check_not_null(eventlog);
    check_not_null(io);
    check_not_null(pcrList);

    if (pcrListSize > TPM2_MAX_PCRS) {
        LOG_ERROR("pcrList too long %zi > %i", pcrListSize, TPM2_MAX_PCRS);
        return TSS2_FAPI_RC_BAD_VALUE;
    }

    LOG_TRACE("called for pcrListSize=%zi", pcrListSize);

    memcpy(&eventlog->pcrList, pcrList, pcrListSize * sizeof(TPM2_HANDLE));
    eventlog->pcrListSize = pcrListSize;
    eventlog->pcrListIdx = 0;

    eventlog->log = json_object_new_array();
    return_if_null(eventlog->log, "Out of memory", TSS2_FAPI_RC_MEMORY);

    return TSS2_RC_SUCCESS;
}

/** Retrieve the eventlog for a given list of pcrs using asynchronous io.
 *
 * Call after ifapi_eventlog_get_async.
 *
 * @param[in,out] eventlog The context area for the eventlog.
 * @param[in,out] io The context area for the asynchronous io module.
 * @param[out] log The event log for the requested PCRs in JSON format
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_IO_ERROR if creation of log_dir failed or log_dir is not writable.
 * @retval TSS2_FAPI_RC_MEMORY if memory allocation failed.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if the I/O operation is not finished yet and this function needs
 *         to be called again.
 * @retval TSS2_FAPI_RC_BAD_VALUE if an invalid value was passed into
 *         the function.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_FAPI_RC_BAD_REFERENCE a invalid null pointer is passed.
 */
TSS2_RC
ifapi_eventlog_get_finish(
    IFAPI_EVENTLOG *eventlog,
    IFAPI_IO *io,
    char **log)
{
    /* eventlog parameter currently not used */
    check_not_null(eventlog);
    check_not_null(io);
    check_not_null(log);

    TSS2_RC r;
    char *event_log_file, *logstr;
    json_object *logpart, *event;

    LOG_TRACE("called");

loop:
    /* If we're dune with adding all eventlogs to the json array, we can serialize it and return
       it to the caller. */
    if (eventlog->pcrListIdx >= eventlog->pcrListSize) {
        LOG_TRACE("Done reading pcrLog");
        *log = strdup(json_object_to_json_string_ext(eventlog->log, JSON_C_TO_STRING_PRETTY));
        check_oom(*log);
        json_object_put(eventlog->log);
        eventlog->log = NULL;
        eventlog->state = IFAPI_EVENTLOG_STATE_INIT;
        return TSS2_RC_SUCCESS;
    }

    switch (eventlog->state) {
    statecase(eventlog->state, IFAPI_EVENTLOG_STATE_INIT)
        /* Construct the filename for the eventlog file */
        r = ifapi_asprintf(&event_log_file, "%s/%s%i",
                           eventlog->log_dir, IFAPI_PCR_LOG_FILE,
                           eventlog->pcrList[eventlog->pcrListIdx]);
        return_if_error(r, "Out of memory.");

        if (!ifapi_io_path_exists(event_log_file)) {
            LOG_DEBUG("No event log for pcr %i", eventlog->pcrList[eventlog->pcrListIdx]);
            SAFE_FREE(event_log_file);
            eventlog->pcrListIdx += 1;
            goto loop;
        }

        /* Initiate the reading of the eventlog file */
        r = ifapi_io_read_async(io, event_log_file);
        free(event_log_file);
        if (r) {
            LOG_DEBUG("No event log for pcr %i", eventlog->pcrList[eventlog->pcrListIdx]);
            eventlog->pcrListIdx += 1;
            goto loop;
        }
        fallthrough;

    statecase(eventlog->state, IFAPI_EVENTLOG_STATE_READING)
        /* Finish the reading of the eventlog file and return it directly to the output parameter */
        r = ifapi_io_read_finish(io, (uint8_t **)&logstr, NULL);
        return_try_again(r);
        return_if_error(r, "read_finish failed");

        logpart = json_tokener_parse(logstr);
        SAFE_FREE(logstr);
        return_if_null(log, "JSON parsing error", TSS2_FAPI_RC_BAD_VALUE);

        /* Append the log-entry from logpart to the eventlog */
        json_type jso_type = json_object_get_type(logpart);
        if (jso_type != json_type_array) {
            /* libjson-c does not deliver an array if array has only one element */
            json_object_array_add(eventlog->log, logpart);
        } else {
            /* Iterate through the array of logpart and add each item to the eventlog */
            /* The return type of json_object_array_length() was changed, thus the case */
            for (int i = 0; i < (int)json_object_array_length(logpart); i++) {
                event = json_object_array_get_idx(logpart, i);
                /* Increment the refcount of event so it does not get freed on put(logpart) below */
                json_object_get(event);
                json_object_array_add(eventlog->log, event);
            }
            json_object_put(logpart);
        }

        eventlog->pcrListIdx += 1;
        eventlog->state = IFAPI_EVENTLOG_STATE_INIT;
        goto loop;

    statecasedefault(eventlog->state);
    }
    return TSS2_RC_SUCCESS;
}

/** Append an event to the existing event log.
 *
 * Call ifapi_eventlog_append_finish to finalize this operation.
 *
 * @param[in,out] eventlog The context area for the eventlog.
 * @param[in,out] io The context area for the asynchronous io module.
 * @param[in] event The event to be appended to the eventlog.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_IO_ERROR if creation of log_dir failed or log_dir is not writable.
 * @retval TSS2_FAPI_RC_MEMORY if memory allocation failed.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_FAPI_RC_BAD_REFERENCE a invalid null pointer is passed.
 */
TSS2_RC
ifapi_eventlog_append_async(
    IFAPI_EVENTLOG *eventlog,
    IFAPI_IO *io,
    const IFAPI_EVENT *event)
{
    check_not_null(eventlog);
    check_not_null(io);
    check_not_null(event);

    TSS2_RC r;
    char *event_log_file;

    if (eventlog->state != IFAPI_EVENTLOG_STATE_INIT) {
        LOG_ERROR("Wrong state: %i", eventlog->state);
        return TSS2_FAPI_RC_BAD_SEQUENCE;
    }

    eventlog->event = *event;

    /* Construct the filename for the eventlog file */
    r = ifapi_asprintf(&event_log_file, "%s/%s%i",
                       eventlog->log_dir, IFAPI_PCR_LOG_FILE, event->pcr);
    return_if_error(r, "Out of memory.");

    /* Initiate the reading of the eventlog file */
    r = ifapi_io_read_async(io, event_log_file);
    if (r) {
        LOG_DEBUG("Eventlog file %s could not be opened, creating...", event_log_file);
        free(event_log_file);
        eventlog->state = IFAPI_EVENTLOG_STATE_APPENDING;
        return TSS2_RC_SUCCESS;
    }
    free(event_log_file);

    eventlog->state = IFAPI_EVENTLOG_STATE_READING;
    return TSS2_RC_SUCCESS;
}

/** Append an event to the existing event log.
 *
 * Call after ifapi_eventlog_get_async.
 *
 * @param[in,out] eventlog The context area for the eventlog.
 * @param[in,out] io The context area for the asynchronous io module.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_IO_ERROR if creation of log_dir failed or log_dir is not writable.
 * @retval TSS2_FAPI_RC_MEMORY if memory allocation failed.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if the I/O operation is not finished yet and this function needs
 *         to be called again.
 * @retval TSS2_FAPI_RC_BAD_VALUE if an invalid value was passed into
 *         the function.
 * @retval TSS2_FAPI_RC_GENERAL_FAILURE if an internal error occurred.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_FAPI_RC_BAD_REFERENCE a invalid null pointer is passed.
 */
TSS2_RC
ifapi_eventlog_append_finish(
    IFAPI_EVENTLOG *eventlog,
    IFAPI_IO *io)
{
    check_not_null(eventlog);
    check_not_null(io);

    TSS2_RC r;
    char *logstr = NULL, *event_log_file;
    const char *logstr2 = NULL;
    json_object *log, *event = NULL;

    switch (eventlog->state) {
    statecase(eventlog->state, IFAPI_EVENTLOG_STATE_READING)
        /* Finish the reading of the eventlog file and return it directly to the output parameter */
        r = ifapi_io_read_finish(io, (uint8_t **)&logstr, NULL);
        return_try_again(r);
        return_if_error(r, "read_finish failed");
        fallthrough;

    statecase(eventlog->state, IFAPI_EVENTLOG_STATE_APPENDING)
        /* If a log was read, we deserialize it to JSON. Otherwise we start a new log. */
        if (logstr) {
            log = json_tokener_parse(logstr);
            SAFE_FREE(logstr);
            return_if_null(log, "JSON parsing error", TSS2_FAPI_RC_BAD_VALUE);

             /* libjson-c does not deliver an array if array has only one element */
            json_type jso_type = json_object_get_type(log);
            if (jso_type != json_type_array) {
                json_object *json_array = json_object_new_array();
                json_object_array_add(json_array, log);
                log = json_array;
            }
        } else {
            log = json_object_new_array();
            return_if_null(log, "Out of memory", TSS2_FAPI_RC_MEMORY);
        }

        /* Extend the eventlog with the data */
        eventlog->event.recnum = json_object_array_length(log) + 1;

        r = ifapi_json_IFAPI_EVENT_serialize(&eventlog->event, &event);
        if (r) {
            json_object_put(log);
            LOG_ERROR("Error serializing event data");
            return TSS2_FAPI_RC_GENERAL_FAILURE;
        }

        json_object_array_add(log, event);
        logstr2 = json_object_to_json_string_ext(log, JSON_C_TO_STRING_PRETTY);

        /* Construct the filename for the eventlog file */
        r = ifapi_asprintf(&event_log_file, "%s/%s%i",
                           eventlog->log_dir, IFAPI_PCR_LOG_FILE, eventlog->event.pcr);
        return_if_error(r, "Out of memory.");

        /* Start writing the eventlog back to disk */
        r = ifapi_io_write_async(io, event_log_file, (uint8_t *) logstr2, strlen(logstr2));
        free(event_log_file);
        json_object_put(log); /* this also frees logstr2 */
        return_if_error(r, "write_async failed");
        fallthrough;

    statecase(eventlog->state, IFAPI_EVENTLOG_STATE_WRITING)
        /* Finish writing the eventlog */
        r = ifapi_io_write_finish(io);
        return_try_again(r);
        return_if_error(r, "read_finish failed");

        eventlog->state = IFAPI_EVENTLOG_STATE_INIT;
        break;

    statecasedefault(eventlog->state);
    }

    return TSS2_RC_SUCCESS;
}


/** Free allocated memory for an ifapi event.
 *
 * @param[in,out] event The structure to be cleaned up.
 */
void
ifapi_cleanup_event(IFAPI_EVENT * event) {
    if (event != NULL) {
        if (event->type == IFAPI_IMA_EVENT_TAG) {
            SAFE_FREE(event->sub_event.ima_event.eventName);
        } else if (event->type == IFAPI_TSS_EVENT_TAG) {
            SAFE_FREE(event->sub_event.tss_event.event);
        }
    }
}
