bluetooth/btstack/avctp/avctpPacketMgr.cpp
changeset 0 29b1cd4cb562
child 19 4b81101308c6
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bluetooth/btstack/avctp/avctpPacketMgr.cpp	Fri Jan 15 08:13:17 2010 +0200
@@ -0,0 +1,760 @@
+// 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();
+		}
+	}