// Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "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:
//

/**
 @file
 @internalComponent
*/

#include "hciserverrequestmgr.h"
#include "HciCorePluginImpl.h"
#include "corehciutil.h"

#include <bluetooth/hci/hciipc.h>
#include <bluetooth/hci/vendordebugcommand.h>
#include <bluetooth/hci/vendordebugcompleteevent.h>
#include <bluetooth/hci/vendordebugevent.h>

#include <bluetooth/hcicommandqueueclient.h>
#include <bluetooth/hcicommandqueue.h>
#include <bluetooth/btpowercontrol.h>
#include <bluetooth/logger.h>

#include <es_sock.h>

#ifdef __FLOG_ACTIVE
_LIT8(KLogComponent, LOG_COMPONENT_HCISERVER);
#endif

const TInt KHCIServerIpcDesOutParam = 2;

void CHCIServerRequestManager::BlockAsync(CHCISession& aSession, const RMessage2& aMessage)
	{
	LOG_FUNC

	iAsyncTaskMessage = aMessage;
	iAsyncTaskSession = &aSession;
	iBlockMask |= MaskBit();
	}
	
void CHCIServerRequestManager::CompleteAsyncTask(TInt aError)
	{
	LOG_FUNC
	
	//check message is not already complete
	if(iAsyncTaskMessage.Handle())
		{
		iAsyncTaskMessage.Complete(aError);
		}
	else
		{
		__ASSERT_DEBUG(EFalse, PANIC(KHciServerPanicCat, EMessageAlreadyCompleted));
		}

	// Remove Async block		
	iAsyncTaskSession = 0;
	iBlockMask &= ~MaskBit();
	}
	
TBool CHCIServerRequestManager::IsAsyncBlocked()
	{
	LOG_FUNC
	
	return (iBlockMask & MaskBit());
	}
	
CHCIServerRequestManager::CHCIServerRequestManager(CCoreHCIPluginImpl& aHCI)
 :	iHCI(aHCI)
	{
	LOG_FUNC
	}
	
void CHCIServerRequestManager::ConstructL()
	{
	LOG_FUNC
	}

void CHCIServerRequestManager::ServiceL(CHCISession& aSession, const RMessage2& aMessage)
	{
	if(aMessage.Function() == EServiceSpecificRequest)
		{
		if(HasCapabilities(aMessage))
			{
			DoServiceL(aSession, aMessage);
			}
		// else HasCapabilities failed (and has dealt with aMessage).
		}
	else
		{
		// The client has issued a bad HCI Server request, we only expect a server specific
		// request to be made (the specific request being encoded in the TIpcArgs).
		PanicClient(aMessage, EUnknownHciServerFunction);
		}
	}

//
// HCI Direct Access Service
//
CHCIDirectAccessManager* CHCIDirectAccessManager::NewL(CCoreHCIPluginImpl& aHCI)
	{
	LOG_STATIC_FUNC
	
	CHCIDirectAccessManager* self = new (ELeave) CHCIDirectAccessManager(aHCI);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop(self);
	return self;
	}
	
void CHCIDirectAccessManager::SetHCICommandQueue(MHCICommandQueue& aHCICommandQueue)
	{
	LOG_FUNC
	
	__ASSERT_ALWAYS(!iHCICommandQueue, PANIC(KCoreHciPanicCat, EInterfaceAlreadyInitialised));
	iHCICommandQueue = &aHCICommandQueue;
	}

/*virtual*/ TBool CHCIDirectAccessManager::HasCapabilities(const RMessage2& aMessage)
	{
	LOG_FUNC
	
	TBool allowed = EFalse;
	
	switch (aMessage.Int0())
		{
	case KHCIDirectAccessVendorCommand:
		// Drop through
	case KHCIDirectAccessCancel: // requires Network Control + Local Services
		allowed = aMessage.HasCapability(ECapabilityNetworkControl) && aMessage.HasCapability(ECapabilityLocalServices);
		break;

	default:
		PanicClient(aMessage, EUnknownDirectAccessFunction);
		return EFalse;
		}
	
	if(!allowed)
		{
		aMessage.Complete(KErrPermissionDenied);
		}
		
	return allowed;
	}

