accessoryservices/remotecontrolfw/server/src/controllersession.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Mon, 04 Oct 2010 02:28:24 +0300
changeset 74 9d35fd98f273
permissions -rw-r--r--
Revision: 201039 Kit: 201039

// Copyright (c) 2010 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 "Symbian Foundation License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.symbianfoundation.org/legal/sfl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
//

#include <bluetooth/logger.h>
#include "activehelper.h"
#include "bearermanager.h"
#include "controllersession.h"
#include "messagequeue.h"
#include "remconmessage.h"
#include "remconserver.h"
#include "server.h"
#include "utils.h"

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

#ifdef _DEBUG
PANICCATEGORY("ctsession");
#endif


CRemConControllerSession* CRemConControllerSession::NewL(CRemConServer& aServer,
	CBearerManager& aBearerManager,
	const TClientInfo& aClientInfo,
	TUint aId)
	{
	LOG_STATIC_FUNC;
	CRemConControllerSession* self = new(ELeave) CRemConControllerSession(aServer, aBearerManager, aId);
	CleanupStack::PushL(self);
	self->ConstructL(aClientInfo);
	CLEANUPSTACK_POP1(self);
	return self;
	}

CRemConControllerSession::~CRemConControllerSession()
	{
	LOG_FUNC;

	delete iPendingMsgProcessor;

	// we will need to tell the server which bearer this used to be connected to
	// this enables the server to not inform a bearer that is already connected
	// that its been connected
	// Tell the server we've gone away- it may start its shutdown timer.
	iServer.ControllerClientClosed(*this, iRemoteAddress.BearerUid());
	iPlayerName.Close();
	}

CRemConControllerSession::CRemConControllerSession(CRemConServer& aServer, 
	CBearerManager& aBearerManager,
	TUint aId)
	: CRemConSession(aServer, aBearerManager, aId)
	{
	LOG_FUNC;
	}

void CRemConControllerSession::ConstructL(const TClientInfo& aClientInfo)
	{
	LOG_FUNC;
	
	BaseConstructL(aClientInfo);

	iPendingMsgProcessor = new (ELeave) CActiveHelper(*this);
	
	LEAVEIFERRORL(iServer.ControllerClientOpened(*this));

	// Set our pointer into the connection history at the current/'Last' item.
	// Can't do this til we've told the server we exist
	iServer.SetConnectionHistoryPointer(Id());
	}

TBool CRemConControllerSession::SupportedMessage(const CRemConMessage& aMsg) const
	{
	LOG_FUNC;
	LOG1(_L("\taMsg.InterfaceUid() = 0x%08x"), aMsg.InterfaceUid());

	// Return true unless this is a command for an unsupported interface
	TBool result = !(aMsg.MsgType() == ERemConCommand && !FindInterfaceByUid(aMsg.InterfaceUid()));

	LOG1(_L("result = %d"), result);
	return result;
	}

void CRemConControllerSession::SetPlayerType(const RMessage2& aMessage)
	{
	LOG_FUNC;

	// Controller clients don't provide the additional parameters are optional,
	// so we just complete the message with KErrNone.
	CompleteClient(aMessage, KErrNone);
	}

void CRemConControllerSession::CompleteConnect(const TRemConAddress& aAddr, TInt aError)
	{
	LOG_FUNC;
	LOG2(_L("\taError = %d, aAddr.BearerUid = 0x%08x"), aError, aAddr.BearerUid());

	LOG1(_L("\tiRemoteAddress.BearerUid = 0x%08x"), iRemoteAddress.BearerUid());
	LOG1(_L("\tiConnectBearerMsg.Handle = %d"), iConnectBearerMsg.Handle());

	if ( iRemoteAddress == aAddr )
		{
		if ( iConnectBearerMsg.Handle() )
			{
			// We are a session that has an outstanding request on this specific 
			// connection address.
			CompleteClient(iConnectBearerMsg, aError);
			}
		else 
			{
			// Connect bearer message is not valid. 
			// Check for pending messages.
			CheckForPendingMsg();
			}
		}
	}

