//this file is part of eMule
//Copyright (C)2002 Merkur ( devs@emule-project.net / http://www.emule-project.net )
//
//This program 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.
//
//This program 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., 675 Mass Ave, Cambridge, MA 02139, USA.
#include "stdafx.h"
#include <math.h>
#include "emule.h"
#include "ClientCredits.h"
#include "OtherFunctions.h"
#include "Preferences.h"
#include "SafeFile.h"
#include "Opcodes.h"
#include "Packets.h" // NEO: MC - [MetCredits] <-- Xanatos --
#include "KnownFile.h" // NEO: NCS - [NeoCreditSystem] <-- Xanatos --
#include "ServerConnect.h"
#pragma warning(disable:4516) // access-declarations are deprecated; member using-declarations provide a better alternative
#pragma warning(disable:4244) // conversion from 'type1' to 'type2', possible loss of data
#pragma warning(disable:4100) // unreferenced formal parameter
#include <crypto51/base64.h>
#include <crypto51/osrng.h>
#include <crypto51/files.h>
#include <crypto51/sha.h>
#pragma warning(default:4100) // unreferenced formal parameter
#pragma warning(default:4244) // conversion from 'type1' to 'type2', possible loss of data
#pragma warning(default:4516) // access-declarations are deprecated; member using-declarations provide a better alternative
#include "emuledlg.h"
#include "Log.h"
#include "Neo/EMBackup.h" // NEO: NB - [NeoBackup] <-- Xanatos --// NEO: OCS - [OtherCreditSystems]
// NEO: OCS - [OtherCreditSystems] -- Xanatos -->
#include "SharedFileList.h" 
#include "CLientList.h" 
#include "UpDownClient.h" 
// NEO: OCS END <-- Xanatos --
#ifdef ISPBYPASS // NEO: SCC - [SecureClinetConnection] -- Xanatos -->
#include "Neo/Functions.h"
#endif // ISPBYPASS // NEO: SCC END <-- Xanatos --
#include "ClientList.h" // NEO: XCTA - [XmanExtenedCreditTableArragement] <-- Xanatos --


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define CLIENTS_MET_FILENAME	_T("clients.met")

// NEO: MC - [MetCredits] -- Xanatos -->
CClientCredits::CClientCredits(CFileDataIO* file)
{
	m_pCredits = new CreditStruct;
	memset(m_pCredits, 0, sizeof(CreditStruct));

	file->ReadHash16(m_pCredits->abyKey);

	UINT tagcount = file->ReadUInt32();
	for (UINT j = 0; j < tagcount; j++){
		CTag* newtag = new CTag(file, false);
		switch(newtag->GetNameID()){
			case CT_UPLOADEDLO:{
				if (newtag->IsInt())
					((uint32*)&m_pCredits->nUploaded)[0] = newtag->GetInt();
				delete newtag;
				break;
			}
			case CT_UPLOADEDHI:{
				if (newtag->IsInt())
					((uint32*)&m_pCredits->nUploaded)[1] = newtag->GetInt();
				delete newtag;
				break;
			}
			case CT_DOWNLOADEDLO:{
				if (newtag->IsInt())
					((uint32*)&m_pCredits->nDownloaded)[0] = newtag->GetInt();
				delete newtag;
				break;
			}
			case CT_DOWNLOADEDHI:{
				if (newtag->IsInt())
					((uint32*)&m_pCredits->nDownloaded)[1] = newtag->GetInt();
				delete newtag;
				break;
			}
			case CT_LASTSEEN:{
				if (newtag->IsInt())
					m_pCredits->nLastSeen = newtag->GetInt();
				delete newtag;
				break;
			}
			case CT_SECUREIDENT:{
				if (newtag->IsStr()){
					int len = newtag->GetStr().GetLength();
					if (len <= 2*MAXPUBKEYSIZE && ((len & 1) == 0)){
						m_pCredits->nKeySize = (uint8)(len >> 1);
						DecodeBase16(newtag->GetStr(), len, m_pCredits->abySecureIdent, sizeof(m_pCredits->abySecureIdent));
					}
				}
				delete newtag;
				break;
			}
			// NEO: SQ - [SaveUploadQueue]
			case CT_SECUREWAITTIME:{
				if (newtag->IsInt())
					m_pCredits->nSecuredWaitTime = newtag->GetInt();
				delete newtag;
				break;
			}
			case CT_UNSECUREWAITTIME:{
				if (newtag->IsInt())
					m_pCredits->nUnSecuredWaitTime = newtag->GetInt();
				delete newtag;
				break;
			}
			// NEO: SQ END
			// NEO: NCS - [NeoCreditSystem]
			case CT_DELTALO:{
				if (newtag->IsInt())
					((uint32*)&m_pCredits->nDelta)[0] = newtag->GetInt();
				delete newtag;
				break;
			}
			case CT_DELTAHI:{
				if (newtag->IsInt())
					((uint32*)&m_pCredits->nDelta)[1] = newtag->GetInt();
				delete newtag;
				break;
			}
			// NEO: NCS END
			default:{
				taglist.Add(newtag);
			}
		}
	}

	InitalizeIdent();
	m_dwUnSecureWaitTime = 0;
	m_dwSecureWaitTime = 0;
	m_dwWaitTimeIP = 0;
	m_bmarktodelete = false; // NEO: XCTA - [XmanExtenedCreditTableArragement]
}
// NEO: MC END <-- Xanatos --

CClientCredits::CClientCredits(CreditStruct_30d* in_credits) // NEO: MC - [MetCredits] <-- Xanatos --
{
	// NEO: MC - [MetCredits] -- Xanatos -->
	m_pCredits = new CreditStruct;
	memset(m_pCredits, 0, sizeof(CreditStruct));
	md4cpy(m_pCredits->abyKey, in_credits->abyKey);
	m_pCredits->nUploaded = (((uint64)in_credits->nUploadedHi)<<32)+in_credits->nUploadedLo;
	m_pCredits->nDownloaded = (((uint64)in_credits->nDownloadedHi)<<32)+in_credits->nDownloadedLo;
	m_pCredits->nLastSeen = in_credits->nLastSeen;
	m_pCredits->nKeySize = in_credits->nKeySize;
	memcpy(m_pCredits->abySecureIdent, in_credits->abySecureIdent, MAXPUBKEYSIZE);
	// NEO: MC END <-- Xanatos --
	InitalizeIdent();
	m_dwUnSecureWaitTime = 0;
	m_dwSecureWaitTime = 0;
	m_dwWaitTimeIP = 0;
	m_bmarktodelete = false; // NEO: XCTA - [XmanExtenedCreditTableArragement] <-- Xanatos --
}

CClientCredits::CClientCredits(const uchar* key)
{
	m_pCredits = new CreditStruct;
	memset(m_pCredits, 0, sizeof(CreditStruct));
	md4cpy(m_pCredits->abyKey, key);
	InitalizeIdent();
	m_dwUnSecureWaitTime = ::GetTickCount();
	m_dwSecureWaitTime = ::GetTickCount();
	m_dwWaitTimeIP = 0;
	m_bmarktodelete = false; // NEO: XCTA - [XmanExtenedCreditTableArragement] <-- Xanatos --
}

CClientCredits::~CClientCredits()
{
	ClearTags(); // NEO: MC - [MetCredits] <-- Xanatos --
	delete m_pCredits;
}

// NEO: MC - [MetCredits] -- Xanatos -->
bool CClientCredits::IsEmpty() {
	return !m_pCredits->nUploaded && !m_pCredits->nDownloaded && !m_pCredits->nKeySize 
		&& !m_pCredits->nSecuredWaitTime && !m_pCredits->nUnSecuredWaitTime // NEO: SQ - [SaveUploadQueue]
		&& !m_pCredits->nDelta;	// NEO: NCS - [NeoCreditSystem]
}

