// 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:
//

/**
 @file
 @internalComponent
*/

#include <bluetooth/logger.h>
#include <bluetoothav.h>
#include <es_mbuf.h>

#include "avctpPacketMgr.h" 
#include "Avctp.h"
#include "avctpmuxer.h"
#include "avctpconstants.h"

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

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

using namespace SymbianAvctp;

/**
Static factory function for an AVCTP Packet Manager

  @internalComponent
  @leave KErrNoMemory if the new packet could not be allocated
  @return A pointer to an AVCTP Packet Manager
*/
CAvctpPacketMgr* CAvctpPacketMgr::NewL(CAvctpTransport& aMuxer,
										CAvctpProtocol& aProtocol)
	{
	LOG_STATIC_FUNC
	CAvctpPacketMgr* mgr = new(ELeave) CAvctpPacketMgr(aMuxer, aProtocol);
	CleanupStack::PushL(mgr);
	mgr->ConstructL();
	CleanupStack::Pop(mgr);
	return mgr;	
	}

void CAvctpPacketMgr::ConstructL()
	{
	LOG_FUNC
	iIncomingAssemblers[KAvctpPrimaryChannel] = CIncomingSduAssembler::NewL(*this,0);
	iOutgoingFragmenters[KAvctpPrimaryChannel] = COutgoingSduFragmenter::NewL(*this,0);	
	iIncomingAssemblers[KAvctpSecondaryChannel] = CIncomingSduAssembler::NewL(*this,1);
	iOutgoingFragmenters[KAvctpSecondaryChannel] = COutgoingSduFragmenter::NewL(*this,1);	
	}

/**
Default constructor for an AVCTP Packet Manager

  @internalComponent
*/
CAvctpPacketMgr::CAvctpPacketMgr(CAvctpTransport& aTransport, CAvctpProtocol& aProtocol)
	: iTransport(aTransport),
	  iProtocol(aProtocol)
	{
	LOG_FUNC
	}

/**
Default destructor for an AVCTP Packet Manager

  @internalComponent
*/
CAvctpPacketMgr::~CAvctpPacketMgr()
	{
	LOG_FUNC
	
	delete iIncomingAssemblers[0];
	delete iOutgoingFragmenters[0];
	delete iIncomingAssemblers[1];
	delete iOutgoingFragmenters[1];
	}

/**
Transfers ownership of the aOutgoingSdu to CAvctpPacketMgr if the write succeeded
i.e. didn't return 0

Note this function should not be used by other classes to send IPID responses. 
Use WriteIpid for this instead.
*/
void CAvctpPacketMgr::Write(HAvctpOutgoingSdu*& aOutgoingSdu, TInt aChannel)
	{
	LOG_FUNC
	
	iOutgoingFragmenters[aChannel]->Write(aOutgoingSdu);
	}
	
/**
This functions

Transfers ownership of the aOutgoingSdu to CAvctpPacketMgr if the write succeeded
i.e. didn't return 0

Note this function should only be used to send IPID responses. Use Write for all other
Sdus instead.
*/
TInt CAvctpPacketMgr::WriteIpid(HAvctpOutgoingSdu*& aOutgoingSdu)
	{
	LOG_FUNC
	
	Write(aOutgoingSdu, aOutgoingSdu->Channel());
	
	TInt ret = KErrNone;
	if (!aOutgoingSdu)
		{
		++iIpidSdusSent;
		LOG1(_L("Sent %d IPID responses"), iIpidSdusSent);
		if (iIpidSdusSent >= KMaximumIpidResponsesAllowed)
			{
			ret = KErrRemoteSentTooManyIpidSdus;
			}
		}
	else
		{
		ret = KErrMuxerBlocked;	// if the sdu is not null means it hasn't been sent.
		}
	return ret;
	}
	
/** 
This is the lower protocol notifying us it can now send more data
We check to see if we were in the middle of sending fragments and
if so we write all those we can.
@return EFalse if we've blocked the muxer o/w EFalse
*/
void CAvctpPacketMgr::CanSend(TInt aChannel)
	{
	LOG_FUNC

	iOutgoingFragmenters[aChannel]->CanSend();
	}

