bluetooth/btstack/avdtp/avdtpMuxChannel.cpp
changeset 0 29b1cd4cb562
child 22 786b94c6f0a4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bluetooth/btstack/avdtp/avdtpMuxChannel.cpp	Fri Jan 15 08:13:17 2010 +0200
@@ -0,0 +1,674 @@
+// 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:
+// Implements the avdtp mux channel
+// 
+//
+
+/**
+ @file
+ @internalComponent
+*/
+
+#include <bluetooth/logger.h>
+#include "avdtpMuxChannel.h"
+#include "avdtpTransportSession.h"
+#include "avdtp.h"
+#include "avdtpSignallingChannel.h"
+#include "avdtputil.h"
+
+#ifdef __FLOG_ACTIVE
+_LIT8(KLogComponent, LOG_COMPONENT_AVDTP);
+#endif
+
+/*static*/ CAvdtpOutboundMuxedMessage* CAvdtpOutboundMuxedMessage::NewL()
+	{
+	LOG_STATIC_FUNC
+	return new (ELeave) CAvdtpOutboundMuxedMessage;
+	}
+
+
+TInt CAvdtpOutboundMuxedMessage::NewData(TUint /*aCount*/)
+	{
+	LOG_FUNC
+	__DEBUGGER();
+	return 0;
+	}
+
+CAvdtpOutboundMuxedMessage::TMuxedMessageOperation CAvdtpOutboundMuxedMessage::TryToAddPacket(TTSID aTSID, RMBufChain& aChain)
+	{
+	LOG_FUNC
+	if (Data().Length() + KALHeaderLength + aChain.Length() > KMaxMuxedPacketLength)
+		{
+		// this would cause a jumbogram - we don't support them, so send
+		return EReadyToSendPacketNotConsumed;
+		}
+	
+	TInt r = AddHeader(aTSID, aChain.Length());
+
+	if (!r)
+		{
+		// add in the payload
+		Data().Append(aChain);
+		// see whether to send or not - strategy is that if just under 3 slot we wait
+		// for callback, otherwise send now
+		if (Data().Length() > KMuxedPacketLengthReadyToSend)
+			{
+			return EReadyToSendPacketAdded;	
+			}
+		else
+			{
+			return EPacketAdded;
+			}
+		
+		}
+	else
+		{
+		return ENoMemory;
+		}
+	}
+	
+TInt CAvdtpOutboundMuxedMessage::AddHeader(TTSID aTSID, TInt aPacketLen)
+	{
+	LOG_FUNC
+	// grow chain for adding tsid - put in same chain for ease of sending
+	TRAPD(r, Data().PrependL(KALHeaderLength));
+	if (!r)
+		{
+		TUint8& ALhdr = Data().First()->Ptr()[0];
+		ALhdr = static_cast<TUint8>(aTSID << KALHeaderTSIDOffset);
+		// make sure frag = 0
+		ALhdr &= ~(1<<KALHeaderFOffset);
+		// set LCODE - no support of jumbograms, so only choice of 2 LCODEs
+		// i.e. we always set the msb of LCODE then figure out lsb
+		ALhdr &= ((1 << KALHeaderLCODE_MSBOffset) | (aPacketLen >= 255 ? 1 : 0));
+		}
+	return r;	
+	}
+
+TInt CAvdtpOutboundMuxedMessage::Reset()
+	{
+	LOG_FUNC
+	// get a packet of 1byte for header, then we jion on payload later
+	TRAPD(res, Data().AllocL(KALHeaderLength));
+	return res;
+	}
+
+#ifdef HAVE_INBOUND_MUX_PACKET
+/*static*/ CAvdtpInboundMuxedMessage* CAvdtpInboundMuxedMessage::NewL(CMuxChannel& aMuxChannel)
+	{
+	LOG_STATIC_FUNC
+	return new (ELeave) CAvdtpInboundMuxedMessage(aMuxChannel);
+	}
+
+CAvdtpInboundMuxedMessage::CAvdtpInboundMuxedMessage(CMuxChannel& aMuxChannel)
+: iMuxChannel(aMuxChannel)
+	{
+	LOG_FUNC
+	}
+
+void CAvdtpInboundMuxedMessage::Reset()
+	{
+	LOG_FUNC
+	// get a packet of 1byte for header, then we jion on payload later
+	Data().Free();
+	}
+	
+TInt CAvdtpInboundMuxedMessage::NewData(TUint aCount)
+	{
+	LOG_FUNC
+	LOG1(_L("Count = %d (check for >1 !)"), aCount);	
+	
+	TInt ret = KErrNone;
+	// get the data now?
+	// need to parse of the AL header - and give to relevant session
+	// as we don't support fragmentation we can "assume" the this l2cap packet
+	// is a whole mux packet - of course we need to test the frag bit and moan if set
+	while (aCount--)
+		{
+		RMBufChain frag;
+		ret = iMuxChannel.GetData(frag);
+		Data().Append(frag);
+		}
+		
+	LOG(_L("Inbound packet built - now parse"))
+	
+	return ret;
+	}
+
+#endif //HAVE_INBOUND_MUX_PACKET
+
+CMuxChannel* CMuxChannel::NewL(CAvdtpProtocol& aProtocol,
+								const TBTDevAddr& aRemoteDevice,
+								TTCID aRemotelyAssignedTCID/*=KInvalidTCID*/)
+	{
+	LOG_STATIC_FUNC
+	CMuxChannel* s = new (ELeave) CMuxChannel(aProtocol, aRemoteDevice);
+	CleanupStack::PushL(s);
+	s->ConstructL(aRemotelyAssignedTCID);
+	CleanupStack::Pop(s);
+	return s;
+	}
+	
+void CMuxChannel::ConstructL(TTCID aRemotelyAssignedTCID)
+	{
+	LOG_FUNC
+//	iHeartbeat = CHeartbeat::NewL()
+	iMuxSendTimer = CPeriodic::NewL(EPriorityHigh);
+#ifdef HAVE_INBOUND_MUX_PACKET
+	iInboundMessage = CAvdtpInboundMuxedMessage::NewL(*this);
+#endif
+	iOutboundMessage = CAvdtpOutboundMuxedMessage::NewL();
+	
+	CSignallingChannel* sc = Protocol().FindSignallingChannel(RemoteAddress());
+	if (!sc)
+		{
+		User::Leave(KErrDisconnected);
+		}
+		
+	if (aRemotelyAssignedTCID==KInvalidTCID)
+		{
+		User::LeaveIfError(sc->TCIDManager().GetTCID(iTCID));
+		}
+	else
+		{
+		User::LeaveIfError(sc->TCIDManager().GrabTCID(iTCID, aRemotelyAssignedTCID));
+		}
+		
+	User::LeaveIfError(iSessions.Append(iMediaSessions.Array()));
+	User::LeaveIfError(iSessions.Append(iReportingSessions.Array()));
+	User::LeaveIfError(iSessions.Append(iRecoverySessions.Array()));
+	}
+
+CMuxChannel::CMuxChannel(CAvdtpProtocol& aProtocol,
+						 const TBTDevAddr& aRemoteDevice)
+: CTransportChannel(aProtocol, aRemoteDevice),
+#ifdef SESSION_ITERATOR_CONCRETE
+  iIter(iMediaSessions, iReportingSessions, iRecoverySessions)
+#else
+	iIter(iSessions.Array())
+#endif
+	{
+	LOG_FUNC
+	}
+	
+CMuxChannel::~CMuxChannel()
+	{
+	LOG_FUNC
+	iMuxSendTimer->Cancel();
+	iTCID.Close();
+	}
+	
+TTCID CMuxChannel::TCID() const
+	{
+	LOG_FUNC
+	return iTCID.TCID();
+	}
+	
+/*static*/ TInt CMuxChannel::MuxSendIntervalCb(TAny* aCMuxChannel)
+	{
+	LOG_STATIC_FUNC
+	// mux send interval has expired
+	// this will send the packet regardless of whether it's full
+	// thus guarding against noone filling the packet, and keeping some finite latency
+	CMuxChannel& me = *static_cast<CMuxChannel*>(aCMuxChannel);
+	(void)me.DoSend();
+	return KErrNone;
+	}
+		
+TUint CMuxChannel::SendPacket(TTSID aTSID, RMBufChain& aPacket)
+	{
+	LOG_FUNC
+	// also allows the odd case of "muxing" from one session on one stream!
+	// this is useful as it allows the same "fill a baseband packet for 10p" game
+	TUint wrote = 0;
+	
+	// we always know all our bound sessions can send on this channel
+	CAvdtpOutboundMuxedMessage::TMuxedMessageOperation result = iOutboundMessage->TryToAddPacket(aTSID, aPacket);
+	
+	switch (result)
+		{
+		case CAvdtpOutboundMuxedMessage::ENoMemory:
+			Error(KErrNoMemory);
+			break;
+		case CAvdtpOutboundMuxedMessage::EReadyToSendPacketAdded:
+		case CAvdtpOutboundMuxedMessage::EReadyToSendPacketNotConsumed:
+			wrote = DoSend();
+			break;
+		case CAvdtpOutboundMuxedMessage::EPacketAdded:
+			{
+			// guard against no other write by starting timer
+			TCallBack cb(MuxSendIntervalCb, this);
+
+			iMuxSendTimer->Start(KMuxSendInitial,
+								 KMuxSendPeriod,
+								 cb);
+	
+			break;
+			}
+		}
+		
+	if (result==CAvdtpOutboundMuxedMessage::EReadyToSendPacketNotConsumed && wrote !=0)
+		{
+		// need to start new muxed packet with aChain as payload
+		// old one was sent though, and we notice that l2cap hasn't blocked us
+		TInt r = iOutboundMessage->Reset();
+		if (!r)
+			{
+			// recurse
+			wrote = SendPacket(aTSID, aPacket);	
+			}
+		else
+			{
+			// for *this* write we need to tell them we're blocked
+			wrote = 0;
+			}
+		}
+	
+	return wrote;
+	}
+	
+TUint CMuxChannel::DoSend()
+	{
+	LOG_FUNC
+	TUint wrote =0;
+	if (iLogicalChannel)
+		{
+		wrote = iLogicalChannel->Write(iOutboundMessage->Data(), 0);
+		}
+	return wrote;
+	}
+/*	
+TInt CMuxChannel::SessionDataReceived(TTSID aTSID, RMBufChain& aChain)
+	{
+	// put on pool and notify
+	#pragma message("is there n pools for eg media, or just one?")
+	}
+*/	
+TInt CMuxChannel::AttachTransportSession(CUserPlaneTransportSession& aSession, TAvdtpTransportSessionType aType)
+/**
+Protocol must ensure it has found appropriate muxchannel for
+Recovery packets
+*/
+	{
+	LOG_FUNC
+	RArray<TUserPlaneTransportSessionState>* sessionArray = NULL;
+	
+	switch (aType)
+		{
+		case EMedia:
+		sessionArray = &iMediaSessions;
+		break;
+		
+		case EReporting:
+		sessionArray = &iReportingSessions;
+		break;
+		
+		case ERecovery:
+		sessionArray = &iRecoverySessions;
+		break;
+		}
+	
+	__ASSERT_DEBUG(sessionArray, Panic(EAVDTPBadSessionAttachToTransportChannel));
+	
+	return sessionArray->Append(TUserPlaneTransportSessionState(aSession));
+	}
+
+/**
+Actually activate multiplexing mode in packet domain
+- we are either multiplexing for one session, or many and should expect and generate
+AL headers in the packets.
+
+This channel may have previously been declared as a multiplexor inbound, as we support
+muxing, but remote SEP may not have configured it: another remote SEP (same device)
+may then choose to use muxing, which is why we need this "upgradeability"
+*/
+/*
+void CMuxChannel::ActiveMultiplexer()
+	{
+	iActivatedMultiplexer = ETrue;
+	}
+*/	
+TBool CMuxChannel::CouldAttachSession(const TAvdtpSockAddr& aAddr)
+	{
+	LOG_FUNC
+	// need to see if this is for the same device, and not an incompatible session
+	if (aAddr.BTAddr()!=RemoteAddress())
+		{
+		// can't go to different device!
+		return EFalse;
+		}
+	
+	// we know of the different session types by class design
+	// so we just want to check that:
+	// if this prospective session is Recovery that we dont' have other sessions with same SEID
+	// or vice versa
+	TInt i=0;
+	if (aAddr.Session() == ERecovery)
+		{
+		for (i=0;i<iMediaSessions.Count();i++)
+			{
+			if (iMediaSessions[i].iSession.SEID()==aAddr.SEID())
+				{
+				// got media session for same stream - can't attach
+				return EFalse;
+				}
+			}
+		for (i=0;i<iReportingSessions.Count();i++)
+			{
+			if (iReportingSessions[i].iSession.SEID()==aAddr.SEID())
+				{
+				// got reporting session for same stream - can't attach
+				return EFalse;
+				}			
+			}
+		}
+	return ETrue;
+	}
+
+	
+void CMuxChannel::DetachTransportSession(CUserPlaneTransportSession& aSession, TAvdtpTransportSessionType aType)
+	{
+	LOG_FUNC
+	// find it - for runtime efficiency the session tells us its type
+	// we also don't make it virtual to save ram/rom and runtime (can't inline)
+	
+	// remove it - having asserted that it is found
+	RArray<TUserPlaneTransportSessionState>* array = NULL;
+		
+	switch (aType)
+		{
+		case EMedia:
+			array = &iMediaSessions;
+			break;
+		case EReporting:
+			array = &iReportingSessions;
+			break;
+		case ERecovery:
+			array = &iRecoverySessions;
+			break;
+		}
+	
+#ifdef _DEBUG
+	TInt found =0;
+#endif
+
+	// find in the array
+	for (TInt i=0; i<array->Count(); i++)	
+		{
+		if (&(*array)[i].iSession == &aSession)
+			{
+			// found it
+			array->Remove(i);	// don't delete as don't own
+#ifdef _DEBUG
+			found++;
+#else
+			break;
+#endif
+			}
+		}
+	__ASSERT_DEBUG(found==1, Panic(EAVDTPBadSessionDetachFromTransportChannel));
+
+	CheckForClose();
+	}
+
+CServProviderBase* CMuxChannel::ObtainSAP()
+	{
+	__ASSERT_DEBUG(EFalse, Panic(EAvdtpTransferSapCalledForMuxChannel));
+	return NULL;
+	}
+	
+// from logical channel
+void CMuxChannel::CanSend()
+	{
+	LOG_FUNC
+	iLogicalChannelBlocked = EFalse;
+	
+	// send muxed packet, and see if we're still unblocked
+	TUint r = DoSend();
+	
+	if (!r)
+		{
+		//iterate over all sessions to induce them to send
+		iIter.Reset();
+		
+		while(iIter)
+			{
+			iIter++.iSession.CanSend();
+			}		
+		}
+	}
+	
+	
+void CMuxChannel::NewData(TUint aCount)
+/**
+Must get out of L2CAP so that we can read the TSID (and check internally the TCID)
+*/
+	{
+	LOG_FUNC
+#ifdef HAVE_INBOUND_MUX_PACKET
+	iInboundPacket->NewData(aCount);
+#else	
+	LOG1(_L("Count = %d (check for >1 !)"), aCount);	
+	
+	// get the data now?
+	// need to parse of the AL header - and give to relevant session
+	// as we don't support fragmentation we can "assume" the this l2cap packet
+	// is a whole mux packet - of course we need to test the frag bit and moan if set
+	while (aCount--)
+		{
+		RMBufChain packet;
+		TInt err = iLogicalChannel->GetData(packet,0,0); //returns negative error code or the number of datagrams ( = 1 )
+		if (err > 0)
+			{
+			// without frag one L2CAP datagram is one muxed packet
+			LOG(_L("Inbound packet built - now parse"))
+
+			while (packet.Length())
+				{
+				LOG1(_L("Muxed packet length remaining =%d byte"), packet.Length());
+				// iterate over all ALHeaders in muxed packet
+				// check for frag - we don't support, so if set drop - remote is bad
+				TUint8 ALHeader = *packet.First()->Ptr();
+				packet.TrimStart(1);// ALHeader
+
+				LOG1(_L("AL Header = 0x%02x"), ALHeader);
+				
+				if (!(ALHeader & (1<<KALHeaderFOffset)))
+					{
+					// F-bit not set, so proceed
+					TTSID tsid(ALHeader >> 3);
+					TInt lenCode(ALHeader & 0x3);
+					TInt len=0;
+					
+					switch (lenCode)
+						{
+						case 0x00:
+							LOG(_L("no len field"));
+							// no length (for last packet in the muxed packet
+							len = packet.Length();	
+							break;
+						case 0x01:
+							LOG(_L("16 bit len field"));
+							// 16 bit length field
+							if (packet.Length()>=2)
+								{
+								len = *reinterpret_cast<TUint16*>(packet.First()->Ptr());
+								packet.TrimStart(2);
+								}
+							else
+								{
+								// bad mux packet, try to parse rest lurking in l2cap
+								packet.Free();
+								continue;							
+								}
+							break;
+						case 0x02:
+							LOG(_L("9 bit len field MSB=0"));
+							// 9 bit length field = basically 8 bit but don't add 256
+							len = *packet.First()->Ptr();
+							// drop through
+						case 0x03:
+							LOG(_L("9 bit len field MSB=1"));
+							// 9 bit length field = basically 8 bit but add 256
+							len += 256;
+							packet.TrimStart(1);
+							break;
+						} // switch
+					LOG1(_L("Session data len = %d bytes"), len);
+					// split chain so that we can pass relevant bit to session
+					RMBufChain sessionData;
+					
+					iIter.Reset();
+					while (iIter)
+						{
+						CUserPlaneTransportSession* session = &(iIter++).iSession;
+						if (session && session->TSID() == tsid)
+							{
+							session->NewData(sessionData);
+							break;
+							}
+						}															
+					} // end if F bit test			
+				} // end while data left in muxed packet
+			} // end if no error from l2cap
+		} // end aCount while
+#endif
+	}
+
+/**
+Logical Channel has Disconnected - no point our staying around
+Error all sessions and die.
+*/
+void CMuxChannel::Disconnect()
+	{
+	LOG_FUNC
+	iLogicalChannel = NULL;
+	Error(KErrDisconnected);
+	}
+	
+/**
+Iterate over all sessions and error them.
+*/
+void CMuxChannel::Error(TInt aError, TUint /*aOperationMask*/)
+	{
+	LOG_FUNC
+	iIter.Reset();
+	while (iIter)
+		{
+		iIter++.iSession.ChannelError(aError);
+		}
+	}
+	
+			
+void CMuxChannel::CheckForClose()
+	{
+	LOG_FUNC
+	if (!iMediaSessions.Count() &!iReportingSessions.Count() &!iRecoverySessions.Count())
+		{
+		Protocol().TransportChannelClosing(*this);
+		CloseLogicalChannel();
+		}
+	}
+	
+/**
+Called by mux message to get data from logical channel
+*/
+TInt CMuxChannel::GetData(RMBufChain& aRxBuffer)
+	{
+	return iLogicalChannel->GetData(aRxBuffer,0,0);
+	}
+
+void CMuxChannel::TransportSessionBlocked(TAvdtpTransportSessionType /*aSession*/, TBool /*aBlocked*/)
+	{
+	LOG_FUNC
+	
+	}
+	
+	
+// helper class
+
+template <class T>	
+TSessionIterator<T>::TSessionIterator(const TArray< const TArray<T> >& aArrayOfArrays)
+: iArrays(aArrayOfArrays)
+	{
+	Reset();
+	}
+					 
+template <class T>
+const T& TSessionIterator<T>::operator++(TInt /*aPostIncrementDummy*/)
+	{
+	const T& t = iArrays[iArray].operator[](iArrayIndex);
+	DoIncrement();
+	return t;
+	}
+
+template <class T>
+void TSessionIterator<T>::DoIncrement()
+	{
+	// increment index of current array
+	iArrayIndex++;
+	// if we max out then move onto next array
+	if (iArrayIndex == iArrays[iArray].Count())
+		{
+		// got to the end of the iArray-th array, go onto the next
+		iArrayIndex =0;
+		iArray++;
+		}
+	// the next array may be blank, as may the next next etc, so skip them
+	do
+		{
+		// it may be that we get to the very end of all the arrays
+		if (iArray == iArrays.Count())
+			{
+			// got the last array, finish
+			iFinished = ETrue;
+			}
+		if (!iFinished && !iArrays[iArray].Count())
+			{
+			// skip empty array
+			iArray++;
+			}
+		else
+			{
+			// this array has stuff in it
+			break;
+			}
+		}
+	while (iArray<iArrays.Count());
+	if (iArray == iArrays.Count())
+		{
+		// got the last array, finish
+		iFinished = ETrue;
+		}	
+	}
+
+
+/*
+Conversion operator - 'returns' a Bool. ETrue if not completed
+Required to have an iterator of the usual form.
+*/
+template <class T>	
+TSessionIterator<T>::operator TBool()
+	{
+	return !iFinished;
+	}
+	
+template <class T>	
+void TSessionIterator<T>::Reset()
+	{
+	iFinished = EFalse;
+	iArrayIndex =0;
+	iArray =0;
+	}
+