///////////////////////////////////////////////////////////////////////////////
/// @file dePluginTwofish.cpp
///
/// @brief twofish encryption plugin
///
/// @author Lightning
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2003 Novus Delta, LLC. All Rights Reserved.
///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date Nov 2001
/// @author Lightning
/// @remarks Creation
///
/// @date Jun 2003
/// @author Lightning
/// @remarks Renamed from deEncryptionTwofish.cpp to dePluginTwofish.cpp
///
///////////////////////////////////////////////////////////////////////////////

#include <windows.h>
#include "dePlugins_priv.hpp"

//have to include the twofish encryption inside the C block
extern "C"
{
	#include "TwoFish/Aes.h"
}

//the twofish encryption plugin

//ID of twofish encryption
#ifdef DE3D_CPU_LITTLE_ENDIAN
#define TwofishID	0x00004654		//TF\0\0, TwoFish, version 0.0
#else
#define TwofishID	0x54460000		//TF\0\0, TwoFish, version 0.0
#endif

dePluginTwofish::dePluginTwofish()
{
	//setup the defaults
	memset(priv_Key, 0, sizeof(priv_Key));
	memset(priv_IV, 0, sizeof(priv_IV));
	priv_KeySize = 128;
	priv_Rounds = 16;
	priv_Cipher = ECB;
}

dePluginTwofish::~dePluginTwofish()
{
	//clear out all the settings
	memset(priv_Key, 0, sizeof(priv_Key));
	memset(priv_IV, 0, sizeof(priv_IV));
	priv_KeySize = 0;
	priv_Rounds = 0;
	priv_Cipher = ECB;
}

void* dePluginTwofish::GetInterface(IdePlugin::interface_t i)
{
	if (i == IdePlugin::iface_IdePlugin)
		return (IdePlugin*)(this);
	else if (i == IdePlugin::iface_IdePluginTwofish)
		return (dePluginTwofish*)(this);
	return NULL;
}

int dePluginTwofish::Release()
{
	delete this;
	return 0;
}

deBoolean dePluginTwofish::EncodeData(void *Buffer, DWORD Length, void *NewBuffer, DWORD *NewLength)
{
	keyInstance		ki;
	cipherInstance	ci;
	DWORD			NeededLength;

	//if no valid pointers, return deFALSE
	if(!Buffer || !Length || !NewBuffer || !NewLength)
		return deFALSE;

	//clear out the key instance
	memset(&ki, 0, sizeof(ki));

	//setup the encryption
	if(!makeKey(&ki, DIR_ENCRYPT, priv_KeySize, NULL))
		return deFALSE;

	if(!cipherInit(&ci, (BYTE)priv_Cipher, NULL))
		return deFALSE;

	//set the number of rounds and the key to use
	ki.numRounds = priv_Rounds;
	memcpy(ki.key32, priv_Key, priv_KeySize / 8);
	
	//rekey the key instance
	reKey(&ki);

	//if the mode is CBC or CFB1 then copy the IV
	if(priv_Cipher == CBC || priv_Cipher == CFB1)
	{
		//copy the IV
		memcpy(ci.IV, priv_IV, 16);
		memcpy(ci.iv32, priv_IV, 16);
	}

	//verify that *NewLength is at least the size of the needed length
	if(*NewLength < MaxEncodeLength(Length))
		return deFALSE;

	//setup the beginning part of the buffer for the header
	((TwofishHeader *)NewBuffer)->ID = TwofishID;			//id that it is twofish
	((TwofishHeader *)NewBuffer)->OriginalSize = deByteOrderBE32(Length);	//original length

	//now, if the buffer is not a multiple of BLOCK_SIZE / 8 then we have
	//to copy it's data to the new buffer to encrypt
	if(Length % (BLOCK_SIZE / 8))
	{
		//it isn't a multiple, copy
		memcpy(&((BYTE *)NewBuffer)[sizeof(TwofishHeader)], Buffer, Length);

		//change the buffer pointer to point to the new buffer
		Buffer = &((BYTE *)NewBuffer)[sizeof(TwofishHeader)];
	}

	//set the length to not include the header then convert to bit count
	Length = (*NewLength - sizeof(TwofishHeader)) * 8;

	//encrypt it
	NeededLength = blockEncrypt(&ci, &ki, (BYTE *)Buffer, Length, &((BYTE *)NewBuffer)[sizeof(TwofishHeader)]);
	
	//wipe out the key and cipher instance
	memset(&ki, 0, sizeof(ki));
	memset(&ci, 0, sizeof(ci));

	//see if anything went wrong
	if(NeededLength != Length)
		return deFALSE;

	return deTRUE;
}

