#include "sgx_eid.h"
#include "error_codes.h"
#include "datatypes.h"
#include "sgx_urts.h"
#include "sgx_dh.h"

// For msg1
#define SGX_TARGET_INFO_RESERVED1_BYTES 4
#define SGX_TARGET_INFO_RESERVED2_BYTES 456
#define SGX_ECP256_KEY_SIZE 32
#define SGX_HASH_SIZE 32 /* SHA256 */
// For msg2
#define SGX_REPORT_DATA_SIZE 64
#define SGX_KEYID_SIZE 32
#define SGX_DH_MAC_SIZE 16
#define SGX_REPORT_BODY_RESERVED1 28
#define SGX_REPORT_BODY_RESERVED2 32
#define SGX_REPORT_BODY_RESERVED3 96
#define SGX_REPORT_BODY_RESERVED4 60


#include <stdio.h>
// For google protobufs and deserialization/serialization
#include "ProtobufLAMessages.pb.h"
#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
using namespace google::protobuf::io;
#include <inttypes.h>

// TODO: PRIVATE
int fit_32_into_uint8_t(google::protobuf::uint32 temp32, uint8_t* temp8)
{
 if(temp32 > UINT8_MAX)
	 return -1;
 else
 {
	 //		 *temp8 = *(uint8_t*)&temp32; // Probably works irrespective of endianness but not sure.
	 *temp8 = (uint8_t)temp32;
	 return 0;
 }
}

// TODO: PRIVATE
int fit_32_into_uint16_t(google::protobuf::uint32 temp32, uint16_t* temp16)
{
 if(temp32 > UINT16_MAX)
         return -1;
 else
 {
         //              *temp8 = *(uint8_t*)&temp32; // Probably works irrespective of endianness but not sure.
         *temp16 = (uint16_t)temp32;
         return 0;
 }
}

// TODO: PRIVATE
void encode_ec256_public_key_to_protobuf(protobuf_sgx_ec256_public_t* protobuf_g_a , sgx_ec256_public_t* g_a)
{
	printf("\n ec256 public key gx and gy \n");
	int counter; google::protobuf::uint32 temp32;
	for(counter=0;counter<SGX_ECP256_KEY_SIZE;counter++)
	{
		temp32 = g_a->gx[counter];
		protobuf_g_a->add_gx(temp32);
		printf("%d ", temp32);
		temp32 = g_a->gy[counter];
		protobuf_g_a->add_gy(temp32);
		printf("%d ", temp32);
	}
	printf("\n");
}

// TODO: PRIVATE
void encode_attributes_to_protobuf(protobuf_sgx_attributes_t* protobuf_attributes, sgx_attributes_t* attributes)
{
	protobuf_attributes->set_flags(attributes->flags); // 64 bit
	protobuf_attributes->set_xfrm(attributes->xfrm); // 64 bit
}

