// Copyright (c) 2004-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:
// btcmtm.cpp
//

//class include
#include <btcmtm.h>

//system includes
#include <e32std.h>
#include <e32base.h>
#include <txtrich.h>	// CRichText

#include <mtmuids.h>	// KUidMtmQueryCanSendMsg
#include <msvreg.h>		// CRegisteredMtmDll
#include <mtmdef.h>		// KUidMtmQueryxxx & TMsvPartList flags
#include <msvuids.h>	// KUidMsvMessageEntry
#include "btmtmcmds.h"	//EBtMtmCmdSend

//user includes
#include <btheader.h>
#include "btmsgtypeuid.h" //KUidMsgTypeBt
#include <cobexsendoperation.h>

#ifdef SYMBIAN_ENABLE_SPLIT_HEADERS  
#include "msvconsts.h"
#include <mtmuidsdef.hrh>
#endif

const TUint8 KObexConnectionIDHeader = 0xCB;

CBtClientMtm::CBtClientMtm(CRegisteredMtmDll& aRegisteredMtmDll, CMsvSession& aMsvSession)
: CObexClientMtm(aRegisteredMtmDll, aMsvSession, KUidMsgTypeBt)
/**
 * Constructor--not for use by client applications
 *
 * @param aRegisteredMtmDll Registration data for MTM DLL.
 * @param aMsvSession CMsvSession of the client requesting the object. 
 */
	{
	}

void CBtClientMtm::InitialiseHeaderL()
/**
 * Deletes the old header, then creates a new CBtHeader.
 *
 * @leave KErrXXX System-wide error codes if allocation fails
 */
	{
	delete iHeader;
	iHeader = 0;

	iHeader = CBtHeader::NewL();
	}

EXPORT_C CBtClientMtm* CBtClientMtm::NewL(CRegisteredMtmDll& aRegisteredMtmDll, CMsvSession& aMsvSession)
/**
 * Canonical NewL factory function. 
 *
 * @param aRegisteredMtmDll Reference to registration data for MTM DLL.
 * @param aMsvSession Reference to CMsvSession of the client requesting the object.
 * @return Pointer to a new, constructed CBtClientMtm
 * @leave Leaves if no memory is available.
 */
	{
	CBtClientMtm* self = new(ELeave) CBtClientMtm(aRegisteredMtmDll, aMsvSession);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop();
	return self;
	}

CMsvOperation* CBtClientMtm::InvokeAsyncFunctionL(TInt aFunctionId, 
												    const CMsvEntrySelection& aSelection,
													TDes8& aParameter, 
												    TRequestStatus& aCompletionStatus)
/**
 * Starts an asynchronous function as an active object. Only works for EBtcCmdSend.
 *
 * @param aFunctionId Identifier of the function to be invoked. Only supports EBtcCmdSend and
 * KMTMStandardFunctionsSendMessage.
 * @param aSelection Selction of message entries for the requested function to operate on.
 * @param aParameter Buffer containing input and output parameters.
 * @param aCompletionStatus Canonical TRequestStatus used for control of the active object.
 * @return Pointer to a new asynchronously completing CMsvOperation. If failed, this is a completed operation with 
 * status set to the relevant error code.
 * @leave Leaves if no memory is available, or if the specified aFunctionId is unsupported.
 */	{
	__TEST_INVARIANT_VIRTUAL

	CMsvOperation* op = NULL;
	switch (aFunctionId)
		{
	case KMTMStandardFunctionsSendMessage:
			{
			// parameter ignored, it's assumed that the 1st addressee will contain all the
			// required information to send this item. Only the 1st item in the selection
			// list is sent.
			CreateMessageOperationL(op, aSelection, aCompletionStatus);
			break;
			}
	case EBtMtmCmdSend:
			{
			/*
			In order to get our password over the client-server boundary without breaking the client interface
			we will repack the client side package buffer into a server package buffer.
			*/
			TPckgBuf<CBtClientMtm::SBtcCmdSendServerParams> serverParams;

			TPckgBuf<CBtClientMtm::SBtcCmdSendParams>& clientParams = (TPckgBuf<CBtClientMtm::SBtcCmdSendParams>&)aParameter;

			/* at this point, it may be worth checking the length of the supplied password
			and leaving with KErrArgument if it's longer than allowed */
			if(!clientParams().iConnectPassword || clientParams().iConnectPassword->Length() > KBlueToothObexPasswordLength)
				User::Leave(KErrArgument);
				
			serverParams().iTimeouts = clientParams().iTimeouts;
			serverParams().iRemoteObexPort = clientParams().iRemoteObexPort;
			serverParams().iConnectPassword = *(clientParams().iConnectPassword);

			op = Session().TransferCommandL(aSelection,	aFunctionId, serverParams,
											aCompletionStatus);
			break;
			}
	default:
		User::Leave(KErrNotSupported);
		}
	return(op); // ownership of op is passed to caller
	}


