/* 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" // IWYU pragma: keep
#endif

#include <inttypes.h>  // for uintptr_t, uint8_t, int64_t, PRId64
#include <stdio.h>     // for sscanf
#include <stdlib.h>    // for calloc
#include <string.h>    // for strlen, strncmp, memcmp, strcmp
#include <strings.h>   // for strncasecmp
#include <sys/types.h> // for uint
#include <uchar.h>     // for char16_t

#include "efi_event.h"      // for TCG_EVENT, TCG_EVENT2, TCG_EVENT_H...
#include "fapi_crypto.h"    // for ifapi_hash_get_digest_size
#include "ifapi_eventlog.h" // for IFAPI_EVENT_TYPE
#include "ifapi_eventlog_system.h"
#include "ifapi_macros.h"         // for check_oom, return_error2
#include "tpm_json_deserialize.h" // for ifapi_get_sub_object, ifapi_json_U...
#include "tss2_tpm2_types.h"      // for TPM2_MAX_PCRS, TPMI_ALG_HASH

#define LOGMODULE fapi
#include "util/log.h" // for LOG_ERROR, LOG_TRACE, SAFE_FREE

static char *tss_const_prefixes[] = { "TPM2_ALG_", "TPM2_", "TPM_", "TPMA_", "POLICY", NULL };

/** Get number from a string.
 *
 * A string which represents a number or hex number (prefix 0x) is converted
 * to an int64 number.
 *
 * param[in] token the string representing the number.
 * param[out] num the converted number.
 * @retval true if token represents a number
 * @retval false if token does not represent a number.
 */
static bool
get_number(const char *token, int64_t *num) {
    int itoken = 0;
    int pos = 0;
    if (strncmp(token, "0x", 2) == 0) {
        itoken = 2;
        sscanf(&token[itoken], "%" PRIx64 "%n", num, &pos);
    } else {
        sscanf(&token[itoken], "%" PRId64 "%n", num, &pos);
    }
    if ((size_t)pos == strlen(token) - itoken)
        return true;
    else
        return false;
}

/** Get the index of a sub string after a certain prefix.
 *
 * The prefixes from table tss_const_prefixes will be used for case
 * insensitive comparison.
 *
 * param[in] token the token with a potential prefix.
 * @retval the position of the sub string after the prefix.
 * @retval 0 if no prefix is found.
 */
static unsigned int
get_token_start_idx(const char *token) {
    uint  itoken = 0;
    char *entry;
    uint  i;

    for (i = 0, entry = tss_const_prefixes[0]; entry != NULL; i++, entry = tss_const_prefixes[i]) {
        if (strncasecmp(token, entry, strlen(entry)) == 0) {
            itoken += strlen(entry);
            break;
        }
    }
    return itoken;
}

bool
digest2_accumulator_callback(TCG_DIGEST2 const *digest, size_t size, void *data) {
    if (digest == NULL || data == NULL) {
        LOG_ERROR("neither parameter may be NULL");
        return false;
    }
    size_t *accumulator = (size_t *)data;

    *accumulator += sizeof(*digest) + size;

    return true;
}
/*
 * Invoke callback function for each TCG_DIGEST2 structure in the provided
 * TCG_EVENT_HEADER2. The callback function is only invoked if this function
 * is first able to determine that the provided buffer is large enough to
 * hold the digest. The size of the digest is passed to the callback in the
 * 'size' parameter.
 */
