// SPDX-License-Identifier: BSD-2-Clause
/*
 * Copyright (c) 2015, Linaro Limited
 * All rights reserved.
 */

#include <tee_api.h>
#include <storage_benchmark.h>
#include <ta_storage_benchmark.h>
#include <tee_internal_api_extensions.h>

#define DEFAULT_CHUNK_SIZE (1 << 10)
#define DEFAULT_DATA_SIZE (1024)

#define SCRAMBLE(x) ((x & 0xff) ^ 0xaa)

#define ASSERT_PARAM_TYPE(pt_in, pt_expect) \
do { \
	if ((pt_in) != (pt_expect)) \
		return TEE_ERROR_BAD_PARAMETERS; \
} while (0)

static uint8_t filename[] = "BenchmarkTestFile";

static void fill_buffer(uint8_t *buf, size_t size)
{
	size_t i = 0;

	if (!buf)
		return;

	for (i = 0; i < size; i++)
		buf[i] = SCRAMBLE(i);
}

static TEE_Result verify_buffer(uint8_t *buf, size_t size)
{
	size_t i = 0;

	if (!buf)
		return TEE_ERROR_BAD_PARAMETERS;

	for (i = 0; i < size; i++) {
		uint8_t expect_data = SCRAMBLE(i);

		if (expect_data != buf[i]) {
			return TEE_ERROR_CORRUPT_OBJECT;
		}
	}

	return TEE_SUCCESS;
}

static inline uint32_t tee_time_to_ms(TEE_Time t)
{
	return t.seconds * 1000 + t.millis;
}

static inline uint32_t get_delta_time_in_ms(TEE_Time start, TEE_Time stop)
{
	return tee_time_to_ms(stop) - tee_time_to_ms(start);
}

static TEE_Result prepare_test_file(size_t data_size, uint8_t *chunk_buf,
				size_t chunk_size)
{
	size_t remain_bytes = data_size;
	TEE_Result res = TEE_SUCCESS;
	TEE_ObjectHandle object = TEE_HANDLE_NULL;

	res = TEE_CreatePersistentObject(TEE_STORAGE_PRIVATE,
			filename, sizeof(filename),
			TEE_DATA_FLAG_ACCESS_READ |
			TEE_DATA_FLAG_ACCESS_WRITE |
			TEE_DATA_FLAG_ACCESS_WRITE_META |
			TEE_DATA_FLAG_OVERWRITE,
			NULL, NULL, 0, &object);
	if (res != TEE_SUCCESS) {
		EMSG("Failed to create persistent object, res=%#"PRIx32, res);
		goto exit;
	}

	while (remain_bytes) {
		size_t write_size = 0;

		if (remain_bytes < chunk_size)
			write_size = remain_bytes;
		else
			write_size = chunk_size;
		res = TEE_WriteObjectData(object, chunk_buf, write_size);
		if (res != TEE_SUCCESS) {
			EMSG("Failed to write data, res=%#"PRIx32, res);
			goto exit_close_object;
		}
		remain_bytes -= write_size;
	}
exit_close_object:
	TEE_CloseObject(object);
exit:
	return res;
}

static TEE_Result test_write(TEE_ObjectHandle object, size_t data_size,
		uint8_t *chunk_buf, size_t chunk_size,
		uint32_t *spent_time_in_ms)
{
	TEE_Time start_time = { };
	TEE_Time stop_time = { };
	size_t remain_bytes = data_size;
	TEE_Result res = TEE_SUCCESS;

	TEE_GetSystemTime(&start_time);

	while (remain_bytes) {
		size_t write_size = 0;

		DMSG("Write data, remain bytes: %zu", remain_bytes);
		if (chunk_size > remain_bytes)
			write_size = remain_bytes;
		else
			write_size = chunk_size;
		res = TEE_WriteObjectData(object, chunk_buf, write_size);
		if (res != TEE_SUCCESS) {
			EMSG("Failed to write data, res=%#"PRIx32, res);
			goto exit;
		}
		remain_bytes -= write_size;
	}

	TEE_GetSystemTime(&stop_time);

	*spent_time_in_ms = get_delta_time_in_ms(start_time, stop_time);

	IMSG("start: %"PRIu32".%"PRIu32"(s), stop: %"PRIu32".%"PRIu32"(s), delta: %"PRIu32"(ms)",
	     start_time.seconds, start_time.millis,
	     stop_time.seconds, stop_time.millis, *spent_time_in_ms);

exit:
	return res;
}