void CClientCredits::WriteToFile(CFileDataIO* file) {
	file->WriteHash16(m_pCredits->abyKey);

	uint32 uTagCount = 0;
	ULONG uTagCountFilePos = (ULONG)file->GetPosition();
	file->WriteUInt32(uTagCount);

	// standard tags
	if (m_pCredits->nUploaded) {
		CTag(CT_UPLOADEDLO, ((uint32*)&m_pCredits->nUploaded)[0]).WriteTagToFile(file);
		CTag(CT_UPLOADEDHI, ((uint32*)&m_pCredits->nUploaded)[1]).WriteTagToFile(file);
		uTagCount += 2;
	}
	if (m_pCredits->nDownloaded) {
		CTag(CT_DOWNLOADEDLO, ((uint32*)&m_pCredits->nDownloaded)[0]).WriteTagToFile(file);
		CTag(CT_DOWNLOADEDHI, ((uint32*)&m_pCredits->nDownloaded)[1]).WriteTagToFile(file);
		uTagCount += 2;
	}
	CTag(CT_LASTSEEN, m_pCredits->nLastSeen).WriteTagToFile(file);
	uTagCount++;

	// secure id
	if (m_pCredits->nKeySize) {
		CTag(CT_SECUREIDENT, EncodeBase16(m_pCredits->abySecureIdent, m_pCredits->nKeySize)).WriteTagToFile(file);
		uTagCount++;
	}

	// NEO: SQ - [SaveUploadQueue]
	if (m_pCredits->nSecuredWaitTime) {
		CTag(CT_SECUREWAITTIME, m_pCredits->nSecuredWaitTime).WriteTagToFile(file);
		uTagCount++;
	}
	if (m_pCredits->nUnSecuredWaitTime) {
		CTag(CT_UNSECUREWAITTIME, m_pCredits->nUnSecuredWaitTime).WriteTagToFile(file);
		uTagCount++;
	}
	// NEO: SQ END

	// NEO: NCS - [NeoCreditSystem]
	if (m_pCredits->nUploaded) {
		CTag(CT_DELTALO, ((uint32*)&m_pCredits->nDelta)[0]).WriteTagToFile(file);
		CTag(CT_DELTAHI, ((uint32*)&m_pCredits->nDelta)[1]).WriteTagToFile(file);
		uTagCount += 2;
	}
	// NEO: NCS END

	// unidentified tags(from mods/future versions)
	for (int j = 0; j < taglist.GetCount(); j++) {
		taglist[j]->WriteTagToFile(file);
		uTagCount++;
	}

	file->Seek(uTagCountFilePos, CFile::begin);
	file->WriteUInt32(uTagCount);
	file->Seek(0, CFile::end);
}

void CClientCredits::ClearTags()
{
	for (int i = 0; i < taglist.GetSize(); i++)
		delete taglist[i];
	taglist.RemoveAll();
}
// NEO: MC END <-- Xanatos --

void CClientCredits::AddDownloaded(uint32 bytes, uint32 dwForIP, bool bHaveFile) { // NEO: NCS - [NeoCreditSystem] <-- Xanatos --
	m_nDownloaded += bytes; // NEO: NCS - [NeoCreditSystem] <-- Xanatos --
	if ( ( GetCurrentIdentState(dwForIP) == IS_IDFAILED || GetCurrentIdentState(dwForIP) == IS_IDBADGUY || GetCurrentIdentState(dwForIP) == IS_IDNEEDED) && theApp.clientcredits->CryptoAvailable() ){
		return;
	}
	// NEO: NCS - [NeoCreditSystem] -- Xanatos -->
	if(bHaveFile)
		m_pCredits->nDelta -= bytes;
	// NEO: NCS END <-- Xanatos --
	m_pCredits->nDownloaded += bytes; // NEO: MC - [MetCredits] <-- Xanatos --
}

void CClientCredits::AddUploaded(uint32 bytes, uint32 dwForIP, bool bHaveFile) { // NEO: NCS - [NeoCreditSystem] <-- Xanatos --
	m_nUploaded += bytes; // NEO: NCS - [NeoCreditSystem] <-- Xanatos --
	if ( ( GetCurrentIdentState(dwForIP) == IS_IDFAILED || GetCurrentIdentState(dwForIP) == IS_IDBADGUY || GetCurrentIdentState(dwForIP) == IS_IDNEEDED) && theApp.clientcredits->CryptoAvailable() ){
		return;
	}
	// NEO: NCS - [NeoCreditSystem] -- Xanatos -->
	if(bHaveFile)
		m_pCredits->nDelta += bytes;
	// NEO: NCS END <-- Xanatos --
	m_pCredits->nUploaded += bytes;	// NEO: MC - [MetCredits] <-- Xanatos --
}

uint64	CClientCredits::GetUploadedTotal() const{
	return m_pCredits->nUploaded; // NEO: MC - [MetCredits] <-- Xanatos --
}

uint64	CClientCredits::GetDownloadedTotal() const{
	return m_pCredits->nDownloaded; // NEO: MC - [MetCredits] <-- Xanatos --
}

// NEO: NCS - [NeoCreditSystem] -- Xanatos -->
uint32	CClientCredits::GetUploadedSession() const{
	return m_nUploaded;
} 

uint32	CClientCredits::GetDownloadedSession() const{
	return m_nDownloaded;
}

sint64	CClientCredits::GetTransferDelta(bool bSession, bool bNeg) const{
	if(bSession){
		sint32 delta = m_nUploaded - m_nDownloaded;
		return bNeg ? (-1*delta) : (delta);
	}

	return bNeg ? (-1*m_pCredits->nDelta) : (m_pCredits->nDelta); // NEO: MC - [MetCredits]
}

/* Xanatos:
* I generally dislike the idea of a credit system, it is very bad for rare/small files.
* Users that don't have files which you want will be discriminated,
* even if they are super uploaders, but for you they don't have wanted files.
*
* So the only fair credit system works just when both users want something from each other !!!
* And don't punish users who don't have wanted files !!!
*
* The idea is to give equal and fair score for all,
* and punish only the users that download from you
* but don't upload wanted files.
*
*/

float CClientCredits::GetNeoScoreRatio(bool bHaveFile, uint32 dwForIP) const
{
	if(!bHaveFile)
		return 1.0F;

	bool bSessionOnly = false;
	// check the client ident status
	if ( ( GetCurrentIdentState(dwForIP) == IS_IDFAILED || GetCurrentIdentState(dwForIP) == IS_IDBADGUY || GetCurrentIdentState(dwForIP) == IS_IDNEEDED) && theApp.clientcredits->CryptoAvailable() ){
		// bad guy - no credits for you
		bSessionOnly = true;
	}

	float result = 1.0F;
	if (GetTransferDelta(bSessionOnly) > 1){
		result = (float)(( (PARTSIZE * bSessionOnly ? 2 : 4 ) / GetTransferDelta(bSessionOnly) ) ^ 2);
		
		if (result > 1.0F) 
			result = 1.0F;
		else if (result < 0.1F)
			result = 0.1F;
	}else if (GetTransferDelta(bSessionOnly) < -1){
		result = (float)(( GetTransferDelta(bSessionOnly,true) / (PARTSIZE * bSessionOnly ? 1 : 2) ) ^ 2);
		
		if (result > 2.0F) 
			result = 2.0F;
		else if (result < 1.0F)
			result = 1.0F;
	}
	
	return result;
}
// NEO: NCS <-- Xanatos --