bool
foreach_digest2(tpm2_eventlog_context *ctx,
                UINT32                 event_type,
                TCG_DIGEST2 const     *digest,
                size_t                 count,
                size_t                 size) {

    if (digest == NULL) {
        LOG_ERROR("digest cannot be NULL");
        return false;
    }

    bool ret = true;

    size_t i, j;
    for (i = 0; i < count; i++) {
        if (size < sizeof(*digest)) {
            LOG_ERROR("insufficient size for digest header");
            return false;
        }

        const TPMI_ALG_HASH alg = digest->AlgorithmId;
        const size_t        alg_size = ifapi_hash_get_digest_size(alg);
        if (size < sizeof(*digest) + alg_size) {
            LOG_ERROR("insufficient size for digest buffer");
            return false;
        }

        if (event_type == EV_NO_ACTION) {
            /* Digest for EV_NO_ACTION must consist of 0 bytes. */
            for (j = 0; j < alg_size; j++) {
                if (digest->Digest[j]) {
                    LOG_ERROR("No zero digest for EV_NO_ACTION.");
                    return false;
                }
            }
        }

        if (ctx->digest2_cb != NULL) {
            ret = ctx->digest2_cb(digest, alg_size, ctx->data);
            if (!ret) {
                LOG_ERROR("callback failed for digest at %p with size %zu", digest, alg_size);
                break;
            }
        }

        size -= sizeof(*digest) + alg_size;
        digest = (TCG_DIGEST2 *)((uintptr_t)digest->Digest + alg_size);
    }

    return ret;
}
/*
 * given the provided event type, parse event to ensure the structure / data
 * in the buffer doesn't exceed the buffer size
 */
bool
parse_event2body(TCG_EVENT2 const *event, UINT32 type) {

    switch (type) {
    /* TCG PC Client FPF section 9.2.6 */
    case EV_EFI_VARIABLE_DRIVER_CONFIG:
    case EV_EFI_VARIABLE_BOOT:
    case EV_EFI_VARIABLE_AUTHORITY: {
        UEFI_VARIABLE_DATA *data = (UEFI_VARIABLE_DATA *)event->Event;
        if (event->EventSize < sizeof(*data)) {
            LOG_ERROR("size is insufficient for UEFI variable data");
            return false;
        }

        if (event->EventSize < sizeof(*data) + data->UnicodeNameLength * sizeof(char16_t)
                                   + data->VariableDataLength) {
            LOG_ERROR("size is insufficient for UEFI variable data");
            return false;
        }
    } break;
    /* TCG PC Client FPF section 2.3.4.1 and 9.4.1 */
    case EV_POST_CODE:
        // the event is a string, so there are no length requirements.
        break;
    /* TCG PC Client FPF section 9.2.5 */
    case EV_S_CRTM_CONTENTS:
    case EV_EFI_PLATFORM_FIRMWARE_BLOB: {
        UEFI_PLATFORM_FIRMWARE_BLOB *data = (UEFI_PLATFORM_FIRMWARE_BLOB *)event->Event;
        UNUSED(data);
        if (event->EventSize < sizeof(*data)) {
            LOG_ERROR("size is insufficient for UEFI FW blob data");
            return false;
        }
    } break;
    case EV_EFI_BOOT_SERVICES_APPLICATION:
    case EV_EFI_BOOT_SERVICES_DRIVER:
    case EV_EFI_RUNTIME_SERVICES_DRIVER: {
        UEFI_IMAGE_LOAD_EVENT *data = (UEFI_IMAGE_LOAD_EVENT *)event->Event;
        UNUSED(data);
        if (event->EventSize < sizeof(*data)) {
            LOG_ERROR("size is insufficient for UEFI image load event");
            return false;
        }
        /* what about the device path? */
    } break;
    /* TCG PC Client Platform Firmware Profile Specification Level 00 Version 1.05 Revision 23
     * section 10.4.1 */
    case EV_EFI_HCRTM_EVENT: {
        const char hcrtm_data[] = "HCRTM";
        size_t     len = strlen(hcrtm_data);
        BYTE      *data = (BYTE *)event->Event;
        if (event->EventSize != len || strncmp((const char *)data, hcrtm_data, len) != 0) {
            LOG_ERROR("HCRTM Event Data MUST be the string: \"%s\"", hcrtm_data);
            return false;
        }
    } break;
    default:
        /* no special handling */
        break;
    }

    return true;
}
/*
 * parse event structure, including header, digests and event buffer ensuring
 * it all fits within the provided buffer (buf_size).
 */