static TEE_Result test_read(TEE_ObjectHandle object, size_t data_size,
		uint8_t *chunk_buf, size_t chunk_size,
		uint32_t *spent_time_in_ms)
{
	TEE_Time start_time = { };
	TEE_Time stop_time = { };
	size_t remain_bytes = data_size;
	TEE_Result res = TEE_SUCCESS;
	size_t read_bytes = 0;

	TEE_GetSystemTime(&start_time);

	while (remain_bytes) {
		size_t read_size = 0;

		DMSG("Read data, remain bytes: %zu", remain_bytes);
		if (remain_bytes < chunk_size)
			read_size = remain_bytes;
		else
			read_size = chunk_size;
		res = TEE_ReadObjectData(object, chunk_buf, read_size,
				&read_bytes);
		if (res != TEE_SUCCESS) {
			EMSG("Failed to read data, res=%#"PRIx32, res);
			goto exit;
		}

		remain_bytes -= read_size;
	}

	TEE_GetSystemTime(&stop_time);

	*spent_time_in_ms = get_delta_time_in_ms(start_time, stop_time);

	IMSG("start: %"PRIu32".%"PRIu32"(s), stop: %"PRIu32".%"PRIu32"(s), delta: %"PRIu32"(ms)",
	     start_time.seconds, start_time.millis,
	     stop_time.seconds, stop_time.millis, *spent_time_in_ms);

exit:
	return res;
}

static TEE_Result test_rewrite(TEE_ObjectHandle object, size_t data_size,
		uint8_t *chunk_buf, size_t chunk_size,
		uint32_t *spent_time_in_ms)
{
	TEE_Time start_time = { };
	TEE_Time stop_time = { };
	size_t remain_bytes = data_size;
	TEE_Result res = TEE_SUCCESS;
	size_t read_bytes = 0;

	TEE_GetSystemTime(&start_time);

	while (remain_bytes) {
		size_t write_size = 0;
		int32_t negative_chunk_size = 0;

		if (remain_bytes < chunk_size)
			write_size = remain_bytes;
		else
			write_size = chunk_size;
		negative_chunk_size = -(int32_t)write_size;

		/* Read a chunk */
		res = TEE_ReadObjectData(object, chunk_buf, write_size,
				&read_bytes);
		if (res != TEE_SUCCESS) {
			EMSG("Failed to read data, res=%#"PRIx32, res);
			goto exit;
		}

		if (read_bytes != write_size) {
			EMSG("Partial data read, bytes=%zu", read_bytes);
			res = TEE_ERROR_CORRUPT_OBJECT;
			goto exit;
		}

		/* Seek to the position before read */
		res = TEE_SeekObjectData(object, negative_chunk_size,
				TEE_DATA_SEEK_CUR);
		if (res != TEE_SUCCESS) {
			EMSG("Failed to seek to previous offset");
			goto exit;
		}

		/* Write a chunk*/
		res = TEE_WriteObjectData(object, chunk_buf, write_size);
		if (res != TEE_SUCCESS) {
			EMSG("Failed to write data, res=%#"PRIx32, res);
			goto exit;
		}

		remain_bytes -= write_size;
	}

	TEE_GetSystemTime(&stop_time);

	*spent_time_in_ms = get_delta_time_in_ms(start_time, stop_time);

	IMSG("start: %"PRIu32".%"PRIu32"(s), stop: %"PRIu32".%"PRIu32"(s), delta: %"PRIu32"(ms)",
	     start_time.seconds, start_time.millis,
	     stop_time.seconds, stop_time.millis, *spent_time_in_ms);

exit:
	return res;
}

static TEE_Result verify_file_data(TEE_ObjectHandle object, size_t data_size,
		uint8_t *chunk_buf, size_t chunk_size)
{
	TEE_Result res = TEE_ERROR_GENERIC;
	size_t tmp_data_size = data_size;

	res = TEE_SeekObjectData(object, 0, TEE_DATA_SEEK_SET);
	if (res != TEE_SUCCESS) {
		EMSG("Failed to seek to offset 0");
		goto exit;
	}

	TEE_MemFill(chunk_buf, 0, chunk_size);

	tmp_data_size = data_size;
	while (tmp_data_size > 0) {
		size_t read_bytes = 0;

		res = TEE_ReadObjectData(object, chunk_buf, chunk_size,
				&read_bytes);
		if (res != TEE_SUCCESS) {
			EMSG("Failed to read data, res=%#"PRIx32, res);
			goto exit;
		}

		if (read_bytes != chunk_size) {
			EMSG("Data size not match");
			res = TEE_ERROR_CORRUPT_OBJECT;
			goto exit;
		}

		res = verify_buffer(chunk_buf, chunk_size);
		if (res != TEE_SUCCESS) {
			EMSG("Verify data failed, res=%#"PRIx32, res);
			goto exit;
		}

		tmp_data_size -= chunk_size;
	}

exit:
	return res;
}

