/*
@file app_grdm.c
@brief Contains test code for most of the QSEE fuse APIs.

*/
/*===========================================================================
   Copyright (c) 2011 by Qualcomm Technologies, Incorporated.  All Rights Reserved.
===========================================================================*/

/*===========================================================================

                            EDIT HISTORY FOR FILE
  $Header:
  $DateTime:
  $Author: pwbldsvc $

# when       who     what, where, why
# 2019-03-15 sh4615.lee first implement

===========================================================================*/

#include <stdbool.h>
//#include "qsee_log.h"

#include <app_grdm.h>
#include <app_grdm_rpmb.h>

#include <ese_api.h>
#include <grdm_bl.h>
#include <grdm_internal.h>
#include <star_api.h>
#include <star_apdu.h>

#include <grdm_common.h>
#include <grdm_crypto.h>
#include <grdm_log.h>
#include <grdm_transport.h>

#include <qsee_hash.h>
#include <qsee_kdf.h>
#include <qsee_sync.h>

#include <sha.h>

#include <qsee_timer.h>

#define ESE_UDELAY				qsee_spin

//#define CHECK_DATA

void grdm_wipe_key(void *p, size_t len)
{
	volatile uint8_t *_p = p;
	while (len--) *_p++ = 0;
}

uint64_t tz_get_timestamp(void)
{
	uint64_t time = 0;
	static uint64_t prev = 0;
	int i;

	/* qsee_get_uptime returns ms */
	for (i = 0 ; i < 10 ;i++)
	{
		time = qsee_get_uptime();
		if (time >= prev  && time != 0) break;
		LOG_E("qsee_get_uptime returned invalid value: prev(0x%x) current(0x%x)", prev, time);
	}

	if(i == 10) {
		LOG_E("tz_get_timestamp error prev(0x%x) current(0x%x)", prev, time);
	}
	else {
		prev = time;
	}

	return time;
}


static void get_hostid(uint8_t* hostid, uint32_t hostid_len, uint32_t serial_no)
{
	int i;

	if (hostid == NULL) {
		LOG_E("hostid is null");
		return;
	}

	for(i=0; i<hostid_len;i++) {
		hostid[i] = (uint8_t)((serial_no>>((i%4)*8))&0xff);
	}
}

GRDM_RESULT grdm_auth_key_init(grdm_session_info_t *bl_secure_info, uint8_t* auth_key)
{
	static char auth_key_context[] = {"GRDM BL Auth key derived from SHK on bksecapp"};
	uint8_t salt[SALT_SIZE];
	uint8_t digest[SHA256_DIGEST_LENGTH] = {0x0,};
	uint8_t chipid[SALT_SIZE-SHA256_DIGEST_LENGTH] = {0x0,};
	uint32_t chipid_len = SALT_SIZE-SHA256_DIGEST_LENGTH; //256-32
	uint8_t hostid[16] = {0x0,};
	uint8_t stored_hostid[32] = {0x0,};
	uint32_t hostid_len = 16;

	uint8_t BL_keyflag = 0;
	GRDM_RESULT grdm_ret = GRDM_NO_ERROR;
	int ret;

	grdm_ret = grdm_BL_putkey_init (chipid, &chipid_len, stored_hostid, &hostid_len, &BL_keyflag);
	if (grdm_ret != GRDM_NO_ERROR) {
		LOG_E("grdm_BL_putkey_init error : %d", grdm_ret);
		return grdm_ret;
	}
	if (chipid_len == 0) {
		LOG_E("chipid_len is zero!");
	}
#if 0
	else {
		LOG_E("chipid[%d] : 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x", chipid_len,\
			chipid[0], chipid[1], chipid[2], chipid[3], chipid[4], chipid[5], chipid[6], chipid[7]);
	}
#endif
	if(hostid_len == 0) {
		LOG_E("hostid_len is zero!");
	}

	if (chipid_len > SALT_SIZE-SHA256_DIGEST_LENGTH) {
		chipid_len = SALT_SIZE-SHA256_DIGEST_LENGTH;
	}

	// auth_key
	memset(salt, 0xff, 256);
	memcpy(salt, bl_secure_info->devInfoBuff, SHA256_DIGEST_LENGTH);
	memcpy(salt+SHA256_DIGEST_LENGTH, chipid, chipid_len);

	ret = qsee_hash(QSEE_HASH_SHA256, salt, 256, digest, SHA256_DIGEST_LENGTH);
	ret = qsee_kdf(NULL, 32, digest, SHA256_DIGEST_LENGTH,
					auth_key_context, strlen(auth_key_context), auth_key, AUTH_KEY_SIZE);

	if (BL_keyflag !=0 && bl_secure_info->magic != GRDM_RPMB_MAGIC) {
		LOG_E("grdm_BL_putkey Magic error : 0x%08x", bl_secure_info->magic);
		return GRDM_BL_MAGIC_FAILURE;
	}

	LOG_E("BL_keyflag is %d", BL_keyflag);
	// When Key is re-writed?
	// 1. BL_keyflag is 0 (key is not writed.)
	// 2. BL is not fused.

	if (BL_keyflag == 0 || bl_secure_info->is_fused == 0) {
		hostid_len = sizeof(hostid);
		get_hostid(hostid, hostid_len, bl_secure_info->ap_serial_0);
		grdm_ret = grdm_BL_putkey(auth_key, AUTH_KEY_SIZE, hostid, hostid_len);
		if (grdm_ret != 0) {
			LOG_E("grdm_BL_putkey error : %d", grdm_ret);
		}
	}

	return grdm_ret;
}

