/*
* Copyright (c) 1997-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 "pkixchainbuilder.h"

CPKIXChainBuilder* CPKIXChainBuilder::NewL()
	{
	CPKIXChainBuilder* s = CPKIXChainBuilder::NewLC();
	CleanupStack::Pop(s);
	return s;
	}

CPKIXChainBuilder* CPKIXChainBuilder::NewLC()
	{
	CPKIXChainBuilder* s = new (ELeave) CPKIXChainBuilder;
	CleanupStack::PushL(s);
	s->ConstructL();
	return s;
	}

CPKIXChainBuilder::~CPKIXChainBuilder()
	{
	Cancel();
	iSources.Close();
	iCandidates.ResetAndDestroy();
	iCandidates.Close();
	}

void CPKIXChainBuilder::AddSourceL(MPKIXCertSource* aSource)
	{
	User::LeaveIfError(iSources.Append(aSource));
	}

void CPKIXChainBuilder::AddIssuer(TInt& aNumberOfCertsAdded,
								  TBool& aResult, 
								  CArrayPtrFlat<CX509Certificate>& aChain, 
								  TRequestStatus& aStatus)
	{
	iOriginalRequestStatus = &aStatus;
	aStatus = KRequestPending;
	iResult = &aResult;
	iChain = &aChain;
	iNumberOfCertsAdded = &aNumberOfCertsAdded;

	iSubject = aChain[aChain.Count()-1];
	__ASSERT_DEBUG(iSubject, User::Panic(_L("CPKICCertChainAO"), 1));
	__ASSERT_DEBUG(!iCandidates.Count(), User::Panic(_L("CPKICCertChainAO"), 1));

	iIndex = -1;
	iState = EAddCandidate;
	TRequestStatus* status = &iStatus;
	User::RequestComplete(status, KErrNone);
	SetActive();
	}

CPKIXChainBuilder::CPKIXChainBuilder()
: CActive(EPriorityNormal)
	{
	CActiveScheduler::Add(this);
	}

void CPKIXChainBuilder::ConstructL()
	{
	}

TBool CPKIXChainBuilder::ResolveIssuersL(CArrayPtr<CX509Certificate>& aChain,
										 const RPointerArray<CX509Certificate>& aCandidates) const
	{
	//*this function attempts to figure out which certificate in aCandidates is the issuer of
	//the last cert in the aChain, and adds a *copy* of the best guess to aChain
	//*it assumes that the names match already
	//*if it establishes that none are any good it returns EFalse
	TInt count = aCandidates.Count();
	if (count == 0)
		{
		return EFalse;
		}
	if (count == 1)
		{
		CX509Certificate* cert = CX509Certificate::NewLC(*aCandidates[0]);
		aChain.AppendL(cert);
		(*iNumberOfCertsAdded)++;
		CleanupStack::Pop(cert);
		return ETrue;
		}

	const CX509Certificate* current = aChain[aChain.Count() - 1];
			//1)  look for SKI/AKI to distinguish
	const CX509CertExtension* akiExt = current->Extension(KAuthorityKeyId);
	if (akiExt)
		{
		const CX509AuthorityKeyIdExt* aki = CX509AuthorityKeyIdExt::NewLC(akiExt->Data());
		TPtrC8 authorityKeyId = aki->KeyId();
		if (authorityKeyId != KNullDesC8)
			{
			for (TInt i = 0; i < count; i++)
				{
				const CX509CertExtension* skiExt = (aCandidates[i])->Extension(KSubjectKeyId);
				if (skiExt)
					{
					const CX509SubjectKeyIdExt* ski = CX509SubjectKeyIdExt::NewLC(skiExt->Data());
					if (authorityKeyId == ski->KeyId())
						{
						CX509Certificate* issuer = CX509Certificate::NewLC(*aCandidates[i]);
						aChain.AppendL(issuer);
						(*iNumberOfCertsAdded)++;
						CleanupStack::Pop();//issuer
						CleanupStack::PopAndDestroy(2);//aki, ski
						return ETrue;
						}
					else
						{
						CleanupStack::PopAndDestroy();//ski
						}
					}
				}
			}
		//ok, we haven't got a key ID for the issuer, so try for a serial number instead...
		else
			{
			TPtrC8 authoritySerialNo = aki->AuthorityCertSerialNumber();
			for (TInt i = 0; i < count; i++)
				{
				const CX509Certificate* candidate = aCandidates[i];
				if (authoritySerialNo == candidate->SerialNumber())
					{
					CX509Certificate* issuer = CX509Certificate::NewLC(*candidate);
					aChain.AppendL(issuer);
					(*iNumberOfCertsAdded)++;
					CleanupStack::Pop();//issuer
					CleanupStack::PopAndDestroy();//aki
					return ETrue;
					}
				}
			}
		CleanupStack::PopAndDestroy();//aki
		}

	return EFalse;
	}


void CPKIXChainBuilder::RunL()
	{
	User::LeaveIfError(iStatus.Int());

	switch (iState)
		{
	case EAddCandidate:
		iIndex++;
		if (iIndex < iSources.Count())
			{
			iSources[iIndex]->CandidatesL(*iSubject, iCandidates, iStatus);
			}
		else
			{
			iState = EFinished;
			TRequestStatus* status = &iStatus;
			User::RequestComplete(status, KErrNone);
			}
		SetActive();
		break;

	case EFinished:
		iState = EIdle;
		*iResult = ResolveIssuersL(*iChain, iCandidates);
		iCandidates.ResetAndDestroy();
		User::RequestComplete(iOriginalRequestStatus, KErrNone);
		break;

	default:
		User::Panic(_L("CPKIXChainBuilder"), 1);
		break;
		}
	}

void CPKIXChainBuilder::DoCancel()
	{
	int i = 0;
	int end = iSources.Count();
	while (i < end)
		{
		iSources[i]->CancelCandidates();
		i++;
		}
	iCandidates.ResetAndDestroy();

	User::RequestComplete(iOriginalRequestStatus, KErrCancel);

	iState = EIdle;
	}

TInt CPKIXChainBuilder::RunError(TInt aError)
	{
	iState = EIdle;
	iCandidates.ResetAndDestroy();
	User::RequestComplete(iOriginalRequestStatus, aError);
	return KErrNone;
	}