void CRemConControllerSession::CompleteDisconnect(const TRemConAddress& aAddr, TInt aError)
	{
	LOG_FUNC;
	LOG2(_L("\taError = %d, aAddr.BearerUid = 0x%08x"), aError, aAddr.BearerUid());

	LOG1(_L("\tiRemoteAddress.BearerUid = 0x%08x"), iRemoteAddress.BearerUid());
	LOG1(_L("\tiDisconnectBearerMsg.Handle = %d"), iDisconnectBearerMsg.Handle());

	if ( iRemoteAddress == aAddr )
		{
		if ( iDisconnectBearerMsg.Handle() )
			{
			// We are a session that has an outstanding request on this specific 
			// connection address.
			CompleteClient(iDisconnectBearerMsg, aError);
			}
		else 
			{
			// Diconnect bearer message is not valid. 
			// Check for pending messages.
			CheckForPendingMsg();
			}

		}
	}

void CRemConControllerSession::ProcessPendingMsgL()
	{
	LOG_FUNC;
	if (!iPendingMsg.Handle())
		{
		// This means that the pending connect or disconnect message,
		// has been cancelled by the time we got here.
		// (It was cancelled between two following calls:
		// iPendingMsgProcessor::Complete and iPendingMsgProcessor::RunL
		return;
		}

	ServiceL(iPendingMsg);
	if (iPendingMsg.Handle())
		{
		// This means that the pending msg has not been completed in ServiceL call.
		// It was stored either in iConnectBearerMsg or iDisconnectBearerMsg member.
		// This also means that this message is not "pending" any more 
		// (as processing of its copy has been started). 
		// However because the copy will get completed we need to 
		// clean iPendingMsg.iHandle here
		// To supress coverity error for uninitialized use of 'emptyMsg' coverity annotations
		// are used as the in-line default constructor of RMessage2 doesn't initialize all member variables.
		// coverity[var_decl]
		RMessage2 emptyMsg;
		iPendingMsg = emptyMsg;
		}
	}

void CRemConControllerSession::CheckForPendingMsg() const
	{
	LOG_FUNC;
	if (iPendingMsg.Handle())
		{
		ASSERT_DEBUG(iPendingMsgProcessor);
		iPendingMsgProcessor->Complete();
		}
	}

CRemConMessage* CRemConControllerSession::DoPrepareSendMessageL(const RMessage2& aMessage)
	{
	LOG_FUNC;

	// Check we don't have a disconnect outstanding- this makes no sense from 
	// a client viewpoint (they should cancel the disconnect first).
	// [The client is allowed to have a connect request outstanding- the 
	// bearer manager makes sure a bearer-level connect is not posted on the 
	// same address twice.]
	if ( iDisconnectBearerMsg.Handle() )
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicBearerControlOutstanding);
		return NULL;
		}

	// Get the data the client wants to send.
	TUid interfaceUid;
	TUint operationId;

	TRemConMessageSubType messageSubType;
	RBuf8 sendDes;
	if (!DoGetSendInfoLC(aMessage, interfaceUid, operationId, messageSubType, sendDes))
		{
		// DoGetSendInfoLC() panicked the message
		return NULL;
		}

	CRemConMessage* msg = NULL;
	LOG(_L("\tCONTROLLER send"));
	if (  (messageSubType == ERemConNotifyCommandAwaitingInterim)
	   || (messageSubType == ERemConNotifyCommandAwaitingChanged)
		)
		{
		LOG(_L("\terror, not allowed to use Send() to send notify command"));
		CleanupStack::PopAndDestroy(&sendDes);
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicIllegalIpc);
		}
	else
		{
		msg = CRemConMessage::NewL(
				iRemoteAddress, // either specified (if we're connection-oriented) or null (we're connectionless- this field will be filled in by the TSP)
				ERemConCommand, 
				messageSubType,
				interfaceUid,
				operationId,
				sendDes, // msg takes ownership
				Id(), // session id for when the response comes back
				0, // we let the bearer manager invent a new transaction id when the message gets to it
				ETrue);
		CLEANUPSTACK_POP1(&sendDes); // now owned by msg
		}		

	return msg;
	}