float CClientCredits::GetScoreRatio(uint32 dwForIP) const
{
	// check the client ident status
	//if ( ( GetCurrentIdentState(dwForIP) == IS_IDFAILED || GetCurrentIdentState(dwForIP) == IS_IDBADGUY || GetCurrentIdentState(dwForIP) == IS_IDNEEDED) && theApp.clientcredits->CryptoAvailable() ){
	//	// bad guy - no credits for you
	//	return 1.0F;
	//}

	// NEO: OCS - [OtherCreditSystems]
	EIdentState currentIDstate =  GetCurrentIdentState(dwForIP);
	switch (thePrefs.GetCreditSystem())
	{
	case 0: // Official
		{
			if(currentIDstate != IS_IDENTIFIED  && currentIDstate != IS_NOTAVAILABLE && theApp.clientcredits->CryptoAvailable())
				return 1.0F;

			if (GetDownloadedTotal() < 1000000)
				return 1.0F;
			float result = 0.0F;
			if (!GetUploadedTotal())
				result = 10.0F;
			else
				result = (float)(((double)GetDownloadedTotal()*2.0)/(double)GetUploadedTotal());
			float result2 = 0.0F;
			result2 = (float)(GetDownloadedTotal()/1048576.0);
			result2 += 2.0F;
			result2 = (float)sqrt(result2);

			if (result > result2)
				result = result2;

			if (result < 1.0F)
				return 1.0F;
			else if (result > 10.0F)
				return 10.0F;
			return result;
		}
	case 1: // Eastshare
		{
			if(currentIDstate != IS_IDENTIFIED  && currentIDstate != IS_NOTAVAILABLE && theApp.clientcredits->CryptoAvailable())
				return 1.0F;

			float result = (IdentState == IS_NOTAVAILABLE) ? 80.0F : 100.0F;
			result += (float)((double)GetDownloadedTotal()/174762.67 - (double)GetUploadedTotal()/524288);
			
			if ((double)GetDownloadedTotal() > 1048576) {
				result += 100; 
				if (result<50 && ((double)GetDownloadedTotal()*10 > (double)GetUploadedTotal())) result=50;
			}

			if ( result < 10 )
				return 10;
			if ( result > 5000 )
				return 5000;
			return result / 100;
		}
	case 2: // Lovelace
		{
			if(currentIDstate != IS_IDENTIFIED  && currentIDstate != IS_NOTAVAILABLE && theApp.clientcredits->CryptoAvailable())
				return 1.0F;

			double cl_up = GetUploadedTotal()/(double)1048576;
			double cl_down = GetDownloadedTotal()/(double)1048576;
			float result=(float)(3.0 * cl_down * cl_down - cl_up * cl_up);
			if(result < -13000.0f)
				return 0.0f;
			if (fabs(result)>20000.0f) 
				result*=20000.0f/fabs(result);
			result=100.0f*powf((float)(1-1/(1.0f+expf(result*0.001f))),6.6667f);

			if (result < 0.1f) 
				return 0.1f;
			if (result > 10.0f && IdentState == IS_NOTAVAILABLE)
				return 10.0f;
			return result;
		}
	case 3: // Pawcio
		{
			if(currentIDstate != IS_IDENTIFIED  && currentIDstate != IS_NOTAVAILABLE && theApp.clientcredits->CryptoAvailable())
				return 1.0F;

			if ((GetDownloadedTotal() < 1000000)&&(GetUploadedTotal() > 1000000))
				return 1.0f;
			else if ((GetDownloadedTotal() < 1000000)&&(GetUploadedTotal()<1000000))
				return 3.0f;

			float result = 0.0F;
			if (GetUploadedTotal()<1000000)
				result = 10.0f * GetDownloadedTotal()/1000000.0f;
			else
				result = (float)(GetDownloadedTotal()*3)/GetUploadedTotal();
			if ((GetDownloadedTotal() > 100000000)&&(GetUploadedTotal()<GetDownloadedTotal()+8000000)&&(result<50)) result=50;
			else if ((GetDownloadedTotal() > 50000000)&&(GetUploadedTotal()<GetDownloadedTotal()+5000000)&&(result<25)) result=25;
			else if ((GetDownloadedTotal() > 25000000)&&(GetUploadedTotal()<GetDownloadedTotal()+3000000)&&(result<12)) result=12;
			else if ((GetDownloadedTotal() > 10000000)&&(GetUploadedTotal()<GetDownloadedTotal()+2000000)&&(result<5)) result=5;

			if (result > 100.0f)
				return 100.0f;
			if (result < 1.0f)
				return 1.0f;
			return result;
		}
	case 4: // Peace
		{
			if(currentIDstate != IS_IDENTIFIED  && currentIDstate != IS_NOTAVAILABLE && theApp.clientcredits->CryptoAvailable())
				return 1.0F;

			uint64 DownloadedTotal = GetDownloadedTotal();
			if (DownloadedTotal < 1000000)
				return 1.0f;

			uint64 UploadedTotal = GetUploadedTotal();

			float result = !UploadedTotal ? 10.0f : (float)(2*DownloadedTotal/(float)UploadedTotal);

			float result2 = (float)sqrt(2.0 + (double)DownloadedTotal/1048576.0);

			if (result > result2)
				result = result2;

			if (result > 10.0f)
				return 10.0f;
			if (result < 1.0f)
				return 1.0f;
			return result;
		}
	case 5: // RT
		{
			if(currentIDstate != IS_IDENTIFIED  && currentIDstate != IS_NOTAVAILABLE && theApp.clientcredits->CryptoAvailable())
				return 1.0F;

			float UploadedTotalMB = (float)GetUploadedTotal() / 1048576.0f;
			float DownloadedTotalMB = (float)GetDownloadedTotal() / 1048576.0f;
			if (DownloadedTotalMB <= 1){
				if (UploadedTotalMB <= 1)
					return 1.0F;
				else
					return (float)(1 / sqrt((double)UploadedTotalMB) );
			}

			if (UploadedTotalMB > 1){
				float Basic = (float)sqrt( (double)(DownloadedTotalMB + 1) );
				if (DownloadedTotalMB > UploadedTotalMB){
					return ( Basic + (float)sqrt((double)(DownloadedTotalMB - UploadedTotalMB)) );
				}
				else{
					if ( (UploadedTotalMB - DownloadedTotalMB) <= 1 )   return Basic;
					float Result = ( Basic / (float)sqrt((double)(UploadedTotalMB - DownloadedTotalMB)) );
					if (DownloadedTotalMB >= 9){
						float Lowest = 0.7f + (Basic / 10);
						if (Result < Lowest)   Result = Lowest;
					}
					else{
						if (Result < 1)   Result = DownloadedTotalMB / 9;
					}
					/*RT only
					if ( (thePrefs.GetMaxCredit1Slot() > 0) && (Result > 1) ){
					if ( (UploadedTotalMB - DownloadedTotalMB) > (Basic * 2) )   Result = 1;
					}*/
					return Result;
				}
			}else
				return DownloadedTotalMB;
		}
	case 6: // Sivka
		{
			switch(currentIDstate)
			{
			case IS_IDNEEDED: if(theApp.clientcredits->CryptoAvailable()) return 0.75f;
			case IS_IDFAILED: return 0.5f;
			case IS_IDBADGUY:
				return 0.0f;
			}

			if(GetDownloadedTotal() > GetUploadedTotal())
			{
				const uint64 diffTransfer = GetDownloadedTotal() - GetUploadedTotal() + 1048576;

				if( diffTransfer >= 1073741824 ) // >= 1024MB
					return 32.0f;

				return sqrtf((float)diffTransfer/1048576.0f);
			}
			else
				return 1.0f;
		}
	case 7: // Swat
		{
			if(currentIDstate != IS_IDENTIFIED  && currentIDstate != IS_NOTAVAILABLE && theApp.clientcredits->CryptoAvailable())
				return 1.0F;

			if (GetDownloadedTotal() < 1048576) //pcsl999
				return 1.0F;
			float result = 0.0F;
			if (!GetUploadedTotal())
				result = 10.0F;
			else
				result = (float)(((double)GetDownloadedTotal()*2.2)/(double)GetUploadedTotal()); //pcsl999
			float result2 = 0.0F;
			result2 = (float)GetDownloadedTotal()/1048576.0f;
			result2 += 2.0F;
			result2 = (float)sqrt((double)result2);

			if (result > result2)
				result = result2;

			if (result < 1)
				return 1.0F;
			else if (result > 100) //pcsl999
				return 100.0F; //pcsl999
			return result;
		}
	case 8: // Tk4
		{
			CUpDownClient* client = theApp.clientlist->FindClientByIP(dwForIP);

			float result = 10.0F;
			//if SUI failed then credit starts at 10 as for everyone else but will not go up
			if((currentIDstate == IS_IDFAILED || currentIDstate == IS_IDBADGUY || currentIDstate == IS_IDNEEDED) && theApp.clientcredits->CryptoAvailable()){
				//CUpDownClient* pClient = theApp.clientlist->FindClientByIP(dwForIP);//Get 'client' so we can get file info
				float dOwnloadedSessionTotal = (float)client->GetTransferredDown();
				float uPloadedSessionTotal = (float)client->GetTransferredUp();
				float allowance = dOwnloadedSessionTotal/4.0F;
				if(uPloadedSessionTotal > (float)(dOwnloadedSessionTotal + allowance + 1048576.0F)){
					CKnownFile* file = theApp.sharedfiles->GetFileByID(client->GetUploadFileID());
					if(file!=NULL){//Are they requesting a file? NULL can be produced when client details calls getscoreratio() without this line eMule will crash.
						if(file->IsPartFile()){//It's a file we are trying to obtain so we want to give to givers so we may get the file quicker.
							float MbSqd =sqrt((float)(uPloadedSessionTotal-(dOwnloadedSessionTotal + allowance))/1048576.0F);
							if(MbSqd > 9.0F) result = 9.0F / MbSqd;  //above 81mb values 1 - 0 9/(9 - x)
							else result = 10.0F - MbSqd; //for the first 81Mb (10 -(0-9))
						}

					}
				}
				return result; //partfile 10 - 0.14 complete 10
			}
			//float is 1e38 it should be sufficient given 1 Gig is 1e9 hence 1000G is 1e12....
			float dOwnloadedTotal = (float)GetDownloadedTotal();//(Given to us)
			float uPloadedTotal = (float)GetUploadedTotal(); //(Taken from us)
			/* Base allowance for a client that has given us nothing is 1Mb
			But if someone has give us 100Mb and take 130Mb they should not be penalized as someone who has give 0Mb and taken 30Mb?
			So if you've given 100Mb and taken 130Mb you will only be penalized for 5Mb*/
			float allowance = dOwnloadedTotal/4.0F; //reward uploaders with 1 Mb allowance for every 4Mb uploaded over what they have uploaded.
			if(uPloadedTotal>(float)(dOwnloadedTotal + allowance + 1048576.0F)) //If they have taken above (1Mb + 'allowance')
			{/*They may owe us, is it on a file we want or a completed file we are sharing. If it's a completed file progrssively lowering someone score
			 who cannot pay us back could make it very difficult for them to complete the file esp. if it's rare and we hold one of the few complete copies, better for everyone if
			 we share completed files based on time waited + any credit thay have for giving us stuff.
			 If the files a partfile we are trying to get the modifier will start to get smaller -1 to -90Mb range 9 to 1 beyond that 1 to 0 eg: -400Mb = 0.452839 */
				//CUpDownClient* pClient = theApp.clientlist->FindClientByIP(dwForIP);//Get 'client' so we can get file info
				CKnownFile* file = theApp.sharedfiles->GetFileByID(client->GetUploadFileID());
				if(file!=NULL){//Are they requesting a file? NULL can be produced when client details calls getscoreratio() without this line eMule will crash.
					if(file->IsPartFile()){//It's a file we are trying to obtain so we want to give to givers so we may get the file quicker.
						float MbSqd =sqrt((float)(uPloadedTotal-(dOwnloadedTotal + allowance))/1048576.0F);
						if(MbSqd > 9.0F) result = 9.0F / MbSqd;  //above 81mb values 1 - 0 9/(9 - x)
						else		 result = 10.0F - MbSqd; //for the first 81Mb (10 -(0-9))
					}
				}
			} else //We may owe them :o) give a small proportional boost to an uploader
				if(dOwnloadedTotal>uPloadedTotal){ // result =  log(2.72 + (given - taken in Mb * 4)) + given in bytes / 12Mb (eg +1 for every 12Mb +.5  6Mb etc)
					result+=log(2.72F+(float)(dOwnloadedTotal-uPloadedTotal)/262144.0F)+(float)(dOwnloadedTotal/12582912.0F);
				}
			return result;
		}
	case 9: // Xtreme
		{
			if((GetCurrentIdentState(dwForIP) == IS_IDFAILED || GetCurrentIdentState(dwForIP) == IS_IDBADGUY || GetCurrentIdentState(dwForIP) == IS_IDNEEDED) && theApp.clientcredits->CryptoAvailable() == true){
				// bad guy - no credits for you
				//return 1.0f;
				return 0.8f; //Xman 80% for non SUI-clients.. (and also bad guys)
			}

			CUpDownClient* client = theApp.clientlist->FindClientByIP(dwForIP);

			#define PENALTY_UPSIZE 8388608 //8 MB
			// Cache value
			const uint64 downloadTotal = GetDownloadedTotal();

			float m_bonusfaktor = 0.0F;
			// Check if this client has any credit (sent >1.65MB)
			const float difference2=(float)client->GetTransferredUp() - client->GetTransferredDown();	
			if(downloadTotal < 1650000)
			{	
				if ( difference2 > (2*PENALTY_UPSIZE))
					m_bonusfaktor=(-0.2f);
				else if (difference2 > PENALTY_UPSIZE)
					m_bonusfaktor=(-0.1f);
				else
					m_bonusfaktor=0;

				return (1.0f + m_bonusfaktor);
			}

			// Cache value
			const uint64 uploadTotal = GetUploadedTotal();

			// Bonus Faktor calculation
			float difference = (float)downloadTotal - uploadTotal;
			if (difference>=0)
			{
				m_bonusfaktor=difference/10485760.0f - (1.5f/(downloadTotal/10485760.0f));  //pro MB difference 0.1 - pro MB download 0.1
				if (m_bonusfaktor<0)
					m_bonusfaktor=0;
			}
			else 
			{
				difference=abs(difference);
				if (difference> (2*PENALTY_UPSIZE) && difference2 > (2*PENALTY_UPSIZE))
					m_bonusfaktor=(-0.2f);
				else if (difference>PENALTY_UPSIZE && difference2 > PENALTY_UPSIZE)
					m_bonusfaktor=(-0.1f);
				else
					m_bonusfaktor=0;
			}
			// Factor 1
			float result = (uploadTotal == 0) ?
				10.0f : (float)(2*downloadTotal)/(float)uploadTotal;

			// Factor 2
			//Xman slightly changed to use linear function until half of chunk is transferred
			float trunk;
			if(downloadTotal < 4718592)  //half of a chunk and a good point to keep the function consistent
				trunk = (float)(1.0 + (double)downloadTotal/(1048576.0*3.0));
			else
				trunk = (float)sqrt(2.0 + (double)downloadTotal/1048576.0);
			//Xman end


			if(result>10.0f)
			{
				result=10.0f;
				m_bonusfaktor=0;
			}
			else
				result += m_bonusfaktor;
			if(result>10.0f)
			{
				m_bonusfaktor -= (result-10.0f);
				result=10.0f;
			}

			if(result > trunk)
			{
				result = trunk;
				m_bonusfaktor=0;
			}

			// Trunk final result 1..10
			if(result < 1.0f)
				return (1.0f + m_bonusfaktor );
			if (result > 10.0f)
				return 10.0f;
			return result;
		}
	case 10: // Zzul
		{
			if(currentIDstate != IS_IDENTIFIED  && currentIDstate != IS_NOTAVAILABLE && theApp.clientcredits->CryptoAvailable())
				return 1.0F;

			if (GetDownloadedTotal() < 1)
				return 1.0F;
			float result = 0.0F;
			if (!GetUploadedTotal())
				result = 10.0F;
			else
				result = (float)(((double)GetDownloadedTotal()*2.0)/(double)GetUploadedTotal());
			float result2 = 0.0F;
			result2 = (float)(GetDownloadedTotal()/1048576.0);
			result2 += 1.0F;
			result2 = (float)sqrt(result2);

			if (result > result2)
				result = result2;

			if (result < 1.0F)
				return 1.0F;
			else if (result > 10.0F)
				return 10.0F;
			return result;
		}
	default:
		ASSERT(0);
		return 1.0F;
	}
	// NEO: OCS END
}

