/*
 *  Copyright (c) 2014, Oculus VR, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  RakNet License.txt file in the licenses directory of this source tree. An additional grant 
 *  of patent rights can be found in the RakNet Patents.txt file in the same directory.
 *
 */

#include "SecurityFunctionsTest.h"

/*
Description:

Tests:
virtual void RakPeerInterface::AddToSecurityExceptionList  	(  	const char *   	 ip  	 )   	
virtual void RakPeerInterface::AddToBanList  	(  	const char *   	 IP, 		TimeMS  	milliseconds = 0	  	) 	
virtual void RakPeerInterface::GetIncomingPassword  	(  	char *   	 passwordData, 		int *  	passwordDataLength	  	) 
virtual void RakPeerInterface::InitializeSecurity  	(  	const char *   	 pubKeyE, 		const char *  	pubKeyN, 		const char *  	privKeyP, 		const char *  	privKeyQ	  	) 			
virtual bool RakPeerInterface::IsBanned  	(  	const char *   	 IP  	 )   	 
virtual bool RakPeerInterface::IsInSecurityExceptionList  	(  	const char *   	 ip  	 )  
virtual void RakPeerInterface::RemoveFromSecurityExceptionList  	(  	const char *   	 ip  	 )  
virtual void RakPeerInterface::RemoveFromBanList  	(  	const char *   	 IP  	 )   
virtual void RakPeerInterface::SetIncomingPassword  	(  	const char *   	 passwordData, 		int  	passwordDataLength	  	) 	
virtual void 	ClearBanList (void)=0

Success conditions:
All functions pass tests.

Failure conditions:
Any function fails test.

Client connects with no password
Client connects with wrong password
Client failed to connect with correct password
Client was banned but connected anyways
GetIncomingPassword returned wrong password
IsBanned does not show localhost as banned
Localhost was not unbanned
Client failed to connect after banlist removal
Client failed to connect after banlist removal with clear function
Client did not connect encrypted
Client connected encrypted but shouldn't have
IsInSecurityExceptionList does not register localhost addition

RakPeerInterface Functions used, tested indirectly by its use:
Startup
SetMaximumIncomingConnections
Receive
DeallocatePacket
Send
IsConnected
GetStatistics

RakPeerInterface Functions Explicitly Tested:
SetIncomingPassword  
GetIncomingPassword  
AddToBanList
IsBanned
RemoveFromBanList  
ClearBanList 
InitializeSecurity  //Disabled because of RakNetStatistics changes 		 		
AddToSecurityExceptionList  //Disabled because of RakNetStatistics changes 		   	 
IsInSecurityExceptionList //Disabled because of RakNetStatistics changes 	  	  
RemoveFromSecurityExceptionList //Disabled because of RakNetStatistics changes 	  

*/
int SecurityFunctionsTest::RunTest(DataStructures::List<RakString> params,bool isVerbose,bool noPauses)
{

	char thePassword[]="password";
	server=RakPeerInterface::GetInstance();

	client=RakPeerInterface::GetInstance();

	client->Startup(1,&SocketDescriptor(),1);
	server->Startup(1,&SocketDescriptor(60000,0),1);
	server->SetMaximumIncomingConnections(1);
	server->SetIncomingPassword(thePassword,(int)strlen(thePassword));

	char returnedPass[22];
	int returnedLen=22;
	server->GetIncomingPassword(returnedPass,&returnedLen);
	returnedPass[returnedLen]=0;//Password is a data block convert to null terminated string to make the test easier

	if (strcmp(returnedPass,thePassword)!=0)
	{
		if (isVerbose)
		{

			printf("%s was returned but %s is the password\n",returnedPass,thePassword);
			DebugTools::ShowError("GetIncomingPassword returned wrong password\n",!noPauses && isVerbose,__LINE__,__FILE__);
		}
		return 5;
	}

	SystemAddress serverAddress;

	serverAddress.SetBinaryAddress("127.0.0.1");
	serverAddress.port=60000;
	TimeMS entryTime=GetTimeMS();

	if (isVerbose)
		printf("Testing if  no password is rejected\n");

	while(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true)&&GetTimeMS()-entryTime<5000)
	{

		if(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true,true,true,true))
		{
			client->Connect("127.0.0.1",serverAddress.port,0,0);
		}

		RakSleep(100);

	}

	if (CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true))
	{
		if (isVerbose)
			DebugTools::ShowError("Client connected with no password\n",!noPauses && isVerbose,__LINE__,__FILE__);
		return 1;
	}

	if (isVerbose)
		printf("Testing if incorrect password is rejected\n");

	char badPass[]="badpass";
	entryTime=GetTimeMS();
	while(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true)&&GetTimeMS()-entryTime<5000)
	{

		if(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true,true,true,true))
		{
			client->Connect("127.0.0.1",serverAddress.port,badPass,(int)strlen(badPass));
		}

		RakSleep(100);

	}

	if (CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true))
	{
		if (isVerbose)
			DebugTools::ShowError("Client connected with wrong password\n",!noPauses && isVerbose,__LINE__,__FILE__);
		return 2;
	}

	if (isVerbose)
		printf("Testing if correct password is accepted\n");

	entryTime=GetTimeMS();
	while(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true)&&GetTimeMS()-entryTime<5000)
	{

		if(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true,true,true,true))
		{
			client->Connect("127.0.0.1",serverAddress.port,thePassword,(int)strlen(thePassword));
		}

		RakSleep(100);

	}

	if (!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true))
	{
		if (isVerbose)
			DebugTools::ShowError("Client failed to connect with correct password\n",!noPauses && isVerbose,__LINE__,__FILE__);
		return 3;
	}

	while(CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true,true,true,true))//disconnect client
	{

		client->CloseConnection (serverAddress,true,0,LOW_PRIORITY); 
	}

	if (isVerbose)
		printf("Testing if connection is rejected after adding to ban list\n");

	server->AddToBanList("127.0.0.1",0);

	entryTime=GetTimeMS();
	while(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true)&&GetTimeMS()-entryTime<5000)
	{

		if(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true,true,true,true))
		{
			client->Connect("127.0.0.1",serverAddress.port,thePassword,(int)strlen(thePassword));
		}

		RakSleep(100);

	}

	if(!server->IsBanned("127.0.0.1"))
	{

		if (isVerbose)
			DebugTools::ShowError("IsBanned does not show localhost as banned\n",!noPauses && isVerbose,__LINE__,__FILE__);
		return 6;

	}

	if (CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true))
	{
		if (isVerbose)
			DebugTools::ShowError("Client was banned but connected anyways\n",!noPauses && isVerbose,__LINE__,__FILE__);
		return 4;
	}

	if (isVerbose)
		printf("Testing if connection is accepted after ban removal by RemoveFromBanList\n");

	server->RemoveFromBanList("127.0.0.1");
	if(server->IsBanned("127.0.0.1"))
	{

		if (isVerbose)
			DebugTools::ShowError("Localhost was not unbanned\n",!noPauses && isVerbose,__LINE__,__FILE__);
		return 7;

	}

	entryTime=GetTimeMS();
	while(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true)&&GetTimeMS()-entryTime<5000)
	{

		if(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true,true,true,true))
		{
			client->Connect("127.0.0.1",serverAddress.port,thePassword,(int)strlen(thePassword));
		}

		RakSleep(100);

	}

	if (!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true))
	{
		if (isVerbose)
			DebugTools::ShowError("Client failed to connect after banlist removal\n",!noPauses && isVerbose,__LINE__,__FILE__);
		return 8;
	}

	while(CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true,true,true,true))//disconnect client
	{

		client->CloseConnection (serverAddress,true,0,LOW_PRIORITY); 
	}

	if (isVerbose)
		printf("Testing if connection is rejected after adding to ban list\n");

	server->AddToBanList("127.0.0.1",0);

	entryTime=GetTimeMS();
	while(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true)&&GetTimeMS()-entryTime<5000)
	{

		if(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true,true,true,true))
		{
			client->Connect("127.0.0.1",serverAddress.port,thePassword,(int)strlen(thePassword));
		}

		RakSleep(100);

	}

	if(!server->IsBanned("127.0.0.1"))
	{

		if (isVerbose)
			DebugTools::ShowError("IsBanned does not show localhost as banned\n",!noPauses && isVerbose,__LINE__,__FILE__);
		return 6;

	}

	if (CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true))
	{
		if (isVerbose)
			DebugTools::ShowError("Client was banned but connected anyways\n",!noPauses && isVerbose,__LINE__,__FILE__);
		return 4;
	}

	if (isVerbose)
		printf("Testing if connection is accepted after ban removal by ClearBanList\n");

	server->ClearBanList();
	if(server->IsBanned("127.0.0.1"))
	{

		if (isVerbose)
			DebugTools::ShowError("Localhost was not unbanned\n",!noPauses && isVerbose,__LINE__,__FILE__);
		return 7;

	}

	entryTime=GetTimeMS();
	while(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true)&&GetTimeMS()-entryTime<5000)
	{

		if(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true,true,true,true))
		{
			client->Connect("127.0.0.1",serverAddress.port,thePassword,(int)strlen(thePassword));
		}

		RakSleep(100);

	}

	if (!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true))
	{
		if (isVerbose)
			DebugTools::ShowError("Client failed to connect after banlist removal with clear function\n",!noPauses && isVerbose,__LINE__,__FILE__);
		return 9;
	}

	while(CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true,true,true,true))//disconnect client
	{

		client->CloseConnection (serverAddress,true,0,LOW_PRIORITY); 
	}

