/*
* Copyright (c) 2003-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:
*
*/

#include <e32uid.h>
#include <usbman.h>
#include <usb.h>
#include <e32base.h>
#include "rusb.h"
#include <usb/usblogger.h>

#ifdef __FLOG_ACTIVE
_LIT8(KLogComponent, "USBMAN");
#endif

#ifdef __USBMAN_NO_PROCESSES__
#include <e32math.h>
#endif


static TInt StartServer()
//
// Start the server process or thread
//
	{
	const TUidType serverUid(KNullUid, KNullUid, KUsbmanSvrUid);

#ifdef __USBMAN_NO_PROCESSES__
	//
	// In EKA1 WINS the server is a DLL, the exported entrypoint returns a TInt
	// which represents the real entry-point for the server thread
	//
	RLibrary lib;
	TInt err = lib.Load(KUsbmanImg, serverUid);
	
	if (err != KErrNone)
		{
		return err;
		}

	TLibraryFunction ordinal1 = lib.Lookup(1);
	TThreadFunction serverFunc = reinterpret_cast<TThreadFunction>(ordinal1());

	//
	// To deal with the unique thread (+semaphore!) naming in EPOC, and that we may
	// be trying to restart a server that has just exited we attempt to create a
	// unique thread name for the server.
	// This uses Math::Random() to generate a 32-bit random number for the name
	//
	TName name(KUsbServerName);
	name.AppendNum(Math::Random(),EHex);
	
	RThread server;
	err = server.Create (
		name,
		serverFunc,
		KUsbmanStackSize,
		NULL,
		&lib,
		NULL,
		KUsbmanMinHeapSize,
		KUsbmanMaxHeapSize,
		EOwnerProcess
	);

	lib.Close();	// if successful, server thread has handle to library now
#else
	//
	// EPOC and EKA2 is easy, we just create a new server process. Simultaneous
	// launching of two such processes should be detected when the second one
	// attempts to create the server object, failing with KErrAlreadyExists.
	//
	RProcess server;
	TInt err = server.Create(KUsbmanImg, KNullDesC, serverUid);
#endif //__USBMAN_NO_PROCESSES__
	
	if (err != KErrNone)
		{
		return err;
		}

	TRequestStatus stat;
	server.Rendezvous(stat);
	
	if (stat!=KRequestPending)
		server.Kill(0);		// abort startup
	else
		server.Resume();	// logon OK - start the server

	User::WaitForRequest(stat);		// wait for start or death

	// we can't use the 'exit reason' if the server panicked as this
	// is the panic 'reason' and may be '0' which cannot be distinguished
	// from KErrNone
	err = (server.ExitType() == EExitPanic) ? KErrServerTerminated : stat.Int();

	server.Close();
	
	LOGTEXT2(_L8("USB server started successfully: err = %d\n"),err);

	return err;
	}




EXPORT_C RUsb::RUsb() 
	: iDeviceStatePkg(0), iServiceStatePkg(0), iMessagePkg(0), 
	  iHostPkg(TDeviceEventInformation())
	{
	LOG_LINE
	LOG_FUNC
	}

EXPORT_C RUsb::~RUsb()
	{
	LOG_LINE
	LOG_FUNC
	}

EXPORT_C TVersion RUsb::Version() const
	{
	return(TVersion(KUsbSrvMajorVersionNumber,KUsbSrvMinorVersionNumber,KUsbSrvBuildVersionNumber));
	}

EXPORT_C TInt RUsb::Connect()
	{
	LOG_LINE
	LOG_FUNC

	TInt retry = 2;
	
	FOREVER
		{
		// Create the session to UsbSrv with 10 asynchronous message slots
		TInt err = CreateSession(KUsbServerName, Version(), 10);

		if ((err != KErrNotFound) && (err != KErrServerTerminated))
			{
			return err;
			}

		if (--retry == 0)
			{
			return err;
			}

		err = StartServer();

		if ((err != KErrNone) && (err != KErrAlreadyExists))
			{
			return err;
			}
		}
	}

EXPORT_C void RUsb::Start(TRequestStatus& aStatus)
	{
	LOG_LINE
	LOG_FUNC

	SendReceive(EUsbStart, aStatus);
	}

EXPORT_C void RUsb::StartCancel()
	{
	LOG_LINE
	LOG_FUNC

	SendReceive(EUsbStartCancel);
	}

EXPORT_C void RUsb::Stop()
	{
	LOG_LINE
	LOG_FUNC

	SendReceive(EUsbStop);
	}