#ifdef ARGOS // NEO: NA - [NeoArgos] -- Xanatos -->
float CClientCredits::GetArgosScoreRatio(uint32 dwForIP) const
{
	// check the client ident status
	if ( ( GetCurrentIdentState(dwForIP) == IS_IDFAILED || GetCurrentIdentState(dwForIP) == IS_IDBADGUY || GetCurrentIdentState(dwForIP) == IS_IDNEEDED) && theApp.clientcredits->CryptoAvailable() ){
		// bad guy - no credits for you
		return 1.0F;
	}

	if (GetDownloadedTotal() < GetUploadedTotal()) // When he uploaded les than he downloaded no compensation for him
		return 1.0F;

	float result = 0.0F;
	if (!GetUploadedTotal()) // When he uploaded something to us, may be only one byte
		result = (GetDownloadedTotal() >= PARTSIZE) ? 10.0F : 1.0F; // dont recompensate untill we get one chunk, to make an exploit hareder
	else 
		result = (float)(((double)GetDownloadedTotal()*3.0)/((double)GetUploadedTotal()*2)); // Mor equal credit calculation

	float result2 = 0.0F;
	result2 = ((float)(GetDownloadedTotal() - GetUploadedTotal())/MB2B(1)); // Take the difference, so when the leecher uploaded 100 MB to us more then downloaded from us
	result2 = (float)sqrt(result2); // give him a full compensation even if he is a leecher but as long as we have downloaded much more from him then he from us dont punish him

	result = max(result,result2); // Take the biger, see comment of result2

	if (result < 1.0F)
		return 1.0F;
	else if (result > 10.0F)
		return 10.0F;
	return result;
}
#endif // ARGOS // NEO: NA END <-- Xanatos --