// TODO: PRIVATE
void encode_report_to_protobuf(protobuf_sgx_report_t* protobuf_report, sgx_report_t* report)
{
	printf("\n report \n key id \n");
	int counter; google::protobuf::uint32 temp32;
	for(counter=0;counter<SGX_KEYID_SIZE;counter++)
	{
		temp32=report->key_id.id[counter];
		protobuf_report->add_key_id(temp32);
		printf("%d ",temp32);
	}

	printf("\n mac \n");
	for(counter=0;counter<SGX_MAC_SIZE;counter++)
	{
		temp32=report->mac[counter];
		protobuf_report->add_mac(temp32);
		printf("%d ", temp32);
	}

	protobuf_report->mutable_body()->set_misc_select(report->body.misc_select); // 32 bit
	protobuf_report->mutable_body()->set_isv_svn(report->body.isv_svn); // 16 bit
	protobuf_report->mutable_body()->set_isv_prod_id(report->body.isv_prod_id); // 16 bit
	encode_attributes_to_protobuf(protobuf_report->mutable_body()->mutable_attributes(), &(report->body.attributes));

	for(counter=0;counter<SGX_CPUSVN_SIZE;counter++)
	{
		temp32=report->body.cpu_svn.svn[counter];
		protobuf_report->mutable_body()->add_cpu_svn(temp32);
	}

	for(counter=0;counter<SGX_REPORT_BODY_RESERVED1;counter++)
	{
		temp32=report->body.reserved1[counter]; // TODO: Could be optimized out - if these are determined to be 0s.
		protobuf_report->mutable_body()->add_reserved1(temp32);
 	}

	for(counter=0;counter<SGX_REPORT_BODY_RESERVED2;counter++)
	{
		temp32=report->body.reserved2[counter]; // TODO: Could be optimized out - if these are determined to be 0s.
		protobuf_report->mutable_body()->add_reserved2(temp32);
	}

	for(counter=0;counter<SGX_REPORT_BODY_RESERVED3;counter++)
	{
		temp32=report->body.reserved3[counter]; // TODO: Could be optimized out - if these are determined to be 0s.
		protobuf_report->mutable_body()->add_reserved3(temp32);
 	}

	for(counter=0;counter<SGX_REPORT_BODY_RESERVED4;counter++)
	{
		temp32=report->body.reserved4[counter]; // TODO: Could be optimized out - if these are determined to be 0s.
		protobuf_report->mutable_body()->add_reserved4(temp32);
 	}

	printf("\nmr enclave\n");
	fflush(stdout);
	for(counter=0;counter<SGX_HASH_SIZE;counter++)
	{
		temp32=report->body.mr_enclave.m[counter];
		protobuf_report->mutable_body()->add_mr_enclave(temp32);
		printf("%x ", temp32);
 	}

	printf("\n mr signer\n");  fflush(stdout);
	for(counter=0;counter<SGX_HASH_SIZE;counter++)
	{
		temp32=report->body.mr_signer.m[counter];
		protobuf_report->mutable_body()->add_mr_signer(temp32);
		printf("%x ", temp32);
 	}


	for(counter=0;counter<SGX_REPORT_DATA_SIZE;counter++)
	{
		temp32=report->body.report_data.d[counter];
		protobuf_report->mutable_body()->add_report_data(temp32);
 	}
}

// TODO: PRIVATE
int decode_attributes_from_protobuf(protobuf_sgx_attributes_t* protobuf_attributes, sgx_attributes_t* attributes)
{
        attributes->flags = protobuf_attributes->flags();
  printf("\n flags %" PRIu64 " \n", attributes->flags);
        attributes->xfrm = protobuf_attributes->xfrm();
  printf("\n xfrm %" PRIu64 " \n", attributes->xfrm);
        return 0;
}