/**
We don't pass on the send / recv errors to the saps
because these errors only concern the packets we've
tried to send over L2CAP.

We don't do anything on fatal errors cause the muxer 
goes down in that situation and takes the packet mgr
with it.
*/
void CAvctpPacketMgr::SignalMuxerError(TInt /*aError*/,TUint aOperationMask)
	{
	LOG_FUNC

	if (aOperationMask & (MSocketNotify::EErrorSend))
		{
		iIncomingAssemblers[KAvctpPrimaryChannel]->Reset();
		iIncomingAssemblers[KAvctpSecondaryChannel]->Reset();
		}
	
	if (aOperationMask & (MSocketNotify::EErrorRecv))
		{
		iOutgoingFragmenters[KAvctpPrimaryChannel]->Reset();
		iOutgoingFragmenters[KAvctpSecondaryChannel]->Reset();
		}
	}
	
/**
This function transfers the ownership of aIncomingPdu from the
muxer to CAvctpPacketMgr
*/ 
TInt CAvctpPacketMgr::NewData(RMBufChain& aIncomingPdu, TInt aChannel)
	{
	LOG_FUNC

	TAvctpStartHeaderInfo headerInfo;
	TInt err = CAvctpPacket::ParseHeader(aIncomingPdu, headerInfo);
	if (err == KErrNone)
		{
		TRAP(err, iIncomingAssemblers[aChannel]->AddDataL(headerInfo, aIncomingPdu)); // transfers ownership of aIncomingPdu on success 
		if (err != KErrNone)
			{
			// We've OOM'd so drop packet
			aIncomingPdu.Free();
			
			// Don't reset iIncomingAssembler since it'll reset itself on 
			// the next start or normal packet if we missed a vital fragment here
			}
			
		err=KErrNone; //Want to read remaining packets, so don't pass an error back up.
		}
	else
		{
		// There was something wrong with the PDU so punish the remote
		Transport().Shutdown(err);
		aIncomingPdu.Free();
		err = KErrMuxerShutDown;
		}
		
	return err;
	}

TBool CAvctpPacketMgr::WouldLikeToSend()
	{
	return iOutgoingFragmenters[KAvctpPrimaryChannel]->HasData() || iOutgoingFragmenters[KAvctpSecondaryChannel]->HasData();  
	}
	
//
//
// implementation of COutgoingSduFragmenter
//
//

/**
Default constructor for an outgoing partial AVCTP Sdu
  @internalComponent
*/
COutgoingSduFragmenter::COutgoingSduFragmenter(CAvctpPacketMgr& aMgr, TInt aChannel)
	: iMgr(aMgr), iChannel(aChannel)
	{
	LOG_FUNC

	}

/**
Static factory function

  @internalComponent
  @leave KErrNoMemory if the new packet could not be allocated
  @return A pointer to an outgoing partial AVCTP Sdu
*/
COutgoingSduFragmenter* COutgoingSduFragmenter::NewL(CAvctpPacketMgr& aMgr, TInt aChannel)
	{
	LOG_STATIC_FUNC

	COutgoingSduFragmenter* packet = new(ELeave) COutgoingSduFragmenter(aMgr, aChannel);
	CleanupStack::PushL(packet);
	packet->ConstructL();
	CleanupStack::Pop(packet);
	return packet;
	}	

void COutgoingSduFragmenter::ConstructL()
	{
	LOG_FUNC
	
	TCallBack cb(SendAsyncCallBack, this);
	iSendAsyncCallBack = new(ELeave) CAsyncCallBack(cb, EActiveHighPriority);
	}

COutgoingSduFragmenter::~COutgoingSduFragmenter()
	{
	LOG_FUNC

	delete iSendAsyncCallBack;
	}	
	
void COutgoingSduFragmenter::Reset()
	{
	LOG_FUNC

	iSduData.Free();
	iCurrentWriteState = ENormal;
	// we don't have anymore data to send, so check for idle so that if the transport is idle, the 
	// timer will be set and when expires it will be destroyed.
	iMgr.Transport().CheckForIdle();
	}

TBool COutgoingSduFragmenter::HasData()
	{
	return iSduData.Length() > 0;
	}