GRDM_RESULT grdm_fuse(uint32_t seed_id)
{
	GRDM_RESULT grdm_ret = GRDM_NO_ERROR;
	GRDM_RESULT ret = GRDM_NO_ERROR;

	uint64_t startTime = 0;
	uint64_t fuseTime = 0;

	LOG_E("grdm_fuse");

	startTime = tz_get_timestamp();
	grdm_ret = grdm_openLogicalChannel();
	if (grdm_ret != GRDM_NO_ERROR) {
		LOG_E("grdm_openLogicalChannel failed : %d", grdm_ret);
		return grdm_ret;
	}

	grdm_ret = grdm_BL_fuse();
	if (grdm_ret != GRDM_NO_ERROR && grdm_ret != GRDM_ALREADY_FUSED) {
		LOG_E("grdm_ret failed : %d", grdm_ret);
	}
	if ((grdm_ret == GRDM_NO_ERROR || grdm_ret == GRDM_ALREADY_FUSED)
	  && seed_id != 0) {
		grdm_set_fuse_flag(seed_id, 1);
	}

	ret = grdm_closeLogicalChannel();
	if (ret != GRDM_NO_ERROR) {
		LOG_E("grdm_closeLogicalChannel failed : %d", ret);
	}
	fuseTime =  tz_get_timestamp()-startTime;

	LOG_E("[time check] grdm_fuse time : %d ms", fuseTime);

  return grdm_ret;
}

#ifdef GRDM_RESET
GRDM_RESULT grdm_reset(uint32_t seed_id)
{
	GRDM_RESULT grdm_ret = GRDM_NO_ERROR;
	uint8_t apdu[4] = {0x80, 0xE8, 0x00, 0xFF};
	secGrdm_7816_rpdu_t rpdu;
	uint32_t version = 0;

	LOG_E("grdm_reset");

	// check MW version : star_getMWversion
	star_getMWversion(&version);
	if (version != 0x00000404) {
		LOG_E("grdm_reset is not supported in this version(0x%x)", version);
		return GRDM_NO_ERROR;
	}

	grdm_ret = grdm_openLogicalChannel();
	if (grdm_ret != GRDM_NO_ERROR) {
		LOG_E("grdm_openLogicalChannel failed : %d", grdm_ret);
		return grdm_ret;
	}

	memset(&rpdu, 0x0, sizeof(secGrdm_7816_rpdu_t));
	grdm_ret = secGrdmAPDUTransmit(apdu, 4, &rpdu);

	LOG_E("rpdu : sw1 : 0x%x", rpdu.sw1);
	LOG_E("rpdu : sw2 : 0x%x", rpdu.sw2);
	LOG_E("rpdu len : %d", rpdu.len);

	if (grdm_closeLogicalChannel() != GRDM_NO_ERROR) {
		LOG_E("grdm_closeLogicalChannel failed : %d", grdm_ret);
	}

	if (grdm_ret == GRDM_NO_ERROR && seed_id != 0) {
		grdm_set_fuse_flag(seed_id, 0);
	}
	return grdm_ret;
}
#endif