bool
parse_event2(TCG_EVENT_HEADER2 const *eventhdr,
             size_t                   buf_size,
             size_t                  *event_size,
             size_t                  *digests_size) {

    bool ret;

    if (buf_size < sizeof(*eventhdr)) {
        LOG_ERROR("corrupted log, insufficient size for event header: %zu", buf_size);
        return false;
    }
    *event_size = sizeof(*eventhdr);

    tpm2_eventlog_context ctx = {
        .data = digests_size,
        .digest2_cb = digest2_accumulator_callback,
    };
    ret = foreach_digest2(&ctx, eventhdr->EventType, eventhdr->Digests, eventhdr->DigestCount,
                          buf_size - sizeof(*eventhdr));
    if (ret != true) {
        return false;
    }
    *event_size += *digests_size;

    TCG_EVENT2 *event = (TCG_EVENT2 *)((uintptr_t)eventhdr + *event_size);
    if (buf_size < *event_size + sizeof(*event)) {
        LOG_ERROR("corrupted log: size insufficient for EventSize");
        return false;
    }
    *event_size += sizeof(*event);

    if (buf_size < *event_size + event->EventSize) {
        LOG_ERROR("size insufficient for event data");
        return false;
    }
    *event_size += event->EventSize;

    return true;
}

bool
parse_sha1_log_event(TCG_EVENT const *event, size_t size, size_t *event_size) {

    /* enough size for the 1.2 event structure */
    if (size < sizeof(*event)) {
        LOG_ERROR("insufficient size for SpecID event header");
        return false;
    }
    if (event->pcrIndex > TPM2_MAX_PCRS) {
        LOG_ERROR("Invalid PCR index");
        return false;
    }
    *event_size = sizeof(*event);

    /* buffer size must be sufficient to hold event and event data */
    if (size < sizeof(*event) + (sizeof(event->event[0]) * event->eventDataSize)) {
        LOG_ERROR("insufficient size for SpecID event data");
        return false;
    }
    *event_size += event->eventDataSize;
    return true;
}

bool
foreach_sha1_log_event(tpm2_eventlog_context *ctx, TCG_EVENT const *eventhdr_start, size_t size) {

    if (eventhdr_start == NULL) {
        LOG_ERROR("invalid parameter");
        return false;
    }
    if (size == 0) {
        return true;
    }

    TCG_EVENT const *eventhdr;
    size_t           event_size;
    bool             ret;

    for (eventhdr = eventhdr_start, event_size = 0; size > 0;
         eventhdr = (TCG_EVENT *)((uintptr_t)eventhdr + event_size), size -= event_size) {

        ret = parse_sha1_log_event(eventhdr, size, &event_size);
        if (!ret) {
            return ret;
        }

        TCG_EVENT2 *event = (TCG_EVENT2 *)((uintptr_t)&eventhdr->eventDataSize);

        /* event header callback */
        if (ctx->log_eventhdr_cb != NULL) {
            ret = ctx->log_eventhdr_cb(eventhdr, event_size, ctx->data);
            if (ret != true) {
                return false;
            }
        }

        ret = parse_event2body(event, eventhdr->eventType);
        if (ret != true) {
            return ret;
        }

        /* event data callback */
        if (ctx->event2_cb != NULL) {
            ret = ctx->event2_cb(event, eventhdr->eventType, ctx->data);
            if (ret != true) {
                return false;
            }
        }
    }

    return true;
}

