/* Benchmarks.cpp
*
* Copyright (C) 2006-2020 wolfSSL Inc.
*
* This file is part of wolfSSL.
*
* wolfSSL is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* wolfSSL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/


#include "stdafx.h"
#include "Benchmarks.h" /* contains include of Enclave_u.h which has wolfSSL header files */

/* Check settings of wolfSSL */
#if !defined(HAVE_AESGCM) || defined(NO_RSA) || defined(NO_SHA256)
#error please enable AES-GCM, RSA, and SHA256
#endif

/* Use Debug SGX ? */
#if _DEBUG
	#define DEBUG_VALUE SGX_DEBUG_FLAG
#else
	#define DEBUG_VALUE 1
#endif

#define WIN32_LEAN_AND_MEAN
#include <windows.h>


/* Choose AES Key size  */
//#define WC_AES_KEY_SZ 16 /* 128 bit key */
#define WC_AES_KEY_SZ 24 /* 192 bit key */

static double current_time(int reset)
{
	static int init = 0;
	static LARGE_INTEGER freq;
	LARGE_INTEGER count;

	(void)reset;

	if (!init) {
		QueryPerformanceFrequency(&freq);
		init = 1;
	}

	QueryPerformanceCounter(&count);
	return (double)count.QuadPart / freq.QuadPart;
}


static void free_resources(byte* plain, byte* cipher) {
	delete[] plain;
	delete[] cipher;
}


/* benchmark is performed calling into Enclave on each update
 * This function tests speeds at different message sizes during update */
static double sha256_getTime_multiple(sgx_enclave_id_t id, double* total) {
	double start, end;
	int ret, sgxStatus;
	byte* plain;
	byte digest[64];
	int plainSz = (1024 * 1024);
	int tSz = (1024 * 1024) * numBlocks;
	int i, k;

	Sha256 sha256;

	ret = 0;
	k = numBlocks;
	printf("\n");
	for (k = 1; k <= numBlocks; k++) {
		plainSz = tSz / k;
		plain = new byte[plainSz];
		ret |= wc_sha256_init(id, &sgxStatus, &sha256);
		start = current_time(1);
		for (i = 0; i < k; i++) {
			ret |= wc_sha256_update(id, &sgxStatus, &sha256, plain, plainSz);
		}
		ret |= wc_sha256_final(id, &sgxStatus, &sha256, digest);
		end = current_time(0);

		if (ret != SGX_SUCCESS || sgxStatus != 0) {
			printf("Error in SHA256 operation with Enclave: %d sgxStatus = %d.\n", ret, sgxStatus);
			return -1;
		}
		*total = end - start;
		printf("%8.3f\n", *total);
		delete[] plain;
	}
	printf("\n");
	*total = end - start;
	return 1 / *total * numBlocks;
}


/* benchmark is performed calling into Enclave on each update */
static double sha256_getTime(sgx_enclave_id_t id, double* total) {
	double start, end;
	int ret = 0;
	int sgxStatus = 0;
	int i;
	byte* plain;
	byte digest[64];
	int plainSz = (1024 * 1024);

	Sha256 sha256;

	plain = new byte[plainSz];
	ret |= wc_sha256_init(id, &sgxStatus, &sha256);
	start = current_time(1);
	
	/* perform work and get digest */
	for (i = 0; i < numBlocks; i++) {
		ret |= wc_sha256_update(id, &sgxStatus, &sha256, plain, plainSz);
	}
	ret |= wc_sha256_final(id, &sgxStatus, &sha256, digest);
	end = current_time(0);

	delete[] plain;
	if (ret != SGX_SUCCESS || sgxStatus != 0) {
		printf("Error in SHA256 operation with Enclave: %d sgxStatus = %d.\n", ret, sgxStatus);
		return -1;
	}

	*total = end - start;
	return 1 / *total * numBlocks;
}


static int sha256_print(sgx_enclave_id_t id)
{
	double total, persec;

	printf("SHA-256           ");
	persec = sha256_getTime(id, &total);
	printf("%d megs took %5.3f seconds , %8.3f MB/s\n", numBlocks, total, persec);

	return 0;
}