GRDM_RESULT grdm_write_bl_rot_data(grdm_session_info_t *grdm_session_info,  grdm_bl_rot_data_t *bl_rot_data)
{
	GRDM_RESULT grdm_ret = GRDM_NO_ERROR;
	GRDM_RESULT ret = GRDM_NO_ERROR;
	uint8_t auth_key[AUTH_KEY_SIZE] = {0x0,};
	int i;

	grdm_rot_status_t stored_rot_cred_buffer;
	grdm_bl_status_t stored_bl_cred_buffer;
	uint32_t rot_cred_size = sizeof(grdm_rot_status_t);
	uint32_t bl_cred_size = sizeof(grdm_bl_status_t);

	uint64_t startTime = 0;
	uint64_t openChannelTime = 0;
	uint64_t authKeyInitTime = 0;
	uint64_t setROTTime = 0;
	uint64_t getROTTime = 0;
	uint64_t setStatusTime = 0;
	uint64_t getStatusTime = 0;
	uint64_t closeChannelTime = 0;

	LOG_E("grdm_write_bl_rot_data");
	startTime = tz_get_timestamp();
	LOG_E("grdm_openLogicalChannel startTime : %d", startTime);
	grdm_ret = grdm_openLogicalChannel();
	if (grdm_ret != GRDM_NO_ERROR) {
		LOG_E("grdm_openLogicalChannel failed : %d", grdm_ret);
		return grdm_ret;
	}
	openChannelTime =  tz_get_timestamp() - startTime;

	startTime = tz_get_timestamp();
	LOG_E("grdm_auth_key_init startTime : %d", startTime);
	grdm_ret = grdm_auth_key_init(grdm_session_info, auth_key);
	if (grdm_ret != GRDM_NO_ERROR && grdm_ret != GRDM_ALREADY_INJECTED) {
		LOG_E("grdm_auth_key_init failed : %d", grdm_ret);
		goto exit;
	}
	authKeyInitTime =  tz_get_timestamp()-startTime;

	startTime = tz_get_timestamp();
	LOG_E("grdm_BL_storeCredential(ROT) startTime : %d", startTime);
	grdm_ret = grdm_BL_storeCredential(auth_key, AUTH_KEY_SIZE, BL_CREDENTIAL_ROT, (uint8_t*)&(bl_rot_data->grdm_secure_info_rot), sizeof(grdm_rot_status_t));
	if (grdm_ret != GRDM_NO_ERROR) {
		LOG_E("grdm_BL_storeCredential failed : %d", grdm_ret);
		goto exit;
	}
	setROTTime =  tz_get_timestamp()-startTime;
#ifdef CHECK_DATA
	startTime = tz_get_timestamp();
	LOG_E("grdm_BL_getCredential(ROT) startTime : %d", startTime);
	grdm_ret = grdm_BL_getCredential(auth_key, AUTH_KEY_SIZE, BL_CREDENTIAL_ROT, (uint8_t*)&stored_rot_cred_buffer, &rot_cred_size);
	if (grdm_ret != GRDM_NO_ERROR) {
		LOG_E("grdm_BL_getCredential failed : %d", grdm_ret);
		goto exit;
	}
	getROTTime =  tz_get_timestamp()-startTime;


	if (rot_cred_size != sizeof(grdm_rot_status_t)
	|| (0 != memcmp(&(bl_rot_data->grdm_secure_info_rot), &stored_rot_cred_buffer, sizeof(grdm_rot_status_t)))) {
		LOG_E("Data is different");

		grdm_ret = GRDM_INVALID_DATA;
		goto exit;
	}
	else {
		LOG_E("Succeed to Get/Store Credential : %d", grdm_ret);
	}
#endif
//	ESE_UDELAY(20*1000);
	startTime = tz_get_timestamp();
	LOG_E("grdm_BL_storeCredential(BL) startTime : %d", startTime);
	grdm_ret = grdm_BL_storeCredential(auth_key, AUTH_KEY_SIZE, BL_CREDENTIAL_STATUS, (uint8_t*)&(bl_rot_data->grdm_secure_info_bl), sizeof(grdm_bl_status_t));
	if (grdm_ret != GRDM_NO_ERROR) {
		LOG_E("grdm_BL_storeCredential failed : %d", grdm_ret);
		goto exit;
	}
	setStatusTime =  tz_get_timestamp()-startTime;

#ifdef CHECK_DATA
	startTime = tz_get_timestamp();
	LOG_E("grdm_BL_getCredential(BL) startTime : %d", startTime);
	grdm_ret = grdm_BL_getCredential(auth_key, AUTH_KEY_SIZE, BL_CREDENTIAL_STATUS, (uint8_t*)&stored_bl_cred_buffer, &bl_cred_size);
	if (grdm_ret != GRDM_NO_ERROR) {
		LOG_E("grdm_BL_getCredential failed : %d", grdm_ret);
		goto exit;
	}
	getStatusTime =  tz_get_timestamp()-startTime;


	if (bl_cred_size != sizeof(grdm_bl_status_t)
	|| (0 != memcmp(&(bl_rot_data->grdm_secure_info_bl), &stored_bl_cred_buffer, sizeof(grdm_bl_status_t)))) {
		LOG_E("Data is different");
		grdm_ret = GRDM_INVALID_DATA;
		goto exit;
	}
	else {
		LOG_E("Succeed to Get/Store Credential : %d", grdm_ret);
	}
#endif
exit:
	grdm_wipe_key(auth_key, AUTH_KEY_SIZE);

	startTime = tz_get_timestamp();
	ret = grdm_closeLogicalChannel();
	if (ret != GRDM_NO_ERROR) {
		LOG_E("grdm_closeLogicalChannel failed : %d", ret);
	}
	closeChannelTime =  tz_get_timestamp()-startTime;


	LOG_E("[time check] grdm_openLogicalChannel time : %d ms", openChannelTime);
	LOG_E("[time check] grdm_auth_key_init time : %d ms", authKeyInitTime);
	LOG_E("[time check] grdm_BL_storeCredential BL_CREDENTIAL_ROT time : %d ms", setROTTime);
	LOG_E("[time check] grdm_BL_getCredential BL_CREDENTIAL_ROT time : %d ms", getROTTime);
	LOG_E("[time check] grdm_BL_storeCredential BL_CREDENTIAL_STATUS time : %d ms", setStatusTime);
	LOG_E("[time check] grdm_BL_getCredential BL_CREDENTIAL_STATUS time : %d ms", getStatusTime);
	LOG_E("[time check] grdm_closeLogicalChannel time : %d ms", closeChannelTime);

	return grdm_ret;
}