// TODO: PRIVATE
int decode_report_from_protobuf(protobuf_sgx_report_t* protobuf_report, sgx_report_t* report)
{
  int counter; google::protobuf::uint32 temp32;
  printf("\n----------------------Decoding received msg3 ------------------------\n");
  printf("\nreport body keyid\n");
  for(counter=0;counter<SGX_KEYID_SIZE;counter++)
  {
    temp32=protobuf_report->key_id(counter);
    if(fit_32_into_uint8_t(temp32, &(report->key_id.id[counter]))!=0)
		  return -1;
    printf("%d ", report->key_id.id[counter]);
  }

  printf("\nreport mac\n");
  for(counter=0;counter<SGX_MAC_SIZE;counter++)
  {
    temp32=protobuf_report->mac(counter);
    if(fit_32_into_uint8_t(temp32, &(report->mac[counter]))!=0)
      return -1;
    printf("%d ", report->mac[counter]);
  }

  report->body.misc_select=protobuf_report->mutable_body()->misc_select(); // 32 bit
  temp32=protobuf_report->mutable_body()->isv_svn();
  if(fit_32_into_uint16_t(temp32, &(report->body.isv_svn))!=0)
   return -1;
  printf("\nmisc select %d \n", report->body.misc_select);

  temp32=protobuf_report->mutable_body()->isv_prod_id();
   if(fit_32_into_uint16_t(temp32, &(report->body.isv_prod_id))!=0)
    return -1;
  printf("\nprod id %d \n", report->body.isv_prod_id);

  decode_attributes_from_protobuf(protobuf_report->mutable_body()->mutable_attributes(), &(report->body.attributes));

  printf("\n cpu svn\n");
  for(counter=0;counter<SGX_CPUSVN_SIZE;counter++)
  {
    temp32=protobuf_report->mutable_body()->cpu_svn(counter);
    if(fit_32_into_uint8_t(temp32, &(report->body.cpu_svn.svn[counter]))!=0)
      return -1;
    printf("%d ", report->body.cpu_svn.svn[counter]);

  }

  printf("\n reserved1 \n");
  for(counter=0;counter<SGX_REPORT_BODY_RESERVED1;counter++)
  {
    temp32=protobuf_report->mutable_body()->reserved1(counter);
    if(fit_32_into_uint8_t(temp32, &(report->body.reserved1[counter]))!=0)
      return -1;
    printf("%d ", report->body.reserved1[counter]);
  }

  printf("\n reserved2 \n");
  for(counter=0;counter<SGX_REPORT_BODY_RESERVED2;counter++)
  {
    temp32=protobuf_report->mutable_body()->reserved2(counter);
    if(fit_32_into_uint8_t(temp32, &(report->body.reserved2[counter]))!=0)
      return -1;
    printf("%d ", report->body.reserved2[counter]);
  }

  printf("\n reserved3 \n");
  for(counter=0;counter<SGX_REPORT_BODY_RESERVED3;counter++)
  {
    temp32=protobuf_report->mutable_body()->reserved3(counter);
    if(fit_32_into_uint8_t(temp32, &(report->body.reserved3[counter]))!=0)
      return -1;
    printf("%d ", report->body.reserved3[counter]);
  }

  printf("\n reserved4 \n");
  for(counter=0;counter<SGX_REPORT_BODY_RESERVED4;counter++)
  {
    temp32=protobuf_report->mutable_body()->reserved4(counter);
    if(fit_32_into_uint8_t(temp32, &(report->body.reserved4[counter]))!=0)
      return -1;
    printf("%d ", report->body.reserved4[counter]);

  }

  printf("\n mrenclave \n");
  for(counter=0;counter<SGX_HASH_SIZE;counter++)
  {
    temp32=protobuf_report->mutable_body()->mr_enclave(counter);
    if(fit_32_into_uint8_t(temp32, &(report->body.mr_enclave.m[counter]))!=0)
      return -1;
    printf("%x ", report->body.mr_enclave.m[counter]);
  }

  printf("\n mrsigner \n");
  for(counter=0;counter<SGX_HASH_SIZE;counter++)
  {
    temp32=protobuf_report->mutable_body()->mr_signer(counter);
    if(fit_32_into_uint8_t(temp32, &(report->body.mr_signer.m[counter]))!=0)
      return -1;
    printf("%x ", report->body.mr_signer.m[counter]);
  }

  printf("\n report data\n");
  for(counter=0;counter<SGX_REPORT_DATA_SIZE;counter++)
  {
    temp32=protobuf_report->mutable_body()->report_data(counter);
    if(fit_32_into_uint8_t(temp32, &(report->body.report_data.d[counter]))!=0)
      return -1;
    printf("%d ", report->body.report_data.d[counter]);
  }
  printf("\n------------------------ end of msg3 --------------------------\n");
	return 0;
}

