/*
* Copyright (c) 2007-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: 
* Kernel-side implementation of the KMS LDD.
*
*/


/**
 @file
*/


#include "kmsldd.h"
#include "kmslddk.h"

using namespace KmsLddImpl;


// -------- factory object --------


DKmsLddFactory::DKmsLddFactory()
/**
	Sets iVersion and iParseMask in DLogicalDevice superclass,

	iVersion specifies the currently supported version.
	iParseMask specifies
		Units are not supported (KDeviceAllowUnit is not set;)
		No PDD is required (KDeviceAllocPhysicalDevice is not set;)
		No additional information is used (KDeviceAllowInfo is not set.)
 */
	{
	iVersion = KmsLddImpl::Version();
	iParseMask = 0;
	}


void DKmsLddFactory::GetCaps(TDes8& aCapsDes) const
/**
	Implement DLogicalDevice by populating the supplied buffer
	with information about this logical device.
	
	Currently no information is supported and this function
	resets the supplied buffer.

	@param	aCapsDes		Buffer to populate with information
							about this logical device.
 */
	{
	aCapsDes.Zero();
	}


TInt DKmsLddFactory::Install()
/**
	Implement DLogicalDevice by setting the device's name.

	@return					Symbian OS error code generated by call to SetName.
	@see DObject::SetName
 */
	{
	return SetName(&RKmsChannel::FactoryName());
	}


TInt DKmsLddFactory::Create(DLogicalChannelBase*& aChannel)
/**
	Implement DLogicalDevice by allocating a new instance of
	DKmsLddChannel.

	@param	aChannel		On success this points to a newly-allocated
							and initialized channel object which is owned
							by the caller.  Its value is undefined on failure.
	@return					Symbian OS error code.
 */
	{
	aChannel = new DKmsLddChannel;
	if (aChannel == 0)
		return KErrNoMemory;

	return KErrNone;
	}


// -------- channel --------


DKmsLddChannel::DKmsLddChannel()
/**
	Open the client thread which has opened the channel, so it can be
	referred to later when the asynchronous operations complete.
 */
	{
	iClient = &Kern::CurrentThread();

	// open reference so client will cannot be destroyed until it has closed this channel
	iClient->Open();
	}

TInt DKmsLddChannel::DoCreate(TInt /* aUnit */, const TDesC8* /* aInfo */, const TVersion& aVer)
/**
	Override DLogicalChannelBase by initializing this channel.

	@param	aUnit			Device unit.  Not used (KDeviceAllowUnit flag is not set
							in iParseMask.)
	@param	aInfo			Additional information supplied by user-side process about how to
							open channel.  Not used (KDeviceAllowInfo is not set in iParseMask.)
	@param	aVer			Requested version number.  This function will fail with KErrNotSupported
							if the driver does not support at least this version.
	@return					Symbian OS error code.  Specific values include, but
								are not limited to
								KErrNotSupported if the requested version is
									greater than the current version.
								KErrPermissionDenied if the thread which opened
									this channel is not the key management server.
 */
	{
	if (! Kern::QueryVersionSupported(/* aCurrent */ KmsLddImpl::Version(), /* aRequested */ aVer))
		return KErrNotSupported;

	_LIT_SECURITY_POLICY_S0(KKmsServerUidPolicy, KKmsServerUidValue);
	if (KKmsServerUidPolicy().CheckPolicy(iClient, __PLATSEC_DIAGNOSTIC_STRING("KMSLDDK: channel can only be opened by KMS server")) == EFalse)
		return KErrPermissionDenied;

	// all key store requests must be handled on the key store DFC queue
	TDfcQue& keyStoreDfcQ = HwKeyStore::DfcQ();
	SetDfcQ(&keyStoreDfcQ);
	iMsgQ.Receive();

	return KErrNone;
	}

DKmsLddChannel::~DKmsLddChannel()
/**
	Prepares this object for destruction by cancelling any outstanding requests
	and closing reference to the client thread.
 */
	{
	DoCancel(EAllRequests);
	iClient->Close(/* aPtr */ NULL);
	}