/* return time in MB/s with crossing into enclave boundary with each encrypt */
static double aesgcm_encrypt_getTime(sgx_enclave_id_t id, double* total, byte* plain, byte* cipher, word32 sz, byte* tag, word32 tagSz)
{
	Aes aes;
	double start, end;
	int i;
	int ret, sgxStatus;

	const byte ad[13] = { 0 };

	const XGEN_ALIGN byte iv[] =
	{
		0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef,
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
		0x11, 0x21, 0x31, 0x41, 0x51, 0x61, 0x71, 0x81
	};

	const XGEN_ALIGN byte key[] =
	{
		0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
		0xfe, 0xde, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
		0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67
	};

	ret = wc_aesgcm_setKey(id, &sgxStatus, &aes, key, WC_AES_KEY_SZ);
	if (ret != SGX_SUCCESS || sgxStatus != 0) {
		printf("AES set key failed %d sgxStatus = %d\n", ret, sgxStatus);
		return -1;
	}

	start = current_time(1);
	for (i = 0; i < numBlocks; i++) {
		ret = wc_aesgcm_encrypt(id, &sgxStatus, &aes, cipher, plain, sz, iv, 12, tag, tagSz, ad, 13);
	}
	end = current_time(0);

	if (ret != SGX_SUCCESS || sgxStatus != 0) {
		printf("Error in AES-GCM encrypt operation with Enclave: %d sgxStatus = %d.\n", ret, sgxStatus);
		return -1;
	}

	*total = end - start;
	return 1 / *total * numBlocks;
}


static int aesgcm_encrypt_print(sgx_enclave_id_t id, byte* plain, byte* cipher, word32 sz, byte* tag, word32 tagSz)
{
	double total, persec;

	printf("AES-GCM encrypt   ");
	persec = aesgcm_encrypt_getTime(id, &total, plain, cipher, sz, tag, tagSz);
	printf("%d megs took %5.3f seconds , %8.3f MB/s\n", numBlocks, total, persec);

	return 0;
}


/* return MB/s with crossing into Enclave boundary with each decrypt */
static double aesgcm_decrypt_getTime(sgx_enclave_id_t id, double* total, byte* plain, const byte* cipher, word32 sz, const byte* tag, word32 tagSz)
{
	Aes aes;
	double start, end;
	int ret, sgxStatus;
	int i;

	const byte ad[13] = { 0 };

	const XGEN_ALIGN byte iv[] =
	{
		0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef,
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
		0x11, 0x21, 0x31, 0x41, 0x51, 0x61, 0x71, 0x81
	};

	const XGEN_ALIGN byte key[] =
	{
		0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
		0xfe, 0xde, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
		0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67
	};

	ret = wc_aesgcm_setKey(id, &sgxStatus, &aes, key, WC_AES_KEY_SZ);
	if (ret != SGX_SUCCESS || sgxStatus != 0) {
		printf("AES set key failed %d sgxStatus = %d\n", ret, sgxStatus);
		return -1;
	}

	start = current_time(1);
	for (i = 0; i < numBlocks; i++) {
		ret = wc_aesgcm_decrypt(id, &sgxStatus, &aes, plain, cipher, sz, iv, 12, tag, tagSz, ad, 13);
	}
	end = current_time(0);

	if (ret != SGX_SUCCESS || sgxStatus < 0) {
		printf("Error in AES-GCM decrypt operation with Enclave: %d sgxStatus = %d.\n", ret, sgxStatus);
		return -1;
	}

	*total = end - start;
	return 1 / *total * numBlocks;
}


static int aesgcm_decrypt_print(sgx_enclave_id_t id, byte* plain, byte* cipher, word32 sz, const byte* tag, word32 tagSz)
{
	double total, persec;

	printf("AES-GCM decrypt   ");
	persec = aesgcm_decrypt_getTime(id, &total, plain, cipher, sz, tag, tagSz);
	printf("%d megs took %5.3f seconds , %8.3f MB/s\n", numBlocks, total, persec);

	return 0;
}