void CRemConControllerSession::SendUnreliable(const RMessage2& aMessage)
	{
	LOG_FUNC;

	// Check we've had our features set...
	if (!ClientAvailable())
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicClientFeaturesNotSet);
		return;
		}

	// Check we don't have a disconnect outstanding- this makes no sense from 
	// a client viewpoint (they should cancel the disconnect first).
	// [The client is allowed to have a connect request outstanding- the 
	// bearer manager makes sure a bearer-level connect is not posted on the 
	// same address twice.]
	if ( iDisconnectBearerMsg.Handle() )
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicBearerControlOutstanding);
		return;
		}

	CRemConMessage* msg = NULL;
	TRAPD(err, msg = DoCreateUnreliableMessageL(aMessage));
	CompleteClient(aMessage, err);
	if (err == KErrNone)
		{
		ASSERT_DEBUG(iSendQueue);
		if (iSending || !iSendQueue->IsEmpty())
			{
			iSendQueue->Append(*msg);
			}
		else
			{
			SendToServer(*msg);
			}
		}
	}

CRemConMessage* CRemConControllerSession::DoCreateUnreliableMessageL(const RMessage2& aMessage)
	{
	LOG_FUNC;

	// Get the data the client wants to send.
	TUid interfaceUid;
	TUint operationId;
	TRemConMessageSubType messageSubType;
	RBuf8 sendDes;
	DoGetSendInfoLC(aMessage, interfaceUid, operationId, messageSubType, sendDes);

	// Before we ask the server to send, we must set our ClientInfo 
	// correctly so the TSP can get information about the client. 
	iClientInfo.Message() = aMessage;

	CRemConMessage* msg = NULL;
	
	LOG(_L("\tCONTROLLER send"));
	
	// A client is not allowed to send an unreliable notify command.
	if	(	(messageSubType == ERemConNotifyCommandAwaitingInterim)
		||	(messageSubType == ERemConNotifyCommandAwaitingChanged)
		)
		{
		LOG(_L8("\tNot allowed to send unreliable notify command"));
		CleanupStack::PopAndDestroy(&sendDes);
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicIllegalIpc);
		LEAVEL(KErrBadDescriptor);
		}
	
	msg = CRemConMessage::NewL(
		iRemoteAddress, // either specified (if we're connection-oriented) or null (we're connectionless- this field will be filled in by the TSP)
		ERemConCommand, // controllers can only send commands
		messageSubType,
		interfaceUid,
		operationId,
		sendDes, // msg takes ownership
		Id(), // session id for when the response comes back
		0, // we let the bearer manager invent a new transaction id when the message gets to it
		EFalse);
	CLEANUPSTACK_POP1(&sendDes); // now owned by msg

	return msg;
	}

void CRemConControllerSession::RegisterInterestedAPIs(const RMessage2& aMessage)
	{
	LOG_FUNC;
	// No interfaces should have been registered yet!
	ASSERT_DEBUG(iInterestedAPIs == NULL);
	
	TRAPD(err, iInterestedAPIs = ExtractInterestedAPIsL(aMessage));
	
	iServer.ControllerClientAvailable();

	CompleteClient(aMessage, err);
	}

void CRemConControllerSession::GoConnectionOriented(const RMessage2& aMessage)
	{
	LOG_FUNC;

	// Check we've had our features set...
	if (!ClientAvailable())
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicClientFeaturesNotSet);
		return;
		}

	if ( !iRemoteAddress.IsNull() )
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicAlreadyConnectionOriented);
		return;
		}

	if ( iConnectBearerMsg.Handle() || iDisconnectBearerMsg.Handle() || iSendMsg.Handle())
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicBearerControlOutstanding);
		return;
		}
	if (iSending != ENotSending)
		{
		DoSendCancel();
		}
	EmptySendQueue();
	
	// Get the desired address from the message and check it.
	const TUid uid = TUid::Uid(aMessage.Int0());
	LOG1(_L("\tuid = 0x%08x"), uid);
	// Check the requested bearer exists.
	TBool bearerExists = iBearerManager.BearerExists(uid);
	if ( !bearerExists)
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicBearerPluginIncorrectInterface);
		return;
		}
	// Check the bearer-specific part of the address.
	TBuf8<TRemConAddress::KMaxAddrSize> buf;
	TInt err = aMessage.Read(1, buf);
	if ( err != KErrNone )
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicBadDescriptor);
		return;
		}

	// Do security check- if this client won't be allowed to use the bearer 
	// then fail the request. 
	// NB This security check (repeated in debug at ConnectBearer and 
	// DisconnectBearer time) is all that stands between a connection-oriented 
	// client and the bearer, and is all the caps checking that RemCon does!
	err = KErrPermissionDenied;
	if ( iBearerManager.CheckPolicy(uid, aMessage) )
		{
		err = KErrNone;
		}
		
		
	// if alls well and we're connection oriented then set up as such
	if (KErrNone == err)
		{
		// The client has passed all our checks- set our data member.
		iRemoteAddress.BearerUid() = uid;
		iRemoteAddress.Addr() = buf;
		// tell the server
		iServer.ClientGoConnectionOriented(*this,uid);
		}
				
	CompleteClient(aMessage, err);
	}