/*virtual*/ void CHCIDirectAccessManager::DoServiceL(CHCISession& aSession, const RMessage2& aMessage)
	{
	LOG_FUNC
	
	TInt request = aMessage.Int0();

	switch (request)
		{
	case KHCIDirectAccessCancel:
		if (iAsyncTaskSession != &aSession)
			{
			return; //drop - not for current async task
			}

		// clear cache
		iMessage = NULL;
		
		// perform cancellation procedure
		CompleteAsyncTask(KErrCancel);
		break;

	case KHCIDirectAccessVendorCommand:
		{
		if (IsAsyncBlocked())
			{
			aMessage.Complete(KErrInUse);
			return;
			}

		const TInt len = User::LeaveIfError(aMessage.GetDesLength(1));
		
		// Check the Vendor Command is at least 3 bytes long for the opcode
		// and the length
		if (len < 3)
			{
			PanicClient(aMessage, EInvalidVendorCommand);
			break;
			}
		
		HBufC8* buf = HBufC8::NewLC(len);
		TPtr8 params(buf->Des());
		aMessage.ReadL(1, params);
		
		// Extract the opcode and create the new command
		TUint16 opcode = LittleEndian::Get16(params.Ptr());
		
		if (iHCICommandQueue)
			{
			CVendorDebugCommand* cmd = CVendorDebugCommand::NewL(opcode);

			// Remove the opcode and length from the Vendor Command parameters,
			// these will be added again to the command frame once it has been
			// formatted
			cmd->Command() = params.RightTPtr(params.Length() - 3);
			
			// cmd will be deleted in MhcqAddCommandL if Leaves.
			TUint qid = iHCICommandQueue->MhcqAddCommandL(cmd, *this);

			// Block only after we succeed in adding a command.
			BlockAsync(aSession, aMessage);

			// Cache to return completion data to client
			iMessage = &aMessage;
			}
		else
			{
			// Slight possible race that a request is made before we have been
			// given the command queue to work with - so don't panic just error.
			aMessage.Complete(KErrNotReady);
			}
		CleanupStack::PopAndDestroy(buf);
		}
		break;

	default:
		// Unknown message, panic client
		PanicClient(aMessage, EUnknownDirectAccessFunction);
		break;
		}
	}

/*virtual*/ void CHCIDirectAccessManager::MhcqcCommandErrored(TInt aErrorCode, const CHCICommandBase* /*aCommand*/)
	{
	LOG_FUNC

	// clear cache
	iMessage = NULL;

	CompleteAsyncTask(aErrorCode);
	}

/*virtual*/ void CHCIDirectAccessManager::MhcqcCommandEventReceived(const THCIEventBase& aEvent, const CHCICommandBase* aCommand)
	{
	LOG_FUNC

	// We are not the unmatched event observer therefore we must always get a
	// command
	__ASSERT_DEBUG(aCommand, PANIC(KCoreHciPanicCat, EReceivedUnmatchedEvent));
	
	// It is possible to receive an event but not have a message to complete. This
	// could happen if a request was cancelled after a Vendor Specific command was
	// sent, in this case we do nothing.
	if (iMessage)
		{
		TInt err = KErrNone;
		
		const THCIEventCode eventcode(aEvent.EventCode());
		switch(eventcode)
			{
		case ECommandCompleteEvent:
			{
			if ((aCommand->Opcode() & KVendorDebugOGF) == KVendorDebugOGF)
				{
				const TVendorDebugCompleteEvent& event = TVendorDebugCompleteEvent::Cast(aEvent);
				
				// Return the event
				err = iMessage->Write(KHCIServerIpcDesOutParam, event.VendorDebugData());
				}
			else
				{
				__ASSERT_DEBUG(EFalse, PANIC(KHciServerPanicCat, ENonVendorOpcodeCompleted));
				}
			}
			break;

		case EVendorDebugEvent:
			{
			const TVendorDebugEvent& event = TVendorDebugEvent::Cast(aEvent);

			// Return the event
			err = iMessage->Write(KHCIServerIpcDesOutParam, event.VendorDebugData());
			}
			break;
			
		default:
			__ASSERT_DEBUG(EFalse, PANIC(KCoreHciPanicCat, EReceivedUnknownEvent));
			break;
			}

		CompleteAsyncTask(err);
		}
	}

