bluetooth/btstack/avctp/avctpmuxer.cpp
changeset 0 29b1cd4cb562
child 23 5b153be919d4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bluetooth/btstack/avctp/avctpmuxer.cpp	Fri Jan 15 08:13:17 2010 +0200
@@ -0,0 +1,1017 @@
+// Copyright (c) 2005-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:
+// AVCTP muxer
+// 
+//
+
+/**
+ @file
+ @internalComponent
+*/
+
+#include <bluetooth/logger.h>
+#include <bt_sock.h>
+#include <es_mbuf.h>
+
+#include "avctpmuxer.h"
+#include "avctpcommon.h"
+#include "avctpmuxerstates.h"
+#include "Avctp.h"
+#include "avctputils.h"
+#include "avctppacket.h"
+#include "avctpsap.h"
+#include "avctpPacketMgr.h"
+
+#ifdef __FLOG_ACTIVE
+_LIT8(KLogComponent, LOG_COMPONENT_AVCTP);
+#endif
+
+#ifdef _DEBUG
+PANICCATEGORY("avctpmux");
+#endif
+
+using namespace SymbianAvctp;
+
+#ifdef __FLOG_ACTIVE	
+#define LOG_CLIENTS LogClients();
+#else
+#define LOG_CLIENTS
+#endif
+
+/**
+Factory function for CAvctpTransport - called when local device initiates
+the connection.
+
+Note the CProtocolBase passed in so that the muxer can create its own L2CAP SAP
+
+  @internalComponent
+  @leave KErrNoMemory if the muxer could not be allocated
+  @param aProtocol The AVCTP protocol object, used to make callbacks on the protocol
+  @return A new muxer in the right state
+*/
+CAvctpTransport* CAvctpTransport::NewL(CAvctpProtocol& aProtocol)
+	{
+	LOG_STATIC_FUNC
+
+	CAvctpTransport* transport = new(ELeave) CAvctpTransport(aProtocol);
+	CleanupStack::PushL(transport);
+	transport->ConstructL();
+	CleanupStack::Pop(transport);
+	return transport;
+	}
+
+/**
+AVCTP SubConnection Provider Constructor
+
+Set up the async. callback.
+
+  @internalComponent
+  @param aProt The protocol object
+*/
+CAvctpTransport::CAvctpTransport(CAvctpProtocol& aProt) :
+	iProtocol(aProt),
+	iSecondChannelSocket(*this),
+	iState(&(aProt.MuxerStateFactory().GetState(CAvctpMuxerStateFactory::EClosed))),
+	iAddress(TBTDevAddr(0))
+	{
+	LOG_FUNC
+
+	TCallBack cb(IdleTimerExpired, this);
+	iIdleTimerEntry.Set(cb);
+	};
+
+/**
+Second phase muxer construction.
+
+  @internalComponent
+*/
+void CAvctpTransport::ConstructL()
+	{
+	LOG_FUNC
+
+	TCallBack cb(SecondChannelNewDataAsyncCallBack, this);
+	iNewDataAsyncCallBack = new (ELeave)CAsyncCallBack(cb, EActiveHighPriority);
+	iPacketMgr = CAvctpPacketMgr::NewL(*this, iProtocol);
+
+	SetSendBlocked(KAvctpPrimaryChannel);
+	SetSendBlocked(KAvctpSecondaryChannel);
+	CheckForIdle();
+	}
+
+/**
+Muxer d'tor
+
+We only Shutdown the lower protocol sap in an immediate way because in normal
+circumstances we will have decided to die after idling for a while which means
+no-one had data to send via this muxer. Hence there should be little chance
+of losing data that could otherwise have been left in the L2CAP sap. If we did a
+graceful close we would then have had to wait for the CanClose response which is
+an unnecessary overhead if we've already used a timeout to decide no-one wants
+the link.
+
+If the muxer died gracefully, there will be no data in the system that needs
+to be sent over this muxer, however if the muxer goes down suddenly, e.g. the
+remote disconnects, a connection attempt fails or a Shutdown is called there
+will be data left in the protocol as a whole that would attempt a reconnection
+to the remote device of this muxer. Hence we tell the saps we've gone down to 
+let them decide whether or not their packets have become stale or not.
+
+@internalComponent
+*/
+CAvctpTransport::~CAvctpTransport()
+	{
+	LOG_FUNC
+
+	DequeIdleTimer();
+	
+	iProtocol.SignalMuxerDownToSaps(DevAddr());
+	iProtocol.RemoveTransport(*this);  // it's okay to deque the link even if that's already been done
+
+	// the first channel (index 0) is the most important, so better closing from the last one
+	
+	TInt index;
+	for(index = iChannelSAPs.Count()-1;index>=0;index--)
+		{
+		if (iChannelSAPs[index])
+			{
+			iChannelSAPs[index]->Shutdown(CServProviderBase::EImmediate);
+			delete iChannelSAPs[index];
+			}
+		}
+	
+	CancelSecondChannelNewDataAsyncCallBack();
+	delete iNewDataAsyncCallBack;
+	
+	delete iPacketMgr;
+	iClientItems.Close();
+	}
+
+
+TInt CAvctpTransport::AddSecondChannel(CServProviderBase& aSAP)
+	{
+	LOG_FUNC
+	return iState->AddSecondChannel(*this, aSAP);
+	}
+
+TBool CAvctpTransport::HasSecondChannel() const
+	{
+	LOG_FUNC
+	return iChannelSAPs[KAvctpSecondaryChannel] != NULL ? ETrue : EFalse;
+	}
+
+void CAvctpTransport::RemoveSecondChannel()
+	{
+	LOG_FUNC
+	iState->RemoveSecondChannel(*this);
+	}
+
+TInt CAvctpTransport::GetChannelMtu(TInt aChannel, TInt& aMtu) const
+	{
+	LOG_FUNC
+	
+	TInt err = KErrNotReady;
+	if(iChannelSAPs[aChannel])
+		{
+		TPckgBuf<TInt> mtuBuf;
+		err = iChannelSAPs[aChannel]->GetOption(KSolBtL2CAP, KL2CAPNegotiatedOutboundMTU, mtuBuf);
+		if (err == KErrNone)
+			{
+			// MTU for AVCTP clients is L2CAP MTU - AVCTP Header
+			aMtu = mtuBuf() - SymbianAvctp::ENormalHeaderLength;
+			}
+		}
+
+	return err;
+	}
+
+
+/****************************************************************************/
+/*
+  Notifications from the MSocketNotify interface
+  I.e calls from iChannelSAPs[KAvctpPrimaryChannel]
+*/
+
+/**
+Called when new data is available.
+
+This is called each time a new packet of data arrives from L2CAP.
+We get to the data by calling GetData on the L2CAP SAP.
+
+This assumes a packet interface from L2CAP.
+
+  @internalComponent
+  @param aCount Number of new packets waiting
+*/
+void CAvctpTransport::NewData(TUint aCount)
+	{
+	LOG_FUNC
+
+	LOG1(_L("%d packets available"), aCount);
+
+	if (aCount == KNewDataEndofData)
+		{
+		Disconnect();
+		}
+	else 
+		{
+		// for latency reasons, we process channel 1 data synchronously		
+		// Only need to do this once rather than for each NewData call
+		// because these calls are all synchronous and hence the MTU
+		// of L2CAP can't change in-between GetData calls (changing
+		// the MTU requires negotiation with a remote device
+		// which is naturally asynchronous)
+		TInt mtu = GetCurrentInboundMtu(KAvctpPrimaryChannel);
+		
+		if(mtu >= 0)
+			{
+			__DEBUG_ONLY(TInt err;)
+			while (aCount--)
+				{
+				if ((__DEBUG_ONLY(err =) iState->NewData(*this, mtu, 0)) != KErrNone)
+					{
+					__ASSERT_DEBUG(err == KErrMuxerShutDown, Panic(EUnexpectedErrorCode));
+					break;
+					}
+				}
+			}
+		}
+	}
+
+
+/**
+Notification that we can now send data to the lower layer.
+
+  @internalComponent
+*/
+void CAvctpTransport::CanSend()
+	{
+	LOG_FUNC
+	iState->CanSend(*this, KAvctpPrimaryChannel); //channel is implicitly primary one because this upcall from the sap1
+	}
+
+/**
+Signal from the lower protocol SAP that a connection has occurred.
+	   
+Part of the MSocketNotify interface. This is called when L2CAP
+has brought up the lower layer link to the remote device.
+
+  @internalComponent
+*/
+void CAvctpTransport::ConnectComplete()
+	{
+	LOG_FUNC
+
+	IF_FLOGGING
+		(
+		TBuf<KBTAddressLength> address;
+		iRemoteAddr.GetReadable(address);		
+		)
+
+	LOG1(_L("from BT Device 0x%S"), &address);
+	
+	iState->ConnectComplete(*this);
+	}
+
+/**
+Version with connection data.
+
+Ignore the data (since L2CAP should never provide this!)
+
+  @internalComponent
+  @param aConnectData The data received on connection
+*/
+void CAvctpTransport::ConnectComplete(const TDesC8& /*aConnectData*/)
+	{
+	LOG_FUNC
+	LOG(_L("ConnectComplete with aConnectData"));
+	ConnectComplete();
+	}
+
+/**
+Incoming connection completed on listening socket.
+
+  @internalComponent
+  @param aSSP The new SAP for the completed connection
+*/
+void CAvctpTransport::ConnectComplete(CServProviderBase& /*aSSP*/)
+	{
+	LOG_FUNC
+	LOG(_L("ConnectComplete with aSSP"));
+	ConnectComplete();
+	}
+
+/**
+Incoming connection completed on listening socket with connection data.
+
+  @internalComponent
+  @param aSSP The new SAP for the completed connection
+*/
+void CAvctpTransport::ConnectComplete(CServProviderBase& /*aSSP*/,const TDesC8& /*aConnectData*/)
+	{
+	LOG_FUNC
+	LOG(_L("ConnectComplete with aSSP and aConnectData"));
+	ConnectComplete();
+	}
+
+/**
+We must have asked the lower protocol socket to shutdown, which means we're dying.
+However we only ask for an immediate shutdown so we shouldn't get a CanClose callback
+
+  @internalComponent
+  @param aDelete How to shutdown
+*/
+void CAvctpTransport::CanClose(TDelete /*aDelete*/)
+	{
+	LOG_FUNC
+	Panic(ELowerProtocolSapCanClose);
+	}
+
+/**
+The lower socket can close, with disconnection data.
+
+We don't support this so panic
+
+  @internalComponent
+  @param aDisconnectData Data from the disconnecting socket
+  @param aDelete How to shutdown
+*/
+void CAvctpTransport::CanClose(const TDesC8& /*aDisconnectData*/, TDelete /*aDelete*/)
+	{
+	LOG_FUNC
+	Panic(ELowerProtocolSapCanClose);
+	}
+
+/**
+Something has gone wrong.
+
+We get told whether it's just this operation or others that are
+affected.  We use this to decide what to do.
+
+  @internalComponent
+  @param aError The error code
+  @param aOperationMask The operation(s) affected
+*/
+void CAvctpTransport::Error(TInt aError, TUint aOperationMask)
+	{
+	LOG_FUNC
+
+	LOG2(_L("Error %d, OperationMask 0b%b"), aError, aOperationMask);
+	iState->Error(*this, aError, aOperationMask, KAvctpPrimaryChannel);
+	}
+
+/**
+The L2CAP link has disconnected. This is the result of a remote device disconnecting
+
+  @internalComponent
+*/
+void CAvctpTransport::Disconnect()
+	{
+	LOG_FUNC
+	iState->Disconnect(*this);
+	}
+
+/**
+Disconnect with disconnection data.
+
+Ignore the data (since L2CAP should never provide this!)
+
+  @internalComponent
+  @param aDisconnectData Data from the disconnecting socket
+*/
+void CAvctpTransport::Disconnect(TDesC8& /*aDisconnectData*/)
+	{
+	LOG_FUNC
+	LOG(_L("Disconnect with aDisconnectData"));
+	Disconnect();
+	}
+
+/**
+IOCTL forwarding not support, and none initiated by AVCTP so NOP
+*/
+void CAvctpTransport::IoctlComplete(TDesC8* /*aBuf*/)
+	{
+	LOG_FUNC
+	__PANIC_UNEXPECTED_CALL
+	}
+ 
+/**
+This function allows the muxer to be shutdown irrespective of whether 
+it is idle. This is intended to be used to punish a remote device that
+has done something naughty.
+
+  @internalComponent
+*/
+void CAvctpTransport::Shutdown(TInt aError)
+	{
+	LOG_FUNC
+	NotifyLinkDown(iAddress, KAvctpPrimaryChannel, aError);
+	iState->Shutdown(*this, aError);
+	}
+
+TBool CAvctpTransport::HasDataToSend()
+	{
+	LOG_FUNC
+	
+	TBool ret = PacketMgr().WouldLikeToSend();
+	if (ret == EFalse)
+		{
+		ret = iProtocol.SapsHaveDataFor(DevAddr());
+		}
+	return ret;
+	}
+
+/**********************************************************************/
+/*
+  Commands from the AVCTP SAP.
+*/
+
+
+TInt CAvctpTransport::Start(const TBTDevAddr& aAddr, TUint16 aClientId)		
+	{
+	LOG_FUNC
+
+	IF_FLOGGING
+		(
+		TBuf<KBTAddressLength> address;
+		aAddr.GetReadable(address);
+		)
+
+	LOG1(_L("from BT Device 0x%S"), &address);
+	
+	iAddress = aAddr;
+
+	return iState->Start(*this, aAddr, aClientId);
+	}
+
+TInt CAvctpTransport::StartIncoming(const TBTDevAddr& aAddr, CServProviderBase* aL2CAPConSAP)
+	{
+	LOG_FUNC
+	LOG1(_L("CServProviderBase* aL2CAPConSAP 0x%08x"), aL2CAPConSAP);
+	
+	iAddress = aAddr;
+
+	TInt err = iState->StartIncoming(*this, aAddr, aL2CAPConSAP);
+	return err;
+	}
+
+
+/**********************************************************************/
+/*
+  Internal functions.
+*/
+
+/**
+Set the BTDevAddr for this Muxer & add us to the Protocol's Q
+
+  @internalComponent
+  @param aDevAddr The Muxer's new remote address
+  @return KErrInUse if there is already a Muxer on aRemoteAddr, otherwise KErrNone
+*/
+void CAvctpTransport::AssignToDevice(const TBTDevAddr& aRemoteAddr)
+	{
+	LOG_FUNC
+
+	// Note the below assumes that there is at most one data client (or they're all on the same PID)
+	__ASSERT_DEBUG(iRemoteAddr == TBTDevAddr(0) || iRemoteAddr == aRemoteAddr, Panic(EMuxerAlreadyBound));
+
+	if (iRemoteAddr == TBTDevAddr(0))
+		{
+		iRemoteAddr = aRemoteAddr;
+		}
+	}
+
+/**
+ New data on the secondary channel is async because we want to be responsive while processing a 
+ potentially big amount of data
+ */
+/*static*/ TInt CAvctpTransport::SecondChannelNewDataAsyncCallBack(TAny* aTransport)
+	{
+	LOG_STATIC_FUNC
+
+	CAvctpTransport* t = static_cast<CAvctpTransport*>(aTransport);
+	
+	// Only need to do this once rather than for each NewData call
+	// because these calls are all synchronous and hence the MTU
+	// of L2CAP can't change in-between GetData calls (changing
+	// the MTU requires negotiation with a remote device
+	// which is naturally asynchronous)
+	TInt mtu = t->GetCurrentInboundMtu(1);
+	__DEBUG_ONLY(TInt err;)
+	if(mtu >= 0)
+		{
+		while (t->iSecondChannelPacketsWaiting)
+			{
+			if ((__DEBUG_ONLY(err =) t->iState->NewData(*t, mtu, KAvctpSecondaryChannel)) != KErrNone)
+				{
+				__ASSERT_DEBUG(err == KErrMuxerShutDown, Panic(EUnexpectedErrorCode));
+				break;
+				}
+			--(t->iSecondChannelPacketsWaiting);
+			}
+		}
+	// else we have an error getting the MTU, this is probably because the 
+	// L2CAP channel is in the process of shutting down.  We leave the data
+	// there where it'll be cleared up later.  If not we'll pick this data
+	// up next time we get signalled NewData from the lower protocol.
+
+	return EFalse;
+	}
+	
+/**
+Check to see if we're still needed.  If not, Q a delayed delete.
+
+  @internalComponent
+*/
+void CAvctpTransport::CheckForIdle()
+	{
+	LOG_FUNC
+
+	if ( IsIdle())
+		{
+		QueIdleTimer();
+		}
+	}
+
+/**
+@return System wide error code if the value could not be retrieved, otherwise the current
+		inbound MTU.
+*/
+TInt CAvctpTransport::GetCurrentInboundMtu(TInt aChannel)
+	{
+	LOG_FUNC
+	TPckgBuf<TInt> buf;
+	
+	__ASSERT_DEBUG(iChannelSAPs[aChannel], Panic(ENullLowerProtocolSap));
+	
+	TInt err = iChannelSAPs[aChannel]->GetOption(KSolBtL2CAP, KL2CAPInboundMTU, buf);
+	if(!err)
+		{
+		return buf();
+		}
+	else
+		{
+		return err;
+		}
+	}
+
+/**
+@return System wide error code if the value could not be retrieved, otherwise the current
+		outbound MTU.
+*/
+TInt CAvctpTransport::GetCurrentOutboundMtu(TInt aChannel)
+	{
+	LOG_FUNC
+	TPckgBuf<TInt> buf;
+	
+	__ASSERT_DEBUG(iChannelSAPs[aChannel], Panic(ENullLowerProtocolSap));
+	TInt err = iChannelSAPs[aChannel]->GetOption(KSolBtL2CAP, KL2CAPOutboundMTUForBestPerformance, buf);
+	if(!err)
+		{
+		return buf();
+		}
+	else
+		{
+		return err;
+		}
+	}
+	
+TInt CAvctpTransport::DoWrite(RMBufChain& aPDU, TInt aChannel)
+	{
+	LOG_FUNC
+	
+	__ASSERT_DEBUG(iChannelSAPs[aChannel], Panic(ENullLowerProtocolSap));
+	return iChannelSAPs[aChannel]->Write(aPDU,0);
+	}
+
+/**
+Check to see if the Muxer is idle. 
+Note this subConProvider shouldn't have any direct data clients.
+It's only important if there are control clients or there are saps that have data to send
+through the muxer. So a data client could effectively keep us non idle by queueing data 
+for us.
+
+  @internalComponent
+*/
+TBool CAvctpTransport::IsIdle()
+	{
+	LOG_FUNC
+	return iState->IsIdle(*this);
+	}
+
+/**
+Queues the idle timer if necessary
+
+  @internalComponent
+*/
+void CAvctpTransport::QueIdleTimer()
+	{
+	LOG_FUNC
+
+	if (!iIdleTimerQueued)
+		{
+		LOG(_L("Queued idle timer"));
+
+		iIdleTimerQueued = ETrue;
+		BTSocketTimer::Queue(KTransportIdleTimeout, iIdleTimerEntry);
+		}
+	}
+
+/**
+Deques idle timer if necessary
+
+  @internalComponent
+*/
+void CAvctpTransport::DequeIdleTimer()
+	{
+	LOG_FUNC
+
+	if (iIdleTimerQueued)
+		{
+		LOG(_L("Dequeued idle timer"));
+
+		iIdleTimerQueued = EFalse;
+		BTSocketTimer::Remove(iIdleTimerEntry);
+		}
+	}
+
+/**
+Static idle callback.
+
+This is entered if we remain idle (as defined by IsIdle()) for the duration of the timer.
+  @internalComponent
+  @param aMux The muxer that is idle
+  @return EFalse - do no reissue callback
+*/
+TInt CAvctpTransport::IdleTimerExpired(TAny* aTransport)
+	{
+	LOG_STATIC_FUNC
+	LOG1(_L("on Transport 0x%08x"), aTransport);
+	
+	CAvctpTransport* transport = static_cast<CAvctpTransport*>(aTransport);
+	
+	__ASSERT_DEBUG(transport, Panic(ENullMuxer));
+	// If the following panics it means we've not cancelled our IdleTimer at some previous point
+	__ASSERT_DEBUG(transport->IsIdle(), Panic(EIdleTimeoutWhenNotIdle));
+
+	transport->iIdleTimerQueued = EFalse;  // Obviously...
+
+	transport->iState->IdleTimerExpired(*transport);
+	
+	return EFalse;
+	}
+
+void CAvctpTransport::NotifyLinkState(const TBTDevAddr& aAddr, TControlIoctls aIotcl, TInt aChannel, TInt aError)
+	{
+	LOG_FUNC
+	TControlIoctlMessage msg(aIotcl, aAddr, aError);
+	TPckgC<TControlIoctlMessage> pck(msg);
+	
+	TClientItemIter iter(iClientItems);
+	while(iter.NextKey())
+		{
+		const TClientItem** pitem = iter.CurrentValue();
+		MSocketNotify* socket = aChannel == KAvctpSecondaryChannel ? (*pitem)->SecondaryChannel() : (*pitem)->PrimaryChannel();
+		if(socket)
+			{
+			socket->IoctlComplete(&pck);
+			}
+		}
+	}
+
+void CAvctpTransport::NotifyLinkUp(const TBTDevAddr& aAddr, TBool aIsSecondChannel)
+	{
+	LOG_FUNC
+	iProtocol.NotifyLinkUp(aAddr, aIsSecondChannel);
+	}
+
+void CAvctpTransport::NotifyLinkDown(const TBTDevAddr& aAddr, TInt aChannel, TInt aError)
+	{
+	LOG_FUNC
+	NotifyLinkState(aAddr, ELinkDown, aChannel, aError);
+	}
+
+void CAvctpTransport::NotifyAttachConfirm(TInt aError, TBool aIsSecondChannel)
+	{
+	LOG_FUNC
+	TControlIoctlMessage msg(SymbianAvctp::EAttachConfirm, iRemoteAddr, aError);
+	TPckgC<TControlIoctlMessage> pck(msg);
+	
+	TClientItemIter iter(iClientItems);
+	while(iter.NextKey())
+		{
+		const TClientItem** pitem = iter.CurrentValue();
+		MSocketNotify* socket = aIsSecondChannel ? (*pitem)->SecondaryChannel() : (*pitem)->PrimaryChannel();
+		__ASSERT_DEBUG(socket, Panic(EAvctpInvalidChannelNotify));
+		socket->IoctlComplete(&pck);
+		}
+	}
+
+void CAvctpTransport::NotifyAttachConfirm(TUint16 aClientId, TInt aError, TBool aIsSecondChannel)
+	{
+	LOG_FUNC
+	TPtrC8 addr(iRemoteAddr.Des());
+	TControlIoctlMessage msg(SymbianAvctp::EAttachConfirm, iRemoteAddr, aError);
+	TPckgC<TControlIoctlMessage> pck(msg);
+	
+	const TClientItem** pitem = iClientItems.Find(aClientId);
+	if (pitem)
+		{
+		MSocketNotify* socket = aIsSecondChannel ? (*pitem)->SecondaryChannel() : (*pitem)->PrimaryChannel();
+		if (aIsSecondChannel && !(*pitem)->IsSecondaryChannelAttached())
+			return;
+		
+		__ASSERT_DEBUG(socket, Panic(EAvctpInvalidChannelNotify));
+		if (socket)
+			{
+			socket->IoctlComplete(&pck);
+			}
+		}
+	}
+
+void CAvctpTransport::NotifyDetachConfirm(TUint16 aClientId, TInt aError, TBool aIsSecondChannel)
+	{
+	LOG_FUNC
+	TPtrC8 addr(iRemoteAddr.Des());
+	TControlIoctlMessage msg(SymbianAvctp::EDetachConfirm, iRemoteAddr, aError);
+	TPckgC<TControlIoctlMessage> pck(msg);
+	
+	const TClientItem** pitem = iClientItems.Find(aClientId);
+	if (*pitem)
+		{
+		MSocketNotify* socket = aIsSecondChannel ? (*pitem)->SecondaryChannel() : (*pitem)->PrimaryChannel();
+		__ASSERT_DEBUG(socket, Panic(EAvctpInvalidChannelNotify));
+		socket->IoctlComplete(&pck);
+		}
+	}
+
+void CAvctpTransport::NotifyLinkError(TInt aError, TBool aIsSecondChannel)
+	{
+	LOG_FUNC
+	TPtrC8 addr(iRemoteAddr.Des());
+	TControlIoctlMessage msg(SymbianAvctp::EError, iRemoteAddr, aError);
+	TPckgC<TControlIoctlMessage> pck(msg);
+	
+	TClientItemIter iter(iClientItems);
+	while(iter.NextKey())
+		{
+		const TClientItem** pitem = iter.CurrentValue();
+		MSocketNotify* notify = aIsSecondChannel ? (*pitem)->SecondaryChannel() : (*pitem)->PrimaryChannel();
+		if (notify)
+			{
+			notify->IoctlComplete(&pck);
+			}
+		}
+	}
+
+void CAvctpTransport::MbpsnNewData(TUint aCount)
+	{
+	// new data from the second channel
+
+	LOG_FUNC
+
+	LOG1(_L("%d packets available"), aCount);
+
+	if (aCount == KNewDataEndofData)
+		{
+		this->MbpsnDisconnect();
+		}
+	else 
+		{
+		iSecondChannelPacketsWaiting += aCount;
+		StartSecondChannelNewDataAsyncCallBack();
+		}
+
+	}
+
+void CAvctpTransport::MbpsnCanSend()
+	{
+	LOG_FUNC
+	iState->CanSend(*this, KAvctpSecondaryChannel); //channel is implicitly the second channel with this upcall
+	}
+
+void CAvctpTransport::MbpsnConnectComplete()
+	{
+	LOG_FUNC
+	
+	IF_FLOGGING
+		(
+		TBuf<KBTAddressLength> address;
+		iRemoteAddr.GetReadable(address);
+		)
+
+	LOG1(_L("from BT Device 0x%S"), &address);
+	
+	// state machine should be able to resolve the difference in saps completing
+	iState->ConnectComplete(*this);
+	}
+
+void CAvctpTransport::MbpsnConnectComplete(CServProviderBase& aSAP)
+	{
+	LOG_FUNC
+	// it is an incoming connection and we don't know the pid. 
+	// but that's fine because it will be ignored in the state machine.
+	iState->AddSecondChannel(*this, aSAP);
+	}
+
+//only initiate immediate shutdowns so there should be no canclose callback
+void CAvctpTransport::MbpsnCanClose(MSocketNotify::TDelete /*aDelete*/)
+	{
+	LOG_FUNC
+	Panic(ELowerProtocolSapCanClose);
+	}
+
+void CAvctpTransport::MbpsnError(TInt aError, TUint aOperationMask)
+	{
+	LOG_FUNC
+
+	LOG2(_L("Error %d, OperationMask 0b%b"), aError, aOperationMask);
+	
+	iState->Error(*this, aError, aOperationMask, KAvctpSecondaryChannel);
+	}
+
+void CAvctpTransport::MbpsnDisconnect()
+	{
+	LOG_FUNC
+	iState->SecondChannelRemoved(*this);
+	}
+
+void CAvctpTransport::SetSecondChannelCtrlNotify(TUint16 aClientId, MSocketNotify& aSecondChannelControlSocket)
+	{
+	LOG_FUNC
+	iProtocol.SetSecondChannelCtrlNotify(aClientId, aSecondChannelControlSocket);
+	}
+
+
+TInt CAvctpTransport::AddPrimaryChannelRef(const TClientItem* item)
+	{
+	LOG_FUNC
+	DequeIdleTimer();	// adding a client reference means not to be idle anymore
+	TUint16 clientId = item->ClientId();
+	return iClientItems.Insert(clientId, item);
+	}
+
+void CAvctpTransport::AddSecondaryChannelRef()
+	{
+	iSecondaryChannelClientRefCount++;
+	}
+
+void CAvctpTransport::RemovePrimaryChannelRef(TUint16 aClientId)
+	{
+	LOG_FUNC
+	
+	TClientItem** item = const_cast<TClientItem**>(iClientItems.Find(aClientId));
+	if (item && *item)
+		{
+		iClientItems.Remove(aClientId);
+		}
+	if (iClientItems.Count() == 0 && IsIdle())
+		{
+		iProtocol.RemoveTransport(*this);
+		delete this;
+		}
+	}
+
+/**
+ This method is called from ReleaseExtendedTransport() and RemoveSap().
+ When called from RemoveSap (that is called when the sap is shutdown) the secondary channel
+ may or may not exist. If the muxer state is, for example, Open then the secondary channel
+ does not exist. In this case, HasSecondChannel() return false and the method exits.
+ If the muxer is in SecondChannelPending state HasSecondChannel returns true. we can have a
+ situation where 
+ */
+void CAvctpTransport::RemoveSecondaryChannelRef(TUint16 aClientId)
+	{
+	LOG_FUNC
+	
+	if (HasSecondChannel())
+		{
+		TClientItem** item = const_cast<TClientItem**>(iClientItems.Find(aClientId));
+		__ASSERT_DEBUG(item, Panic(EAvctpClientNotFound));
+		
+		if (iSecondaryChannelClientRefCount > 0)
+			{
+			iSecondaryChannelClientRefCount--;
+			}
+		
+		if (iSecondaryChannelClientRefCount == 0)
+			{
+			RemoveSecondChannel();
+			}
+		}
+	}
+
+TInt CAvctpTransport::ClientCount()
+	{
+	return iClientItems.Count();
+	}
+
+TInt CAvctpTransport::ClientSecondaryChannelCount()
+	{
+	TInt count = 0;
+	TClientItemIter iter(iClientItems);
+	while(iter.NextKey())
+		{
+		const TClientItem** pitem = iter.CurrentValue();
+		if ((*pitem)->IsSecondaryChannelAttached())
+			{
+			count++;
+			}
+		}
+	return count;
+	}
+
+TBool CAvctpTransport::HasClient(TUint16 aClientId)
+	{
+	LOG_FUNC
+	return (iClientItems.Find(aClientId) != NULL) ? ETrue : EFalse;
+	}
+
+void CAvctpTransport::BindSecondaryChannelSap(CServProviderBase& aSAP)
+	{
+	iChannelSAPs[KAvctpSecondaryChannel] = &aSAP;
+	iChannelSAPs[KAvctpSecondaryChannel]->SetNotify(&iSecondChannelSocket);
+	}
+
+//pseudosocket stuff
+TAvctpSecondChannelPseudoSocket::TAvctpSecondChannelPseudoSocket(MSecondChannelPseudoSocketNotify& aNotify)
+:iNotify(aNotify)
+	{
+	LOG_FUNC
+	}
+
+
+void TAvctpSecondChannelPseudoSocket::NewData(TUint aCount)
+	{
+	LOG_FUNC
+	iNotify.MbpsnNewData(aCount);
+	}
+	
+void TAvctpSecondChannelPseudoSocket::CanSend()
+	{
+	LOG_FUNC
+	iNotify.MbpsnCanSend();
+	}
+	
+void TAvctpSecondChannelPseudoSocket::ConnectComplete()
+	{
+	LOG_FUNC
+	iNotify.MbpsnConnectComplete();
+	}
+	
+void TAvctpSecondChannelPseudoSocket::ConnectComplete(const TDesC8& /*aConnectData*/)
+	{
+	LOG_FUNC
+	/*drop*/
+	}
+	
+void TAvctpSecondChannelPseudoSocket::ConnectComplete(CServProviderBase& aSSP)
+	{
+	LOG_FUNC
+	iNotify.MbpsnConnectComplete(aSSP);
+	}
+	
+void TAvctpSecondChannelPseudoSocket::ConnectComplete(CServProviderBase& /*aSSP*/,const TDesC8& /*aConnectData*/)
+	{
+	LOG_FUNC
+	/*drop*/
+	}
+	
+void TAvctpSecondChannelPseudoSocket::CanClose(TDelete aDelete)
+	{
+	LOG_FUNC
+	iNotify.MbpsnCanClose(aDelete);
+	}
+	
+void TAvctpSecondChannelPseudoSocket::CanClose(const TDesC8& /*aDisconnectData*/,TDelete /*aDelete*/)
+	{
+	LOG_FUNC
+	/*drop*/
+	}
+	
+void TAvctpSecondChannelPseudoSocket::Error(TInt aError, TUint aOperationMask)
+	{
+	LOG_FUNC
+	iNotify.MbpsnError(aError, aOperationMask);
+	}
+	
+void TAvctpSecondChannelPseudoSocket::Disconnect()
+	{
+	LOG_FUNC
+	iNotify.MbpsnDisconnect();
+	}
+
+void TAvctpSecondChannelPseudoSocket::IoctlComplete(TDesC8* /*aBuf*/)
+	{
+	LOG_FUNC
+	}