void CRemConControllerSession::GoConnectionless(const RMessage2& aMessage)
	{
	LOG_FUNC;

	// Check we've had our features set...
	if (!ClientAvailable())
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicClientFeaturesNotSet);
		return;
		}

	if ( iRemoteAddress.IsNull() )
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicNotConnectionOriented);
		return;
		}

	if ( iConnectBearerMsg.Handle() || iDisconnectBearerMsg.Handle() || iSendMsg.Handle())
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicBearerControlOutstanding);
		return;
		}
	
	if (iSending != ENotSending)
		{
		DoSendCancel();
		}
	EmptySendQueue();
	
	// we will need to tell the server which bearer this used to be connected to
	// this enables the server to not inform a bearer that is already connected
	// that its been connected
	TUid oldUid = iRemoteAddress.BearerUid();
	
	iRemoteAddress.BearerUid() = KNullUid;	

	// tell the server
	iServer.ClientGoConnectionless(*this, oldUid);

	CompleteClient(aMessage, KErrNone);
	}

void CRemConControllerSession::ConnectBearer(const RMessage2& aMessage)
	{
	LOG_FUNC;

	// Check we've had our features set...
	if (!ClientAvailable())
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicClientFeaturesNotSet);
		return;
		}

	if ( iConnectBearerMsg.Handle() || iDisconnectBearerMsg.Handle() )
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicBearerControlOutstanding);
		return;
		}

	if ( iRemoteAddress.IsNull() )
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicNotConnectionOriented);
		return;
		}

	// Check the requested bearer exists.
	TBool bearerExists = iBearerManager.BearerExists(iRemoteAddress.BearerUid());
	// This check was done at GoConnectionOriented time.
	ASSERT_DEBUG(bearerExists);
	// So was this one.
	ASSERT_DEBUG(iBearerManager.CheckPolicy(iRemoteAddress.BearerUid(), aMessage));

	// Check the state of our given connection at the bearer level. If it is: 
	// -) disconnected request the connection to come up,
	// -) connecting or disconnecting, add message to the queue of pending 
	//		messages, and process it once connecting/disconnecting has been completed
	// -) connected, complete the client's message,

	TConnectionState conState;
	conState = iServer.ConnectionState(iRemoteAddress);

	if ( conState == EDisconnected )
		{
		// The bearer may indicate connection synchronously, so set this 
		// message _before_ we ask them
		iConnectBearerMsg = aMessage;
		TInt err = iBearerManager.Connect(iRemoteAddress);
		if ( err != KErrNone )
			{
			CompleteClient(iConnectBearerMsg, err);
			}
		}
	else if ( conState == EDisconnecting ||  conState == EConnecting )
		{
		if ( iPendingMsg.Handle() )
			{
			PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicBearerControlOutstanding);
			return;
			}
		// Store the message, it will get processed later.
		iPendingMsg = aMessage;
		}
	else // EConnected
		{
		CompleteClient(aMessage, KErrNone);
		}
		
	}

void CRemConControllerSession::ConnectBearerCancel(const RMessage2& aMessage)
	{
	LOG_FUNC;

	// Check we've had our features set...
	if (!ClientAvailable())
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicClientFeaturesNotSet);
		return;
		}

	if ( iConnectBearerMsg.Handle() )
		{
		CompleteClient(iConnectBearerMsg, KErrCancel);
		}
	else if ( iPendingMsg.Handle() && ( iPendingMsg.Function() == ERemConConnectBearer ))
		{
		CompleteClient(iPendingMsg, KErrCancel);
		}
		
	CompleteClient(aMessage, KErrNone);
	// At no point do we make any change to the processes going on underneath
	// us- 'Cancel' APIs are just for cancelling interest in an async
	// operation.
	}