EXPORT_C void RUsb::Stop(TRequestStatus& aStatus)
	{
	LOG_LINE
	LOG_FUNC

	SendReceive(EUsbStop, aStatus);
	}

EXPORT_C void RUsb::StopCancel()
	{
	LOG_LINE
	LOG_FUNC

	SendReceive(EUsbStopCancel);
	}

EXPORT_C TInt RUsb::GetServiceState(TUsbServiceState& aState)
	{
	LOG_LINE
	LOG_FUNC

	TPckg<TUint32> pkg(aState);
	TInt ret=SendReceive(EUsbGetCurrentState, TIpcArgs(&pkg));
	aState=(TUsbServiceState)pkg();
	return ret;
	}

EXPORT_C TInt RUsb::GetCurrentState(TUsbServiceState& aState)
	{
	LOG_LINE
	LOG_FUNC

	return GetServiceState(aState);
	}

EXPORT_C void RUsb::ServiceStateNotification(TUsbServiceState& aState,
	TRequestStatus& aStatus)
	{
	LOG_LINE
	LOG_FUNC

	iServiceStatePkg.Set((TUint8*)&aState, sizeof(TUint32), sizeof(TUint32));

	SendReceive(EUsbRegisterServiceObserver, TIpcArgs(&iServiceStatePkg), aStatus);
	}

EXPORT_C void RUsb::ServiceStateNotificationCancel()
	{
	LOG_LINE
	LOG_FUNC

	SendReceive(EUsbCancelServiceObserver);
	}

EXPORT_C TInt RUsb::GetDeviceState(TUsbDeviceState& aState)
	{
	LOG_LINE
	LOG_FUNC

	TPckg<TUint32> pkg(aState);
	TInt ret=SendReceive(EUsbGetCurrentDeviceState, TIpcArgs(&pkg));
	aState=(TUsbDeviceState)pkg();
	return ret;
	}

EXPORT_C void RUsb::DeviceStateNotification(TUint aEventMask, TUsbDeviceState& aState,
											TRequestStatus& aStatus)
	{
	LOG_LINE
	LOG_FUNC

	iDeviceStatePkg.Set((TUint8*)&aState, sizeof(TUint32), sizeof(TUint32));

	SendReceive(EUsbRegisterObserver, TIpcArgs(aEventMask, &iDeviceStatePkg), aStatus);
	}

EXPORT_C void RUsb::DeviceStateNotificationCancel()
	{
	LOG_LINE
	LOG_FUNC

	SendReceive(EUsbCancelObserver);
	}

EXPORT_C void RUsb::StateNotification(TUint aEventMask, TUsbDeviceState& aState, TRequestStatus& aStatus)
	{
	LOG_LINE
	LOG_FUNC

	DeviceStateNotification(aEventMask, aState, aStatus);
	}

EXPORT_C void RUsb::StateNotificationCancel()
	{
	LOG_LINE
	LOG_FUNC

	DeviceStateNotificationCancel();
	}
	
EXPORT_C void RUsb::TryStart(TInt aPersonalityId, TRequestStatus& aStatus)
	{
	LOG_LINE
	LOG_FUNC

	TIpcArgs ipcArgs(aPersonalityId);
	SendReceive(EUsbTryStart, ipcArgs, aStatus);
	}

EXPORT_C void RUsb::TryStop(TRequestStatus& aStatus)
	{
	LOG_LINE
	LOG_FUNC

	SendReceive(EUsbTryStop, aStatus);
	}
	
EXPORT_C TInt RUsb::CancelInterest(TUsbReqType aMessageId)
	{
	LOG_LINE
	LOG_FUNC

	TInt messageId;
	switch (aMessageId)
		{
	case EStart:
		messageId = EUsbStart;
		break;
	case EStop:
		messageId = EUsbStop;
		break;
	case ETryStart:
		messageId = EUsbTryStart;
		break;
	case ETryStop:
		messageId = EUsbTryStop;
		break;
	default:
		return KErrNotSupported;
		}
		
	TIpcArgs ipcArgs(messageId);
	return SendReceive(EUsbCancelInterest, ipcArgs);
	}