/*//Disabled because of statistics changes

	if (isVerbose)
		printf("Testing InitializeSecurity on server\n");

	//-----------------------------

	// RSACrypt is a using namespace RakNet;
class that handles RSA encryption/decryption internally

	RSACrypt rsacrypt;

	uint32_t e;
	uint32_t modulus[RAKNET_RSA_FACTOR_LIMBS];

	uint32_t p[RAKNET_RSA_FACTOR_LIMBS/2],q[RAKNET_RSA_FACTOR_LIMBS/2];

	printf("Generating %i bit key. This will take a while...\n", RAKNET_RSA_FACTOR_LIMBS*32);
	rsacrypt.generatePrivateKey(RAKNET_RSA_FACTOR_LIMBS);
	e=rsacrypt.getPublicExponent();
	rsacrypt.getPublicModulus(modulus);
	rsacrypt.getPrivateP(p);
	rsacrypt.getPrivateQ(q);

	RakPeerInterface::DestroyInstance(server);
	server=RakPeerInterface::GetInstance();

	server->InitializeSecurity(0,0,(char*)p, (char*)q);
	server->Startup(1,30,&SocketDescriptor(60000,0),1);
	server->SetMaximumIncomingConnections(1);
	server->SetIncomingPassword(thePassword,strlen(thePassword));

	if (isVerbose)
		printf("Testing if client connects encrypted\n");

	entryTime=GetTimeMS();
	while(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true)&&GetTimeMS()-entryTime<5000)
	{

		if(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true,true,true,true))
		{
			client->Connect("127.0.0.1",serverAddress.port,thePassword,strlen(thePassword));
		}

		RakSleep(100);

	}

	char str2[]="AAAAAAAAAA";
	str2[0]=(char)(ID_USER_PACKET_ENUM+1);
	client->Send(str2,(int) strlen(str2)+1, HIGH_PRIORITY, RELIABLE_ORDERED ,0, UNASSIGNED_SYSTEM_ADDRESS, true);
	client->Send(str2,(int) strlen(str2)+1, HIGH_PRIORITY, RELIABLE_ORDERED ,0, UNASSIGNED_SYSTEM_ADDRESS, true);

	Packet *packet;
	entryTime=GetTimeMS();
	while(GetTimeMS()-entryTime<1000)
	{
		for (packet=server->Receive(); packet;server->DeallocatePacket(packet), packet=server->Receive())
		{

		}
	}

	RakNetStatistics *rss;

	rss=client->GetStatistics(serverAddress);

	if (rss->encryptionBitsSent<=0)//If we did connect encrypted we should see encryptionBitsSent
	{
		if (isVerbose)
			DebugTools::ShowError("Client did not connect encrypted\n",!noPauses && isVerbose,__LINE__,__FILE__);
		return 10;
	}

	while(CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true,true,true,true))//disconnect client
	{

		client->CloseConnection (serverAddress,true,0,LOW_PRIORITY); 
	}

	//Destroy to clear statistics
	RakPeerInterface::DestroyInstance(client);

	client=RakPeerInterface::GetInstance();

	client->Startup(1,30,&SocketDescriptor(),1);

	if (isVerbose)
		printf("Testing AddToSecurityExceptionList client should connect without encryption\n");

	server->AddToSecurityExceptionList("127.0.0.1");

	if (!server->IsInSecurityExceptionList("127.0.0.1"))
	{
		if (isVerbose)
			DebugTools::ShowError("IsInSecurityExceptionList does not register localhost addition\n",!noPauses && isVerbose,__LINE__,__FILE__);
		return 12;
	}

	entryTime=GetTimeMS();
	while(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true)&&GetTimeMS()-entryTime<5000)
	{

		if(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true,true,true,true))
		{
			client->Connect("127.0.0.1",serverAddress.port,thePassword,strlen(thePassword));
		}

		RakSleep(100);

	}

	str2[0]=(char)(ID_USER_PACKET_ENUM+1);
	client->Send(str2,(int) strlen(str2)+1, HIGH_PRIORITY, RELIABLE_ORDERED ,0, UNASSIGNED_SYSTEM_ADDRESS, true);
	client->Send(str2,(int) strlen(str2)+1, HIGH_PRIORITY, RELIABLE_ORDERED ,0, UNASSIGNED_SYSTEM_ADDRESS, true);

	//	Packet *packet;

	entryTime=GetTimeMS();
	while(GetTimeMS()-entryTime<1000)
	{
		for (packet=server->Receive(); packet;server->DeallocatePacket(packet), packet=server->Receive())
		{

		}
	}

	rss=client->GetStatistics(serverAddress);

	if (rss->encryptionBitsSent>0)//If we did connect encrypted we should see encryptionBitsSent
	{
		if (isVerbose)
			DebugTools::ShowError("Client connected encrypted but shouldn't have\n",!noPauses && isVerbose,__LINE__,__FILE__);
		return 11;
	}

	if (isVerbose)
		printf("Testing RemoveFromSecurityExceptionList\n");

	while(CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true,true,true,true))//disconnect client
	{

		client->CloseConnection (serverAddress,true,0,LOW_PRIORITY); 
	}

	server->RemoveFromSecurityExceptionList("127.0.0.1");

	if (isVerbose)
		printf("Testing if client connects encrypted\n");

	entryTime=GetTimeMS();
	while(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true)&&GetTimeMS()-entryTime<5000)
	{

		if(!CommonFunctions::ConnectionStateMatchesOptions (client,serverAddress,true,true,true,true))
		{
			client->Connect("127.0.0.1",serverAddress.port,thePassword,strlen(thePassword));
		}

		RakSleep(100);

	}

	str2[0]=(char)(ID_USER_PACKET_ENUM+1);
	client->Send(str2,(int) strlen(str2)+1, HIGH_PRIORITY, RELIABLE_ORDERED ,0, UNASSIGNED_SYSTEM_ADDRESS, true);
	client->Send(str2,(int) strlen(str2)+1, HIGH_PRIORITY, RELIABLE_ORDERED ,0, UNASSIGNED_SYSTEM_ADDRESS, true);

	entryTime=GetTimeMS();
	while(GetTimeMS()-entryTime<1000)
	{
		for (packet=server->Receive(); packet;server->DeallocatePacket(packet), packet=server->Receive())
		{

		}
	}

	rss=client->GetStatistics(serverAddress);

	if (rss->encryptionBitsSent<=0)//If we did connect encrypted we should see encryptionBitsSent
	{
		if (isVerbose)
			DebugTools::ShowError("Client did not connect encrypted\n",!noPauses && isVerbose,__LINE__,__FILE__);
		return 10;
	}

*/

	return 0;

}