TInt DKmsLddChannel::RequestUserHandle(DThread* aThread, TOwnerType aType)
/**
	Override DObject by deciding whether a handle to this object can
	be given to a user thread.  In this case, only the thread which opened
	the channel can duplicate it, and only then as a thread-relative handle.

	@param	aThread			Thread which should be given a duplicate of this handle.
	@param	aType			Whether the new handle is thread- or process-relative.
	@return					Symbian OS error code.  KErrAccessDenied means the thread
							cannot duplicate this handle.  KErrNone means that it can.
 */
	{
	// only the thread which opened this channel can use it
	if (!(aThread == iClient && aType == EOwnerThread))
		return KErrAccessDenied;
	
	return KErrNone;
	}


void DKmsLddChannel::HandleMsg(TMessageBase* aMsg)
/**
	Implement DLogicalChannel by processing a message from user-side.

	This function is called in the context of the DFC queue thread which
	was passed to SetDfcQ in DoCreate.

	@param	aMsg			Kernel-side message which describes the request, control,
							or cancel request which was sent from user-side.
 */
	{
	TThreadMessage& m = *static_cast<TThreadMessage*>(aMsg);

	// these constants are defined for readability only
	static const TBool KReceiveNext = ETrue;
	static const TBool KDoNotReceiveNext = EFalse;

	TInt msgReason = KErrNone;					// reason with which to complete request
	TBool msgNext = KReceiveNext;
	
	TInt id = m.iValue;
	if (id == ECloseMsg)
		{
		DoCancel(EAllRequests);
		msgNext = KDoNotReceiveNext;
		}
	
	// id == KMaxTInt when user cancels asynchronous operations
	else if (id == KMaxTInt)
		{
		DoCancel(m.Int0());
		}

	// id < 0 means asynchronous request, i.e. DoRequest.  Complement to get actual request number.
	else if (id < 0)
		{
		TInt r = DoRequest(~id, m);

		// if could not launch async request then complete TRS here
		if (r != KErrNone)			// KErrDied is redundant but harmless
			{
			if (r == KErrCompletion)
				r = KErrNone;
			
			TRequestStatus* pS = reinterpret_cast<TRequestStatus*>(m.Ptr0());
			Kern::RequestComplete(iClient, pS, r);
			}

		msgReason = r;
		}

	// >= 0 identifier means synchronous request, i.e. DoControl. 
	else
		{
		// no control requests are currently supported
		msgReason = KErrNotSupported;
		}
	
	if (msgReason != KErrDied)
		m.Complete(msgReason, msgNext);
	}

inline TInt NoneToCompletion(TInt aError)
/**
	Utility function translates KErrNone to KErrCompletion, so HandleMsg
	completes the request immediately in event of success.
	
	@param	aError			Symbian OS error code to translate.
	@return					aError, unless it was KErrNone, in which case it
							is translated to KErrCompletion.
 */
{
	return (aError == KErrNone) ? KErrCompletion : aError;
}

