/*****************************************************************************
 *
 * Copyright (c) 2018-19, Eduard Gerlits, Paula Herber, Josef Treus
 * Westfälische Wilhelms-Universität Münster, AG Embedded Systems 
 * All rights reserved.
 *
 * This file is part of SysCSec
 *
 * SysCSec 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 3 of the License, or (at your
 * option) any later version.
 *
 * SysCSec 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 SysCSec.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 *  Please report any problems or bugs to: sysc.sec@uni-muenster.de
 *
 ****************************************************************************/

#include "Client.h"

void Client::client_main()
{
  //sc_time t1(1000.0, "SC_MS");

	sc_biguint<960> p_data_ca;
	sc_biguint<960> p_data_server;
	sc_biguint<160> d0;
	sc_biguint<160> d1;
	sc_biguint<160> d2;
	sc_biguint<160> d3;

	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Client started!" << std::endl;
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Generate private and public key from the elliptic curve ..." << std::endl;
	ecdsa_generate_key_pair(d_u, q_x, q_y);
	//Private key des Clients wird hinterlegt
	stringstream buffer;
	buffer << d_u;
	RessourceHandler::getInstance()->logValue(1, buffer.str());

	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Keys are generated." << std::endl;
	std::cout << "\t" << "Private Key (d_u): " << d_u << std::endl;
	std::cout << "\t" << "Public Key (Q_u): (" << q_x << "," << q_y << ")" << std::endl;
	
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Send Public Key (Q_u) to CA:" << std::endl;
	pack_data(q_x, q_y, 0, 0, 0, 0, p_data_ca);
	send_data_ca(p_data_ca);
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Public Key successfully sent!" << std::endl;
	std::cout << std::endl;


	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Wait for (Q_ca, I_u, (r_u, s_u), t_u) from CA ..." << std::endl;
	receive_data_ca(p_data_ca);
	unpack_data(p_data_ca, q_ca_x, q_ca_y, I_u, r_u, s_u, t_u);
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "(Q_ca, I_u, (r_u, s_u), t_u) received from CA." << std::endl;
	std::cout << "\t" << "Public Key (Q_ca): (" << q_ca_x << "," << q_ca_y << ")" << std::endl; 
	std::cout << "\t" << "Client ID (I_u): " << I_u << std::endl;
	std::cout << "\t" << "Digital Signature (r_u, s_u): (" << r_u << "," << s_u << ")" << std::endl;
	std::cout << "\t" << "Client TTL (t_u): " << t_u << std::endl;
	std::cout << std::endl;


	// Generate Hash from Q_u.x , I_u and t_u
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Generate client hash from Q_u.x I_u and t_u..." << std::endl;
	hash_data(q_x, I_u, t_u, e_u);
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Client hash generated!" << std::endl;
	std::cout << "\t" << "e_u = H(Q_u.x, I_u, t_u): " << e_u << std::endl;
	std::cout << std::endl;


	/// 2. Mutual Authentification between Client and Server

	// Wait for Public Key from Server
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Wait for public key of the servers (Q_s) ..." << std::endl;
	receive_data_server(p_data_server);
	sc_biguint<160> q_s_x;
	sc_biguint<160> q_s_y;
	unpack_data(p_data_server, q_s_x, q_s_y, d0, d1, d2, d3);
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Public key from server (Q_s) successfully received." << std::endl;
	std::cout << "\t" << "Public Key (Q_s): (" << q_s_x << "," << q_s_y << ")" << std::endl;
	std::cout << std::endl;

	// Generate Random Number
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Generate random number g_u ..." << std::endl;
	sc_biguint<160> g_u = random_number_generation();
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Random number successfully generated!" << std::endl;
	std::cout << "\t" << "g_u: " << g_u << std::endl;
	std::cout << std::endl;	

	// Send Q_u and g_u to server
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Send (Q_u) and (g_u) to server ..." << std::endl;
	pack_data(q_x, q_y, g_u, 0, 0, 0, p_data_server);
	send_data_server(p_data_server);
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "(Q_u) and (g_u) successfully sent!" << std::endl;
	std::cout << std::endl;	

	// Compute Q_k_x after ECDH key exchange
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Determine Q_k = d_u * Q_s after ECDH key exchange!" << std::endl;
	sc_biguint<160> q_k_x;
	sc_biguint<160> q_k_y;
	pt_multiply(q_s_x, q_s_y, d_u, q_k_x, q_k_y);
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Q_k successfully computed." << std::endl;
	std::cout << "\t" << "(Q_k): (" << q_k_x << "," << q_k_y << ")" << std::endl;
	std::cout << std::endl;

	// Receive encrypted data from server
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Wait for C_0 from server ..." << std::endl;
	receive_data_server(p_data_server);
	sc_biguint<160> e_s;
	sc_biguint<160> r_s;
	sc_biguint<160> s_s;
	sc_biguint<160> t_s;
	sc_biguint<160> g_u_s;
	sc_biguint<160> g_s;
	unpack_data(p_data_server, e_s, r_s, s_s, t_s, g_u_s, g_s);
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "C_0 successfully received from server." << std::endl;


	// decrypt server data
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Decrypt C_0 from server ..." << std::endl;
	symmetric_data_encryption(q_k_x, e_s, r_s, s_s, t_s, g_u_s, g_s);
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Data successfully decrypted!" << std::endl;
	std::cout << "\t" << "e_s: " << e_s << std::endl;
	std::cout << "\t" << "(r_s, s_s): (" << r_s << "," << s_s << ")" << std::endl;
	std::cout << "\t" << "t_s: " << t_s << std::endl;
	std::cout << "\t" << "g_u (sent): " << g_u_s<< std::endl;
	std::cout << "\t" << "g_s: " << g_s << std::endl;

	// compare generated g_u with g_u received from server
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Compare generated g_u with g_u from server ..." << std::endl;
	if(g_u!=g_u_s){
		std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Generated g_u not equivalent with g_u from server." << std::endl;
		std::cout << "\t" << "g_u (locally generated): " << g_u << std::endl;
		std::cout << "\t" << "g_u (from server): " << g_u_s << std::endl;
		std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Authentification failed!" << std::endl;		
		sc_stop();
	}

	else {

		std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Generated g_u is equivalent to g_u from server." << std::endl;
		std::cout << "\t" << "g_u (locally generated): " << g_u << std::endl;
		std::cout << "\t" << "g_u (from server): " << g_u_s << std::endl;
		std::cout << std::endl;
		
	}


	// encrypt data with symmetric xor-encryption using Q_k_x
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Encrypting data with symmetric XOR encryption, C_1 = E(Q_k_x,(e_u, (r_u, s_u), t_u, g_u, g_s) )" << std::endl;	
	sc_biguint<160> e_u_enc=e_u;
	sc_biguint<160> r_u_enc=r_u;
	sc_biguint<160> s_u_enc=s_u;
	sc_biguint<160> t_u_enc=t_u;
	sc_biguint<160> g_u_enc=g_u;
	sc_biguint<160> g_s_enc=g_s;
	symmetric_data_encryption(q_k_x, e_u_enc, r_u_enc, s_u_enc, t_u_enc, g_u_enc, g_s_enc);
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Data successfully encrypted!" << std::endl;
	std::cout << std::endl;


	// send encrypted data to server
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Send C_1 to server:" << std::endl;
	pack_data(e_u_enc, r_u_enc, s_u_enc, t_u_enc, g_u_enc, g_s_enc, p_data_server);
	send_data_server(p_data_server);
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "C_1 successfully sent!" << std::endl;
	std::cout << std::endl;


	// Verify digital signature of the server with hash
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Verify digital signature fo the servers with hash ..." << std::endl;
	bool verification_result = ecdsa_verify(e_s, r_s, s_s);
	if(verification_result==false){
		std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Digital signature of the server not valid!" << std::endl;
		std::cout << "\t" << "e_s: " << e_s << std::endl;
		std::cout << "\t" << "(r_s, s_s): (" << r_s << "," << s_s << ")" << std::endl;
		std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Authentification failed!" << std::endl;		
		sc_stop();
	}
	else {
		std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Digital signature of the server valid!" << std::endl;
		std::cout << "\t" << "e_s: " << e_s << std::endl;
		std::cout << "\t" << "(r_s, s_s): (" << r_s << "," << s_s << ")" << std::endl;
		std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Authentification successful!" << std::endl;		
		std::cout << std::endl;	
	}

	// compute session key
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Compute session key: k_m = H(Q_k.x, g_s, g_u)..." << std::endl;
	sc_biguint<160> k_m;
	hash_data(q_k_x, g_s, g_u, k_m);
	std::cout << "[" << sc_time_stamp() << "]" << "[Client]" << ": " << "Session key k_m successfully computed:" << std::endl;
	std::cout << "\t" << "k_m: " << k_m << std::endl;
	std::cout << std::endl;		
}