/*virtual*/ void CHCIDirectAccessManager::CompleteAsyncTask(TInt aError)
	{
	LOG_FUNC

	if (!IsAsyncBlocked())
		{
		return; //drop
		}

	CHCIServerRequestManager::CompleteAsyncTask(aError);
	}

CHCIDirectAccessManager::CHCIDirectAccessManager(CCoreHCIPluginImpl& aHCI)
:	CHCIServerRequestManager(aHCI)
	{
	LOG_FUNC
	}
	

TUint CHCIDirectAccessManager::MaskBit() const
	{
	LOG_FUNC
	
	return EHCIDirectAccessMaskBit;
	}
			
//
// Power Control Service
//
CBTPowerControlManager* CBTPowerControlManager::NewL(CCoreHCIPluginImpl& aHCI)
	{
	LOG_STATIC_FUNC
	
	CBTPowerControlManager* self = new (ELeave) CBTPowerControlManager(aHCI);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop(self);
	return self;
	}

TBool CBTPowerControlManager::HasCapabilities(const RMessage2& aMessage)
	{
	LOG_FUNC
	
	TBool allowed(EFalse);
	
	switch (aMessage.Int0())
		{
	case KHCICancelPower:
		// fall through
	case EHCISetPower: // requires Network Control + Local Services
		allowed = aMessage.HasCapability(ECapabilityNetworkControl) && aMessage.HasCapability(ECapabilityLocalServices);
		break;
	
	case EHCIGetPower: // requires Local Services
		allowed = aMessage.HasCapability(ECapabilityLocalServices);
		break;

	default:
		PanicClient(aMessage, EUnknownPowerControlFunction);
		return EFalse;
		}
		
	if(!allowed)
		{
		aMessage.Complete(KErrPermissionDenied);
		}
		
	return allowed;
	}

/*virtual*/ void CBTPowerControlManager::DoServiceL(CHCISession& aSession, const RMessage2& aMessage)
	{
	LOG_FUNC

	TInt request = aMessage.Int0();
	TInt err;
	TBTPowerState state;
	
	switch (request)
		{
	case KHCICancelPower:
		if(iAsyncTaskSession!=&aSession)
			{
			return; //drop - not for current async task
			}

		//perform cancellation procedure
		CompleteAsyncTask(KErrCancel);
		break;

	case EHCISetPower:
		if(IsAsyncBlocked())
			{
			aMessage.Complete(KErrInUse);
			return;
			}
		//Should be called before set power because it could complete synchronously.
		BlockAsync(aSession, aMessage);
		state = static_cast<TBTPowerState>(aMessage.Int3());
		err = iHCI.SetPower(state, NULL);
		if(err != KErrNone)
			{
			aMessage.Complete(err);
			return;
			}
		break;

	case EHCIGetPower:
		{
		err = iHCI.GetPower(state, NULL);
		if(err != KErrNone)
			{
			aMessage.Complete(err);
			return;
			}
		
		TPckg<TBTPowerState> pckg(state);
		TRAP(err, aMessage.WriteL(EPSStateReturnSlot, pckg));
		aMessage.Complete(err);
		}
		break; //synchronous so do not block
	
	default:
		// Unknown message
		PanicClient(aMessage, EUnknownPowerControlFunction);
		break;
		}
	}

/*virtual*/ void CBTPowerControlManager::CompleteAsyncTask(TInt aError)
	{
	LOG_FUNC
	
	if(!IsAsyncBlocked())
		{
		return; //drop
		}

	CHCIServerRequestManager::CompleteAsyncTask(aError);
	}

void CBTPowerControlManager::CompleteRequest(TInt aError)
	{
	CompleteAsyncTask(aError);
	}

CBTPowerControlManager::CBTPowerControlManager(CCoreHCIPluginImpl& aHCI)
:	CHCIServerRequestManager(aHCI)
	{
	LOG_FUNC
	}
	

TUint CBTPowerControlManager::MaskBit() const
	{
	LOG_FUNC
	
	return EBTPowerControlMaskBit;
	}
		