TInt DKmsLddChannel::DoRequest(TInt aReqNo, TThreadMessage& aMsg)
/**
	Process asynchronous request.

	@param	aReqNo			Asynchronous request numeric identifier.
	@param	aMsg			The message object that is being processed by
							the driver's DFC thread.  This is used to panick
							the client when invalid input is detected.
	@return					Symbian OS error from setting up the request.
							Specific values include
								KErrNone: The operation was set up successfully
									but has not yet completed.
								KErrCompletion: The operation has already completed
									successfully.  HandleMsg completes the request
									with KErrNone.
	@see HandleMsg
 */
	{
	TAny* a1 = aMsg.Ptr1();
	TAny* a2 = aMsg.Ptr2();

	TRequest req = static_cast<TRequest>(aReqNo);
	switch (req)
		{
	case EGenerateKey:
		{
		TGenKeyArgs gkaK;
		TGenKeyArgsPckg gkaKPckg(gkaK);
		TInt r = Kern::ThreadDesRead(iClient, a1, gkaKPckg, /* aOffset */ 0);
		if (r != KErrNone)
			return PanicClient(aMsg, EGenKeyBadArgDescriptor);
		
		return GenerateKey(gkaK.iLength, gkaK.iOwner, gkaK.iUserHandle, aMsg);
		}
	
	case EStoreKey:
		{
		TStoreKeyArgs skaK;
		TStoreKeyArgsPckg skaKPckg(skaK);
		TInt r = Kern::ThreadDesRead(iClient, a2, skaKPckg, /* aOffset */ 0);
		if (r != KErrNone)
			return PanicClient(aMsg, EStoreKeyBadArgDescriptor);
		return StoreKey(/*aDataPtr*/ a1, skaK.iOwner, skaK.iUserHandle, aMsg);
		}
			
	case EDeleteKey:
		{
		TSecureId claimedOwner(reinterpret_cast<TUint32>(a1));
		TKeyHandle kh = {reinterpret_cast<TInt>(a2)};
		TInt r = HwKeyStore::DeleteKey(claimedOwner, kh);
		return NoneToCompletion(r);
		}
	
	case EAddUsage:
	case EDeleteUsage:
		{
		TUsageIdentifierArgs aia;
		TUsageIdentifierArgsPckg uiaPckg(aia);
		TInt r = Kern::ThreadDesRead(iClient, a1, uiaPckg, /* aOffset */ 0);
		if (r != KErrNone)
			return PanicClient(aMsg, EAddUsageBadArgDescriptor);
		
		if (aReqNo == EAddUsage)
			{
			TSecurityPolicyBuf secPolBuf;
			r = Kern::ThreadDesRead(iClient, a2, secPolBuf, /* aOffset */ 0);
			if (r != KErrNone)
				return PanicClient(aMsg, EAddUsageBadSecPolDescriptor);
			
			r = HwKeyStore::AddUsage(aia.iClaimedOwner, aia.iHandle, aia.iOperation, secPolBuf());
			}
		else
			{
			r = HwKeyStore::DeleteUsage(aia.iClaimedOwner, aia.iHandle, aia.iOperation);
			}
		
		return NoneToCompletion(r);
		}
	
	case EUsageAllowed:
		{
		TUsageAllowedArgs uaa;
		TUsageAllowedArgsPckg uaaPckg(uaa);
		TInt r = Kern::ThreadDesRead(iClient, a1, uaaPckg, /* aOffset */ 0);
		if (r != KErrNone)
			return PanicClient(aMsg, EUsageAllowedBadArgDescriptor);
		
		// this driver's client has a handle on the user process.  I.e., the
		// user process is the KMS client, not the KMS itself.
		NKern::LockSystem();			// system lock must be held for handle lookup
		DProcess* userProc;
		// current process handle always resolves to the currently running process,
		// which is the kernel process in this case.
		if (uaa.iUserProcHandle == KCurrentProcessHandle)
			userProc = iClient->iOwningProcess;
		else
			userProc = static_cast<DProcess*>(iClient->ObjectFromHandle(uaa.iUserProcHandle, EProcess));
		NKern::UnlockSystem();
		if (userProc == 0)				// handle not owned by client
			return KErrArgument;
		
		return UsageAllowed(userProc, uaa.iKeyHandle, uaa.iOperation, a2, aMsg);
		}
	
	default:
		return KErrNotSupported;
		}
	}

TInt DKmsLddChannel::GenerateKey(TInt aLength, TSecureId aOwner, TAny* aHandlePtr, TThreadMessage& aMsg)
/**
	Generate a key and store it in the hardware.
	
	@param	aLength			Length of new key in bytes.
	@param	aOwner			Identifies process which will own this key.
	@param	aHandlePtr		On success this is set to the newly-created key's handle.
							Its value is undefined on failure.  The handle object itself
							is in the client's address space.
	@param	aMsg			Kernel message, used to panick client if the handle pointer is invalid.
	@return					KErrCompletion on success, any other error code on failure.
 */
	{
	// the hardware may impose further restrictions on which key lengths
	// are supported.  If the supplied length is invalid they should fail
	// with KErrArgument.
	if (aLength <= 0)
		return KErrArgument;
	
	TKeyHandle kh;
	TInt r = HwKeyStore::GenerateKey(aLength, aOwner, kh);
	if (r != KErrNone)
		return r;
	
	r = Kern::ThreadRawWrite(iClient, aHandlePtr, &kh, sizeof(kh));
	if (r != KErrNone)
		{
		HwKeyStore::DeleteKey(aOwner, kh);	// can't do any more if this fails
		return PanicClient(aMsg, EGenKeyInvalidHandleAddr);
		}
	
	return KErrCompletion;
	}