void Client::initialise_ecc_parameter(sc_biguint<160> ec_param_a_, 
								sc_biguint<160> ec_param_b_, 
								sc_biguint<160> field_param_m_, 
								sc_biguint<160> ec_pt_x_,
								sc_biguint<160> ec_pt_y_,
								sc_biguint<160> ec_pt_order_n_)
{
	ec_param_a = ec_param_a_;
	ec_param_b = ec_param_b_;
	field_param_m = field_param_m_;
	ec_pt_x = ec_pt_x_;
	ec_pt_y = ec_pt_y_;
	ec_pt_order_n = ec_pt_order_n_;
}

void Client::ecdsa_generate_key_pair(sc_biguint<160> &d, sc_biguint<160> &q_x, sc_biguint<160> &q_y){
	ecc_control_fifo.write(_FW_ECDSA_KEY_GENERATION_);
	data_in0_fifo.write(ec_param_a);
	data_in1_fifo.write(ec_param_b);
	data_in2_fifo.write(ec_pt_x);
	data_in3_fifo.write(ec_pt_y);
	data_in4_fifo.write(ec_pt_order_n);
	data_in5_fifo.write(field_param_m);

	sc_uint<2> ecc_status_in=1;

	while(ecc_status_in!=_FW_FINISHED_){
		ecc_status_in = ecc_status_fifo.read();
	}

	data_out0_fifo.nb_read(d);
	data_out1_fifo.nb_read(q_x);
	data_out2_fifo.nb_read(q_y);
}

