// 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;
	}

