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

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

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <openssl/sha.h>
#include <ctype.h>
#include <stdbool.h>
#include <openssl/evp.h>
#include <json-c/json.h>
#include <json-c/json_util.h>
#include "tss2_common.h"
#include "tss2_tpm2_types.h"
#include "fapi_int.h"
#include "ifapi_json_deserialize.h"
#include "tpm_json_deserialize.h"
#include "ifapi_ima_eventlog.h"
#include "ifapi_helpers.h"

#define LOGMODULE fapijson
#include "util/log.h"
#include "util/aux_util.h"

/* Defines from kernel ima.h */
#define IMA_TEMPLATE_FIELD_ID_MAX_LEN 16
#define IMA_TEMPLATE_NUM_FIELDS_MAX 15
/* Define from kernel crypt.h */
#define CRYPTO_MAX_ALG_NAME 128

static uint32_t endian_swap_32(uint32_t data) {
    uint32_t converted;
    uint8_t *bytes = (uint8_t *)&data;
    uint8_t *tmp = (uint8_t *)&converted;
    size_t i;
    for(i=0; i < sizeof(uint32_t); i ++) {
        tmp[i] = bytes[sizeof(uint32_t) - i - 1];
    }
    return converted;
}

static bool big_endian_arch(void) {

    uint32_t test_word;
    uint8_t *test_byte;

    test_word = 0xFF000000;
    test_byte = (uint8_t *) (&test_word);
    return test_byte[0] == 0xFF;
}

static bool little_endian_pcr(uint32_t pcr) {

    uint8_t *test_pcr;

    test_pcr = (uint8_t *) (&pcr);
    return test_pcr[0];
}

static bool need_to_convert_to_big_endian(IFAPI_IMA_TEMPLATE *template)
{
    return big_endian_arch() && little_endian_pcr(template->header.pcr);
}