deBoolean dePluginTwofish::DecodeData(void *Buffer, DWORD Length, void *NewBuffer, DWORD *NewLength)
{
	keyInstance		ki;
	cipherInstance	ci;
	DWORD			Ret;

	//if no valid pointers, return deFALSE
	if(!Buffer || !Length || !NewBuffer || !NewLength)
		return deFALSE;

	//adjust the length to not account for the header
	Length = Length - sizeof(TwofishHeader);

	//length should be a multiple of the block size
	if(Length % (BLOCK_SIZE / 8))
		return deFALSE;

	//verify the header
	if(((TwofishHeader *)Buffer)->ID != TwofishID)
		return deFALSE;

	//clear out the key instance
	memset(&ki, 0, sizeof(ki));

	//setup the encryption
	if(!makeKey(&ki, DIR_DECRYPT, priv_KeySize, NULL))
		return deFALSE;

	if(!cipherInit(&ci, (BYTE)priv_Cipher, NULL))
		return deFALSE;

	//set the number of rounds and the key to use
	ki.numRounds = priv_Rounds;
	memcpy(ki.key32, priv_Key, priv_KeySize / 8);
	
	//rekey the key instance
	reKey(&ki);

	//if the mode is CBC or CFB1 then copy the IV
	if(priv_Cipher == CBC || priv_Cipher == CFB1)
	{
		//copy the IV
		memcpy(ci.IV, priv_IV, 16);
		memcpy(ci.iv32, priv_IV, 16);
	}

	//make sure the new length is at least the size of the original
	if(*NewLength < deByteOrderBE32(((TwofishHeader *)Buffer)->OriginalSize))
		return deFALSE;

	//set the length to the original data length
	*NewLength = deByteOrderBE32(((TwofishHeader *)Buffer)->OriginalSize);

	//convert the length to a bitcount
	Length = Length * 8;

	//decrypt it
	Ret = blockDecrypt(&ci, &ki, &((BYTE *)Buffer)[sizeof(TwofishHeader)], Length, (BYTE *)NewBuffer);
	
	//wipe out the key and cipher instance
	memset(&ki, 0, sizeof(ki));
	memset(&ci, 0, sizeof(ci));

	//see if anything went wrong
	if(Ret != Length)
		return deFALSE;

	return deTRUE;
}

DWORD dePluginTwofish::MaxEncodeLength(DWORD Length)
{
	//return the maximum length for encrypted data
	DWORD	NeededLength;

	//figure out how many blocks is needed
	NeededLength = Length / (BLOCK_SIZE / 8);
	if(Length % (BLOCK_SIZE / 8))
		NeededLength++;

	//add in the length of the header
	NeededLength = (NeededLength * (BLOCK_SIZE / 8)) + sizeof(TwofishHeader);

	return NeededLength;
}

DWORD dePluginTwofish::MaxDecodeLength(void *Buffer)
{
	DWORD	NeededLength;
	DWORD	OriginalSize;

	//return the maximum length for decryption

	//if no buffer, return 0
	if(!Buffer)
		return 0;

	//verify the header
	if(((TwofishHeader *)Buffer)->ID != TwofishID)
		return 0;

	//now, figure out just the needed size to bring the original length up to a block
	//multiple

	OriginalSize = deByteOrderBE32(((TwofishHeader *)Buffer)->OriginalSize);

	//figure out the next multiple of a block with length
	NeededLength = OriginalSize / (BLOCK_SIZE / 8);
	if(OriginalSize % (BLOCK_SIZE / 8))
		NeededLength++;

	//neededlength = number of characters to round to a block. add the original
	//length for total needed length
	NeededLength = NeededLength * (BLOCK_SIZE / 8);

	return NeededLength;
}

deBoolean dePluginTwofish::SetKey(void *KeyBuffer, long Bitsize)
{
	//what key to use

	//verify the bitsize
	if(Bitsize != 128 && Bitsize != 192 && Bitsize != 256)
		return deFALSE;

	//verify the buffer
	if(!KeyBuffer)
		return deFALSE;

	//set the bitsize
	priv_KeySize = Bitsize;

	//copy the key to the key buffer
	memcpy(priv_Key, KeyBuffer, Bitsize / 8);
	return deTRUE;
}

deBoolean dePluginTwofish::SetIV(void *IVBuffer, long Length)
{
	//what IV to use (used on CBC and CFB1)

	//verify the length is correct and the buffer is valid
	if(Length != 16 || IVBuffer == NULL)
		return deFALSE;

	//copy it
	memcpy(priv_IV, IVBuffer, 16);
	return deTRUE;
}

deBoolean dePluginTwofish::SetMode(Mode CipherMode)
{
	//the type of encryption

	//verify it is valid
	if(CipherMode < ECB || CipherMode > CFB1)
		return deFALSE;

	//set it
	priv_Cipher = CipherMode;
	return deTRUE;
}

deBoolean dePluginTwofish::SetRounds(int NumberRounds)
{
	//number of rounds to do

	//verify the new value is valid
	if(NumberRounds < 1 || NumberRounds > MAX_ROUNDS)
		return deFALSE;

	//set it
	priv_Rounds = NumberRounds;
	return deTRUE;
}
