/*
 * Copyright (c) 2020, Arm Limited. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <assert.h>
#include <string.h>

#include <common/debug.h>
#include <drivers/measured_boot/event_log.h>

#if LOG_LEVEL >= EVENT_LOG_LEVEL

/*
 * Print TCG_EfiSpecIDEventStruct
 *
 * @param[in/out] log_addr	Pointer to Event Log
 * @param[in/out] log_size	Pointer to Event Log size
 */
static void id_event_print(uint8_t **log_addr, size_t *log_size)
{
	unsigned int i;
	uint8_t info_size, *info_size_ptr;
	void *ptr = *log_addr;
	id_event_headers_t *event = (id_event_headers_t *)ptr;
	id_event_algorithm_size_t *alg_ptr;
	uint32_t event_size, number_of_algorithms;
	size_t digest_len;
#if ENABLE_ASSERTIONS
	const uint8_t *end_ptr = *log_addr + *log_size;
	bool valid = true;
#endif

	assert(*log_size >= sizeof(id_event_headers_t));

	/* The fields of the event log header are defined to be PCRIndex of 0,
	 * EventType of EV_NO_ACTION, Digest of 20 bytes of 0, and
	 * Event content defined as TCG_EfiSpecIDEventStruct.
	 */
	LOG_EVENT("TCG_EfiSpecIDEvent:\n");
	LOG_EVENT("  PCRIndex           : %u\n", event->header.pcr_index);
	assert(event->header.pcr_index == (uint32_t)PCR_0);

	LOG_EVENT("  EventType          : %u\n", event->header.event_type);
	assert(event->header.event_type == EV_NO_ACTION);

	LOG_EVENT("  Digest             :");
	for (i = 0U; i < sizeof(event->header.digest); ++i) {
		uint8_t val = event->header.digest[i];

		(void)printf(" %02x", val);
		if ((i & U(0xF)) == 0U) {
			(void)printf("\n");
			LOG_EVENT("\t\t      :");
		}
#if ENABLE_ASSERTIONS
		if (val != 0U) {
			valid = false;
		}
#endif
	}
	if ((i & U(0xF)) != 0U) {
		(void)printf("\n");
	}

	assert(valid);

	/* EventSize */
	event_size = event->header.event_size;
	LOG_EVENT("  EventSize          : %u\n", event_size);

	LOG_EVENT("  Signature          : %s\n",
			event->struct_header.signature);
	LOG_EVENT("  PlatformClass      : %u\n",
			event->struct_header.platform_class);
	LOG_EVENT("  SpecVersion        : %u.%u.%u\n",
			event->struct_header.spec_version_major,
			event->struct_header.spec_version_minor,
			event->struct_header.spec_errata);
	LOG_EVENT("  UintnSize          : %u\n",
			event->struct_header.uintn_size);

	/* NumberOfAlgorithms */
	number_of_algorithms = event->struct_header.number_of_algorithms;
	LOG_EVENT("  NumberOfAlgorithms : %u\n", number_of_algorithms);

	/* Address of DigestSizes[] */
	alg_ptr = event->struct_header.digest_size;

	/* Size of DigestSizes[] */
	digest_len = number_of_algorithms * sizeof(id_event_algorithm_size_t);
	assert(((uint8_t *)alg_ptr + digest_len) <= end_ptr);

	LOG_EVENT("  DigestSizes        :\n");
	for (i = 0U; i < number_of_algorithms; ++i) {
		LOG_EVENT("    #%u AlgorithmId   : SHA", i);
		uint16_t algorithm_id = alg_ptr[i].algorithm_id;

		switch (algorithm_id) {
		case TPM_ALG_SHA256:
			(void)printf("256\n");
			break;
		case TPM_ALG_SHA384:
			(void)printf("384\n");
			break;
		case TPM_ALG_SHA512:
			(void)printf("512\n");
			break;
		default:
			(void)printf("?\n");
			ERROR("Algorithm 0x%x not found\n", algorithm_id);
			assert(false);
		}

		LOG_EVENT("       DigestSize    : %u\n",
					alg_ptr[i].digest_size);
	}

	/* Address of VendorInfoSize */
	info_size_ptr = (uint8_t *)alg_ptr + digest_len;
	assert(info_size_ptr <= end_ptr);

	info_size = *info_size_ptr++;
	LOG_EVENT("  VendorInfoSize     : %u\n", info_size);

	/* Check VendorInfo end address */
	assert((info_size_ptr + info_size) <= end_ptr);

	/* Check EventSize */
	assert(event_size == (sizeof(id_event_struct_t) +
				digest_len + info_size));
	if (info_size != 0U) {
		LOG_EVENT("  VendorInfo         :");
		for (i = 0U; i < info_size; ++i) {
			(void)printf(" %02x", *info_size_ptr++);
		}
		(void)printf("\n");
	}

	*log_size -= (uintptr_t)info_size_ptr - (uintptr_t)*log_addr;
	*log_addr = info_size_ptr;
}