bool
foreach_event2(tpm2_eventlog_context *ctx, TCG_EVENT_HEADER2 const *eventhdr_start, size_t size) {

    if (eventhdr_start == NULL) {
        LOG_ERROR("invalid parameter");
        return false;
    }
    if (size == 0) {
        return true;
    }

    TCG_EVENT_HEADER2 const *eventhdr;
    size_t                   event_size;
    bool                     ret;

    for (eventhdr = eventhdr_start, event_size = 0; size > 0;
         eventhdr = (TCG_EVENT_HEADER2 *)((uintptr_t)eventhdr + event_size), size -= event_size) {

        size_t digests_size = 0;

        ret = parse_event2(eventhdr, size, &event_size, &digests_size);
        if (!ret) {
            return ret;
        }

        TCG_EVENT2 *event = (TCG_EVENT2 *)((uintptr_t)eventhdr->Digests + digests_size);

        /* event header callback */
        if (ctx->event2hdr_cb != NULL) {
            ret = ctx->event2hdr_cb(eventhdr, event_size, ctx->data);
            if (ret != true) {
                return false;
            }
        }

        /* digest callback foreach digest */
        ret = foreach_digest2(ctx, eventhdr->EventType, eventhdr->Digests, eventhdr->DigestCount,
                              digests_size);
        if (ret != true) {
            return false;
        }

        ret = parse_event2body(event, eventhdr->EventType);
        if (ret != true) {
            return ret;
        }

        /* event data callback */
        if (ctx->event2_cb != NULL) {
            ret = ctx->event2_cb(event, eventhdr->EventType, ctx->data);
            if (ret != true) {
                return false;
            }
        }
    }

    return true;
}

bool
specid_event(TCG_EVENT const *event, size_t size, TCG_EVENT_HEADER2 **next) {

    /* enough size for the 1.2 event structure */
    if (size < sizeof(*event)) {
        LOG_ERROR("insufficient size for SpecID event header");
        return false;
    }

    if (event->eventType != EV_NO_ACTION) {
        LOG_ERROR("SpecID eventType must be EV_NO_ACTION");
        return false;
    }

    if (event->pcrIndex != 0) {
        LOG_ERROR("bad pcrIndex for EV_NO_ACTION event");
        return false;
    }

    size_t i;
    for (i = 0; i < sizeof(event->digest); ++i) {
        if (event->digest[i] != 0) {
            LOG_ERROR("SpecID digest data malformed");
            return false;
        }
    }

    /* eventDataSize must be sufficient to hold the specid event */
    if (event->eventDataSize < sizeof(TCG_SPECID_EVENT)) {
        LOG_ERROR("invalid eventDataSize in specid event");
        return false;
    }

    /* buffer size must be sufficient to hold event and event data */
    if (size < sizeof(*event) + (sizeof(event->event[0]) * event->eventDataSize)) {
        LOG_ERROR("insufficient size for SpecID event data");
        return false;
    }

    TCG_SPECID_EVENT *event_specid = (TCG_SPECID_EVENT *)event->event;

    /* Check the signature */
    if (strcmp((char *)&event_specid->Signature[0], "Spec ID Event03") != 0) {
        LOG_ERROR("Check of signature \"Spec ID Event03\" failed.");
        return false;
    }

    /* specid event must have 1 or more algorithms */
    if (event_specid->numberOfAlgorithms == 0) {
        LOG_ERROR("numberOfAlgorithms is invalid, may not be 0");
        return false;
    }

    /* buffer size must be sufficient to hold event, specid event & algs */
    if (size < sizeof(*event) + sizeof(*event_specid)
                   + sizeof(event_specid->digestSizes[0]) * event_specid->numberOfAlgorithms) {
        LOG_ERROR("insufficient size for SpecID algorithms");
        return false;
    }

    /* size must be sufficient for event, specid, algs & vendor stuff */
    if (size < sizeof(*event) + sizeof(*event_specid)
                   + sizeof(event_specid->digestSizes[0]) * event_specid->numberOfAlgorithms
                   + sizeof(TCG_VENDOR_INFO)) {
        LOG_ERROR("insufficient size for VendorStuff");
        return false;
    }

    TCG_VENDOR_INFO *vendor = (TCG_VENDOR_INFO *)((uintptr_t)event_specid->digestSizes
                                                  + sizeof(*event_specid->digestSizes)
                                                        * event_specid->numberOfAlgorithms);
    /* size must be sufficient for vendorInfo */
    if (size < sizeof(*event) + sizeof(*event_specid)
                   + sizeof(event_specid->digestSizes[0]) * event_specid->numberOfAlgorithms
                   + sizeof(*vendor) + vendor->vendorInfoSize) {
        LOG_ERROR("insufficient size for VendorStuff data");
        return false;
    }
    *next = (TCG_EVENT_HEADER2 *)((uintptr_t)vendor->vendorInfo + vendor->vendorInfoSize);

    return true;
}