EXPORT_C TInt RUsb::GetDescription(TInt aPersonalityId, HBufC*& aLocalizedPersonalityDescriptor)
	{
	LOG_LINE
	LOG_FUNC

	TInt ret = KErrNone;
	// caller is responsible for freeing up memory allocatd for aLocalizedPersonalityDescriptor
	TRAP(ret, aLocalizedPersonalityDescriptor = HBufC::NewL(KUsbStringDescStringMaxSize));
	if (ret == KErrNone)
		{
		TPtr ptr = aLocalizedPersonalityDescriptor->Des();
		TIpcArgs ipcArgs(0, &ptr);
		ipcArgs.Set(0, aPersonalityId);
		ret = SendReceive(EUsbGetDescription, ipcArgs);
		}
	else
		{
		// just in case caller tries to free the memory before checking the return code
		aLocalizedPersonalityDescriptor = NULL;
		}

	return ret;	
	}
	
EXPORT_C TInt RUsb::GetCurrentPersonalityId(TInt& aPersonalityId)
	{
	LOG_LINE
	LOG_FUNC

	TPckg<TInt> pkg0(aPersonalityId);
	TInt ret = SendReceive(EUsbGetCurrentPersonalityId, TIpcArgs(&pkg0));
	aPersonalityId = static_cast<TInt>(pkg0());
	return ret;	
	}

EXPORT_C TInt RUsb::GetSupportedClasses(TInt aPersonalityId, RArray<TUid>& aClassUids)
	{
	LOG_LINE
	LOG_FUNC

	TInt ret = KErrNone;
	HBufC8* buf = NULL;
	// +1 for the actual count of personality ids
	TRAP(ret, buf = HBufC8::NewL((KUsbMaxSupportedClasses + 1)*sizeof (TInt32)));
	if (ret != KErrNone)
		{
		return ret;
		}

	TPtr8 ptr8 = buf->Des();
	ret = SendReceive(EUsbGetSupportedClasses, TIpcArgs(aPersonalityId, &ptr8));
		
	if (ret == KErrNone)
		{
		const TInt32* recvedIds = reinterpret_cast<const TInt32*>(buf->Ptr());
		if (!recvedIds)
			{
			delete buf;
			return KErrCorrupt;
			}
			
		TInt arraySize = *recvedIds++;
		// Copy received supported class ids to aClassUids
		for (TInt i = 0; i < arraySize; i++)
			{
			if (recvedIds)
				{
				ret = aClassUids.Append(TUid::Uid(*recvedIds++));
				if(ret!=KErrNone)
					{
					//Remove all the ids appended so far (assume the last append failed, because
					//the only reason to fail is if the array couldn't grow to accommodate another
					//element).
					//It would be easier to just reset the array, but we never specified that
					//aClassUids should be an empty array, nor did we specify that this method
					//might empty the array. To maintain exisiting behaviour we should return
					//aClassUids to the state it was in when this method was called.
					TInt last = aClassUids.Count() - 1;
					while(i>0)
						{
						aClassUids.Remove(last);
						i--;
						last--;
						}	
					break;
					}
				}
			else
				{
				ret = KErrCorrupt;
				break;
				}
			}
		}
		
	delete buf;
	return ret;
	}
	
EXPORT_C TInt RUsb::ClassSupported(TInt aPersonalityId, TUid aClassUid, TBool& aSupported)
	{
	LOG_LINE
	LOG_FUNC

	TPckg<TInt32>  	pkg2(aSupported);
	TIpcArgs ipcArgs(aPersonalityId, aClassUid.iUid, &pkg2);
	
	TInt ret = SendReceive(EUsbClassSupported, ipcArgs);
	
	if (ret == KErrNone)
		{
		aSupported = static_cast<TBool>(pkg2());		
		}
		
	return ret;
	}
	
EXPORT_C TInt RUsb::GetPersonalityIds(RArray<TInt>& aPersonalityIds)
	{
	LOG_LINE
	LOG_FUNC

	TInt ret = KErrNone;
	HBufC8* buf = NULL;
	// +1 for the actual count of personality ids
	TRAP(ret, buf = HBufC8::NewL((KUsbMaxSupportedPersonalities + 1)*sizeof (TInt)));
	if (ret != KErrNone)
		{
		return ret;
		}

	TPtr8 ptr8 = buf->Des();
	ret = SendReceive(EUsbGetPersonalityIds, TIpcArgs(&ptr8));
		
	if (ret == KErrNone)
		{
		const TInt* recvedIds = reinterpret_cast<const TInt*>(buf->Ptr());
		if (!recvedIds)
			{
			delete buf;
			return KErrCorrupt;
			}
			
		TInt arraySize = *recvedIds++;
		// Copy received personality ids to aPersonalityIds
		for (TInt i = 0; i < arraySize; i++)
			{
			if (recvedIds)
				{		
				ret = aPersonalityIds.Append(*recvedIds++);
				
				if(ret!=KErrNone)
					{
					//Remove all the ids appended so far (assume the last append failed, because
					//the only reason to fail is if the array couldn't grow to accommodate another
					//element).
					//It would be easier to just reset the array, but we never specified that
					//aPersonalityIds should be an empty array, nor did we specify that this method
					//might empty the array. To maintain exisiting behaviour we should return
					//aPersonalityIds to the state it was in when this method was called.
					TInt last = aPersonalityIds.Count() - 1;
					while(i>0)
						{
						aPersonalityIds.Remove(last);
						i--;
						last--;
						}	
					break;
					}
				}
			else
				{
				ret = KErrCorrupt;
				break;
				}
			}
		}
		
	delete buf;
	return ret;
	}
	