int decode_msg1_from_protobuf( protobuf_sgx_dh_msg1_t& protobuf_dhmsg1, sgx_dh_msg1_t* native_dhmsg1)
{
	int counter; google::protobuf::uint32 temp32;// google::protobuf::uint64 temp64;

	for(counter=0;counter<SGX_ECP256_KEY_SIZE;counter++)
	{
		temp32 = protobuf_dhmsg1.mutable_g_a()->gx(counter);
		if(fit_32_into_uint8_t(temp32, &(native_dhmsg1->g_a.gx[counter]))!=0)
		 return -1;
		temp32 = protobuf_dhmsg1.mutable_g_a()->gy(counter);
		if(fit_32_into_uint8_t(temp32, &(native_dhmsg1->g_a.gy[counter]))!=0)
		 return -1;
	}

	for(counter=0;counter<SGX_HASH_SIZE;counter++)
	{
		temp32 = protobuf_dhmsg1.mutable_target()->mr_enclave(counter);
		if(fit_32_into_uint8_t(temp32, &(native_dhmsg1->target.mr_enclave.m[counter]))!=0)
		 return -1;
	}

	for(counter=0;counter<SGX_TARGET_INFO_RESERVED1_BYTES;counter++)
	{
		temp32 = protobuf_dhmsg1.mutable_target()->reserved1(counter);
		if(fit_32_into_uint8_t(temp32, &(native_dhmsg1->target.reserved1[counter]))!=0)
		 return -1;
	}

	for(counter=0;counter<SGX_TARGET_INFO_RESERVED2_BYTES;counter++)
	{
		temp32 = protobuf_dhmsg1.mutable_target()->reserved2(counter);
		if(fit_32_into_uint8_t(temp32, &(native_dhmsg1->target.reserved2[counter]))!=0)
		 return -1;
	}

	native_dhmsg1->target.attributes.flags = protobuf_dhmsg1.mutable_target()->mutable_attributes()->flags();
	native_dhmsg1->target.attributes.xfrm = protobuf_dhmsg1.mutable_target()->mutable_attributes()->xfrm();
	native_dhmsg1->target.misc_select = protobuf_dhmsg1.mutable_target()->misc_select();
	return 0;
}

int decode_msg3_from_protobuf(protobuf_sgx_dh_msg3_t& protobuf_dhmsg3, sgx_dh_msg3_t* native_dhmsg3)
{
  int counter; google::protobuf::uint32 temp32;
  for(counter=0;counter<SGX_DH_MAC_SIZE;counter++)
  {
		temp32=protobuf_dhmsg3.cmac(counter);
    if(fit_32_into_uint8_t(temp32, &(native_dhmsg3->cmac[counter]))!=0)
      return -1;
  }

  if(decode_report_from_protobuf(protobuf_dhmsg3.mutable_msg3_body()->mutable_report(), &(native_dhmsg3->msg3_body.report))==-1)
    return -1;
  int max_counter=protobuf_dhmsg3.mutable_msg3_body()->additional_prop_size();
  native_dhmsg3->msg3_body.additional_prop_length=max_counter;
  // TODO: Need to assign a variable on the heap and then pass it as an argument to this function - set it to null if protobuf_dhmsg3.mutable_msg3_body()->additional_prop_size() is 0
  // TODO: And then free it in that function (create_session) when it is done. It is likely that it is 0 in the SGX SDK sample code. And SDK people probably didn't deserialize it - as it may contain a pointer in the general case - to the array of additional_properties.
  if(max_counter!=0)
    return -1;
  return 0;
}