static TEE_Result ta_stroage_benchmark_chunk_access_test(uint32_t nCommandID,
		uint32_t param_types, TEE_Param params[4])
{
	TEE_Result res = TEE_ERROR_GENERIC;
	size_t data_size = 0;
	size_t chunk_size = 0;
	TEE_ObjectHandle object = TEE_HANDLE_NULL;
	uint8_t *chunk_buf = NULL;
	uint32_t *spent_time_in_ms = &params[2].value.a;
	bool do_verify = false;

	ASSERT_PARAM_TYPE(param_types, TEE_PARAM_TYPES(
					TEE_PARAM_TYPE_VALUE_INPUT,
					TEE_PARAM_TYPE_VALUE_INPUT,
					TEE_PARAM_TYPE_VALUE_OUTPUT,
					TEE_PARAM_TYPE_NONE));

	data_size = params[0].value.a;
	chunk_size = params[0].value.b;
	do_verify = params[1].value.a;

	if (data_size == 0)
		data_size = DEFAULT_DATA_SIZE;

	if (chunk_size == 0)
		chunk_size = DEFAULT_CHUNK_SIZE;

	IMSG("command id: %u, test data size: %zd, chunk size: %zd",
	     nCommandID, data_size, chunk_size);

	chunk_buf = TEE_Malloc(chunk_size, TEE_MALLOC_FILL_ZERO);
	if (!chunk_buf) {
		EMSG("Failed to allocate memory");
		res = TEE_ERROR_OUT_OF_MEMORY;
		goto exit;
	}

	fill_buffer(chunk_buf, chunk_size);
	res = prepare_test_file(data_size, chunk_buf, chunk_size);
	if (res != TEE_SUCCESS) {
		EMSG("Failed to create test file, res=%#"PRIx32, res);
		goto exit_free_chunk_buf;
	}

	res = TEE_OpenPersistentObject(TEE_STORAGE_PRIVATE,
			filename, sizeof(filename),
			TEE_DATA_FLAG_ACCESS_READ |
			TEE_DATA_FLAG_ACCESS_WRITE |
			TEE_DATA_FLAG_ACCESS_WRITE_META,
			&object);
	if (res != TEE_SUCCESS) {
		EMSG("Failed to open persistent object, res=%#"PRIx32, res);
		goto exit_remove_object;
	}

	switch (nCommandID) {
	case TA_STORAGE_BENCHMARK_CMD_TEST_READ:
		res = test_read(object, data_size, chunk_buf,
				chunk_size, spent_time_in_ms);
		break;

	case TA_STORAGE_BENCHMARK_CMD_TEST_WRITE:
		res = test_write(object, data_size, chunk_buf,
				chunk_size, spent_time_in_ms);
		break;

	case TA_STORAGE_BENCHMARK_CMD_TEST_REWRITE:
		res = test_rewrite(object, data_size, chunk_buf,
				chunk_size, spent_time_in_ms);
		break;

	default:
		res = TEE_ERROR_BAD_PARAMETERS;
	}

	if (res != TEE_SUCCESS)
		goto exit_remove_object;

	if (do_verify)
		res = verify_file_data(object, data_size,
				chunk_buf, chunk_size);


exit_remove_object:
	TEE_CloseAndDeletePersistentObject1(object);
exit_free_chunk_buf:
	TEE_Free(chunk_buf);
exit:

	return res;
}

TEE_Result ta_storage_benchmark_cmd_handler(uint32_t nCommandID,
		uint32_t param_types, TEE_Param params[4])
{
	TEE_Result res = TEE_ERROR_GENERIC;

	switch (nCommandID) {
	case TA_STORAGE_BENCHMARK_CMD_TEST_READ:
	case TA_STORAGE_BENCHMARK_CMD_TEST_WRITE:
	case TA_STORAGE_BENCHMARK_CMD_TEST_REWRITE:
		res = ta_stroage_benchmark_chunk_access_test(nCommandID,
				param_types, params);
		break;

	default:
		res = TEE_ERROR_BAD_PARAMETERS;
	}

	return res;
}

