/*
* Copyright (c) 2002-2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: 
*
*/


#include "bufferedtransformation.h"

#include <cryptospi/cryptospidef.h>
#include <cryptopanic.h>
#include <e32cmn.h>
#include "symmetriccipherplugin.h"

#include "blocktransformation.h"
#include "bufferedtransformationshim.h"
#include "padding.h"
#include "../common/inlines.h"

EXPORT_C CBufferedTransformation::~CBufferedTransformation()
	{
	delete iBT;
	delete iPadding;
	delete iInputStoreBuf;
	}

void CBufferedTransformation::Process(const TDesC8& aInput, TDes8& aOutput)
	{
	__ASSERT_DEBUG(aOutput.MaxLength() >= MaxOutputLength(aInput.Length()), User::Panic(KCryptoPanic, ECryptoPanicOutputDescriptorOverflow));

	TInt blockSize = iBT->BlockSize();

	if ( (aInput.Size() + iInputStore.Size()) < blockSize )
		{
		iInputStore.Append(aInput);
		}
	else
		{
		TInt outputIndex = aOutput.Size();
		aOutput.Append(iInputStore);

		TInt inputIndex = blockSize - iInputStore.Size();
		aOutput.Append(aInput.Mid(0, inputIndex));

		TPtr8 transformBuf((TUint8*)(aOutput.Ptr()) + outputIndex, blockSize,
			blockSize);
		//This should read: 
		//TPtr8 transformBuf(aOutput.Mid(outputIndex, blockSize));
		//but in the wonderful world of descriptors, Mid returns a TPtrC8 even
		//when called on a TPtr8.  Fantastic eh?
		iBT->Transform(transformBuf);

		outputIndex += blockSize;

		TInt len = aInput.Size() - blockSize;

		for (; inputIndex<=len; inputIndex+=blockSize)
			{
			aOutput.Append(aInput.Mid(inputIndex, blockSize));			
			transformBuf.Set((TUint8*)(aOutput.Ptr()) + outputIndex, blockSize,
				blockSize);
			iBT->Transform(transformBuf);
			outputIndex += blockSize;
			}

		iInputStore.Zero();
		if (inputIndex < aInput.Size())
			iInputStore.Append(aInput.Mid(inputIndex));
		}
	}

TInt CBufferedTransformation::MaxOutputLength(TInt aInputLength) const
	{
	TInt rem = (aInputLength + iInputStore.Size()) % (iBT->BlockSize());
	return ((aInputLength + iInputStore.Size()) - rem);
	}

void CBufferedTransformation::Reset()
	{
	iBT->Reset();
	iInputStore.Zero();
	}

TInt CBufferedTransformation::BlockSize() const
	{
	return (iBT->BlockSize());
	}

TInt CBufferedTransformation::KeySize() const
	{
	return (iBT->KeySize());
	}

EXPORT_C CBlockTransformation* CBufferedTransformation::BlockTransformer() const
{
	return (iBT);
}

CBufferedTransformation::CBufferedTransformation()
	: iInputStore(0,0,0)
	{
	}

void CBufferedTransformation::ConstructL(CBlockTransformation* aBT, CPadding* aPadding)
	{
	iInputStoreBuf = HBufC8::NewL(aBT->BlockSize());
	iInputStore.Set(iInputStoreBuf->Des());

	// Take ownership last - doesn't take ownership if we leave
	iBT = aBT;
	iPadding = aPadding;
	}


// CBufferedEncryptor

EXPORT_C CBufferedEncryptor* CBufferedEncryptor::NewL(
	CBlockTransformation* aBT, CPadding* aPadding)
	{
	CBufferedEncryptor* self = CBufferedEncryptorShim::NewL(aBT, aPadding);
	if (! self)
		{			
		// not able to use CryptoSpi, possibly due to an exterally 
		// derived legacy class so fallback to old implementation.			
		self = NewLC(aBT,aPadding);
		CleanupStack::Pop(self);
		}	
	return self;
	}

EXPORT_C CBufferedEncryptor* CBufferedEncryptor::NewLC(
	CBlockTransformation* aBT, CPadding* aPadding)
	{
	CBufferedEncryptor* self = new (ELeave) CBufferedEncryptor();
	CleanupStack::PushL(self);
	self->ConstructL(aBT, aPadding);
	return self;
	}