void CRemConControllerSession::DisconnectBearer(const RMessage2& aMessage)
	{
	LOG_FUNC;

	// Check we've had our features set...
	if (!ClientAvailable())
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicClientFeaturesNotSet);
		return;
		}

	if ( iDisconnectBearerMsg.Handle() || iConnectBearerMsg.Handle() || iSendMsg.Handle())
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicBearerControlOutstanding);
		return;
		}

	if ( iRemoteAddress.IsNull() )
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicNotConnectionOriented);
		return;
		}

	if (iSending != ENotSending)
		{
		DoSendCancel();
		}
	EmptySendQueue();
	
	// Check the requested bearer exists.
	TBool bearerExists = iBearerManager.BearerExists(iRemoteAddress.BearerUid());
	// This check was done at GoConnectionOriented time.
	ASSERT_DEBUG(bearerExists);
	// So was this one.
	ASSERT_DEBUG(iBearerManager.CheckPolicy(iRemoteAddress.BearerUid(), aMessage));

	// Check the state of the given connection. If it is:
	// -) connected, request connection to go away,
	// -) disconnected, compete the client's message,
	// -) connecting or disconnecting, add message to the queue of pending 
	//		messages, and process it once connecting/disconnecting has been completed

	TInt err;
	TConnectionState conState;
	conState = iServer.ConnectionState(iRemoteAddress);

	if ( conState == EConnected )
		{
		// The bearer may indicate disconnection synchronously, so set this 
		// message _before_ we ask them
		iDisconnectBearerMsg = aMessage;
		err = iBearerManager.Disconnect(iRemoteAddress);
		if ( err != KErrNone )
			{
			CompleteClient(iDisconnectBearerMsg, err);
			}
		}
	else if ( conState == EDisconnecting ||  conState == EConnecting )
		{
		if ( iPendingMsg.Handle() )
			{
			PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicBearerControlOutstanding);
			return;
			}
		// Store the message, it will get processed later.
		iPendingMsg = aMessage;
		}
	else //disconnected
		{
		CompleteClient(aMessage, KErrNone);	
		}
	}

void CRemConControllerSession::DisconnectBearerCancel(const RMessage2& aMessage)
	{
	LOG_FUNC;

	// Check we've had our features set...
	if (!ClientAvailable())
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicClientFeaturesNotSet);
		return;
		}

	if ( iDisconnectBearerMsg.Handle() )
		{
		CompleteClient(iDisconnectBearerMsg, KErrCancel);
		}
	else if ( iPendingMsg.Handle() && (iPendingMsg.Function() == ERemConDisconnectBearer ))
		{
		CompleteClient(iPendingMsg, KErrCancel);
		}
		
	CompleteClient(aMessage, KErrNone);
	}

/**
Sends a notify message to the remote device.

This function is intended for the RemCon controller client to send a notify
command to the remote device.
*/
void CRemConControllerSession::SendNotify(const RMessage2& aMessage)
	{
	LOG_FUNC;

	// Check we're not already sending...
	if ( iSendMsg.Handle())
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicSendAlreadyOutstanding);
		return;
		}
	
	iSendMsg = aMessage;
	
	// Check we've had our features set...
	if (!ClientAvailable())
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicClientFeaturesNotSet);
		return;
		}

	// Check we don't have a disconnect outstanding- this makes no sense from 
	// a client viewpoint (they should cancel the disconnect first).
	// [The client is allowed to have a connect request outstanding- the 
	// bearer manager makes sure a bearer-level connect is not posted on the 
	// same address twice.]
	if ( iDisconnectBearerMsg.Handle() )
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicBearerControlOutstanding);
		return;
		}

	TRAPD(err, DoSendNotifyL(aMessage));
	if ( err != KErrNone )
		{
		CompleteClient(aMessage, err);
		}
	}