/*
 * Print TCG_PCR_EVENT2
 *
 * @param[in/out] log_addr	Pointer to Event Log
 * @param[in/out] log_size	Pointer to Event Log size
 */
static void event2_print(uint8_t **log_addr, size_t *log_size)
{
	uint32_t event_size, count;
	size_t sha_size, digests_size = 0U;
	void *ptr = *log_addr;
#if ENABLE_ASSERTIONS
	const uint8_t *end_ptr = *log_addr + *log_size;
#endif

	assert(*log_size >= sizeof(event2_header_t));

	LOG_EVENT("PCR_Event2:\n");
	LOG_EVENT("  PCRIndex           : %u\n",
			((event2_header_t *)ptr)->pcr_index);
	LOG_EVENT("  EventType          : %u\n",
			((event2_header_t *)ptr)->event_type);

	count = ((event2_header_t *)ptr)->digests.count;
	LOG_EVENT("  Digests Count      : %u\n", count);

	/* Address of TCG_PCR_EVENT2.Digests[] */
	ptr = (uint8_t *)ptr + sizeof(event2_header_t);
	assert(((uintptr_t)ptr <= (uintptr_t)end_ptr) && (count != 0U));

	for (unsigned int i = 0U; i < count; ++i) {
		/* Check AlgorithmId address */
		assert(((uint8_t *)ptr + offsetof(tpmt_ha, digest)) <= end_ptr);

		LOG_EVENT("    #%u AlgorithmId   : SHA", i);
		switch (((tpmt_ha *)ptr)->algorithm_id) {
		case TPM_ALG_SHA256:
			sha_size = SHA256_DIGEST_SIZE;
			(void)printf("256\n");
			break;
		case TPM_ALG_SHA384:
			sha_size = SHA384_DIGEST_SIZE;
			(void)printf("384\n");
			break;
		case TPM_ALG_SHA512:
			sha_size = SHA512_DIGEST_SIZE;
			(void)printf("512\n");
			break;
		default:
			(void)printf("?\n");
			ERROR("Algorithm 0x%x not found\n",
				((tpmt_ha *)ptr)->algorithm_id);
			panic();
		}

		/* End of Digest[] */
		ptr = (uint8_t *)ptr + offsetof(tpmt_ha, digest);
		assert(((uint8_t *)ptr + sha_size) <= end_ptr);

		/* Total size of all digests */
		digests_size += sha_size;

		LOG_EVENT("       Digest        :");
		for (unsigned int j = 0U; j < sha_size; ++j) {
			(void)printf(" %02x", *(uint8_t *)ptr++);
			if ((j & U(0xF)) == U(0xF)) {
				(void)printf("\n");
				if (j < (sha_size - 1U)) {
					LOG_EVENT("\t\t      :");
				}
			}
		}
	}

	/* TCG_PCR_EVENT2.EventSize */
	assert(((uint8_t *)ptr + offsetof(event2_data_t, event)) <= end_ptr);

	event_size = ((event2_data_t *)ptr)->event_size;
	LOG_EVENT("  EventSize          : %u\n", event_size);

	/* Address of TCG_PCR_EVENT2.Event[EventSize] */
	ptr = (uint8_t *)ptr + offsetof(event2_data_t, event);

	/* End of TCG_PCR_EVENT2.Event[EventSize] */
	assert(((uint8_t *)ptr + event_size) <= end_ptr);

	if ((event_size == sizeof(startup_locality_event_t)) &&
	     (strcmp((const char *)ptr, TCG_STARTUP_LOCALITY_SIGNATURE) == 0)) {
		LOG_EVENT("  Signature          : %s\n",
			((startup_locality_event_t *)ptr)->signature);
		LOG_EVENT("  StartupLocality    : %u\n",
			((startup_locality_event_t *)ptr)->startup_locality);
	} else {
		LOG_EVENT("  Event              : %s\n", (uint8_t *)ptr);
	}

	*log_size -= (uintptr_t)ptr + event_size - (uintptr_t)*log_addr;
	*log_addr = (uint8_t *)ptr + event_size;
}
#endif	/* LOG_LEVEL >= EVENT_LOG_LEVEL */

/*
 * Print Event Log
 *
 * @param[in]	log_addr	Pointer to Event Log
 * @param[in]	log_size	Event Log size
 */
void dump_event_log(uint8_t *log_addr, size_t log_size)
{
#if LOG_LEVEL >= EVENT_LOG_LEVEL
	assert(log_addr != NULL);

	/* Print TCG_EfiSpecIDEvent */
	id_event_print(&log_addr, &log_size);

	while (log_size != 0U) {
		event2_print(&log_addr, &log_size);
	}
#endif
}