#if 0
GRDM_RESULT grdm_factoryreset()
{
	GRDM_RESULT grdm_ret = GRDM_NO_ERROR;
	uint8_t apdu[4] = {0x80, 0x7f, 0x00, 0x00};
	p_secGrdm_7816_rpdu_t rpdu;

	LOG_E("grdm_factoryreset");
	grdm_ret = grdm_openLogicalChannel();
	if (grdm_ret != GRDM_NO_ERROR) {
		LOG_E("grdm_openLogicalChannel failed : %d", grdm_ret);
		return grdm_ret;
	}

	rpdu = (p_secGrdm_7816_rpdu_t)MALLOC(sizeof(secGrdm_7816_rpdu_t));
	if (rpdu == NULL) {
		return GRDM_INVALID_DATA;
	}

	memset(rpdu, 0x0, sizeof(secGrdm_7816_rpdu_t));
	grdm_ret = secGrdmAPDUTransmit(apdu, 4, rpdu);

	FREE(rpdu);

	if (grdm_closeLogicalChannel() != GRDM_NO_ERROR) {
		LOG_E("grdm_closeLogicalChannel failed : %d", grdm_ret);
	}

	return grdm_ret;
}
#endif

GRDM_RESULT grdm_get_data(uint32_t seed_id, grdm_rpmb_data_t* resp_data)
{
	GRDM_RESULT grdm_ret = GRDM_NO_ERROR;
	grdm_rpmb_data_t rpmb_data;

	grdm_ret = grdm_rpmb_init(seed_id);
	if (GRDM_BL_RPMB_NO_ERROR != grdm_ret) {
		LOG_E("grdm_get_data : RPMB initialization failure");
		goto exit;
	}

	grdm_ret = grdm_rpmb_read(0, &rpmb_data);
	if (grdm_ret != GRDM_BL_RPMB_NO_ERROR) {
		LOG_E("grdm_get_data: rpmb read failed 0x%x", grdm_ret);
		goto exit;
	}

	grdm_ret = grdm_check_magic(rpmb_data);
	if (grdm_ret != GRDM_BL_RPMB_NO_ERROR) {
		LOG_E("grdm_get_data: magic is not matched 0x%x", grdm_ret);
		goto exit;
	}

	memcpy(resp_data, &rpmb_data, sizeof(grdm_rpmb_data_t));

exit:
	return grdm_ret;
}