/**
@see CRemConControllerSession::SendNotify
*/
void CRemConControllerSession::DoSendNotifyL(const RMessage2& aMessage)
	{
	LOG_FUNC;

	// Get the data the client wants to send.
	const TUid interfaceUid = TUid::Uid(aMessage.Int0());
	LOG1(_L("\tinterfaceUid = 0x%08x"), interfaceUid);

	if (aMessage.GetDesLengthL(1) != sizeof(TOperationInformation))
		{
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicBadDescriptor);
		return;
		}

	TPckgBuf<TOperationInformation> opInfoPckg;	
	TInt err= aMessage.Read(
			1, // location of the descriptor in the client's message (as we expect them to have set it up)
			opInfoPckg, // descriptor to write to from client memory space
			0 // offset into our descriptor to put the client's data
			);
	
	if ( err != KErrNone )
		{
		LOG1(_L("\taMessage.Read = %d"), err);
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicBadDescriptor);
		return;
		}	
	
	const TUint operationId = opInfoPckg().iOperationId;
	LOG1(_L("\toperationId = 0x%02x"), operationId);
	
	const TRemConMessageSubType messageSubType = opInfoPckg().iMessageSubType;
	LOG1(_L("\tmessageSubType = 0x%02x"), messageSubType);
	
	const TUint dataLength = (TUint)aMessage.GetDesLengthL(2);
	LOG1(_L("\tdataLength = %d"), dataLength);
	
	// If the client wanted to send some operation-associated data, read it 
	// from them.
	RBuf8 sendDes;
	if ( dataLength != 0 )
		{
		sendDes.CreateL(dataLength);
		TInt err = aMessage.Read(
			2, // location of the descriptor in the client's message (as we expect them to have set it up)
			sendDes, // descriptor to write to from client memory space
			0 // offset into our descriptor to put the client's data
			);
		// NB We don't do LEAVEIFERRORL(aMessage.Read) because a bad client 
		// descriptor is a panicking offence for them, not an 'error the 
		// request' offence.
		if ( err != KErrNone )
			{
			LOG1(_L("\taMessage.Read = %d"), err);
			sendDes.Close();
			PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicBadDescriptor);
			return;
			}
		}

	// Before we ask the server to send, we must set our ClientInfo 
	// correctly so the TSP can get information about the client. 
	iClientInfo.Message() = aMessage;

	CRemConMessage* msg = NULL;
	
	if (messageSubType != ERemConNotifyCommandAwaitingInterim)
		{
		sendDes.Close();
		PANIC_MSG(aMessage, KRemConClientPanicCat, ERemConClientPanicIllegalIpc);
		return;
		}
	
	CleanupClosePushL(sendDes);
	msg = CRemConMessage::NewL(
			iRemoteAddress, // either specified (if we're connection-oriented) or null (we're connectionless- this field will be filled in by the TSP)
			ERemConNotifyCommand, 
			messageSubType,
			interfaceUid,
			operationId,
			sendDes, // msg takes ownership
			Id(), // session id for when the response comes back
			0, // we let the bearer manager invent a new transaction id when the message gets to it
			ETrue);	
	CLEANUPSTACK_POP1(&sendDes); // now owned by msg
	
	LOG(_L("\tCONTROLLER send"));
	ASSERT_DEBUG(iSendQueue);
	if (iSending != ENotSending || !iSendQueue->IsEmpty())
		{
		iSendQueue->Append(*msg);
		}
	else
		{
		SendToServer(*msg);
		}
	}

void CRemConControllerSession::SendToServer(CRemConMessage& aMsg)
	{
	LOG_FUNC;
	
	// Set our completion members.
	NumRemotes() = 0;
	NumRemotesToTry() = 0;
	SendError() = KErrNone;

	
	iSending = (aMsg.IsReliableSend()) ? ESendingReliable: ESendingUnreliable;
	
	iServer.SendCommand(aMsg);
	}

void CRemConControllerSession::EmptySendQueue()
	{
	LOG_FUNC;

	ASSERT_DEBUG(!iSendMsg.Handle())
	ASSERT_DEBUG(iSendNextCallBack);
	iSendNextCallBack->Cancel();
	CRemConMessage* msg;
	ASSERT_DEBUG(iSendQueue);
	TSglQueIter<CRemConMessage>& iter = iSendQueue->SetToFirst();
	while ((msg = iter++) != NULL)
		{
		iSendQueue->RemoveAndDestroy(*msg);
		}
	}