static TSS2_RC
get_json_content(json_object *jso, json_object **jso_sub) {
    if (!ifapi_get_sub_object(jso, CONTENT, jso_sub)) {
        *jso_sub = json_object_new_object();
        return_if_null(*jso_sub, "Out of memory.", TSS2_FAPI_RC_MEMORY);
        if (json_object_object_add(jso, CONTENT, *jso_sub)) {
            return_error(TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.");
        }
    }
    return TSS2_RC_SUCCESS;
}

static TSS2_RC
add_uint8_ary_to_json(UINT8 *buffer, UINT32 size, json_object *jso, const char *jso_tag)
{
    json_object *jso_byte_string = NULL;

    return_if_null(buffer, "Bad reference.", TSS2_FAPI_RC_BAD_VALUE);
    return_if_null(jso, "Bad reference.", TSS2_FAPI_RC_BAD_VALUE);

    char *hex_string = malloc((size) * 2 + 1);
    return_if_null(hex_string, "Out of memory.", TSS2_FAPI_RC_MEMORY);

    if (size > 0) {
        for (size_t i = 0, off = 0; i < size; i++, off += 2)
            sprintf(&hex_string[off], "%02x", buffer[i]);
    }
    hex_string[(size) * 2] = '\0';
    jso_byte_string = json_object_new_string(hex_string);
    SAFE_FREE(hex_string)
    return_if_null(jso_byte_string, "Out of memory", TSS2_FAPI_RC_MEMORY);

    if (json_object_object_add(jso, jso_tag, jso_byte_string)) {
        return_error(TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.");
    }
    return TSS2_RC_SUCCESS;
}

static TSS2_RC
add_string_to_json(const char *string, json_object *jso, const char *jso_tag)
{
    json_object *jso_string = NULL;

    return_if_null(string, "Bad reference.", TSS2_FAPI_RC_BAD_VALUE);
    return_if_null(jso, "Bad reference.", TSS2_FAPI_RC_BAD_VALUE);

    jso_string = json_object_new_string(string);
    return_if_null(jso_string, "Out of memory", TSS2_FAPI_RC_MEMORY);

    if (json_object_object_add(jso, jso_tag, jso_string)) {
        return_error(TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.");
    }
    return TSS2_RC_SUCCESS;
}

static TSS2_RC
add_number_to_json(UINT32 number, json_object *jso, const char *jso_tag)
{
    json_object *jso_number = NULL;

    return_if_null(jso, "Bad reference.", TSS2_FAPI_RC_BAD_VALUE);

    jso_number = json_object_new_int64(number);
    return_if_null(jso_number, "Out of memory", TSS2_FAPI_RC_MEMORY);

    if (json_object_object_add(jso, jso_tag, jso_number)) {
        return_error(TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.");
    }
    return TSS2_RC_SUCCESS;
}

static bool
zero_digest(UINT8 *buffer, size_t size)
{
    if (buffer[0] == 0 && memcmp(&buffer[0], &buffer[1], size - 1) == 0) {
        return true;
    } else {
        return false;
    }
}

static UINT8 digest_ff[TPM2_SHA512_DIGEST_SIZE] = {
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
};

/* Replace current sha1 digest with digest of 0xff values. */
static TSS2_RC
set_ff_digest(json_object *jso) {
    TSS2_RC r;
    json_object
        *jso_digest = NULL,
        *jso_ary = NULL,
        *jso_digest_type = NULL;

    jso_digest = json_object_new_object();
    return_if_null(jso_digest, "Out of memory.", TSS2_FAPI_RC_MEMORY);

    r = add_uint8_ary_to_json(digest_ff,TPM2_SHA1_DIGEST_SIZE, jso_digest, "digest");
    goto_if_error2(r, "Add digest to json", error);

    jso_digest_type = NULL;
    jso_digest_type = json_object_new_string ("sha1");
    goto_if_null(jso_digest_type, "Out of memory.", TSS2_FAPI_RC_MEMORY, error);

    if (json_object_object_add(jso_digest, "hashAlg", jso_digest_type)) {
        goto_error(r, TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.",
                   error);
    }

    jso_ary = json_object_new_array();
    goto_if_null(jso_ary, "Out of memory.", TSS2_FAPI_RC_MEMORY, error);

    if (json_object_array_add(jso_ary, jso_digest)) {
        goto_error(r, TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.",
                   error);
    }
    json_object_object_del(jso, "digests");
    if (json_object_object_add(jso, "digests", jso_ary)) {
        goto_error(r, TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.",
                   error);
    }
    return TSS2_RC_SUCCESS;

 error:
    if (jso_digest)
        json_object_put(jso_digest);
    if (jso_digest_type)
        json_object_put(jso_digest_type);
    if (jso_ary)
        json_object_put(jso_ary);
    return r;
}

/** Callback for digest of old IMA format.
 */
static TSS2_RC
sha_digest_json_cb(UINT8 *digest, UINT8 * buffer, size_t *offset, json_object *jso,
                   IFAPI_IMA_TEMPLATE *template) {
    TSS2_RC r;
    UNUSED(template);

    LOGBLOB_TRACE(&buffer[*offset], TPM2_SHA1_DIGEST_SIZE, "IMA buffer");
    LOGBLOB_TRACE(digest, TPM2_SHA1_DIGEST_SIZE, "IMA digest");

    if (jso && zero_digest(digest, TPM2_SHA1_DIGEST_SIZE) &&
        zero_digest(&buffer[*offset], TPM2_SHA1_DIGEST_SIZE)) {
        r = set_ff_digest(jso);
        return_if_error(r, "Set 0xff in digest.");
    }

    *offset += TPM2_SHA1_DIGEST_SIZE;
    return TSS2_RC_SUCCESS;
}

/** Get UINT32 size value from buffer and increase offset.
 */
UINT32
get_size_from_buffer(UINT8 *buffer, size_t *offset, IFAPI_IMA_TEMPLATE *template) {
    UINT32 size;
    memcpy(&size, &buffer[*offset], sizeof(UINT32));
    *offset += sizeof(UINT32);
    if (template->convert_to_big_endian) {
        size = endian_swap_32(size);
    }
    return size;
}

/** Callback for digest with name of used hash algorithm,
 */
static TSS2_RC
digest_with_hash_name_cb(UINT8 *digest, UINT8 *buffer, size_t *offset, json_object *jso,
                        IFAPI_IMA_TEMPLATE *template) {
    TSS2_RC r;
    char hash_alg[CRYPTO_MAX_ALG_NAME + 1] = { 0 };
    size_t alg_name_size;
    const EVP_MD *md;
    int digest_size;
    UINT32 digest_buffer_size;

    digest_buffer_size = get_size_from_buffer(buffer, offset, template);
    alg_name_size = strlen((char *)&buffer[*offset]) - 1; /**< strip : */
    if (alg_name_size > CRYPTO_MAX_ALG_NAME) {
        return_error(TSS2_FAPI_RC_BAD_VALUE, "Invalid hash name.");
    }
    memcpy(hash_alg, &buffer[*offset], alg_name_size);
    md = EVP_get_digestbyname(hash_alg);
    if (md == NULL) {
        return_error(TSS2_FAPI_RC_BAD_VALUE, "Invalid hash name.");
    }
    *offset += alg_name_size + 2; /**< skip : and '\0' */
    digest_size = EVP_MD_size(md);
    if (alg_name_size + 2 + digest_size != digest_buffer_size) {
        digest_buffer_size = endian_swap_32(digest_buffer_size);
        /* Try with endian swap */
        if (alg_name_size + 2 + digest_size != digest_buffer_size) {
            return_error(TSS2_FAPI_RC_BAD_VALUE, "Invalid IMA binary format.");
        }
    }
    LOGBLOB_TRACE(&buffer[*offset], digest_size, "IMA data_hash");

    if (jso && zero_digest(digest, template->hash_size) &&
        zero_digest(&buffer[*offset], digest_size)) {
        r = set_ff_digest(jso);
        return_if_error(r, "Set 0xff in digest.");
    }
    *offset += digest_size;
    return TSS2_RC_SUCCESS;
}

/** Callback to get digest with size field (UINT32).
 */
static TSS2_RC
signature_cb(UINT8 *digest, UINT8 *buffer, size_t *offset, json_object *jso,
             IFAPI_IMA_TEMPLATE *template) {
    UNUSED(digest);
    UNUSED(jso);
    UINT32 digest_size;
    UNUSED(template);

    digest_size =  get_size_from_buffer(buffer, offset, template);
    LOGBLOB_TRACE(&buffer[*offset], digest_size, "IMA Signature:");
    *offset += digest_size;
    return TSS2_RC_SUCCESS;
}

/** Callback to get null terminated name with size field (n-ng).
 */
static TSS2_RC eventname_ng_json_cb(UINT8 *digest, UINT8 *buffer, size_t *offset, json_object *jso,
                                    IFAPI_IMA_TEMPLATE *template) {
    size_t size;
    UINT32 size_from_buffer;
    UNUSED(digest);
    UNUSED(jso);

    /* Get size from buffer with 0 Terminator. */
    size_from_buffer = get_size_from_buffer(buffer, offset, template);
    size = strlen((const char *)&buffer[*offset]);
    if (size != size_from_buffer - 1) {
        size_from_buffer = endian_swap_32(size_from_buffer);
        /* Try with endian swap */
        if (size != size_from_buffer - 1) {
            return_error2(TSS2_FAPI_RC_BAD_VALUE,
                          "Invalid digest size, string length: %zu size from buffer: %"
                          PRIu32, size, size_from_buffer);
        }
    }
    LOG_TRACE("IMA name: %s", (const char *)&buffer[*offset]);
    template->name = (char *)&buffer[*offset];
    *offset += size + 1; /**< with 0 terminator */
    return TSS2_RC_SUCCESS;
}

/** Callback to get null terminated name (n).
 */
static TSS2_RC eventname_cb(UINT8 *digest, UINT8 *buffer, size_t *offset, json_object *jso,
                            IFAPI_IMA_TEMPLATE *template) {
    size_t size;
    UNUSED(digest);
    UNUSED(jso);

    size = strlen((const char *)&buffer[*offset]); // TODO check
    if (size > TCG_EVENT_NAME_LEN_MAX + 1) {
        return_error(TSS2_FAPI_RC_BAD_VALUE, "Too long event name.");
    }
    LOG_TRACE("IMA name: %s", (const char *)&buffer[*offset]);
    template->name =  (char *)&buffer[*offset];
    *offset += size + 1; /**< with 0 terminator */
    return TSS2_RC_SUCCESS;
}

/** Callback to initialize the json event list.
 */
TSS2_RC
init_event_list_json_cb(json_object **jso) {
    return_if_null(jso, "Bad reference.", TSS2_FAPI_RC_BAD_VALUE);
    if (!*jso) {
        *jso = json_object_new_array();
        return_if_null(*jso, "Out of memory", TSS2_FAPI_RC_MEMORY);
    }
    return TSS2_RC_SUCCESS;
}

/** Callback to convert header of IMA template to JSON.
 */
TSS2_RC
event_header_json_cb(
    size_t recnum,
    UINT32 pcr,
    const char* ima_type,
    UINT8 *digest,
    size_t digest_size,
    json_object *jso_list,
    json_object **jso)
{
    TSS2_RC r;
    json_object *jso_digest, *jso_digest_type, *jso_ary, *jso_content;
    char *hash_name;

    *jso = json_object_new_object();
    return_if_null(*jso, "Out of memory.", TSS2_FAPI_RC_MEMORY);

    r = add_number_to_json(recnum, *jso, "recnum");
    return_if_error(r, "Add number to json object.");

    r = add_number_to_json(pcr, *jso, "pcr");
    return_if_error(r, "Add number to json object.");

    jso_digest = json_object_new_object();
    return_if_null(*jso, "Out of memory.", TSS2_FAPI_RC_MEMORY);

    jso_digest_type = NULL;
    switch (digest_size) {
        case TPM2_SHA1_DIGEST_SIZE:
            hash_name = "sha1";
            break;
        case TPM2_SHA256_DIGEST_SIZE:
            hash_name = "sha256";
            break;
        case TPM2_SHA384_DIGEST_SIZE:
            hash_name = "sha384";
            break;
        case TPM2_SHA512_DIGEST_SIZE:
            hash_name = "sha512";
            break;
        default:
            return_error2(TSS2_FAPI_RC_BAD_VALUE, "Invalid hash size %zu",
                          digest_size);
    }

    jso_digest_type = json_object_new_string (hash_name);
    return_if_null(jso_digest_type, "Out of memory.", TSS2_FAPI_RC_MEMORY);

    if (json_object_object_add(jso_digest, "hashAlg", jso_digest_type)) {
        return_error(TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.");
    }

    jso_ary = json_object_new_array();
    return_if_null(jso_ary, "Out of memory.", TSS2_FAPI_RC_MEMORY);

    if (json_object_array_add(jso_ary, jso_digest)) {
        return_error(TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.");
    }
    if (json_object_object_add(*jso, "digests", jso_ary)) {
        return_error(TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.");
    }

    r = add_uint8_ary_to_json(digest, digest_size, jso_digest, "digest");
    return_if_error(r, "Add digest to json");

    r = add_string_to_json("ima_template", *jso, CONTENT_TYPE);
    return_if_error(r, "Add number to json object.");

    r = get_json_content(*jso, &jso_content);
    return_if_error(r, "Get sub event");

    r = add_string_to_json(ima_type, jso_content, "template_name");
    return_if_error(r, "Add number to json object.");

    if (json_object_array_add(jso_list, *jso)) {
        return_error(TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.");
    }
    return TSS2_RC_SUCCESS;
}

/* Type to store field data and the field callback */
struct template_field {
    const char *field_id;
    TSS2_RC (*field_cb) (UINT8 *digest, UINT8 *buffer, size_t *offset, json_object *jso,
                         IFAPI_IMA_TEMPLATE *template);
};

/* Type for storing the IMA template descriptor */
struct template_description {
    char *ima_type;
    char *format;
};

/* Callbacks to initialize result list and add events to the list */
struct event_callbacks {
    TSS2_RC (*init_list_cb) (json_object **jso);
    TSS2_RC (*add_header_cb) (size_t recnum,
                              UINT32 pcr,
                              const char* ima_type,
                              UINT8 *digest,
                              size_t digest_size,
                              json_object *jso_list,
                              json_object **jso_current_event);
};

static struct event_callbacks event_callbacks = {
    .init_list_cb = init_event_list_json_cb,
    .add_header_cb = event_header_json_cb
};

/*
 * Supported Descriptors and Template Fields.
 */
static struct template_description template_tab[] = {
    { .ima_type = "ima",
      .format = "d|n"},
    { .ima_type = "ima-ng",
      .format = "d-ng|n-ng"},
    { .ima_type = "ima-sig",
      .format = "d-ng|n-ng|sig"}
};

static struct template_field field_tab[] = {
    { .field_id = "d",
      .field_cb = sha_digest_json_cb },
    { .field_id = "n",
      .field_cb = eventname_cb },
    { .field_id = "d-ng",
      .field_cb = digest_with_hash_name_cb},
    { .field_id = "n-ng",
      .field_cb = eventname_ng_json_cb},
    { .field_id = "sig",
      .field_cb = signature_cb},
};

/**  Convert the IMA event to output format.
 *
 * Based on the event description in the ima template the appropriate callbacks are
 * determined and executed to generate the ouput.
 *
 * @param[in] template The IMA event.
 * @param[out] jso The output object.
 * @param[out] name The name of the event object.
 * @retval TSS2_RC_SUCCESS if the function call was a success.
 * @retval TSS2_FAPI_RC_BAD_VALUE If the current template foramt can't be processed.
 * @retval TSS2_FAPI_RC_MEMORY: if the FAPI cannot allocate enough memory.
 */
static TSS2_RC
convert_ima_event_buffer(
    IFAPI_IMA_TEMPLATE *template,
    json_object *jso,
    char **name)
{
    TSS2_RC r;
    size_t offset = 0;
    size_t i, j;
    char *copy_of_template_format = NULL;
    char *copy_of_template_format_orig = NULL;
    char *current_field;
    struct template_description *template_desc = NULL;
    size_t size_of_template_tab = sizeof(template_tab) / sizeof(template_tab[0]);
    size_t size_of_field_tab = sizeof(field_tab) / sizeof(field_tab[0]);
    json_object *jso_content;

    /* Search template decription corresponding to the template type. */
    for (i = 0; i < size_of_template_tab; i++) {
        if (strcmp(template->ima_type, template_tab[i].ima_type) == 0) {
            template_desc = template_tab + i;
            break;
        }
    }

    if (i == size_of_template_tab) {
        return_error(TSS2_FAPI_RC_BAD_VALUE, "Ima template type not supported.");
    }

    copy_of_template_format = strdup(template_desc->format);
    /* Save pointer for cleanup. */
    copy_of_template_format_orig = copy_of_template_format;
    goto_if_null(copy_of_template_format, "Out of memory.", TSS2_FAPI_RC_MEMORY, error);
    /* Loop over template fields separated by | */
    for (i = 0; (current_field = strsep(&copy_of_template_format, "|")) != NULL; i++) {
        struct template_field *field = NULL;

        for (j = 0; j < size_of_field_tab; j++) {
            if (!strcmp(current_field, field_tab[j].field_id)) {
                field = field_tab + j;
                break;
            }
        }

        goto_if_null2(field, "Unknown field %s", r, TSS2_FAPI_RC_BAD_VALUE, error, current_field);

        /* Convert the IMA data with the found callback. */
        r = field->field_cb(&template->header.digest[0],
                            template->event_buffer, &offset, jso, template);
        *name= template->name;
        goto_if_error(r, "Get field", error);
    }
    if (jso) {
        r = get_json_content(jso, &jso_content);
        goto_if_error(r, "Get sub event", error);

        r = add_uint8_ary_to_json(template->event_buffer, template->event_size, jso_content, "template_data");
        goto_if_error(r, "Create data to be hashed", error);
    }

    SAFE_FREE(copy_of_template_format_orig);
    return TSS2_RC_SUCCESS;
error:
    SAFE_FREE(copy_of_template_format_orig);
    return r;
}

/** Get one event from the event file.
 *
 * Retrieve the part of the event behind the fixed event header from the
 * event file.
 *
 * @param[in,out] template The IMA event with the already read event header.
 *                the storage for the rest of the event will be allocated
 *                and stored in the template.
 * @param[in] fp The event stream.
 *
 * @retval TSS2_RC_SUCCESS if the function call was a success.
 * @retval TSS2_FAPI_RC_MEMORY: if the FAPI cannot allocate enough memory.
 */
static TSS2_RC
read_event_buffer(IFAPI_IMA_TEMPLATE *template, FILE *fp)
{
    bool old_ima_format;
    int size, rsize;

    /* Check IMA  legacy format. */
    if (strcmp(template->ima_type, "ima") == 0) {
        old_ima_format = true;
         /* Size is fixed for old IMA format. */
        template->event_size = TPM2_SHA1_DIGEST_SIZE + TCG_EVENT_NAME_LEN_MAX + 1;
        size = TPM2_SHA1_DIGEST_SIZE;
    } else {
        old_ima_format = false;
        /* For the new IMA format get size from event. */
        rsize = fread(&template->event_size, sizeof(UINT32), 1, fp);
        if (rsize != 1) {
            return_error(TSS2_FAPI_RC_BAD_VALUE, "Invalid ima data");
        }
        if (template->convert_to_big_endian) {
            template->event_size = endian_swap_32(template->event_size);
        }
        size = template->event_size;
    }

    template->event_buffer = calloc(template->event_size, sizeof(UINT8));
    return_if_null(template->event_buffer, "Out of memory", TSS2_FAPI_RC_MEMORY);

    rsize = fread(template->event_buffer, size, 1, fp);
    if (rsize != 1) {
        return_error(TSS2_FAPI_RC_BAD_VALUE, "Invalid ima data");
    }
    if (old_ima_format) {
        UINT32 field_len;

        rsize = fread(&field_len, sizeof(UINT32), 1, fp);
        if (rsize != 1) {
            return_error(TSS2_FAPI_RC_BAD_VALUE, "Invalid ima data");
        }
        if (template->convert_to_big_endian) {
            field_len = endian_swap_32(field_len);
        }
        if (field_len > template->event_size - TPM2_SHA1_DIGEST_SIZE) {
             return_error(TSS2_FAPI_RC_BAD_VALUE, "Invalid ima data");
        }
        rsize = fread(template->event_buffer +TPM2_SHA1_DIGEST_SIZE,
                      field_len, 1, fp);
        if (rsize != 1) {
            return_error(TSS2_FAPI_RC_BAD_VALUE, "Invalid ima data");
        }
    }
    return TSS2_RC_SUCCESS;
}

size_t read_ima_header(IFAPI_IMA_TEMPLATE *template, FILE *fp, TSS2_RC *rc)
{
    /* header: pcr register, sha1 digest, size field ima type string, start of ima type */
    size_t header_size = sizeof(UINT32) + TPM2_SHA1_DIGEST_SIZE + sizeof(UINT32) + 3;
    size_t size, rsize;
    size_t pos_ima_type = header_size - 3 - sizeof(UINT32);

    *rc = TSS2_RC_SUCCESS;

    size = fread(&template->header, 1, header_size, fp);
    if (size == 0) {
        return size;
    }

    if (size != header_size) {
        *rc = TSS2_FAPI_RC_BAD_VALUE;
        LOG_ERROR("Invalid ima data");
        return 0;
    }

    template->convert_to_big_endian = need_to_convert_to_big_endian(template);

    if (memcmp(&template->header.digest[pos_ima_type], "ima", 3) == 0) {
        /* Start of IMA type string found. */
        memcpy(&template->ima_type_size,
               &template->header.digest[pos_ima_type - sizeof(UINT32)],
               sizeof(UINT32));
        if (template->convert_to_big_endian) {
            template->ima_type_size = endian_swap_32(template->ima_type_size);
        }
        memcpy(&template->ima_type[0], "ima", 3);
        /* Get the description of the IMA event. */
        if (template->ima_type_size < 3 ||
            template->ima_type_size >= TCG_EVENT_NAME_LEN_MAX) {
            LOG_ERROR("Invalid ima data");
            *rc = TSS2_FAPI_RC_BAD_VALUE;
            return 0;
        }
        size = template->ima_type_size - 3;
        if (size > 0) {
            rsize = fread(&template->ima_type[3], size, 1, fp);
            if (rsize != 1) {
                LOG_ERROR("Invalid ima data");
                *rc = TSS2_FAPI_RC_BAD_VALUE;
                return 0;
            }
            template->ima_type[template->ima_type_size] = '\0';
        } else {
            template->ima_type[3] = '\0';
        }
        template->hash_alg =  TPM2_ALG_SHA1;
        template->hash_size = TPM2_SHA1_DIGEST_SIZE;
        return header_size;
    }
    return 0;
}

/** Read ima eventlog and create JSON list of events.
 *
 * @param[in] filename The filname of the IMA event file.
 * @param[in] pcrList The list of PCRs that are to be quoted
 * @param[in] pcrListSize The size of pcrList in bytes
 * @param[out] The event_list in JSON format.
 * @retval TSS2_RC_SUCCESS if the function call was a success.
 * @retval TSS2_FAPI_RC_IO_ERROR If a event cannot be read.
 * @retval TSS2_FAPI_RC_MEMORY: if the FAPI cannot allocate enough memory for
 *         internal operations or return parameters.
 */
TSS2_RC
ifapi_read_ima_event_log(
    const char *filename,
    const uint32_t *pcrList,
    size_t  pcrListSize,
    json_object **jso_list) {
    TSS2_RC r;
    FILE *fp = NULL;
    IFAPI_IMA_TEMPLATE template = { 0 };
    size_t recnum = 0, i;
    json_object *jso_current_event = NULL;;
    bool add_event;
    uint32_t pcr = 0;

    return_if_null(jso_list, "Bad reference.", TSS2_FAPI_RC_BAD_VALUE);
    template.event_buffer = NULL;

    OpenSSL_add_all_digests();
    fp = fopen(filename, "r");
    goto_if_null2(fp, "Could not open: %s", r, TSS2_FAPI_RC_IO_ERROR, error, filename);
    LOG_INFO("IMA read file: %s", filename);

    r = event_callbacks.init_list_cb(jso_list);
    goto_if_error(r, "Initialize event list.", error);

    /* While the event header with fixed size can be read. */
    while (read_ima_header(&template, fp, &r)) {
        if (r) {
            return_error(r, "Invalid ima data.")
        }
        add_event = true;
        if (!template.ima_type_size ||
            template.ima_type_size > TCG_EVENT_NAME_LEN_MAX) {
            goto_error(r, TSS2_FAPI_RC_BAD_VALUE, "Invalid ima type size", error);
        }

        /* Check whether IMA PCR is member of pcrList. */
        for (i = 0; i < pcrListSize; i++) {
            pcr = template.header.pcr;
            if (template.convert_to_big_endian) {
                pcr= endian_swap_32(pcr);
            }
            if (pcrList[i] == pcr)
                break;
        }
        if (i == pcrListSize) {
            /* PCR  is not used. */
            add_event = false;
        }

        if (add_event) {
            r = event_callbacks.add_header_cb(recnum, pcr,
                                              template.ima_type, template.header.digest,
                                              template.hash_size,
                                              *jso_list, &jso_current_event);
            goto_if_error(r, "Add header to event list.", error);

            recnum += 1;
        }

        /* Read the rest of the event. */
        r = read_event_buffer(&template, fp);
        goto_if_error(r, "Read event buffer.", error);

        if (add_event) {
            char *name;
            r = convert_ima_event_buffer(&template, jso_current_event, &name);
            goto_if_error(r, "Create json event.", error);
        }
        SAFE_FREE(template.event_buffer);

    }
    fclose(fp);
    return TSS2_RC_SUCCESS;

 error:
    SAFE_FREE(template.event_buffer);
    if (fp)
        fclose(fp);
    if (*jso_list)
        json_object_put(*jso_list);
    return r;
}

static char *field_IFAPI_IMA_EVENT_tab[] = {
    "template_value",
    "template_data",
    "template_name"
};

/** Deserialize a IFAPI_IMA_EVENT_TYPE json object.
 *
 * @param[in]  jso the json object to be deserialized.
 * @param[out] out the deserialzed binary object.
 * @retval TSS2_RC_SUCCESS if the function call was a success.
 * @retval TSS2_FAPI_RC_BAD_VALUE if the json object can't be deserialized.
 */
TSS2_RC
ifapi_json_IFAPI_IMA_EVENT_TYPE_deserialize(json_object *jso, IFAPI_IMA_EVENT_TYPE *out)
{
    LOG_TRACE("call");
    return ifapi_json_IFAPI_IMA_EVENT_TYPE_deserialize_txt(jso, out);
}

typedef struct {
    IFAPI_IMA_EVENT_TYPE in;
    char *name;
} IFAPI_IFAPI_IMA_EVENT_TYPE_ASSIGN;

static IFAPI_IFAPI_IMA_EVENT_TYPE_ASSIGN deserialize_IFAPI_IMA_EVENT_TYPE_tab[] = {
    { IFAPI_IMA_EVENT_TAG_IMA, "ima" },
    { IFAPI_IMA_EVENT_TAG_NG, "ima-ng" },
    { IFAPI_IMA_EVENT_TAG_SIG, "ima-sig" },
};

/**  Deserialize a json object of type IFAPI_IMA_EVENT_TYPE.
 *
 * @param[in]  jso the json object to be deserialized.
 * @param[out] out the deserialzed binary object.
 * @retval TSS2_RC_SUCCESS if the function call was a success.
 * @retval TSS2_FAPI_RC_BAD_VALUE if the json object can't be deserialized.
 */
TSS2_RC
ifapi_json_IFAPI_IMA_EVENT_TYPE_deserialize_txt(json_object *jso,
        IFAPI_IMA_EVENT_TYPE *out)
{
    LOG_TRACE("call");
    const char *token = json_object_get_string(jso);
    size_t i;
    size_t n = sizeof(deserialize_IFAPI_IMA_EVENT_TYPE_tab) /
        sizeof(deserialize_IFAPI_IMA_EVENT_TYPE_tab[0]);
    size_t size = strlen(token);
    for (i = 0; i < n; i++) {
        if (strncasecmp(&token[0],
                        &deserialize_IFAPI_IMA_EVENT_TYPE_tab[i].name[0],
                        size) == 0) {
            *out = deserialize_IFAPI_IMA_EVENT_TYPE_tab[i].in;
            return TSS2_RC_SUCCESS;
        }
    }
    return_error(TSS2_FAPI_RC_BAD_VALUE, "Undefined constant.");
}

/** Deserialize a IFAPI_IMA_EVENT json object.
 *
 * @param[in]  jso the json object to be deserialized.
 * @param[out] out the deserialzed binary object.
 * @retval TSS2_RC_SUCCESS if the function call was a success.
 * @retval TSS2_FAPI_RC_BAD_VALUE if the json object can't be deserialized.
 * @retval TSS2_FAPI_RC_BAD_REFERENCE a invalid null pointer is passed.
 * @retval TSS2_FAPI_RC_MEMORY if not enough memory can be allocated.
 */
TSS2_RC
ifapi_json_IFAPI_IMA_EVENT_deserialize(json_object *jso,  IFAPI_IMA_EVENT *out)
{
    json_object *jso2;
    TSS2_RC r;

    LOG_TRACE("call");
    return_if_null(out, "Bad reference.", TSS2_FAPI_RC_BAD_REFERENCE);

    ifapi_check_json_object_fields(jso, &field_IFAPI_IMA_EVENT_tab[0],
                                   SIZE_OF_ARY(field_IFAPI_IMA_EVENT_tab));

    if (!ifapi_get_sub_object(jso, "template_name", &jso2)) {
        LOG_ERROR("Field \"template_name\" not found.");
        return TSS2_FAPI_RC_BAD_VALUE;
    }
    r = ifapi_json_IFAPI_IMA_EVENT_TYPE_deserialize(jso2, &out->template_name);
    return_if_error(r, "Bad value for field \"template_name\".");

    if (!ifapi_get_sub_object(jso, "template_data", &jso2)) {
        if (!ifapi_get_sub_object(jso, "template_value", &jso2)) {
            LOG_ERROR("Field \"template_data\" not found.");
            return TSS2_FAPI_RC_BAD_VALUE;
        }
    }
    r = ifapi_json_UINT8_ARY_deserialize(jso2, &out->template_value);
    return_if_error(r, "Bad value for field \"template_data \".");

    LOG_TRACE("true");
    return TSS2_RC_SUCCESS;
}

TSS2_RC
ifapi_get_ima_eventname(IFAPI_IMA_EVENT *ima_event, char **name)
{
    TSS2_RC r;
    IFAPI_IMA_TEMPLATE template;
    size_t i;
    size_t n = SIZE_OF_ARY(deserialize_IFAPI_IMA_EVENT_TYPE_tab);

    memset(&template, 0, sizeof(template));
    for (i = 0; i < n; i++) {
        if (deserialize_IFAPI_IMA_EVENT_TYPE_tab[i].in ==
            ima_event->template_name) {
            char *tab_name = deserialize_IFAPI_IMA_EVENT_TYPE_tab[i].name;
            memcpy(&template.ima_type, tab_name, strlen(tab_name)+1);
            break;
        }
    }
    if (i >= n) {
        return_error(TSS2_FAPI_RC_BAD_VALUE, "Undefined constant.");
    }
    template.event_size = ima_event->template_value.size;
    template.event_buffer = &ima_event->template_value.buffer[0];
    r = convert_ima_event_buffer(&template, NULL, name);
    return_if_error(r, "Parsing of IMA template failed.");

    return TSS2_RC_SUCCESS;
}