static TCG_EVENT_TYPE_ASSIGN deserialize_TCG_EVENT_TYPE_tab[] = {
    { EV_PREBOOT_CERT, "EV_PREBOOT_CERT" },
    { EV_POST_CODE, "EV_POST_CODE" },
    { EV_UNUSED, "EV_UNUSED" },
    { EV_NO_ACTION, "EV_NO_ACTION" },
    { EV_SEPARATOR, "EV_SEPARATOR" },
    { EV_ACTION, "EV_ACTION" },
    { EV_EVENT_TAG, "EV_EVENT_TAG" },
    { EV_S_CRTM_CONTENTS, "EV_S_CRTM_CONTENTS" },
    { EV_S_CRTM_VERSION, "EV_S_CRTM_VERSION" },
    { EV_CPU_MICROCODE, "EV_CPU_MICROCODE" },
    { EV_PLATFORM_CONFIG_FLAGS, "EV_PLATFORM_CONFIG_FLAGS" },
    { EV_TABLE_OF_DEVICES, "EV_TABLE_OF_DEVICES" },
    { EV_COMPACT_HASH, "EV_COMPACT_HASH" },
    { EV_IPL, "EV_IPL" },
    { EV_IPL_PARTITION_DATA, "EV_IPL_PARTITION_DATA" },
    { EV_NONHOST_CODE, "EV_NONHOST_CODE" },
    { EV_NONHOST_CONFIG, "EV_NONHOST_CONFIG" },
    { EV_NONHOST_INFO, "EV_NONHOST_INFO" },
    { EV_OMIT_BOOT_DEVICE_EVENTS, "EV_OMIT_BOOT_DEVICE_EVENTS" },
    { EV_EFI_VARIABLE_DRIVER_CONFIG, "EV_EFI_VARIABLE_DRIVER_CONFIG" },
    { EV_EFI_VARIABLE_BOOT, "EV_EFI_VARIABLE_BOOT" },
    { EV_EFI_BOOT_SERVICES_APPLICATION, "EV_EFI_BOOT_SERVICES_APPLICATION" },
    { EV_EFI_BOOT_SERVICES_DRIVER, "EV_EFI_BOOT_SERVICES_DRIVER" },
    { EV_EFI_RUNTIME_SERVICES_DRIVER, "EV_EFI_RUNTIME_SERVICES_DRIVER" },
    { EV_EFI_GPT_EVENT, "EV_EFI_GPT_EVENT" },
    { EV_EFI_ACTION, "EV_EFI_ACTION" },
    { EV_EFI_PLATFORM_FIRMWARE_BLOB, "EV_EFI_PLATFORM_FIRMWARE_BLOB" },
    { EV_EFI_HANDOFF_TABLES, "EV_EFI_HANDOFF_TABLES" },
    { EV_EFI_HCRTM_EVENT, "EV_EFI_HCRTM_EVENT" },
    { EV_EFI_VARIABLE_AUTHORITY, "EV_EFI_VARIABLE_AUTHORITY" },
};