/**
If we can take ownership of this we do, and kick off async send.  Otherwise
we do nothing and the SDU remains on the caller's queue.
*/
void COutgoingSduFragmenter::Write(HAvctpOutgoingSdu*& aOutgoingSdu)
	{
	LOG_FUNC

	if (iSduData.Length() == 0)
		{
		iSduData.Assign(aOutgoingSdu->Data());
		iAddr = aOutgoingSdu->BTAddr();
		iHeaderInfo = aOutgoingSdu->HeaderInfo();
		
		delete aOutgoingSdu;
		aOutgoingSdu = NULL;

		StartSendAsyncCallBack();
		}
	}

TInt COutgoingSduFragmenter::CountFragments(const RMBufChain& aSdu, TInt iMtuUsedToFragment) const
	{
	TUint totalFragmentsNeeded = 1;
	if (aSdu.Length() + ENormalHeaderLength > iMtuUsedToFragment)
		{
		TInt dataRemaining = aSdu.Length() + EStartFragHeaderLength
									 - iMtuUsedToFragment;
		while (dataRemaining > 0)
			{
			totalFragmentsNeeded++;
			dataRemaining -= (iMtuUsedToFragment - EOtherFragHeaderLength);
			}
		}
	return totalFragmentsNeeded;
	}

void COutgoingSduFragmenter::DoSendCurrentSDU()
	{
	// we should have one fragmenter allowed to write to a given remote at any time
	// once the fragmenter has done its job then we can ask the SAPs to drain an SDU into the fragger
	TInt mtu = iMgr.Transport().GetCurrentOutboundMtu(iChannel);
	
	// If we couldn't retrieve the MTU we aren't in a state where we can send.
	// Once we are send should be kicked off by mux calling CanSend().
	if(mtu > 0)
		{
		switch(iCurrentWriteState)
			{
			case ENormal:
				{
				BeginSendingSdu(mtu);
				break;
				}
			case EFragmenting:
				{
				ContinueSendingSdu(mtu);
				break;
				}
			default:
				__ASSERT_DEBUG(EFalse, Panic(EAvctpInvalidFragmenterState));
			};
		}
	}

void COutgoingSduFragmenter::BeginSendingSdu(TInt aMtu)
	{
	LOG_FUNC
	
	__ASSERT_DEBUG(iCurrentWriteState == COutgoingSduFragmenter::ENormal, Panic(EAvctpInvalidFragmenterState));

	RMBufChain pdu;
	TInt numberFragments = 1;
	
	if (aMtu >= iSduData.Length() + ENormalHeaderLength)
		{
		pdu.Assign(iSduData);
		}
	else
		{
		numberFragments = CountFragments(iSduData, aMtu);
		pdu.Assign(iSduData);
		
		__DEBUG_ONLY(TInt err =) pdu.Split(aMtu - EStartFragHeaderLength, iSduData);
		__ASSERT_DEBUG(err==KErrNone, Panic(EAvctpRMBufChainSplitError));
		}

	iHeaderInfo.iPktType = (iSduData.Length()) ? EStartFrag : ENormalPkt;

	if (AddHeader(pdu, numberFragments) == KErrNone)
		{
		if (iMgr.Transport().DoWrite(pdu,iChannel))
			{
			if(iSduData.Length() == 0)
				{
				Reset();
				}
			else
				{
				iCurrentWriteState = COutgoingSduFragmenter::EFragmenting;
				}
			
			CheckForCanSend();
			}
		else
			{
			pdu.Remove();	// remove the first RMBuf (that is the header)
			iSduData.Prepend(pdu); // put back the pdu in the iSduData
			}
		}
	else
		{
		LOG(_L("Error creating the header because KErrNoMemory"));
		__ASSERT_DEBUG(!pdu.IsEmpty(), Panic(EAvctpFragmenterEmptyPdu));
		pdu.Free();
		Reset();
		}
	__ASSERT_DEBUG(pdu.IsEmpty(), Panic(EAvctpFragmenterNonEmptyPdu));
	}