#ifndef SUPPORT_SET_RPMB_SEED
GRDM_RESULT grdm_set_data(uint32_t seed_id)
#else
GRDM_RESULT grdm_set_data(uint32_t seed_id, uint32_t value)
#endif
{
	GRDM_RESULT grdm_ret = GRDM_NO_ERROR;
	grdm_rpmb_data_t rpmb_data;
	uint8_t rand_buff[4] = {0x0,};

	grdm_ret = grdm_rpmb_init(seed_id);
	if (GRDM_BL_RPMB_NO_ERROR != grdm_ret) {
		LOG_E("grdm_set_data : RPMB initialization failure");
		goto exit;
	}

	grdm_ret = grdm_rpmb_read(0, &rpmb_data);
	if (grdm_ret != GRDM_BL_RPMB_NO_ERROR) {
		LOG_E("grdm_set_data: rpmb read failed 0x%x", grdm_ret);
		goto exit;
	}
#ifdef SUPPORT_SET_RPMB_SEED
	if (value == 0x0 || value == 0x01010101) {
		memcpy(rand_buff, &value, 4);
	}
	else {
#endif
	grdm_ret = grdm_check_magic(rpmb_data);
	if (grdm_ret == GRDM_BL_RPMB_NO_ERROR) {
		LOG_E("grdm_set_data: no need to set seed");
		grdm_ret = GRDM_BL_RPMB_ALREADY_INITIALIZED;
		goto exit;
	}
	grdm_getSWRandom(rand_buff, 4);
#ifdef SUPPORT_SET_RPMB_SEED
	}
#endif
	rpmb_data.magic = GRDM_RPMB_MAGIC;
	memcpy(&rpmb_data.seed_value, rand_buff, 4);
	LOG_E("grdm_set_data: seed_value(0x%x) is generated", rpmb_data.seed_value);

	grdm_ret = grdm_rpmb_write(0, &rpmb_data);
	if (grdm_ret != GRDM_BL_RPMB_NO_ERROR) {
		LOG_E("grdm_set_data: rpmb read failed 0x%x", grdm_ret);
		goto exit;
	}

exit:
	return grdm_ret;
}

GRDM_RESULT grdm_set_fuse_flag(uint32_t seed_id, uint32_t flag) {
	GRDM_RESULT grdm_ret = GRDM_NO_ERROR;
	grdm_rpmb_data_t rpmb_data;
	uint8_t rand_buff[4] = {0x0,};
	grdm_fw_info_t fw_info;

	grdm_ret = grdm_rpmb_init(seed_id);
	if (GRDM_BL_RPMB_NO_ERROR != grdm_ret) {
		LOG_E("grdm_set_data : RPMB initialization failure");
		goto exit;
	}

	grdm_ret = grdm_rpmb_read(0, &rpmb_data);
	if (grdm_ret != GRDM_BL_RPMB_NO_ERROR) {
		LOG_E("grdm_set_data: rpmb read failed 0x%x", grdm_ret);
		goto exit;
	}

	rpmb_data.is_fused = flag;
	grdm_get_fw_version(&fw_info);
	memcpy(rpmb_data.fw_version, fw_info.fw_version, 8);

	grdm_ret = grdm_rpmb_write(0, &rpmb_data);
	if (grdm_ret != GRDM_BL_RPMB_NO_ERROR) {
		LOG_E("grdm_set_data: rpmb read failed 0x%x", grdm_ret);
		goto exit;
	}

exit:
	return grdm_ret;
}

GRDM_RESULT grdm_set_fw_version(uint8_t *fw_ver) {
	GRDM_RESULT grdm_ret = GRDM_NO_ERROR;
	grdm_rpmb_data_t rpmb_data;
	uint8_t rand_buff[4] = {0x0,};

	grdm_ret = grdm_rpmb_init(GRDM_RPMB_PARITION_ID);
	if (GRDM_BL_RPMB_NO_ERROR != grdm_ret) {
		LOG_E("grdm_set_data : RPMB initialization failure");
		goto exit;
	}

	grdm_ret = grdm_rpmb_read(0, &rpmb_data);
	if (grdm_ret != GRDM_BL_RPMB_NO_ERROR) {
		LOG_E("grdm_set_data: rpmb read failed 0x%x", grdm_ret);
		goto exit;
	}

	memcpy(rpmb_data.fw_version, fw_ver, 8);

	grdm_ret = grdm_rpmb_write(0, &rpmb_data);
	if (grdm_ret != GRDM_BL_RPMB_NO_ERROR) {
		LOG_E("grdm_set_data: rpmb read failed 0x%x", grdm_ret);
		goto exit;
	}

exit:
	return grdm_ret;
}