// NEO: RSR - [RemoteScoreRatio] -- Xanatos -->
float CClientCredits::GetRemoteScoreRatio() const
{
	float result = 0.0F;
	if (GetUploadedTotal() < 1000000)
		return 1.0F;
	if (!GetDownloadedTotal())
		result = 10.0F;
	else
		result = (float)(((double)GetUploadedTotal()*2.0F)/(double)GetDownloadedTotal());
	float result2 = 0.0F;
	result2 = (float)GetUploadedTotal()/1048576.0F;
	result2 += 2.0F;
	result2 = (float)sqrt(result2);

	if (result > result2)
		result = result2;

	if (result < 1.0F)
		return 1.0F;
	else if (result > 10.0F)
		return 10.0F;
	return result;

}
// NEO: RSR END <-- Xanatos --

CClientCreditsList::CClientCreditsList()
{
	m_nLastSaved = ::GetTickCount();
	// NEO: NB - [NeoBackup] -- Xanatos -->
retry: 
	if (!LoadList())
		if (theApp.BackupEngine->RestoreFile(ClientsMet)){
			ClearList();
			goto retry; // Not very nice but it works
		}
	// NEO: NB END <-- Xanatos --
	
	InitalizeCrypting();
}

// NEO: NB - [NeoBackup] -- Xanatos -->
void CClientCreditsList::ClearList()
{
	CClientCredits* cur_credit;
	CCKey tmpkey(0);
	POSITION pos = m_mapClients.GetStartPosition();
	while (pos){
		m_mapClients.GetNextAssoc(pos, tmpkey, cur_credit);
		delete cur_credit;
	}
	m_mapClients.RemoveAll();
}
// NEO: NB END <-- Xanatos --

CClientCreditsList::~CClientCreditsList()
{
	theApp.UpdateSplash(GetResString(IDS_X_SS_E_CC)); // NEO: SS - [SplashScreen] <-- Xanatos --
	SaveList();
	theApp.UpdateSplash(GetResString(IDS_X_SS_E_FS)); // NEO: SS - [SplashScreen] <-- Xanatos --

	ClearList(); // NEO: NB - [NeoBackup] <-- Xanatos --
	delete m_pSignkey;
#ifdef ISPBYPASS // NEO: SCC - [SecureClinetConnection] -- Xanatos -->
	delete m_pDecryptor;
#endif // ISPBYPASS // NEO: SCC END <-- Xanatos --
}

bool CClientCreditsList::LoadList() // NEO: NB - [NeoBackup] <-- Xanatos --
{
	CString strFileName = thePrefs.GetConfigDir() + CLIENTS_MET_FILENAME;
	const int iOpenFlags = CFile::modeRead|CFile::osSequentialScan|CFile::typeBinary|CFile::shareDenyWrite;
	CSafeBufferedFile file;
	CFileException fexp;
	if (!file.Open(strFileName, iOpenFlags, &fexp)){
		if (fexp.m_cause != CFileException::fileNotFound){
			CString strError(GetResString(IDS_ERR_LOADCREDITFILE));
			TCHAR szError[MAX_CFEXP_ERRORMSG];
			if (fexp.GetErrorMessage(szError, ARRSIZE(szError))){
				strError += _T(" - ");
				strError += szError;
			}
			LogError(LOG_STATUSBAR, _T("%s"), strError);
			theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_UNRECOVERABLE); // NEO: NB - [NeoBackup] <-- Xanatos --
			return false;
		}
		theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_RECOVERABLE); // NEO: NB - [NeoBackup] <-- Xanatos --
		return false;
	}
	setvbuf(file.m_pStream, NULL, _IOFBF, 16384);
	
	CClientCredits* newcredits = NULL; // NEO: MC - [MetCredits] <-- Xanatos --

	try{
		uint8 version = file.ReadUInt8();
		if (version != MET_HEADER && version != CREDITFILE_VERSION && version != CREDITFILE_VERSION_29){ // NEO: MC - [MetCredits] <-- Xanatos --
			LogWarning(GetResString(IDS_ERR_CREDITFILEOLD));
			file.Close();
			theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_RECOVERABLE); // NEO: NB - [NeoBackup] <-- Xanatos --
			return false;
		}

		// everything is ok, lets see if the backup exist...
		CString strBakFileName;
		strBakFileName.Format(_T("%s") CLIENTS_MET_FILENAME _T(".bak"), thePrefs.GetConfigDir());
		
		DWORD dwBakFileSize = 0;
		BOOL bCreateBackup = TRUE;

		HANDLE hBakFile = ::CreateFile(strBakFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
										OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
		if (hBakFile != INVALID_HANDLE_VALUE)
		{
			// Ok, the backup exist, get the size
			dwBakFileSize = ::GetFileSize(hBakFile, NULL); //debug
			if (dwBakFileSize > (DWORD)file.GetLength())
			{
				// the size of the backup was larger then the org. file, something is wrong here, don't overwrite old backup..
				bCreateBackup = FALSE;
			}
			//else: backup is smaller or the same size as org. file, proceed with copying of file
			::CloseHandle(hBakFile);
		}
		//else: the backup doesn't exist, create it

		if (bCreateBackup)
		{
			file.Close(); // close the file before copying

			if (!::CopyFile(strFileName, strBakFileName, FALSE))
				LogError(GetResString(IDS_ERR_MAKEBAKCREDITFILE));

			// reopen file
			CFileException fexp;
			if (!file.Open(strFileName, iOpenFlags, &fexp)){
				CString strError(GetResString(IDS_ERR_LOADCREDITFILE));
				TCHAR szError[MAX_CFEXP_ERRORMSG];
				if (fexp.GetErrorMessage(szError, ARRSIZE(szError))){
					strError += _T(" - ");
					strError += szError;
				}
				LogError(LOG_STATUSBAR, _T("%s"), strError);
				theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_UNRECOVERABLE); // NEO: NB - [NeoBackup] <-- Xanatos --
				return false;
			}
			setvbuf(file.m_pStream, NULL, _IOFBF, 16384);
			file.Seek(1, CFile::begin); //set filepointer behind file version byte
		}

		UINT count = file.ReadUInt32();
		m_mapClients.InitHashTable(count+5000); // TODO: should be prime number... and 20% larger

		const uint32 dwExpired = time(NULL) - 12960000; // today - 150 day
		uint32 cDeleted = 0;
		for (UINT i = 0; i < count; i++){
			// NEO: MC - [MetCredits] -- Xanatos -->
			if (version != MET_HEADER) {
				CreditStruct_30d* newcstruct = new CreditStruct_30d;
				memset(newcstruct, 0, sizeof(CreditStruct_30d));
				if (version == CREDITFILE_VERSION_29)
					file.Read(newcstruct, sizeof(CreditStruct_29a));
				else
					file.Read(newcstruct, sizeof(CreditStruct_30d));
				newcredits = new CClientCredits(newcstruct);
				delete newcstruct;
			}
			else
				newcredits = new CClientCredits(&file);

			if (newcredits->GetLastSeen() < dwExpired){
				cDeleted++;
				delete newcredits;
				newcredits = NULL;
				continue;
			}

			m_mapClients.SetAt(CCKey(newcredits->GetKey()), newcredits);
			newcredits = NULL;
			// NEO: MC END <-- Xanatos --
		}
		file.Close();

		if (cDeleted>0)
			AddLogLine(false, GetResString(IDS_CREDITFILELOADED) + GetResString(IDS_CREDITSEXPIRED), count-cDeleted,cDeleted);
		else
			AddLogLine(false, GetResString(IDS_CREDITFILELOADED), count);
	}
	catch(CFileException* error){
		if (error->m_cause == CFileException::endOfFile){
			LogError(LOG_STATUSBAR, GetResString(IDS_CREDITFILECORRUPT));
			theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_RECOVERABLE); // NEO: NB - [NeoBackup] <-- Xanatos --
		}else{
			TCHAR buffer[MAX_CFEXP_ERRORMSG];
			error->GetErrorMessage(buffer, ARRSIZE(buffer));
			LogError(LOG_STATUSBAR, GetResString(IDS_ERR_CREDITFILEREAD), buffer);
			theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_UNRECOVERABLE); // NEO: NB - [NeoBackup] <-- Xanatos --
		}
		error->Delete();
		// NEO: MC - [MetCredits] -- Xanatos -->
		if (newcredits)
			delete newcredits;
		// NEO: MC END <-- Xanatos --
		return false;
	}

	theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_CORRECT); // NEO: NB - [NeoBackup] <-- Xanatos --
	return true;
}