void Client::send_data_ca(sc_biguint<960> p_data)
{
	out_ca->write(p_data);
}

void Client::receive_data_ca(sc_biguint<960> &p_data)
{
	in_ca->read(p_data);
}

void Client::send_data_server(sc_biguint<960> p_data)
{
	out_server->write(p_data);
}

void Client::receive_data_server(sc_biguint<960> &p_data)
{
	in_server->read(p_data);
}

void Client::hash_data(sc_biguint<160> first, sc_biguint<160> second, sc_biguint<160> third, sc_biguint<160> &hash){

	std::string str = first.to_string() + ";" + second.to_string() + ";" + third.to_string();
	sha1_in_fifo.write(str);
	hash = sha1_out_fifo.read();
}

sc_biguint<160> Client::random_number_generation(){
	sc_biguint<160> max = ec_pt_order_n-2;
	sc_biguint<160> min = 2;
	return rand() % (max-min + 1) + min;
}

void Client::symmetric_data_encryption(sc_biguint<160> q_k_x, 
										sc_biguint<160> &e_enc, 
										sc_biguint<160> &r_enc, 
										sc_biguint<160> &s_enc, 
										sc_biguint<160> &t_enc, 
										sc_biguint<160> &g_u_enc, 
										sc_biguint<160> &g_s_enc)
{

	xor_key_in_fifo.write(q_k_x);
	xor_data_in_fifo.write(e_enc);
	e_enc = xor_data_out_fifo.read(); 

	xor_key_in_fifo.write(q_k_x);
	xor_data_in_fifo.write(r_enc);
	r_enc = xor_data_out_fifo.read(); 

	xor_key_in_fifo.write(q_k_x);
	xor_data_in_fifo.write(s_enc);
	s_enc = xor_data_out_fifo.read(); 

	xor_key_in_fifo.write(q_k_x);
	xor_data_in_fifo.write(t_enc);
	t_enc = xor_data_out_fifo.read(); 

	xor_key_in_fifo.write(q_k_x);
	xor_data_in_fifo.write(g_u_enc);
	g_u_enc = xor_data_out_fifo.read(); 

	xor_key_in_fifo.write(q_k_x);
	xor_data_in_fifo.write(g_s_enc);
	g_s_enc = xor_data_out_fifo.read(); 
}