void CBtClientMtm::CreateMessageOperationL(
	CMsvOperation*& aOperation, const CMsvEntrySelection& aSelection, TRequestStatus& aCompletionStatus)
	{		
	// must have at least 1 addressee (if more than 1 the others are ignored)
	if (AddresseeList().Count() == 0)
		{
		User::Leave(KErrArgument);
		}
	
	// extract and parse addressee information
	TPckgBuf<CBtClientMtm::SBtcCmdSendServerParams> serverParams;
	serverParams().iRemoteObexPort = 0;
	
	// Set some default values for the timeouts in case they have not been added to
	// the addressee.
	serverParams().iTimeouts.iConnectTimeout = 0;
	serverParams().iTimeouts.iPutTimeout = 0;

	// address already saved when addressee was added (and we know there is at least one)
	TBuf8<KBlueToothObexDeviceAddressLength> addressIgnored; 
	TBuf<KBlueToothObexPasswordLength> password;
	ParseDestinationL(AddresseeList()[0], addressIgnored, password, 
		serverParams().iTimeouts.iConnectTimeout, 
		serverParams().iTimeouts.iPutTimeout);

	serverParams().iConnectPassword = password;
			
	// obex send operation wrapper, supporting standard progress
	CObexSendOperation* send = new(ELeave) CObexSendOperation(Session(), aCompletionStatus);
	CleanupStack::PushL(send);
	// start the send operation
	CMsvOperation* op = 
		Session().TransferCommandL(aSelection,	EBtMtmCmdSend, serverParams, send->iStatus);
	CleanupStack::PushL(op);
	send->Start(op);
	CleanupStack::Pop(2, send); // op, send
	aOperation = send;
	}

void CBtClientMtm::AddAddresseeL(const TDesC& anAddressee)
	{
	// check addressee parses ok - checks all fields (that are there)
	TBuf8<KBlueToothObexDeviceAddressLength> address;
	TBuf<KBlueToothObexPasswordLength> ignored1;
	TInt ignored2(0);
	TInt ignored3(0);
	ParseDestinationL(anAddressee, address, ignored1, ignored2, ignored3);
	
	// parses ok, try to save it - fails if there is already an addressee
	CObexClientMtm::AddAddresseeL(anAddressee);	

	// parses and saves ok, store device address in header
	iHeader->SetAddrL(address);
	}

/**
 * Parse bluetooth addressee field
 *
 * @param aFieldTag Field tag ID (see TBtClientMtmAddresseeFieldType).
 * @param aField The next field (descriptor buffer of the correct size for the given field).
 * @param aFieldList Addressee field list.
 * @leave Leaves with KErrArgument if parameter or formatting incorrect.
 */
TBool CBtClientMtm::ParseDestinationFieldL(TUint16 aFieldTag, TDes8& aField, TPtrC& aFieldList)
	{
	TBool isDeviceAddress = (aFieldTag == EDeviceAddress);

	aField.Zero();
	TInt length = aFieldList.Length();
	if (!isDeviceAddress)
		{
		// end of field list
		if (length == 0)
			{
			return EFalse;
			}
		// expecting tag
		if (length < 2 || aFieldList[0] != ':' || aFieldList[1] != aFieldTag)
			{
			User::Leave(KErrArgument);
			}
		// remove tag and marker
		aFieldList.Set(aFieldList.Right(aFieldList.Length() - 2));	
		}

	// locate next tag		
	TInt fieldLength = aFieldList.Locate(':');
	if (fieldLength == KErrNotFound)
		{
		fieldLength = aFieldList.Length();
		}

	// twice as many bytes
	TInt fieldLength8 = fieldLength << 1;
	if (fieldLength8 > aField.MaxLength())
		{
		User::Leave(KErrArgument);
		}
	
	// copy field	
	aField.Copy((TUint8*)aFieldList.Ptr(), fieldLength8);

	// remove field from field list
	aFieldList.Set(aFieldList.Right(aFieldList.Length() - fieldLength));	
	
	return ETrue;
	}	
	
/**
 * Parse bluetooth addressing information from addressee list.
 *
 * @param aDeviceAddress Device address (6 bytes).
 * @param aPassword Password (max length 16).
 * @param aConnectTimeout Connection timeout.
 * @param aPutTimeout Put timeout.
 * @leave Leaves with KErrArgument if parameter or formatting incorrect.
 */
void CBtClientMtm::ParseDestinationL(const TDesC& aFieldsToParse, TDes8& aDeviceAddress, 
								   TDes16& aPassword, TInt& aConnectTimeout, TInt& aPutTimeout)
	{
	TPtrC16 fields(aFieldsToParse);

	// get device address
	if (ParseDestinationFieldL(EDeviceAddress, aDeviceAddress, fields))
		{
		// get password
		TPtr8 password((TUint8*)aPassword.Ptr(), aPassword.MaxLength()<<1);
		if (ParseDestinationFieldL(EPassword, password, fields))
			{
			aPassword.SetLength(password.Length()>>1);
			// get connection timeout
			TPckg<TInt> connectTimeout(aConnectTimeout);
			TBool parsedOk = EFalse;

			// The connect timeout can have a field tag defined by ETimeout, or a
			// field tag defined by EAlternativeConnectTimeout. This is because an
			// older version of code looked for the wrong tag (EAlternativeConnectTimeout),
			// and we need to remain backward compatible with it.
			TRAPD(err, parsedOk = ParseDestinationFieldL(ETimeout, connectTimeout, fields));

			if (err == KErrArgument)
				{
				parsedOk = ParseDestinationFieldL(EAlternativeConnectTimeout, connectTimeout, fields);
				}
			else
				{
				User::LeaveIfError(err);
				}

			if (parsedOk)
				{
				// get put timeout
				TPckg<TInt> putTimeout(aPutTimeout);
				ParseDestinationFieldL(EPutTimeout, putTimeout, fields);
				}
			}
		}
	}



#ifdef _DEBUG
void CBtClientMtm::TestInvariant() const
	{
	__ASSERT_DEBUG(iMsvEntry &&
				   iHeader &&
				   (iMsvEntry->Entry().iMtm == KUidMsgTypeBt) &&
				   (iMsvEntry->Entry().iType == KUidMsvMessageEntry),
				   User::Invariant());
	}
#endif //_DEBUG