void CClientCreditsList::SaveList()
{
	if (thePrefs.GetLogFileSaving())
		AddDebugLogLine(false, _T("Saving clients credit list file \"%s\""), CLIENTS_MET_FILENAME);
	m_nLastSaved = ::GetTickCount();

	CString name = thePrefs.GetConfigDir() + CLIENTS_MET_FILENAME;
	CSafeBufferedFile file;	// NEO: MC - [MetCredits] <-- Xanatos --
	CFileException fexp;
	if (!file.Open(name, CFile::modeWrite|CFile::modeCreate|CFile::typeBinary|CFile::shareDenyWrite, &fexp)){
		CString strError(GetResString(IDS_ERR_FAILED_CREDITSAVE));
		TCHAR szError[MAX_CFEXP_ERRORMSG];
		if (fexp.GetErrorMessage(szError, ARRSIZE(szError))){
			strError += _T(" - ");
			strError += szError;
		}
		LogError(LOG_STATUSBAR, _T("%s"), strError);
		return;
	}

	// NEO: MC - [MetCredits] -- Xanatos -->
	setvbuf(file.m_pStream, NULL, _IOFBF, 16384);

	CClientCredits* cur_credit;
	CCKey tempkey(0);
	POSITION pos;
	// NEO: MC END <-- Xanatos --

	try{
		// NEO: MC - [MetCredits] -- Xanatos -->
		file.WriteUInt8(MET_HEADER);
		
		uint32 uTagCount = 0;
		ULONG uTagCountFilePos = (ULONG)file.GetPosition();
		file.WriteUInt32(uTagCount);

		pos = m_mapClients.GetStartPosition();
		while (pos)
		{
			m_mapClients.GetNextAssoc(pos, tempkey, cur_credit);
			if (!cur_credit->IsEmpty()){
				cur_credit->WriteToFile(&file);
				uTagCount++;
			}
		}

		file.Seek(uTagCountFilePos, CFile::begin);
		file.WriteUInt32(uTagCount);
		file.Seek(0, CFile::end);
		// NEO: MC END <-- Xanatos --

		if (thePrefs.GetCommitFiles() >= 2 || (thePrefs.GetCommitFiles() >= 1 && !theApp.emuledlg->IsRunning()))
			file.Flush();
		file.Close();
	}
	catch(CFileException* error){
		CString strError(GetResString(IDS_ERR_FAILED_CREDITSAVE));
		TCHAR szError[MAX_CFEXP_ERRORMSG];
		if (error->GetErrorMessage(szError, ARRSIZE(szError))){
			strError += _T(" - ");
			strError += szError;
		}
		LogError(LOG_STATUSBAR, _T("%s"), strError);
		error->Delete();
	}

}

CClientCredits* CClientCreditsList::GetCredit(const uchar* key)
{
	CClientCredits* result;
	CCKey tkey(key);
	if (!m_mapClients.Lookup(tkey, result)){
		result = new CClientCredits(key);
		m_mapClients.SetAt(CCKey(result->GetKey()), result);
	}
	result->SetLastSeen();

	result->UnMarkToDelete(); // NEO: XCTA - [XmanExtenedCreditTableArragement] <-- Xanatos --
	return result;
}

void CClientCreditsList::Process()
{
	// NEO: XCTA - [XmanExtenedCreditTableArragement] -- Xanatos -->
	if (::GetTickCount() - m_nLastSaved > MIN2MS(39))
	{
		CClientCredits* cur_credit;
		CCKey tmpkey(0);
		POSITION pos = m_mapClients.GetStartPosition();
		while (pos){
			m_mapClients.GetNextAssoc(pos, tmpkey, cur_credit);

			if(cur_credit->GetMarkToDelete() && (time(NULL) - cur_credit->GetLastSeen() > CREDIT_CLEANUP_TIME)) //not seen for > 3 hours
			{
				//two security-checks, it can happen that there is a second user using this hash
				if(cur_credit->IsEmpty() && theApp.clientlist->FindClientByUserHash(cur_credit->GetKey())==NULL)
				{
					//this key isn't longer used
					m_mapClients.RemoveKey(CCKey(cur_credit->GetKey()));
					delete cur_credit;
				}
				else
					cur_credit->UnMarkToDelete();
			}
		}
	}
	// NEO: XCTA END  <-- Xanatos --

	if (::GetTickCount() - m_nLastSaved > MIN2MS(13))
		SaveList();
}

void CClientCredits::InitalizeIdent()
{
	// NEO: NCS - [NeoCreditSystem] -- Xanatos -->
	m_nUploaded = 0; 
	m_nDownloaded = 0;
	// NEO: NCS END <-- Xanatos --

	if (m_pCredits->nKeySize == 0 ){
		memset(m_abyPublicKey,0,MAXPUBKEYSIZE); // for debugging
		m_nPublicKeyLen = 0;
		IdentState = IS_NOTAVAILABLE;
	}
	else{
		m_nPublicKeyLen = m_pCredits->nKeySize;
		memcpy(m_abyPublicKey, m_pCredits->abySecureIdent, m_nPublicKeyLen);
		IdentState = IS_IDNEEDED;
	}
	m_dwCryptRndChallengeFor = 0;
	m_dwCryptRndChallengeFrom = 0;
	m_dwIdentIP = 0;
}

void CClientCredits::Verified(uint32 dwForIP)
{
	m_dwIdentIP = dwForIP;
	// client was verified, copy the keyto store him if not done already
	if (m_pCredits->nKeySize == 0){
		m_pCredits->nKeySize = m_nPublicKeyLen; 
		memcpy(m_pCredits->abySecureIdent, m_abyPublicKey, m_nPublicKeyLen);
		if (GetDownloadedTotal() > 0){
			// for security reason, we have to delete all prior credits here
			// NEO: MC - [MetCredits] -- Xanatos -->
			m_pCredits->nDownloaded = 0;
			m_pCredits->nUploaded = 0;
			// NEO: MC END <-- Xanatos --
			if (thePrefs.GetVerbose())
				DEBUG_ONLY(AddDebugLogLine(false, _T("Credits deleted due to new SecureIdent")));
		}
	}
	IdentState = IS_IDENTIFIED;
}

bool CClientCredits::SetSecureIdent(const uchar* pachIdent, uint8 nIdentLen)  // verified Public key cannot change, use only if there is not public key yet
{
	if (MAXPUBKEYSIZE < nIdentLen || m_pCredits->nKeySize != 0 )
		return false;
	memcpy(m_abyPublicKey,pachIdent, nIdentLen);
	m_nPublicKeyLen = nIdentLen;
	IdentState = IS_IDNEEDED;
	return true;
}