/**  Deserialize a json object of type TCG_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_TCG_EVENT_TYPE_deserialize_txt(json_object *jso, IFAPI_EVENT_TYPE *out) {
    LOG_TRACE("call");
    int64_t     i64;
    const char *token = json_object_get_string(jso);

    check_oom(token);
    LOG_TRACE("TCG Event: %s", token);

    if (get_number(token, &i64)) {
        *out = (IFAPI_EVENT_TYPE)i64;
        if ((int64_t)*out != i64) {
            LOG_ERROR("Bad value");
            return TSS2_FAPI_RC_BAD_VALUE;
        }
        return TSS2_RC_SUCCESS;

    } else {
        unsigned int itoken = get_token_start_idx(token);
        size_t       i;
        size_t       n
            = sizeof(deserialize_TCG_EVENT_TYPE_tab) / sizeof(deserialize_TCG_EVENT_TYPE_tab[0]);
        size_t size = strlen(token) - itoken;
        for (i = 0; i < n; i++) {
            if (strncasecmp(&token[itoken], &deserialize_TCG_EVENT_TYPE_tab[i].name[0], size)
                == 0) {
                *out = deserialize_TCG_EVENT_TYPE_tab[i].in;
                return TSS2_RC_SUCCESS;
            }
        }
        return_error(TSS2_FAPI_RC_BAD_VALUE, "Undefined constant.");
    }
}

/** Deserialize a TCG_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_TCG_EVENT_TYPE_deserialize(json_object *jso, IFAPI_EVENT_TYPE *out) {
    LOG_TRACE("call");

    return ifapi_json_TCG_EVENT_TYPE_deserialize_txt(jso, out);
}

/** Check event field is string with or without NULL terminator.
 */
static TSS2_RC
check_out_string(const IFAPI_FIRMWARE_EVENT *in, const char *string) {
    if (strncmp((const char *)&in->data.buffer[0], string, strlen(string)) != 0) {
        return_error2(TSS2_FAPI_RC_BAD_VALUE, "Invalid event string. %s expected", string);
    }
    if (in->data.size == strlen(string)) {
        /* String without NULL terminator */
        return TSS2_RC_SUCCESS;
    }
    if (in->data.size == strlen(string) + 1 && in->data.buffer[strlen(string)] == '\0') {
        /* String with NULL terminator */
        return TSS2_RC_SUCCESS;
    }
    return_error2(TSS2_FAPI_RC_BAD_VALUE, "Invalid event string. %s expected", string);
}