EXPORT_C TInt RUsb::__DbgMarkHeap()
	{
#ifdef _DEBUG
    return SendReceive(EUsbDbgMarkHeap);
#else
    return KErrNone;
#endif
	}

EXPORT_C TInt RUsb::__DbgCheckHeap(TInt aCount)
	{
#ifdef _DEBUG
    return SendReceive(EUsbDbgCheckHeap, TIpcArgs(aCount));
#else
	(void)aCount; // not used for Release builds
    return KErrNone;
#endif
	}

EXPORT_C TInt RUsb::__DbgMarkEnd(TInt aCount)
	{
#ifdef _DEBUG
    return SendReceive(EUsbDbgMarkEnd, TIpcArgs(aCount));
#else
	(void)aCount; // not used for Release builds
    return KErrNone;
#endif
	}

EXPORT_C TInt RUsb::__DbgFailNext(TInt aCount)
	{
#ifdef _DEBUG
    return SendReceive(EUsbDbgFailNext, TIpcArgs(aCount));
#else
	(void)aCount; // not used for Release builds
    return KErrNone;
#endif
	}

EXPORT_C TInt RUsb::__DbgAlloc()
	{
#ifdef _DEBUG
    return SendReceive(EUsbDbgAlloc);
#else
    return KErrNone;
#endif
	}

EXPORT_C void panic()
	{
	_USB_PANIC(KUsbCliPncCat, EUsbPanicRemovedExport);
	}

EXPORT_C TInt RUsb::SetCtlSessionMode(TBool aValue)
	{
	LOG_LINE
	LOG_FUNC

	TPckg<TBool> pkg(aValue);
	return SendReceive(EUsbSetCtlSessionMode, TIpcArgs(&pkg));
	}

EXPORT_C TInt RUsb::BusRequest()
	{
	LOG_LINE
	LOG_FUNC

	return SendReceive(EUsbBusRequest);
	}

EXPORT_C TInt RUsb::BusRespondSrp()
	{
	LOG_LINE
	LOG_FUNC

	return SendReceive(EUsbBusRespondSrp);
	}

EXPORT_C TInt RUsb::BusClearError()
	{
	LOG_LINE
	LOG_FUNC

	return SendReceive(EUsbBusClearError);
	}


EXPORT_C TInt RUsb::BusDrop()
	{
	LOG_LINE
	LOG_FUNC

	return SendReceive(EUsbBusDrop);
	}

EXPORT_C void RUsb::MessageNotification(TRequestStatus& aStatus, TInt& aMessage)
	{
	LOG_LINE
	LOG_FUNC

	iMessagePkg.Set((TUint8*)&aMessage, sizeof(TInt), sizeof(TInt));

	SendReceive(EUsbRegisterMessageObserver, TIpcArgs(&iMessagePkg), aStatus);
	}

EXPORT_C void RUsb::MessageNotificationCancel()
	{
	LOG_LINE
	LOG_FUNC

	SendReceive(EUsbCancelMessageObserver);
	}

EXPORT_C void RUsb::HostEventNotification(TRequestStatus& aStatus,
										  TDeviceEventInformation& aDeviceInformation)
	{
	LOG_LINE
	LOG_FUNC

	iHostPkg.Set((TUint8*)&aDeviceInformation, sizeof(TDeviceEventInformation), sizeof(TDeviceEventInformation));

	SendReceive(EUsbRegisterHostObserver, TIpcArgs(&iHostPkg), aStatus);
	}
	
EXPORT_C void RUsb::HostEventNotificationCancel()
	{
	LOG_LINE
	LOG_FUNC

	SendReceive(EUsbCancelHostObserver);
	}