EIdentState	CClientCredits::GetCurrentIdentState(uint32 dwForIP) const
{
	if (IdentState != IS_IDENTIFIED)
		return IdentState;
	else{
		if (dwForIP == m_dwIdentIP)
			return IS_IDENTIFIED;
		else
			return IS_IDBADGUY; 
			// mod note: clients which just reconnected after an IP change and have to ident yet will also have this state for 1-2 seconds
			//		 so don't try to spam such clients with "bad guy" messages (besides: spam messages are always bad)
	}
}

using namespace CryptoPP;

void CClientCreditsList::InitalizeCrypting()
{
	m_nMyPublicKeyLen = 0;
	memset(m_abyMyPublicKey,0,MAXPUBKEYSIZE); // not really needed; better for debugging tho
	m_pSignkey = NULL;
#ifdef ISPBYPASS // NEO: SCC - [SecureClinetConnection] -- Xanatos -->
	m_pDecryptor = NULL;
#endif // ISPBYPASS // NEO: SCC END <-- Xanatos --
	if (!thePrefs.IsSecureIdentEnabled())
		return;
	// check if keyfile is there
	bool bCreateNewKey = false;
	HANDLE hKeyFile = ::CreateFile(thePrefs.GetConfigDir() + _T("cryptkey.dat"), GENERIC_READ, FILE_SHARE_READ, NULL,
								   OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hKeyFile != INVALID_HANDLE_VALUE)
	{
		if (::GetFileSize(hKeyFile, NULL) == 0)
			bCreateNewKey = true;
		::CloseHandle(hKeyFile);
	}
	else
		bCreateNewKey = true;
	if (bCreateNewKey)
		CreateKeyPair();
	
	// load key
	try{
		// load private key
		FileSource filesource(CStringA(thePrefs.GetConfigDir() + _T("cryptkey.dat")), true,new Base64Decoder);
		m_pSignkey = new RSASSA_PKCS1v15_SHA_Signer(filesource);
#ifdef ISPBYPASS // NEO: SCC - [SecureClinetConnection] -- Xanatos -->
		FileSource filesource2(CStringA(thePrefs.GetConfigDir() + _T("cryptkey.dat")), true,new Base64Decoder);
		m_pDecryptor = new RSAES_PKCS1v15_Decryptor(filesource2);
#endif // ISPBYPASS // NEO: SCC END <-- Xanatos --
		// calculate and store public key
		RSASSA_PKCS1v15_SHA_Verifier pubkey(*m_pSignkey);
		ArraySink asink(m_abyMyPublicKey, MAXPUBKEYSIZE);
		pubkey.DEREncode(asink);
		m_nMyPublicKeyLen = (uint8)asink.TotalPutLength();
		asink.MessageEnd();
	}
	catch(...)
	{
		delete m_pSignkey;
		m_pSignkey = NULL;
#ifdef ISPBYPASS // NEO: SCC - [SecureClinetConnection] -- Xanatos -->
		delete m_pDecryptor;
		m_pDecryptor = NULL;
#endif // ISPBYPASS // NEO: SCC END <-- Xanatos --
		LogError(LOG_STATUSBAR, GetResString(IDS_CRYPT_INITFAILED));
		ASSERT(0);
	}
	//Debug_CheckCrypting();
}

bool CClientCreditsList::CreateKeyPair()
{
	try{
		AutoSeededRandomPool rng;
		InvertibleRSAFunction privkey;
		privkey.Initialize(rng,RSAKEYSIZE);

		Base64Encoder privkeysink(new FileSink(CStringA(thePrefs.GetConfigDir() + _T("cryptkey.dat"))));
		privkey.DEREncode(privkeysink);
		privkeysink.MessageEnd();

		if (thePrefs.GetLogSecureIdent())
			AddDebugLogLine(false, _T("Created new RSA keypair"));
	}
	catch(...)
	{
		if (thePrefs.GetVerbose())
			AddDebugLogLine(false, _T("Failed to create new RSA keypair"));
		ASSERT ( false );
		return false;
	}
	return true;
}

uint8 CClientCreditsList::CreateSignature(CClientCredits* pTarget, uchar* pachOutput, uint8 nMaxSize, 
										  uint32 ChallengeIP, uint8 byChaIPKind, 
										  CryptoPP::RSASSA_PKCS1v15_SHA_Signer* sigkey)
{
	// sigkey param is used for debug only
	if (sigkey == NULL)
		sigkey = m_pSignkey;

	// create a signature of the public key from pTarget
	ASSERT( pTarget );
	ASSERT( pachOutput );
	uint8 nResult;
	if ( !CryptoAvailable() )
		return 0;
	try{
		
		SecByteBlock sbbSignature(sigkey->SignatureLength());
		AutoSeededRandomPool rng;
		byte abyBuffer[MAXPUBKEYSIZE+9];
		uint32 keylen = pTarget->GetSecIDKeyLen();
		memcpy(abyBuffer,pTarget->GetSecureIdent(),keylen);
		// 4 additional bytes random data send from this client
		uint32 challenge = pTarget->m_dwCryptRndChallengeFrom;
		ASSERT ( challenge != 0 );
		PokeUInt32(abyBuffer+keylen, challenge);
		uint16 ChIpLen = 0;
		if ( byChaIPKind != 0){
			ChIpLen = 5;
			PokeUInt32(abyBuffer+keylen+4, ChallengeIP);
			PokeUInt8(abyBuffer+keylen+4+4, byChaIPKind);
		}
		sigkey->SignMessage(rng, abyBuffer ,keylen+4+ChIpLen , sbbSignature.begin());
		ArraySink asink(pachOutput, nMaxSize);
		asink.Put(sbbSignature.begin(), sbbSignature.size());
		nResult = (uint8)asink.TotalPutLength();			
	}
	catch(...)
	{
		ASSERT ( false );
		nResult = 0;
	}
	return nResult;
}

bool CClientCreditsList::VerifyIdent(CClientCredits* pTarget, const uchar* pachSignature, uint8 nInputSize, 
									 uint32 dwForIP, uint8 byChaIPKind)
{
	ASSERT( pTarget );
	ASSERT( pachSignature );
	if ( !CryptoAvailable() ){
		pTarget->IdentState = IS_NOTAVAILABLE;
		return false;
	}
	bool bResult;
	try{
		StringSource ss_Pubkey((byte*)pTarget->GetSecureIdent(),pTarget->GetSecIDKeyLen(),true,0);
		RSASSA_PKCS1v15_SHA_Verifier pubkey(ss_Pubkey);
		// 4 additional bytes random data send from this client +5 bytes v2
		byte abyBuffer[MAXPUBKEYSIZE+9];
		memcpy(abyBuffer,m_abyMyPublicKey,m_nMyPublicKeyLen);
		uint32 challenge = pTarget->m_dwCryptRndChallengeFor;
		ASSERT ( challenge != 0 );
		PokeUInt32(abyBuffer+m_nMyPublicKeyLen, challenge);
		
		// v2 security improvments (not supported by 29b, not used as default by 29c)
		uint8 nChIpSize = 0;
		if (byChaIPKind != 0){
			nChIpSize = 5;
			uint32 ChallengeIP = 0;
			switch (byChaIPKind){
				case CRYPT_CIP_LOCALCLIENT:
					ChallengeIP = dwForIP;
					break;
				case CRYPT_CIP_REMOTECLIENT:
					if (theApp.serverconnect->GetClientID() == 0 || theApp.serverconnect->IsLowID()){
						if (thePrefs.GetLogSecureIdent())
							AddDebugLogLine(false, _T("Warning: Maybe SecureHash Ident fails because LocalIP is unknown"));
						ChallengeIP = theApp.serverconnect->GetLocalIP();
					}
					else
						ChallengeIP = theApp.serverconnect->GetClientID();
					break;
				case CRYPT_CIP_NONECLIENT: // maybe not supported in future versions
					ChallengeIP = 0;
					break;
			}
			PokeUInt32(abyBuffer+m_nMyPublicKeyLen+4, ChallengeIP);
			PokeUInt8(abyBuffer+m_nMyPublicKeyLen+4+4, byChaIPKind);
		}
		//v2 end

		bResult = pubkey.VerifyMessage(abyBuffer, m_nMyPublicKeyLen+4+nChIpSize, pachSignature, nInputSize);
	}
	catch(...)
	{
		if (thePrefs.GetVerbose())
			AddDebugLogLine(false, _T("Error: Unknown exception in %hs"), __FUNCTION__);
		//ASSERT(0);
		bResult = false;
	}
	if (!bResult){
		if (pTarget->IdentState == IS_IDNEEDED)
			pTarget->IdentState = IS_IDFAILED;
	}
	else{
		pTarget->Verified(dwForIP);
	}
	return bResult;
}

