/**
* \file CryptoPlatform.c
* \brief Platform independent high level crypto functions.
* \author Nikolay Oleschuk (n.oleschuk@samsung.com)
* \version 0.1
* \date Created March 03, 2014
* \par In Samsung Ukraine R&D Center (SRK) under a contract between
* \par LLC "Samsung Electronics Ukraine Company" (Kharkiv, Ukraine) and
* \par "Samsung Elecrtronics Co", Ltd (Seoul, Republic of Korea)
* \par Copyright: (c) Samsung Electronics Co, Ltd 2014. All rights reserved.
**/

#include <string.h>

#include "QSEEComAPI.h"
#include "CommLayerData.h"
#include "log.h"

#include "TAStartupCode.h"
#include "TLV.h"

#ifdef USE_NEW_POS_PATH
#define TZFILELOCATION "/vendor/firmware_mnt/image"
#else
#define TZFILELOCATION "/firmware/image"
#endif

#define TAFILENAME "mldap"

static struct QSEECom_handle *km_QSEEComHandle = NULL;

/**
 * @return int32_t NO_ERROR on success or PLATFORM_INTERNAL_ERROR on error
 */
int32_t kmLoadTA(void)
{
	int32_t ret = 0;

	if (km_QSEEComHandle != NULL) {
		return NO_ERROR;
	}

	ret = QSEECom_start_app(&km_QSEEComHandle, TZFILELOCATION, TAFILENAME, QSEECOM_ALIGN(sizeof(cmd_req_t) + sizeof(cmd_rsp_t)));
	if (ret) {
		LOGE("Failed to start trustlet");
		ret = PLATFORM_INTERNAL_ERROR;
	}

	return ret;
}

int32_t kmUnloadTA(void)
{
	int32_t ret = 0;

	/* shutdown the application */
	if (km_QSEEComHandle != NULL) {
		ret = QSEECom_shutdown_app(&km_QSEEComHandle);
		/* when trustlet failed to shutdown the next attempts to load new
		* trustlet will fail */
		km_QSEEComHandle = NULL;

		if (ret) {
			LOGE("Shutdown app failed with ret = %d", ret);
			ret = PLATFORM_INTERNAL_ERROR;
		}
	} else {
		LOGE("cannot shutdown as the handle is NULL");
		ret = WRONG_DATA;
	}

	return ret;
}

int32_t kmGetTid(uint8_t* tid, uint32_t* tidLen)
{
	if (!tid || !tidLen) {
		LOGE("kmGetTid: Some argument is NULL");
		return WRONG_DATA;
	}

	if (*tidLen < sizeof(TAFILENAME)) {
		LOGE("kmGetTid: Too small out buffer - %d bytes, needs at least %d", *tidLen, sizeof(TAFILENAME));
		*tidLen = sizeof(TAFILENAME);
		return WRONG_DATA;
	}

	memcpy(tid, TAFILENAME, sizeof(TAFILENAME));
	*tidLen = sizeof(TAFILENAME);

	return NO_ERROR;
}

int32_t kmSendCmdInOut(uint32_t cmdId, uint8_t* inData, uint32_t inLen, uint8_t* outData, uint32_t* outLen)
{
	int res = PLATFORM_INTERNAL_ERROR;
	cmd_req_t* request_ptr = NULL;
	cmd_rsp_t* response_ptr = NULL;

	if (!km_QSEEComHandle || !km_QSEEComHandle->ion_sbuffer) {
		LOGE("Trustlet's handler is NULL (trustlet isn't loaded?)");
		return PLATFORM_INTERNAL_ERROR;
	}

	request_ptr = (cmd_req_t*)km_QSEEComHandle->ion_sbuffer;
	response_ptr = (cmd_rsp_t*)(km_QSEEComHandle->ion_sbuffer + sizeof(cmd_req_t));
	request_ptr->cmd_id = cmdId;
	request_ptr->dataLen = inLen;
	LOGI("kmSendCmd: cmdId 0x%x, sending %d bytes", cmdId, inLen);

	if (inLen > MAX_TRANSFER_SIZE) {
		LOGE("Error while sending command: data too large. Command ID: %d", cmdId);
		return WRONG_DATA;
	}

	if (NULL != inData) {
		memcpy(request_ptr->data, inData, inLen);
	}
    
	request_ptr->mldap_flag = 1;
	/* prevent linux kernel panic */
	res = QSEECom_set_bandwidth(km_QSEEComHandle, true);
	if (res < 0) {
		LOGE("QSEE error, Set bandwidth failed. QSEE error code: %d", res);
		return PLATFORM_INTERNAL_ERROR;
	}

	res = QSEECom_send_cmd(km_QSEEComHandle, request_ptr, sizeof(cmd_req_t), response_ptr, sizeof(cmd_rsp_t));

	request_ptr->mldap_flag = 0;
	if (res < 0) {
		LOGE("QSEE error, Send command ID: %d failed. QSEE error code: %d", cmdId, res);
		QSEECom_set_bandwidth(km_QSEEComHandle, false);
		return PLATFORM_INTERNAL_ERROR;
	}

	res = QSEECom_set_bandwidth(km_QSEEComHandle, false);
	if (res < 0) {
		LOGE("QSEE error, Set bandwidth failed. QSEE error code: %d", res);
		return PLATFORM_INTERNAL_ERROR;
	}

	if (response_ptr->status != NO_ERROR) {
		LOGE("Application TA reported error code: %d", response_ptr->status);
		return response_ptr->status;
	}

	LOGI("kmSendCmd: received %d bytes", response_ptr->dataLen);
	if (outData && outLen) {
		if (response_ptr->dataLen > *outLen) {
			LOGE("kmSendCmd: not enough room for outData");
			return WRONG_DATA;
		}

		*outLen = response_ptr->dataLen;
		memcpy(outData, response_ptr->data, *outLen);
	}

	return res;
}