EXPORT_C TInt RUsb::EnableFunctionDriverLoading()
	{
	LOG_LINE
	LOG_FUNC

	return SendReceive(EUsbEnableFunctionDriverLoading);
	}

EXPORT_C void RUsb::DisableFunctionDriverLoading()
	{
	LOG_LINE
	LOG_FUNC

	SendReceive(EUsbDisableFunctionDriverLoading);
	}

EXPORT_C TInt RUsb::GetSupportedLanguages(TUint aDeviceId, RArray<TUint>& aLangIds)
	{
	LOG_LINE
	LOG_FUNC

	aLangIds.Reset();

	TInt ret = KErrNone;
	HBufC8* buf = NULL;
	// +1 for the actual count of language ids
	TRAP(ret, buf = HBufC8::NewL((KUsbMaxSupportedLanguageIds + 1)*sizeof (TUint)));
	if (ret != KErrNone)
		{
		return ret;
		}

	TPtr8 ptr8 = buf->Des();
	ret = SendReceive(EUsbGetSupportedLanguages, TIpcArgs(aDeviceId, &ptr8));
		
	if (ret == KErrNone)
		{
		const TUint* recvedIds = reinterpret_cast<const TUint*>(buf->Ptr());
		if (!recvedIds)
			{
			delete buf;
			return KErrCorrupt;
			}
			
		TInt arraySize = *recvedIds++;
		// Copy received language ids to aLangIds
		for (TInt i = 0; i < arraySize; i++)
			{
			ret = aLangIds.Append(*recvedIds++); // increments by sizeof(TUint)
			if ( ret )
				{
				aLangIds.Reset();
				break;
				}
			}
		}
		
	delete buf;	
	return ret;
	}
	
EXPORT_C TInt RUsb::GetManufacturerStringDescriptor(TUint aDeviceId, TUint aLangId, TName& aString)
	{
	LOG_LINE
	LOG_FUNC

	return SendReceive(EUsbGetManufacturerStringDescriptor, TIpcArgs(aDeviceId, aLangId, &aString));
	}

EXPORT_C TInt RUsb::GetProductStringDescriptor(TUint aDeviceId, TUint aLangId, TName& aString)
	{
	LOG_LINE
	LOG_FUNC

	return SendReceive(EUsbGetProductStringDescriptor, TIpcArgs(aDeviceId, aLangId, &aString));
	}

EXPORT_C TInt RUsb::GetOtgDescriptor(TUint aDeviceId, TOtgDescriptor& aDescriptor)
	{
	LOG_LINE
	LOG_FUNC
		
	TPckg<TOtgDescriptor> otgDescPkg(aDescriptor);
	
	TIpcArgs args;
	args.Set(0, aDeviceId);
	args.Set(1, &otgDescPkg);

	return SendReceive(EUsbGetOtgDescriptor, args);
	}


EXPORT_C TInt RUsb::RequestSession()
	{
	LOG_LINE
	LOG_FUNC

	return SendReceive(EUsbRequestSession);
	}

EXPORT_C TInt RUsb::GetDetailedDescription(TInt aPersonalityId, HBufC*& aLocalizedPersonalityDescriptor)
	{
	LOG_LINE
	LOG_FUNC

 	TInt ret = KErrNone;
	// caller is responsible for freeing up memory allocated for aLocalizedPersonalityDescriptor
	TRAP(ret, aLocalizedPersonalityDescriptor = HBufC::NewL(KUsbStringDescStringMaxSize));
	if (ret == KErrNone)
		{
		TPtr ptr = aLocalizedPersonalityDescriptor->Des();
		TIpcArgs ipcArgs(0, &ptr);
		ipcArgs.Set(0, aPersonalityId);
		ret = SendReceive(EUsbGetDetailedDescription, ipcArgs);
		}
	else
		{
		// just in case caller tries to free the memory before checking the return code
		aLocalizedPersonalityDescriptor = NULL;
		}

	return ret; 
	}

EXPORT_C TInt RUsb::GetPersonalityProperty(TInt aPersonalityId, TUint32& aProperty)
	{
	LOG_LINE
	LOG_FUNC

	TPckg<TUint32> pkg(aProperty);
	TInt ret = SendReceive(EUsbGetPersonalityProperty, TIpcArgs(aPersonalityId, &pkg));
	if (ret == KErrNone)
		{
		aProperty = static_cast<TUint32>(pkg());
		}
	return ret;	
	}