#ifdef ISPBYPASS // NEO: SCC - [SecureClinetConnection] -- Xanatos -->
uint8 CClientCreditsList::DecryptData(const uchar* secureData, UINT size, uchar*& plainData)
{
	ASSERT(plainData == NULL);
	if ( !CryptoAvailable() )
		return 0;

	try{
		if(size != m_pDecryptor->FixedCiphertextLength()) // with our kay length encrypted data are always 48 bytes long
			return 0;

		AutoSeededRandomPool rng;
		plainData = new uchar[m_pDecryptor->MaxPlaintextLength(size)];
		DecodingResult result = m_pDecryptor->Decrypt(rng, secureData, size, plainData);

		if(!result.isValidCoding && result.messageLength > 0){
			delete [] plainData;
			plainData = NULL;
			return 0;
		}

		return (uint8)result.messageLength;
	}
	catch(...)
	{
		delete [] plainData;
		plainData = NULL;
		//ASSERT(0); // this habben everytime we get bad packet...
		return 0;
	}
}

uint8 CClientCreditsList::EncryptData(CClientCredits* pTarget, const uchar* plainData, UINT size, uchar*& secureData)
{
	ASSERT(secureData == NULL);
	ASSERT( pTarget );
	if ( !CryptoAvailable() )
		return 0;

	try{
		StringSource ss_Pubkey((byte*)pTarget->GetSecureIdent(),pTarget->GetSecIDKeyLen(),true,0);
		RSAES_PKCS1v15_Encryptor Encryptor(ss_Pubkey);
		ASSERT( size <= Encryptor.FixedMaxPlaintextLength());

		secureData = new uchar[Encryptor.FixedCiphertextLength()];
		AutoSeededRandomPool rng;
		Encryptor.Encrypt(rng, plainData, size, secureData);
		
		return (uint8)Encryptor.FixedCiphertextLength();
	}
	catch(...)
	{
		delete secureData;
		secureData = NULL;
		ASSERT(0);
		return 0;
	}
}
#endif // ISPBYPASS // NEO: SCC END <-- Xanatos --

bool CClientCreditsList::CryptoAvailable()
{
	return (m_nMyPublicKeyLen > 0 && m_pSignkey != 0 && thePrefs.IsSecureIdentEnabled() );
}


#ifdef _DEBUG
bool CClientCreditsList::Debug_CheckCrypting()
{
	// create random key
	AutoSeededRandomPool rng;

	RSASSA_PKCS1v15_SHA_Signer priv(rng, 384);
	RSASSA_PKCS1v15_SHA_Verifier pub(priv);

	byte abyPublicKey[MAXPUBKEYSIZE];
	ArraySink asink(abyPublicKey, MAXPUBKEYSIZE);
	pub.DEREncode(asink);
	uint8 PublicKeyLen = (uint8)asink.TotalPutLength();
	asink.MessageEnd();
	uint32 challenge = rand();
	// create fake client which pretends to be this emule
	CreditStruct_30d* newcstruct = new CreditStruct_30d; // NEO: MC - [MetCredits] <-- Xanatos --
	memset(newcstruct, 0, sizeof(CreditStruct_30d)); // NEO: MC - [MetCredits] <-- Xanatos --
	CClientCredits* newcredits = new CClientCredits(newcstruct);
	newcredits->SetSecureIdent(m_abyMyPublicKey,m_nMyPublicKeyLen);
	newcredits->m_dwCryptRndChallengeFrom = challenge;
	// create signature with fake priv key
	uchar pachSignature[200];
	memset(pachSignature,0,200);
	uint8 sigsize = CreateSignature(newcredits,pachSignature,200,0,false, &priv);


	// next fake client uses the random created public key
	CreditStruct_30d* newcstruct2 = new CreditStruct_30d; // NEO: MC - [MetCredits] <-- Xanatos --
	memset(newcstruct2, 0, sizeof(CreditStruct_30d)); // NEO: MC - [MetCredits] <-- Xanatos --
	CClientCredits* newcredits2 = new CClientCredits(newcstruct2);
	newcredits2->m_dwCryptRndChallengeFor = challenge;

	// if you uncomment one of the following lines the check has to fail
	//abyPublicKey[5] = 34;
	//m_abyMyPublicKey[5] = 22;
	//pachSignature[5] = 232;

	newcredits2->SetSecureIdent(abyPublicKey,PublicKeyLen);

	//now verify this signature - if it's true everything is fine
	bool bResult = VerifyIdent(newcredits2,pachSignature,sigsize,0,0);

	delete newcredits;
	delete newcredits2;

	return bResult;
}
#endif
uint32 CClientCredits::GetSecureWaitStartTime(uint32 dwForIP)
{
	if (m_dwUnSecureWaitTime == 0 || m_dwSecureWaitTime == 0)
		SetSecWaitStartTime(dwForIP);

	if (m_pCredits->nKeySize != 0){	// this client is a SecureHash Client
		if (GetCurrentIdentState(dwForIP) == IS_IDENTIFIED){ // good boy
			return m_dwSecureWaitTime;
		}
		else{	// not so good boy
			if (dwForIP == m_dwWaitTimeIP){
				return m_dwUnSecureWaitTime;
			}
			else{	// bad boy
				// this can also happen if the client has not identified himself yet, but will do later - so maybe he is not a bad boy :) .
				// NEO: MOD -- Xanatos -- // ReEnabled this log entry
				if (thePrefs.GetLogSecureIdent())
					AddDebugLogLine(false,_T("Warning: WaitTime resetted due to Invalid Ident for Userhash %s"),DbgGetHashTypeString(m_pCredits->abyKey));
				
				m_dwUnSecureWaitTime = ::GetTickCount();
				m_dwWaitTimeIP = dwForIP;
				return m_dwUnSecureWaitTime;
			}	
		}
	}
	else{	// not a SecureHash Client - handle it like before for now (no security checks)
		return m_dwUnSecureWaitTime;
	}
}

void CClientCredits::SetSecWaitStartTime(uint32 dwForIP)
{
	// NEO: SQ - [SaveUploadQueue] -- Xanatos -->
	if(thePrefs.SaveUploadQueueWaitTime()){
		m_dwUnSecureWaitTime = ::GetTickCount() - m_pCredits->nUnSecuredWaitTime - 1 ;
		m_dwSecureWaitTime = ::GetTickCount() - m_pCredits->nSecuredWaitTime - 1;
	}else{
		m_dwUnSecureWaitTime = ::GetTickCount()-1;
		m_dwSecureWaitTime = ::GetTickCount()-1;
	}
	// NEO: SQ END <-- Xanatos --
	m_dwWaitTimeIP = dwForIP;
}

void CClientCredits::ClearWaitStartTime()
{
	m_dwUnSecureWaitTime = 0;
	m_dwSecureWaitTime = 0;
}// NEO: SQ - [SaveUploadQueue] -- Xanatos -->
void CClientCredits::SaveQueueWaitTime() {
	if(!thePrefs.SaveUploadQueueWaitTime()) // X? Is this also relevant?
		return;
	if (m_dwUnSecureWaitTime) m_pCredits->nUnSecuredWaitTime = GetTickCount() - m_dwUnSecureWaitTime;
	if (m_dwSecureWaitTime) m_pCredits->nSecuredWaitTime = GetTickCount() - m_dwSecureWaitTime;
}

void CClientCredits::ClearQueueWaitTime() {
	if(!thePrefs.SaveUploadQueueWaitTime()) // X? Is this also relevant?
		return;
	m_pCredits->nUnSecuredWaitTime = 0;
	m_pCredits->nSecuredWaitTime = 0;
}
// NEO: SQ END <-- Xanatos --