void CRemConControllerSession::DoSendCancel()
	{
	LOG_FUNC;
	// We must tell the server, and pull the CRemConMessage from the 
	// 'outgoing pending TSP' queue if it's on it. If the TSP is currently 
	// processing the CRemConMessage, we must tell it to stop before we 
	// can complete the RMessage2 iSendMsg- the TSP might still be 
	// dereferencing bits of it. (The TSP is given iSendMsg so it can 
	// access the client's secure ID and do a capability check.)
	// NB This only matters for commands- responses don't go through the 
	// TSP.
	// Not also that this processing *stops* this 
	// CRemConSession::SendCancel method from being the very simple 'I'm 
	// no longer interested in the completion of the asynchronous request' 
	// type of API it (and all cancels) should be. It actually does work 
	// as well. As long as this work is implemented _synchronously_, we 
	// should be OK.
	iServer.SendCancel(*this);

	NumRemotesToTry() = 0;
	iSendError = KErrCancel;
	CompleteSend();
	}

void CRemConControllerSession::CompleteMessage(const CRemConMessage& aMessage)
	{
	LOG_FUNC;

	switch (aMessage.MsgType())
		{
	case ERemConCommand:
	case ERemConResponse:
	case ERemConReject:
		{
		CompleteSend();
		break;
		}
	case ERemConNotifyCommand:
		{
		CompleteSendNotify();
		break;
		}
	default:
		ASSERT_DEBUG(EFalse);
		break;
		}

	}

void CRemConControllerSession::DoReceive()
	{
	// Request messages from the server for this controller session.
	// If there's anything waiting to be given to us, ReceiveRequest will call 
	// back to us with it.
	iServer.ReceiveRequest(*this);	
	}

void CRemConControllerSession::MrcmsoMessageSendResult(const CRemConMessage& aMessage, TInt aError)
	{
	LOG_FUNC;

	// We should not already be sending a message to n remotes
	ASSERT_DEBUG(NumRemotesToTry() == 0);

	SendError() = aError;
	CompleteMessage(aMessage);
	}

void CRemConControllerSession::MrcmsoMessageSendOneOrMoreAttempt(const CRemConMessage& /*aMessage*/, TUint aNumRemotes)
	{
	LOG_FUNC;

	// We should not already be sending a message
	ASSERT_DEBUG(NumRemotesToTry() == 0);

	NumRemotes() = 0;
	NumRemotesToTry() = aNumRemotes;
	SendError() = KErrNone;
	}

void CRemConControllerSession::MrcmsoMessageSendOneOrMoreIncremental(const CRemConMessage& /*aMessage*/, TUint /*aNumRemotes*/)
	{
	LOG_FUNC;

	// This method should never be called, as it is not required to support controller sessions.
	ASSERT_DEBUG(EFalse);
	}

void CRemConControllerSession::MrcmsoMessageSendOneOrMoreAttemptFailed(const CRemConMessage& aMessage, TInt aError)
	{
	LOG_FUNC;

	// We should not already be sending a message
	ASSERT_DEBUG(NumRemotesToTry() == 0);

	NumRemotes() = 0;
	SendError() = aError;
	CompleteMessage(aMessage);
	}

void CRemConControllerSession::MrcmsoMessageSendOneOrMoreResult(const CRemConMessage& aMessage, TInt aError)
	{
	LOG_FUNC;

	// Ignore notification if client has been completed
	if (NumRemotesToTry() > 0)
		{
		// Only set error if different from KErrNone
		if (aError == KErrNone)
			{
			++NumRemotes();
			}
		else
			{
			SendError() = aError;
			}

		--NumRemotesToTry();
		if (NumRemotesToTry() == 0)
			{
			CompleteMessage(aMessage);
			}
		}
	}

void CRemConControllerSession::MrcmsoMessageSendOneOrMoreAbandoned(const CRemConMessage& /*aMessage*/)
	{
	LOG_FUNC;

	// This method should never be called, as it is not required to support controller sessions.
	ASSERT_DEBUG(EFalse);
	}