/*****************************************************************************
 *
 * 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 "Server.h"

void Server::server_main()
{
	sc_biguint<960> p_data_ca;
	sc_biguint<960> p_data_client;

	sc_biguint<160> d0;
	sc_biguint<160> d1;
	sc_biguint<160> d2;
	sc_biguint<160> d3;

	// start server and generate key parameters
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Server started!" << std::endl;
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Generate private and public key from curve parameters ..." << std::endl;
	ecdsa_generate_key_pair(d_s, q_x, q_y);
	//Private key des Servers wird hinterlegt
	stringstream buffer;
	buffer << d_s;
	RessourceHandler::getInstance()->logValue(2, buffer.str());


	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Generated key parameters:" << std::endl;
	std::cout << "\t" << "Private Key (d_s): " << d_s<< std::endl;
	std::cout << "\t" << "Public Key (Q_s): (" << q_x<< "," << q_y << ")" << std::endl;
	std::cout << std::endl;


	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Send public key to CA:" << std::endl;
	std::cout << "\t" << "Public Key (Q_s): (" << q_x << "," << q_y<< ")" << 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() << "]" << "[Server]" << ": " << "Public key successfully sent to CA!" << std::endl;
	std::cout << std::endl;


	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Wait for (Q_ca, I_s, (r_s, s_s), t_s) from CA..." << std::endl;
	receive_data_ca(p_data_ca);
	unpack_data(p_data_ca, q_ca_x, q_ca_y, I_s, r_s, s_s, t_s);
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "(Q_ca, I_s, (r_s, s_s), t_s) from CA successfully received:" << std::endl;
	std::cout << "\t" << "Public Key (Q_ca): (" << q_ca_x << "," << q_ca_y<< ")" << std::endl; 
	std::cout << "\t" << "Server ID (I_s): " << I_s<< std::endl;
	std::cout << "\t" << "Digital Signature (r_s, s_s): (" << r_s<< "," << s_s << ")" << std::endl;
	std::cout << "\t" << "Server ID TTL (t_s): " << t_s<< std::endl;
	std::cout << std::endl;


	// Generate hash from Q_s.x , I_s and t_s
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Generate server hash from Q_s.x I_s and t_s..." << std::endl;
	hash_data(q_x, I_s, t_s, e_s);
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Server hash successfully generated!" << std::endl;
	std::cout << "\t" << "e_s = H(Q_s.x, I_s, t_s): " << e_s<< std::endl;
	std::cout << std::endl;


	/// 2.Stage: Mutual Authentification towards the client
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Send public key (Q_s) to client:" << std::endl;
	pack_data(q_x, q_y, 0, 0, 0, 0, p_data_client);
	send_data_client(p_data_client);
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Public key successfully sent!" << std::endl;
	std::cout << std::endl;


	// Warte auf Public Key des Clients
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Wait for public key (Q_u) and nonce (g_u) of the client ..." << std::endl;
	receive_data_client(p_data_client);
	sc_biguint<160> q_u_x;
	sc_biguint<160> q_u_y;
	sc_biguint<160> g_u;
	unpack_data(p_data_client, q_u_x, q_u_y, g_u, d0, d1, d2);
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Public key (Q_u) and nonce (g_u) successfully received:" << std::endl;
	std::cout << "\t" << "Public Key (Q_u): (" << q_u_x << "," << q_u_y<< ")" << std::endl;
	std::cout << "\t" << "Nonce (g_u): " << g_u << std::endl;
	std::cout << std::endl;

	// Berechne Q_k_x nach ECDH-Schlüsselaustausch
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Determine Q_k = d_s * Q_u after ECDH key exchange!" << std::endl;
	sc_biguint<160> q_k_x;
	sc_biguint<160> q_k_y;
	pt_multiply(q_u_x, q_u_y, d_s, q_k_x, q_k_y);
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Q_k successfully computed" << std::endl;
	std::cout << "\t" << "(Q_k): (" << q_k_x << "," << q_k_y<< ")" << std::endl;
	std::cout << std::endl;	


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

	//  Encrypt data with symmetric XOR encryption using Q_k_x
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Encrypt data with symmetric XOR encryption, C_0 = E(Q_k_x,(e_s, (r_s, s_s), t_s, g_u, g_s) )" << std::endl;	
	sc_biguint<160> e_s_enc=e_s;
	sc_biguint<160> r_s_enc=r_s;
	sc_biguint<160> s_s_enc=s_s;
	sc_biguint<160> t_s_enc=t_s;
	sc_biguint<160> g_u_enc=g_u;
	sc_biguint<160> g_s_enc=g_s;
	symmetric_data_encryption(q_k_x, e_s_enc, r_s_enc, s_s_enc, t_s_enc, g_u_enc, g_s_enc);
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Data successfully encrypted!" << std::endl;
	std::cout << std::endl;		


	// Send encrypted data to client
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Send C_0 to client:" << std::endl;
	pack_data(e_s_enc, r_s_enc, s_s_enc, t_s_enc, g_u_enc, g_s_enc, p_data_client);
	send_data_client(p_data_client);
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "C_0 successfully sent!" << std::endl;
	std::cout << std::endl;


	// Receive encrypted data from client
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Wait for C_1 from client ..." << std::endl;
	receive_data_client(p_data_client);
	sc_biguint<160> e_u;
	sc_biguint<160> r_u;
	sc_biguint<160> s_u;
	sc_biguint<160> t_u;
	//sc_biguint<160> g_u;
	sc_biguint<160> g_s_u;
	unpack_data(p_data_client, e_u, r_u, s_u, t_u, g_u, g_s_u);
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "C_1 successfully received:" << std::endl;


	// decrypt client data
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Decrypt C_1 from client ..." << std::endl;
	symmetric_data_encryption(q_k_x, e_u, r_u, s_u, t_u, g_u, g_s_u);
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Data successfully decrypted!" << std::endl;
	std::cout << "\t" << "e_u: " << e_u << std::endl;
	std::cout << "\t" << "(r_u, s_u): (" << r_u<< "," << s_u << ")" << std::endl;
	std::cout << "\t" << "t_u: " << t_u<< std::endl;
	std::cout << "\t" << "g_u: " << g_u << std::endl;
	std::cout << "\t" << "g_s (sent): " << g_s_u << std::endl;	


	// Prüfe, ob g_s von Client mit generierter g_s übereinstimmt
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Check whether generated g_s is identical to g_s received from client ..." << std::endl;
	if(g_s!=g_s_u){
		std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Generated g_s is NOT identical with g_s received from client." << std::endl;
		std::cout << "\t" << "g_s (locally generated): " << g_s << std::endl;
		std::cout << "\t" << "g_s (from client): " << g_s_u << std::endl;
		std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Authentification failed!" << std::endl;		
		sc_stop();
	}

	else {

		std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Generated g_s is identical with g_s received from client." << std::endl;
		std::cout << "\t" << "g_s (locally generated): " << g_s<< std::endl;
		std::cout << "\t" << "g_s (from client): " << g_s_u<< std::endl;
		std::cout << std::endl;
	}


	// Verify digital signature of the client with respect to his hash
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Verifying digital signature of the clients with hash ..." << std::endl;
	bool verification_result = ecdsa_verify(e_u, r_u, s_u);
	if(verification_result==false){
		std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Digital signature of the client is invalid!" << std::endl;
		std::cout << "\t" << "e_u: " << e_u << std::endl;
		std::cout << "\t" << "(r_u, s_u): (" << r_u << "," << s_u << ")" << std::endl;
		std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Authentification failed!" << std::endl;		
		sc_stop();
	}
	else {
		std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Digital signature of the client is valid!" << std::endl;
		std::cout << "\t" << "e_u: " << e_u << std::endl;
		std::cout << "\t" << "(r_u, s_u): (" << r_u << "," << s_u << ")" << std::endl;
		std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "Authentification successful!" << std::endl;		
		std::cout << std::endl;	
	}

	// compute session key
	std::cout << "[" << sc_time_stamp() << "]" << "[Server]" << ": " << "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() << "]" << "[Server]" << ": " << "Session key k_m successfully computed:" << std::endl;
	std::cout << "\t" << "k_m: " << k_m << std::endl;
	std::cout << std::endl;	

}

void Server::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 Server::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 Server::send_data_ca(sc_biguint<960> p_data){
	out_ca->write(p_data);
}

void Server::receive_data_ca(sc_biguint<960> &p_data){
	//sc_time latency(300, "SC_MS");
	//wait(latency);
	in_ca->read(p_data);
}

void Server::send_data_client(sc_biguint<960> p_data){
	out_client->write(p_data);
}

void Server::receive_data_client(sc_biguint<960> &p_data){
	//sc_time latency(300, "SC_MS");
	//wait(latency);
	in_client->read(p_data);
}


void Server::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();
	//hash = (first + second + third) % field_param_m;
}

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

void Server::symmetric_data_encryption(sc_biguint<160> q_k_s, 
										sc_biguint<160> &e_s_enc, 
										sc_biguint<160> &r_s_enc, 
										sc_biguint<160> &s_s_enc, 
										sc_biguint<160> &t_s_enc, 
										sc_biguint<160> &g_u_enc, 
										sc_biguint<160> &g_s_enc)
{

	xor_key_in_fifo.write(q_k_s);
	xor_data_in_fifo.write(e_s_enc);
	e_s_enc = xor_data_out_fifo.read(); 

	xor_key_in_fifo.write(q_k_s);
	xor_data_in_fifo.write(r_s_enc);
	r_s_enc = xor_data_out_fifo.read(); 

	xor_key_in_fifo.write(q_k_s);
	xor_data_in_fifo.write(s_s_enc);
	s_s_enc = xor_data_out_fifo.read(); 

	xor_key_in_fifo.write(q_k_s);
	xor_data_in_fifo.write(t_s_enc);
	t_s_enc = xor_data_out_fifo.read(); 

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

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

void Server::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 Server::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 Server::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 Server::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;
}