void COutgoingSduFragmenter::ContinueSendingSdu(TInt aMtu)
	{
	RMBufChain pdu;
	// if the data payload plus the header fit in the mtu then we can send all the iSduData content 
	if (aMtu >= iSduData.Length() + EOtherFragHeaderLength)
		{
		pdu.Assign(iSduData);
		}
	// we need to split iSduData as it is bigger than the mtu
	else
		{
		pdu.Assign(iSduData);
		
		__DEBUG_ONLY(TInt err =) pdu.Split(aMtu - EOtherFragHeaderLength, iSduData);
		__ASSERT_DEBUG(err==KErrNone, Panic(EAvctpRMBufChainSplitError));
		}

	// pdu now containts either the whole remainend sdu data (first if branch) or a bit of it (second branch)
	// and iSduData contains either nothing or the remained sdu payload. Based on its length we know the packet type
	// we are going to send.
	iHeaderInfo.iPktType = (iSduData.Length()) ? EContinueFrag : EEndFrag;

	if (AddHeader(pdu, 1) == KErrNone)
		{
		if (iMgr.Transport().DoWrite(pdu,iChannel))
			{
			if(iSduData.Length() == 0)
				{
				Reset();
				}
			
			CheckForCanSend();
			}
		else
			{
			pdu.Remove();	// remove the first RMBuf (that is the header)
			iSduData.Prepend(pdu); // put back the pdu in the iSduData
			}
		}
	else
		{
		LOG(_L("Error creating the header because KErrNoMemory"));
		__ASSERT_DEBUG(!pdu.IsEmpty(), Panic(EAvctpFragmenterEmptyPdu));
		pdu.Free();
		Reset();
		}
	__ASSERT_DEBUG(pdu.IsEmpty(), Panic(EAvctpFragmenterNonEmptyPdu));
	}
	
TInt COutgoingSduFragmenter::AddHeader(RMBufChain& aPdu, TInt aNumFragments)
	{
	TInt headerLength = 0;
	switch(iHeaderInfo.iPktType)
		{
		case ENormalPkt:
			headerLength = ENormalHeaderLength;
			break;
		case EStartFrag:
			headerLength = EStartFragHeaderLength;
			break;
		case EContinueFrag:
		case EEndFrag:
			headerLength = EOtherFragHeaderLength;
			break;
		default:
			__ASSERT_DEBUG(EFalse, Panic(EAvctpFragmenterInvalidHeaderType));
		}
	
	RMBuf* header = RMBuf::Alloc(headerLength);
	if (header)
		{
		TUint8& avctpHeaderByte = *(header->Ptr());
		avctpHeaderByte = 0;
	
		// Check the transaction label is valid
		AssertValidTransactionLabel(iHeaderInfo.iTransactionLabel);
	
		avctpHeaderByte |= iHeaderInfo.iTransactionLabel << KTransactionLabelShift;
		avctpHeaderByte |= iHeaderInfo.iPktType << KPacketTypeShift;
			
		if (iHeaderInfo.iMsgType == SymbianAvctp::EResponse)
			{
			avctpHeaderByte |= KResponseMsgBit;
			}
		
		if(iHeaderInfo.iPktType == ENormalPkt)
			{
			BigEndian::Put16(header->Ptr()+KNormalHeaderPidOffset,iHeaderInfo.iPid);
			}
		else if(iHeaderInfo.iPktType == EStartFrag)
			{
			*(header->Ptr() + KNumFragmentsOffset) = aNumFragments;
			BigEndian::Put16(header->Ptr()+KStartHeaderPidOffset,iHeaderInfo.iPid);
			}
		
		if (!iHeaderInfo.iHasValidPid)
			{
			avctpHeaderByte |= KIsValidPidMask;
			}
		
		aPdu.Prepend(header);
		}
	
	return header ? KErrNone : KErrNoMemory;
	}

void COutgoingSduFragmenter::CanSend()
	{
	LOG_FUNC

	// If we have Sdu left to send this will kick off an async send, else
	// it will signal to SAPs that we are ready to receive more data
	CheckForCanSend();
	}

/*static*/ TInt COutgoingSduFragmenter::SendAsyncCallBack(TAny* aFragmenter)
	{
	static_cast<COutgoingSduFragmenter*>(aFragmenter)->DoSendCurrentSDU();
	return KErrNone;
	}


void COutgoingSduFragmenter::StartSendAsyncCallBack()
	{
	iSendAsyncCallBack->CallBack();
	}