/** Deserialize a IFAPI_FIRMWARE_EVENT json object.
 *
 * @param[in]  jso the json object to be deserialized.
 * @param[out] out the deserialzed binary object.
 * @param[out] verify switch whether the digest can be verified.
 * @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_FIRMWARE_EVENT_deserialize(json_object          *jso,
                                            IFAPI_FIRMWARE_EVENT *out,
                                            bool                 *verify) {
    json_object *jso2;
    TSS2_RC      r;
    UINT32       event_type;
    LOG_TRACE("call");

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

    out->data.buffer = NULL;

    if (!ifapi_get_sub_object(jso, "event_type", &jso2)) {
        LOG_ERROR("Bad value");
        return TSS2_FAPI_RC_BAD_VALUE;
    }
    r = ifapi_json_TCG_EVENT_TYPE_deserialize(jso2, &event_type);
    return_if_error(r, "BAD VALUE");

    out->event_type = event_type;

    if (!ifapi_get_sub_object(jso, "event_data", &jso2)) {
        LOG_ERROR("Bad value");
        return TSS2_FAPI_RC_BAD_VALUE;
    }
    r = ifapi_json_UINT8_ARY_deserialize(jso2, &out->data);
    return_if_error(r, "BAD VALUE");

    if (event_type == EV_SEPARATOR) {
        /* The allowed event data for valid separator.
           Other events signal an error. The event data is not
           used to compute the digest. The hash of 0x00000001 will
           be used. */
        BYTE event_0[4] = { 0, 0, 0, 0 };
        BYTE event_ff[4] = { 0xff, 0xff, 0xff, 0xff };

        if (!(out->data.size == 4
              && (memcmp(&event_0[0], &out->data.buffer[0], 4) == 0
                  || memcmp(&event_ff[0], &out->data.buffer[0], 4) == 0))) {
            /* An error is signaled the hash of event_err will be
               used as digest. */
            SAFE_FREE(out->data.buffer);
            out->data.buffer = calloc(1, 4);
            check_oom(out->data.buffer);
            out->data.size = 4;
            /* hash of 0x00000001 will be used as digest. */
            out->data.buffer[3] = 1;
        }
    }

    if (event_type == EV_EFI_HCRTM_EVENT) {
        r = check_out_string(out, "HCRTM");
        goto_if_error(r, "Check HCRTM event.", error);
    } else if (event_type == EV_OMIT_BOOT_DEVICE_EVENTS) {
        r = check_out_string(out, "BOOT ATTEMPTS OMITTED");
        goto_if_error(r, "Check OMIT_BOOT_DEVICES event.", error);
    }

    /* Check whether digest computation for event is possible. */
    if (/* Verification is not possible */
        event_type == EV_EFI_BOOT_SERVICES_APPLICATION || event_type == EV_NO_ACTION
        || event_type == EV_S_CRTM_CONTENTS || event_type == EV_POST_CODE
        || event_type == EV_EFI_HANDOFF_TABLES || event_type == EV_COMPACT_HASH
        || event_type == EV_EFI_BOOT_SERVICES_DRIVER || event_type == EV_EFI_PLATFORM_FIRMWARE_BLOB
        || event_type == EV_PREBOOT_CERT || event_type == EV_UNUSED || event_type == EV_ACTION
        || event_type == EV_CPU_MICROCODE || event_type == EV_IPL
        || event_type == EV_IPL_PARTITION_DATA || event_type == EV_NONHOST_CODE
        || event_type == EV_NONHOST_CONFIG || event_type == EV_EFI_RUNTIME_SERVICES_DRIVER ||
        /* Verification not possible. (TODO check) */
        event_type == EV_EFI_HCRTM_EVENT || event_type == EV_EFI_VARIABLE_BOOT) {
        *verify = false;
    } else if (
        /* Verification is possible. (TODO check) */
        event_type == EV_EVENT_TAG || event_type == EV_TABLE_OF_DEVICES
        || event_type == EV_NONHOST_INFO || event_type == EV_OMIT_BOOT_DEVICE_EVENTS
        || event_type == EV_EFI_VARIABLE_AUTHORITY ||
        /* Verification is possible. */
        event_type == EV_S_CRTM_VERSION || event_type == EV_SEPARATOR
        || event_type == EV_EFI_VARIABLE_DRIVER_CONFIG || event_type == EV_EFI_GPT_EVENT
        || event_type == EV_PLATFORM_CONFIG_FLAGS || event_type == EV_EFI_ACTION) {
        *verify = true;
    } else {
        LOG_WARNING("Unknown Event: %x", event_type);
        *verify = false;
    }

    LOG_TRACE("true");
    return TSS2_RC_SUCCESS;

error:
    SAFE_FREE(out->data.buffer);
    return r;
}

bool
parse_eventlog(tpm2_eventlog_context *ctx, BYTE const *eventlog, size_t size) {

    TCG_EVENT_HEADER2 *next;
    TCG_EVENT         *event = (TCG_EVENT *)eventlog;
    bool               ret;

    if (size == 0) {
        return true;
    }
    if (size < sizeof(*event)) {
        LOG_ERROR("eventlog too small for first TCG_EVENT (%zu < %zu)", size, sizeof(*event));
        return false;
    }

    if (event->eventType == EV_NO_ACTION) {
        ret = specid_event(event, size, &next);
        if (!ret) {
            return false;
        }

        size -= (uintptr_t)next - (uintptr_t)eventlog;

        if (ctx->specid_cb) {
            ret = ctx->specid_cb(event, ctx->data);
            if (!ret) {
                return false;
            }
        }
        return foreach_event2(ctx, next, size);
    } else {
        /* No spec id header found. */
        return foreach_sha1_log_event(ctx, event, size);
    }
}