void Client::pack_data(sc_biguint<160> data0, 
					sc_biguint<160> data1, 
					sc_biguint<160> data2, 
					sc_biguint<160> data3, 
					sc_biguint<160> data4, 
					sc_biguint<160> data5,
					sc_biguint<960> &p_data)
{
	sc_uint<3> io_status=_IO_PROCESSING_;
	sc_biguint<160> data;

	io_control_in_fifo.write(_IO_PACK_DATA_);
	io_data_in_fifo.write(data5);
	p_data_in_fifo.write(0);

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}
	data = io_data_out_fifo.read();
	p_data = p_data_out_fifo.read();	


	io_control_in_fifo.write(_IO_PACK_DATA_);
	io_data_in_fifo.write(data4);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}	
	data = io_data_out_fifo.read();
	p_data = p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_PACK_DATA_);
	io_data_in_fifo.write(data3);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}	
	data = io_data_out_fifo.read();
	p_data = p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_PACK_DATA_);
	io_data_in_fifo.write(data2);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}	
	data = io_data_out_fifo.read();
	p_data = p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_PACK_DATA_);
	io_data_in_fifo.write(data1);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}	
	data = io_data_out_fifo.read();
	p_data = p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_PACK_DATA_);
	io_data_in_fifo.write(data0);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}	
	data = io_data_out_fifo.read();
	p_data = p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_GET_DATA_);
	io_data_in_fifo.write(0);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}
	data = io_data_out_fifo.read();
	p_data = p_data_out_fifo.read();	
}

	
void Client::unpack_data(sc_biguint<960> p_data, 
						sc_biguint<160> &data0, 
						sc_biguint<160> &data1,
						sc_biguint<160> &data2, 
						sc_biguint<160> &data3,
						sc_biguint<160> &data4, 
						sc_biguint<160> &data5)
{
	sc_uint<3> io_status=_IO_PROCESSING_;

	io_control_in_fifo.write(_IO_SET_DATA_);
	io_data_in_fifo.write(0);
	p_data_in_fifo.write(p_data);

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}

	io_data_out_fifo.read();
	p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_UNPACK_DATA_);
	io_data_in_fifo.write(0);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}

	data0 = io_data_out_fifo.read();
	p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_UNPACK_DATA_);
	io_data_in_fifo.write(0);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}
	data1 = io_data_out_fifo.read();
	p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_UNPACK_DATA_);
	io_data_in_fifo.write(0);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}
	data2 = io_data_out_fifo.read();
	p_data_out_fifo.read();	



	io_control_in_fifo.write(_IO_UNPACK_DATA_);
	io_data_in_fifo.write(0);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}
	data3 = io_data_out_fifo.read();
	p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_UNPACK_DATA_);
	io_data_in_fifo.write(0);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}
	data4 = io_data_out_fifo.read();
	p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_UNPACK_DATA_);
	io_data_in_fifo.write(0);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}
	data5 = io_data_out_fifo.read();
	p_data_out_fifo.read();	
}

void Client::pt_multiply(sc_biguint<160> q_x, sc_biguint<160> q_y, sc_biguint<160> d, sc_biguint<160> &out_x, sc_biguint<160> &out_y){

	sc_uint<2> ecc_status_in=_FW_PROCESSING_;

	ecc_control_fifo.write(_FW_PT_MULTIPLY_);
	data_in0_fifo.write(q_x);
	data_in1_fifo.write(q_y);
	data_in2_fifo.write(d);
	data_in3_fifo.write(ec_param_a);
	data_in4_fifo.write(field_param_m);

	while(ecc_status_in!=_FW_FINISHED_){
		ecc_status_in = ecc_status_fifo.read();
	}

	out_x = data_out0_fifo.read();
	out_y = data_out1_fifo.read();

}

bool Client::ecdsa_verify(sc_biguint<160> hash, sc_biguint<160> r, sc_biguint<160> s){
	ecc_control_fifo.write(_FW_ECDSA_VERIFY_);
	data_in0_fifo.write(ec_param_a);
	data_in1_fifo.write(ec_pt_x);
	data_in2_fifo.write(ec_pt_y);
	data_in3_fifo.write(q_ca_x);
	data_in4_fifo.write(q_ca_y);
	data_in5_fifo.write(r);
	data_in6_fifo.write(s);
	data_in7_fifo.write(ec_pt_order_n);
	data_in8_fifo.write(field_param_m);
	data_in9_fifo.write(hash);

	sc_uint<3> ecc_status=_FW_PROCESSING_;

	while(ecc_status!=_FW_FINISHED_){
		ecc_status = ecc_status_fifo.read();
	}
	sc_biguint<160> result = data_out0_fifo.read();

	if(result==_FW_ECDSA_VERIFICATION_SUCCESS_){
		return true;
	}
	return false;
}