// TODO: PRIVATE - OR EVEN GET RID OF IT
int print_initialized_msg1( protobuf_sgx_dh_msg1_t& protobuf_dhmsg1 /*, sgx_dh_msg1_t* native_dhmsg1*/)
{
	int counter;
	printf("gx\n");
	for(counter=0;counter<SGX_ECP256_KEY_SIZE;counter++)
	{
		 printf("%d ", protobuf_dhmsg1.g_a().gx(counter));
	}

	printf("\ngy\n");
	for(counter=0;counter<SGX_ECP256_KEY_SIZE;counter++)
	{
		 printf("%d ", protobuf_dhmsg1.g_a().gy(counter));
	}

	printf("\nmrenclave in target\n");
	for(counter=0;counter<SGX_HASH_SIZE;counter++)
	{
		 printf("%" PRIu32 " ", protobuf_dhmsg1.target().mr_enclave(counter));
	}

	printf("\nreserved1 in target\n");
	for(counter=0;counter<SGX_TARGET_INFO_RESERVED1_BYTES;counter++)
	{
		 printf("%" PRIu32 " ", protobuf_dhmsg1.target().reserved1(counter));
	}

	printf("\nreserved2 in target\n");
	for(counter=0;counter<SGX_TARGET_INFO_RESERVED2_BYTES;counter++)
	{
		 printf("%" PRIu32 " ", protobuf_dhmsg1.target().reserved2(counter));
	}

	printf("\n %" PRIu64 "\n", protobuf_dhmsg1.target().attributes().flags());
	printf("\n %" PRIu64 "\n", protobuf_dhmsg1.target().attributes().xfrm());
	printf("\n %" PRIu32 "\n", protobuf_dhmsg1.target().misc_select());

	return 0;
}

void encode_msg2_to_protobuf( protobuf_sgx_dh_msg2_t& protobuf_dhmsg2, sgx_dh_msg2_t* native_dhmsg2)
{
	int counter; google::protobuf::uint32 temp32; //google::protobuf::uint64 temp64;
	printf("\n msg2 cmac \n");
	for(counter=0;counter<SGX_DH_MAC_SIZE;counter++)
	{
		temp32=native_dhmsg2->cmac[counter];
		protobuf_dhmsg2.add_cmac(temp32);
		printf("%d ", temp32);
	}

	encode_ec256_public_key_to_protobuf(protobuf_dhmsg2.mutable_g_b(), &(native_dhmsg2->g_b));

	encode_report_to_protobuf(protobuf_dhmsg2.mutable_report(), &(native_dhmsg2->report));
}
/*
// Got rid of the session ID - figure out its role.
//message1 from the destination enclave through a socket set up before.
// TODO: What do we do about session id?
int session_request_call(int fd, sgx_dh_msg1_t* dh_msg1) //, uint32_t* session_id)
{
	protobuf_sgx_dh_msg1_t protobuf_msg1;
	printf("reading msg1\n");
	fflush(stdout);
	if(read_protobuf_msg_from_fd(fd, protobuf_msg1)!=0)
		return -1;
	print_initialized_msg1(protobuf_msg1);
	printf("\n done reading msg1 --------------------\n");
	fflush(stdout);
  if(decode_msg1_from_protobuf(protobuf_msg1, dh_msg1)!=0)
    return -1;
  return 0;
}

// Source enclave for exchange_report_ocall (like other ocalls) will be the PHP enclave and the destination enclave will be the decryptor one.
//Makes an sgx_ecall to the destination enclave sends message2 from the source enclave and gets message 3 from the destination enclave
// TODO: What do we do about session id?
int exchange_report_call(int fd, sgx_dh_msg2_t *dh_msg2, sgx_dh_msg3_t *dh_msg3) // , uint32_t* session_id)
{
	protobuf_sgx_dh_msg2_t protobuf_msg2;
  protobuf_sgx_dh_msg3_t protobuf_msg3;
	printf("\n------------------------------------- generating msg2 --------\n");
	// Fill protobuf class for dhmsg2 with contents from its native C struct.
	encode_msg2_to_protobuf(protobuf_msg2, dh_msg2);
	// Write msg length and then write the raw msg.
	if(write_protobuf_msg_to_fd(fd, protobuf_msg2)!=0)
		return -1;
	printf("Wrote msg2 to protobuf ------------------------------------------\n");
	fflush(stdout);
	// Read from socket dh_msg3
	if(read_protobuf_msg_from_fd(fd, protobuf_msg3)!=0)
		return -1;
 	// Decode msg3 from protobuf to native structs
	if(decode_msg3_from_protobuf(protobuf_msg3, dh_msg3)!=0)
		return -1;
 	return 0;

}

//Make an sgx_ecall to the destination enclave to close the session
int end_session_ocall()
{
  return SGX_SUCCESS;
}
*/