TInt DKmsLddChannel::StoreKey(const TAny* aDataPtr, TSecureId aOwner, TAny* aHandlePtr, TThreadMessage& aMsg)
/**
	Write the supplied key to the hardware store.
	
	@param	aDataPtr		Unformatted key data.
	@param	aOwner			Identifies process which will own this key.
	@param	aHandlePtr		On success this is set to the newly-created key's handle.
							Its value is undefined on failure.  The handle object itself
							is in the client's address space.
	@param	aMsg			Kernel message, used to panick client if the handle pointer is invalid.
	@return					KErrCompletion on success, any other error code on failure.
 */
	{
	// verify client has supplied valid descriptor
	TInt r = Kern::ThreadGetDesLength(iClient, aDataPtr);
	if (r < 0)
		return PanicClient(aMsg, EStoreKeyInvalidDataDesc);

	// ensure key data is not empty.  If the hardware does not support
	// the supplied length, the key store implementation should also fail
	// with KErrArgument.
	if (r == 0)
		return KErrArgument;
	
	// make kernel-side copy of supplied key
	HBuf8* dataK = HBuf8::New(r);
	if (dataK == 0)
		return KErrNoMemory;
	
	// this read may fail if the client thread releases the memory after
	// Kern::ThreadGetDesLength has succeeded.
	r = Kern::ThreadDesRead(iClient, aDataPtr, *dataK, /* aOffset */ 0);
	if (r != KErrNone)
		{
		delete dataK;
		return PanicClient(aMsg, EStoreKeyInvalidDataDesc);
		}
	
	// write the key to the store
	TKeyHandle kh;
	r = HwKeyStore::StoreKey(*dataK, aOwner, kh);
	delete dataK;

	if (r != KErrNone)
		return r;
	
	// update the client handle
	r = Kern::ThreadRawWrite(iClient, aHandlePtr, &kh, sizeof(kh));
	if (r != KErrNone)
		{
		HwKeyStore::DeleteKey(aOwner, kh);	// can't do any more if this fails
		return PanicClient(aMsg, EStoreKeyInvalidHandleAddr);
		}
	
	return KErrCompletion;
	}

TInt DKmsLddChannel::UsageAllowed(
	DProcess* aUserProcess, TKeyHandle aHandle, TInt aOperation, TAny* aUserAllowedPtr, TThreadMessage& aMsg)
/**
	@param	aUserProcess	Process which wants to use the key.
	@param	aHandle			Identifies existing key in store.
	@param	aOperation		Numeric identifier for operation.  Example operations
							include signing, but the actual meaning is not defined here.
	@param	aUserAllowedPtr	On success this is set to whether or not the process can
							perform the requested operation with the key.  Its value
							is undefined on failure.  This points to a TBool object in
							the client's address space.
	@param	aMsg			Kernel message, used to panick client if the handle pointer is invalid.
	@return					KErrNone on success, any other error code on failure.
							In this context, "success" means can determine whether or
							not the operation is allowed.  It does not necessarily mean
							the operation <em>is</em> allowed.
 */
	{
	TBool allowed;
	TInt r = HwKeyStore::UsageAllowed(aUserProcess, aHandle, aOperation, allowed);
	
	if (r != KErrNone)
		return r;
	
	r = Kern::ThreadRawWrite(iClient, aUserAllowedPtr, &allowed, sizeof(allowed));
	if (r != KErrNone)
		return PanicClient(aMsg, EUsageAllowedBadAllowedPtr);
	
	return KErrCompletion;
	}

void DKmsLddChannel::DoCancel(TUint aMask)
/**
	Cancel outstanding asynchronous requests.

	@param	aMask			Bitmask of requests to cancel.  If a marked
							request is not outstanding, then this function
							ignores it.
	
	This implementation can be empty because no properly asynchronous requests
	are supported.  They will all be completed, or the client will be panicked,
	before DoRequest returns.
 */
	{
	(void) aMask;
	}

TInt DKmsLddChannel::PanicClient(TThreadMessage& aMsg, KmsLddImpl::TClientPanic aPanic)
/**
	Panic the channel's client with category "KmsLddK" and the supplied reason.

	@param	aMsg			Message received by driver thread.
	@param	aPanic			This is converted to an integer and used as
							the panic reason.
	@return					KErrDied.  This value tells HandleMsg not to
							complete the message or request.
 */
	{
	aMsg.PanicClient(KKmsLddPanicCat, aPanic);
	return KErrDied;
	}


// -------- exported entrypoint --------


DECLARE_STANDARD_LDD()
/**
	This standard exported function allocates a new instance of the
	channel factory object.

	@return					New instance of DKmsLddFactory.  This object has been
							allocated on the heap, but has not yet been initialized.
							(The calling framework does this by calling Install and
							Create.)  NULL if could not allocate heap memory.
	@see DKmsLddFactory
 */
	{
	return new DKmsLddFactory;
	}