/* return time for each in milliseconds */
static double rsa_encrypt_getTime(sgx_enclave_id_t id, int* sgxStatus, double* total, const byte* message, word32 mSz, byte* cipher, word32 cSz)
{
	double start, end;
	int ret, i;
	int freeStatus = 0;
	RsaKey rsa;

	ret = 0; *sgxStatus = 0;
	ret = wc_rsa_init(id, sgxStatus, &rsa); /* loads RSA key from buffer and inits RNG */
	if (ret != SGX_SUCCESS || *sgxStatus != 0) {
		printf("Initializing RSA failed %d sgxStatus = %d\n", ret, *sgxStatus);
		return -1;
	}

	start = current_time(1);
	for (i = 0; i < ntimes; i++) {
		ret = wc_rsa_encrypt(id, sgxStatus, message, mSz, cipher, cSz, &rsa);
	}
	end = current_time(0);

	if (ret != SGX_SUCCESS || *sgxStatus < 0) {
		printf("Error in rsa encrypt operation with Enclave: %d sgxStatus = %d.\n", ret, *sgxStatus);
		return -1;
	}

	ret = wc_rsa_free(id, &freeStatus, &rsa);
	if (ret != 0 || freeStatus != 0) {
		printf("Failed to free RSA key %d sgxStatus = %d\n", ret, freeStatus);
		return -1;
	}

	*total = end - start;
	return (*total / ntimes) * 1000;
}


static int rsa_encrypt_print(sgx_enclave_id_t id, int* sgxStatus, byte* plain, word32 pSz, byte* cipher, word32 cSz)
{
	double total, each;

	printf("RSA-2048 encrypt   ");
	each = rsa_encrypt_getTime(id, sgxStatus, &total, plain, pSz, cipher, cSz);
	printf("took %6.3f milliseconds, avg over %d\n", each, ntimes);

	return 0;
}


/* return time in milliseconds for each */
static double rsa_decrypt_getTime(sgx_enclave_id_t id, double* total, byte* m, word32 mSz, const byte* c, word32 cSz)
{
	double start, end;
	int ret, sgxStatus, i;
	RsaKey rsa;

	ret = wc_rsa_init(id, &sgxStatus, &rsa); /* loads RSA key from buffer and inits RNG */
	if (ret != SGX_SUCCESS || sgxStatus != 0) {
		printf("Initializing RSA failed %d sgxStatus = %d\n", ret, sgxStatus);
		return -1;
	}

	start = current_time(1);
	for (i = 0; i < ntimes; i++) {
		ret = wc_rsa_decrypt(id, &sgxStatus, c, cSz, m, mSz, &rsa);
	}
	end = current_time(0);

	if (ret != SGX_SUCCESS || sgxStatus < 0) {
		printf("Error in rsa decrypt operation with Enclave: %d sgxStatus = %d.\n", ret, sgxStatus);
		return -1;
	}

	ret = wc_rsa_free(id, &sgxStatus, &rsa);
	if (ret != 0 || sgxStatus != 0) {
		printf("Failed to free RSA key %d sgxStatus = %d\n", ret, sgxStatus);
		return -1;
	}

	*total = end - start;
	return (*total / ntimes) * 1000;
}


static int rsa_decrypt_print(sgx_enclave_id_t id, byte* m, word32 mSz, const byte* c, word32 cSz)
{
	double total, each;

	printf("RSA-2048 decrypt   ");
	each = rsa_decrypt_getTime(id, &total, m, mSz, c, cSz);
	printf("took %6.3f milliseconds, avg over %d\n", each, ntimes);

	return 0;
}