void COutgoingSduFragmenter::CancelSendAsyncCallBack()
	{
	iSendAsyncCallBack->Cancel();
	}

void COutgoingSduFragmenter::CheckForCanSend()
	{
	LOG_FUNC

	if (iSduData.Length())
		{
		StartSendAsyncCallBack();
		}
	else
		{	
		// Only signal CanSend to the saps if we haven't got any
		// fragments left to send
		iMgr.Protocol().SignalCanSendToSaps(iMgr);		
		}
	}

//
//
// implementation of CIncomingSduAssembler
//
//

/**
Default constructor for an incoming partial AVCTP Sdu

  @internalComponent
*/
CIncomingSduAssembler::CIncomingSduAssembler(CAvctpPacketMgr& aMgr, TInt aChannel)
	: iMgr(aMgr), iChannel(aChannel)
	{
	LOG_FUNC
	}

/**
Static factory function

  @internalComponent
  @leave KErrNoMemory if the new packet could not be allocated
  @return A pointer to an incoming partial AVCTP Sdu
 */
CIncomingSduAssembler* CIncomingSduAssembler::NewL(CAvctpPacketMgr& aMgr, TInt aChannel)
	{
	LOG_STATIC_FUNC

	CIncomingSduAssembler* assembler = new(ELeave) CIncomingSduAssembler(aMgr, aChannel);
	CleanupStack::PushL(assembler);
	assembler->ConstructL();
	CleanupStack::Pop(assembler);
	return assembler;
	}	

void CIncomingSduAssembler::ConstructL()
	{
	LOG_FUNC

	// chain member just takes ownership of stuff from l2cap
	}

CIncomingSduAssembler::~CIncomingSduAssembler()
	{
	LOG_FUNC

	iAccretingSdu.Free();
	}

void CIncomingSduAssembler::Reset()
	{
	LOG_FUNC

	iStartHeaderInfo = *new (&iStartHeaderInfo) TAvctpStartHeaderInfo;
	iFragmentsReceived = 0;
	iContinueFragmentSize = 0;
	iAccretingSdu.Free();
	}
	
/**
This function transfers the ownership of aIncomingPdu to CIncomingSduAssembler.
aIncomingPdu will represent a valid PDU as described by TAvctpStartHeaderInfo
*/
void CIncomingSduAssembler::AddDataL(TAvctpStartHeaderInfo& aHeaderInfo, 
									RMBufChain& aIncomingPdu)
	{
	LOG_FUNC

	switch (aHeaderInfo.iPktType)
		{
		case ENormalPkt:
			ProcessNormalPduL(aIncomingPdu);
			break;
		case EStartFrag:
			ProcessStartPdu(aHeaderInfo, aIncomingPdu);
			break;
			
		case EContinueFrag:
			ProcessContinuePdu(aHeaderInfo, aIncomingPdu);
			break;
			
		case EEndFrag:
			ProcessEndPduL(aHeaderInfo, aIncomingPdu);
			break;
			
		default:
			Panic(EUnknownPacketType);
		}	
	}
/**
Because fragments in the same SDU can't be interleaved, this functions throws away any existing 
partial SDU
*/	
void CIncomingSduAssembler::ProcessNormalPduL(RMBufChain& aIncomingPdu)
	{
	LOG_FUNC

	HAvctpIncomingSdu* sdu = new (ELeave) HAvctpIncomingSdu(iMgr.DevAddr(), aIncomingPdu);

	iMgr.Protocol().SignalNewDataToSaps(sdu, iChannel); // transfers ownership of iAccretingSdu data via the HAvctpIncomingSdu
	Reset(); 
	}
	