void CBufferedEncryptor::ConstructL(CBlockTransformation* aBT, CPadding* aPadding) 
	{
	CBufferedTransformation::ConstructL(aBT, aPadding);
	}

CBufferedEncryptor::CBufferedEncryptor()
	{
	}

void CBufferedEncryptor::ProcessFinalL(const TDesC8& aInput, TDes8& aOutput)
	{
	__ASSERT_DEBUG(aOutput.MaxLength() >= MaxFinalOutputLength(aInput.Length()), User::Panic(KCryptoPanic, ECryptoPanicOutputDescriptorOverflow));
	Process(aInput, aOutput);
	
	TInt outputIndex = aOutput.Size();
	iPadding->PadL(iInputStore, aOutput);
	assert(aOutput.Size() % iBT->BlockSize() == 0);

	TUint blockSize = iBT->BlockSize();
	TInt len = aOutput.Size() - outputIndex;
	
	for(TInt i=len; i>0; i-=blockSize)
		{
		TPtr8 transformBuf((TUint8*)(aOutput.Ptr()) + outputIndex, blockSize,
			blockSize);
		iBT->Transform(transformBuf);
		outputIndex+=blockSize;
		}

	iInputStore.Zero();
	}

TInt CBufferedEncryptor::MaxFinalOutputLength(TInt aInputLength) const
	{
    return iPadding->MaxPaddedLength(iInputStore.Size() + aInputLength);
	}

// CBufferedDecryptor

EXPORT_C CBufferedDecryptor* CBufferedDecryptor::NewL(
	CBlockTransformation* aBT, CPadding* aPadding)
	{
	CBufferedDecryptor* self = CBufferedDecryptorShim::NewL(aBT, aPadding);
	if (! self)
		{			
		// not able to use CryptoSpi, possibly due to an exterally 
		// derived legacy class so fallback to old implementation.			
		self = NewLC(aBT,aPadding);
		CleanupStack::Pop(self);
		}	
	return self;
	}

EXPORT_C CBufferedDecryptor* CBufferedDecryptor::NewLC(
	CBlockTransformation* aBT, CPadding* aPadding)
	{
	CBufferedDecryptor* self = new (ELeave) CBufferedDecryptor();
	CleanupStack::PushL(self);
	self->ConstructL(aBT, aPadding);
	return self;
	}

CBufferedDecryptor::CBufferedDecryptor()
	{
	}

void CBufferedDecryptor::ProcessFinalL(const TDesC8& aInput, TDes8& aOutput)
	{
	__ASSERT_DEBUG(aOutput.MaxLength() >= MaxFinalOutputLength(aInput.Length()), User::Panic(KCryptoPanic, ECryptoPanicOutputDescriptorOverflow));

	assert((aInput.Size() + iInputStore.Size()) % iPadding->BlockSize()==0);
	assert(aInput.Size() + iInputStore.Size() !=0 );
	assert(iPadding->BlockSize() % BlockSize() == 0);
	//1) Decrypt into aOutput up till the last full _padding_ blocksize
	//If this panics with descriptor problems, you've probably called
	//ProcessFinalL with a non-_padding_ blocksized aligned amount of data.
	TInt lenToDecrypt = aInput.Size() - iPadding->BlockSize();
	if(lenToDecrypt > 0)
		{
		Process(aInput.Left(lenToDecrypt), aOutput);
		assert(iInputStore.Size()==0);
		}
	else
		{
		lenToDecrypt = 0;
		}
	
	//2) Decrypt the last _padding_ blocksize into a new buffer
	HBufC8* padBuf = HBufC8::NewLC(iPadding->BlockSize());
	TPtr8 padPtr = padBuf->Des(); 
	Process(aInput.Mid(lenToDecrypt), padPtr);
	assert(iInputStore.Size()==0);
		
	//3) Unpad that last _padding_ blocksize into aOutput
	// Note that padding systems must always, like everything else in crypto,
	// _append_ data.
	iPadding->UnPadL(padPtr, aOutput);

	CleanupStack::PopAndDestroy(padBuf);
	}

TInt CBufferedDecryptor::MaxFinalOutputLength(TInt aInputLength) const
	{
	return iPadding->MaxUnPaddedLength(aInputLength + iInputStore.Size());
	}

void CBufferedDecryptor::ConstructL(CBlockTransformation* aBT, CPadding* aPadding) 
	{
	CBufferedTransformation::ConstructL(aBT, aPadding);
	}