int main(int argc, char* argv[])
{
	sgx_enclave_id_t id;
	sgx_launch_token_t t;

	int ret = 0;
	int sgxStatus = 0;
	int updated = 0;
	byte message[] = "Secure wolfSSL.";

	byte* plain  = new byte[1024 * 1024];
	byte* cipher = new byte[1024 * 1024];
	const byte tag[16] = { 0 };
	int plainSz = 1024 * 1024;
	int i;

	/* only print off if no command line arguments were passed in */
	if (argc == 1) {
		printf("Setting up Enclave ... ");
	}

	memset(t, 0, sizeof(sgx_launch_token_t));

	ret = sgx_create_enclave(_T("Enclave.signed.dll"), DEBUG_VALUE, &t, &updated, &id, NULL);
	if (ret != SGX_SUCCESS) {
		printf("Failed to create Enclave : error %d - %#x.\n", ret, ret);
		free_resources(plain, cipher);
		return 1;
	}

	/* test if only printing off times */
	if (argc > 1) {
		double total;
		int idx = 1;
		while (1) {
			for (idx = 1; idx < argc; idx++) {
				if (strncmp(argv[idx], "-s256", 6) == 0) {
					printf("%8.3f - SHA-256\n", sha256_getTime(id, &total));
					fflush(stdout);
				}
				else if (strncmp(argv[idx], "-ag", 3) == 0) {
					printf("%8.3f - AES-GCM\n", aesgcm_encrypt_getTime(id, &total, plain, cipher, plainSz, (byte*)tag, sizeof(tag)));
					fflush(stdout);
				}
				else if (strncmp(argv[idx], "-re", 4) == 0) {
					printf("%8.3f - RSA-ENC\n", rsa_encrypt_getTime(id, &sgxStatus, &total, message, sizeof(message), cipher, plainSz));
					fflush(stdout);
				}
				else if (strncmp(argv[idx], "-rd", 4) == 0) {
					rsa_encrypt_getTime(id, &sgxStatus, &total, message, sizeof(message), cipher, plainSz);
					printf("%8.3f - RSA-DEC\n", rsa_decrypt_getTime(id, &total, plain, plainSz, cipher, sgxStatus));
					fflush(stdout);
				}
				else {
					printf("\"%s\" Not yet implemented\n\t-s256 for SHA256\n\t-ag for AES-GCM\n\t-re for RSA encrypt", argv[idx]);
					fflush(stdout);
					free_resources(plain, cipher);
					return 0;
				}
			}
		}
	}

	printf("Success\nCollecting benchmark values for wolfSSL using SGX\n");
	
    /*********** SHA-256 ***************/
	if (sha256_print(id) != 0) {
		free_resources(plain, cipher);
		return -1;
	}
	printf("\n");


	/*********** AES-GCM ***************/
	/* place message in first bytes of plain and test encrypt/decrypt with aesgcm */
	memcpy(plain, message, sizeof(message));

	if (aesgcm_encrypt_print(id, plain, cipher, plainSz, (byte*)tag, sizeof(tag)) != 0) {
		free_resources(plain, cipher);
		return -1;
	}

	memset(plain, 0, plainSz);
	if (aesgcm_decrypt_print(id, plain, cipher, plainSz, tag, sizeof(tag)) != 0) {
		free_resources(plain, cipher);
		return -1;
	}

	printf("\tdecrypted message = ");
	for (i = 0; i < sizeof(message); i++) { printf("%c", plain[i]); }
	printf("\n\n");

	/*********** RSA ***************/
	memset(cipher, 0, 256);
	ret = rsa_encrypt_print(id, &sgxStatus, message, sizeof(message), cipher, plainSz);
	if (ret < 0) {
		free_resources(plain, cipher);
		return -1;
	}

	memset(plain, 0, plainSz);
	ret = rsa_decrypt_print(id, plain, plainSz, cipher, sgxStatus);
	if (ret != 0) {
		free_resources(plain, cipher);
		return -1;
	}

	printf("\tdecrypted message = ");
	for (i = 0; i < 15; i++){ printf("%c", plain[i]); }
	printf("\n");


	/*********** Free arrays and exit ***************/
	free_resources(plain, cipher);
	return 0;
}