/**
Because fragments in the same SDU can't be interleaved, this functions throws away any existing 
partial SDU
*/
void CIncomingSduAssembler::ProcessStartPdu(TAvctpStartHeaderInfo& aHeaderInfo, RMBufChain& aIncomingPdu)
	{
	LOG_FUNC
	
	iStartHeaderInfo.iPktType = EStartFrag;	 //Not sure we want to bother updating this during aggregation
	iStartHeaderInfo.iFragmentsInSdu = aHeaderInfo.iFragmentsInSdu;
	iStartHeaderInfo.iTransactionLabel = aHeaderInfo.iTransactionLabel;
	iStartHeaderInfo.iMsgType = aHeaderInfo.iMsgType;
	iFragmentsReceived = 1;
	iContinueFragmentSize = aIncomingPdu.Length();
	
	// All packets we pass up to AVCTPServices have the same header format.
	// Because fragmentation happens transparently to the client side we 
	// present all packets with a normal header.
	TUint8 headerByte = *(aIncomingPdu.First()->Ptr());
	aIncomingPdu.TrimStart(1);
	*(aIncomingPdu.First()->Ptr()) = headerByte & KAvctpNormalHeaderMask;
	
	iAccretingSdu.Free();
	iAccretingSdu.Assign(aIncomingPdu);
	}
	
void CIncomingSduAssembler::ProcessContinuePdu(const TAvctpStartHeaderInfo& aHeaderInfo,
												RMBufChain& aIncomingPdu)
	{
	LOG_FUNC
	if (iAccretingSdu.Length() &&
			iStartHeaderInfo.iTransactionLabel == aHeaderInfo.iTransactionLabel)
		{
		// the end packet matches by label - good!
		if (iFragmentsReceived < iStartHeaderInfo.iFragmentsInSdu -1 && // not <= since we expect a End Fragment at least after a Continue
			iStartHeaderInfo.iMsgType == aHeaderInfo.iMsgType &&		
			aIncomingPdu.Length() <= iContinueFragmentSize)			
			{
			iFragmentsReceived++;
			aIncomingPdu.TrimStart(EOtherFragHeaderLength);
			iAccretingSdu.Append(aIncomingPdu);
			}
		else if (iFragmentsReceived    >= iStartHeaderInfo.iFragmentsInSdu - 1 || // - 1 cause we've not counted this packet yet
				 aIncomingPdu.Length() != iContinueFragmentSize ||
				 iStartHeaderInfo.iMsgType != aHeaderInfo.iMsgType)
				  
			{
			iMgr.Transport().Shutdown(KErrMalformedPacketFromRemote);
			Reset();
			}
		else
			{
			// else the PDU is out of place but not actually incorrect so just allow it to drop
			aIncomingPdu.Free();
			}
		}
	else
		{
		// else the PDU is not on our current transaction label so just allow it to drop
		aIncomingPdu.Free();
		}
	}
	
void CIncomingSduAssembler::ProcessEndPduL(const TAvctpStartHeaderInfo& aHeaderInfo,
											RMBufChain& aIncomingPdu)
	{
	LOG_FUNC

	if (iAccretingSdu.Length() &&
		iStartHeaderInfo.iTransactionLabel == aHeaderInfo.iTransactionLabel)
		{		
		// the end packet matches by label - good!
		if (iFragmentsReceived == iStartHeaderInfo.iFragmentsInSdu - 1 && // - 1 cause we've not counted this packet yet
			iStartHeaderInfo.iMsgType == aHeaderInfo.iMsgType &&		
			aIncomingPdu.Length() <= iContinueFragmentSize)

			{
			iFragmentsReceived++;
			aIncomingPdu.TrimStart(EOtherFragHeaderLength);
			iAccretingSdu.Append(aIncomingPdu);
			
			// This is only new'd on the heap so we can safely pass it along.
			HAvctpIncomingSdu* completeSdu = new (ELeave) HAvctpIncomingSdu(iMgr.DevAddr(), iAccretingSdu);

			iMgr.Protocol().SignalNewDataToSaps(completeSdu, iChannel); // transfers ownership of iAccretingSdu data via the HAvctpIncomingSdu				
			Reset(); 
			}
		else if (aIncomingPdu.Length() > iContinueFragmentSize ||
				iStartHeaderInfo.iMsgType != aHeaderInfo.iMsgType)
			{
			iMgr.Transport().Shutdown(KErrMalformedPacketFromRemote);
			Reset();
			}
		else
			{
			// else the PDU is out of place but not actually incorrect so just allow it to drop
			iAccretingSdu.Free();
			}
		}
	else
		{
		// else the PDU is not on our current transaction label so just allow it to drop
		iAccretingSdu.Free();
		}
	}