RakString SecurityFunctionsTest::GetTestName()
{

	return "SecurityFunctionsTest";

}

RakString SecurityFunctionsTest::ErrorCodeToString(int errorCode)
{

	switch (errorCode)
	{

	case 0:
		return "No error";
		break;

	case 1:
		return "Client connected with no password";
		break;

	case 2:
		return "Client connected with wrong password";
		break;

	case 3:
		return "Client failed to connect with correct password";
		break;

	case 4:
		return "Client was banned but connected anyways";
		break;

	case 5:
		return "GetIncomingPassword returned wrong password";
		break;

	case 6:
		return "IsBanned does not show localhost as banned";
		break;

	case 7:
		return "Localhost was not unbanned";
		break;

	case 8:
		return "Client failed to connect after banlist removal";
		break;

	case 9:
		return "Client failed to connect after banlist removal with clear function";
		break;

	case 10:
		return "Client did not connect encrypted";
		break;

	case 11:
		return "Client connected encrypted but shouldn't have";
		break;

	case 12:
		return "IsInSecurityExceptionList does not register localhost addition";
		break;

	default:
		return "Undefined Error";
	}

}

SecurityFunctionsTest::SecurityFunctionsTest(void)
{
}

SecurityFunctionsTest::~SecurityFunctionsTest(void)
{
}

void SecurityFunctionsTest::DestroyPeers()
{

RakPeerInterface::DestroyInstance(client);
RakPeerInterface::DestroyInstance(server);

}
