bluetooth/btstack/l2cap/L2CapEnhancedDataController.cpp
changeset 0 29b1cd4cb562
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bluetooth/btstack/l2cap/L2CapEnhancedDataController.cpp	Fri Jan 15 08:13:17 2010 +0200
@@ -0,0 +1,2243 @@
+// Copyright (c) 2008-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:
+//
+
+#include <bluetooth/logger.h>
+#include "btsockettimer.h"
+#include "L2CapEnhancedDataController.h"
+#include "L2CapSDUQueue.h"
+#include "l2signalmgr.h"
+#include "l2util.h"
+#include "L2CapDebugControlInterface.h"
+
+#ifdef __FLOG_ACTIVE
+_LIT8(KLogComponent, LOG_COMPONENT_L2CAP_DATA_CONTROLLER);
+#endif
+
+using namespace L2CapDataUtils;
+
+
+CL2CapStreamingController::CL2CapStreamingController(TL2CAPPort aLocalCID,
+													 TL2CAPPort aRemoteCID,
+													 CL2CAPMux& aMuxer,
+													 CL2CapSDUQueue& aSDUQueue,
+													 TL2CapDataControllerConfig* aConfig)
+ :	CL2CapBasicDataController(aLocalCID, aRemoteCID, aMuxer, aSDUQueue, aConfig, EFalse)
+	{
+	LOG_FUNC
+	}
+
+CL2CapStreamingController::~CL2CapStreamingController()
+	{
+	LOG_FUNC
+	}
+
+void CL2CapStreamingController::ProcessFlushTimerExpiry()
+	{
+	LOG_FUNC
+	__ASSERT_DEBUG(EFalse, Panic(EL2CAPFlushingNotReallySupported));
+	}
+
+HL2CapPDU* CL2CapStreamingController::GetPduL()
+	{
+	LOG_FUNC
+	// This is called from the signal manager.
+	HL2CapPDU* pduToSend = NULL;
+
+	pduToSend = iSDUQueue.GetPDU();
+
+	if(pduToSend)
+		{
+		pduToSend->DeliverOutgoingPDU(*this);
+		}
+	
+    if (iSDUQueue.HavePDUToSend())
+        {
+        iMuxer.PDUAvailable();
+        }
+	
+	return pduToSend;
+	}
+
+void CL2CapStreamingController::HandleIncomingIFrameL(RMBufChain& aIFrame)
+	{
+	LOG_FUNC
+
+	const TUint8 txSeq = HIFramePDU::TxSeqNumber(aIFrame);
+	
+	if (txSeq != iExpectedTxSeq)
+		{
+		// SDU currently being assembled is no good now. Need to tell the SDU Q to flush
+		// it, otherwise if we lost the right amount of I-Frames, we would end up assembling
+		// an FDU (Frankenstein Data Unit) - half SDU number n, half SDU number n+1.
+		// Note that FDUs are still possible if 63 frames are lost. There'se no notion of
+		// SDU checksumming in the protocol, so that can't be prevented.
+		iSDUQueue.FlushCurrentlyAssembledSdu();
+		}
+
+	if (iSDUQueue.IsIncomingQueueFull())
+		{
+		// Make room for new data. From the spec:
+		// "If there is no buffer space for the received I-frame an existing I-frame
+		// (i.e. the oldest) shall be discarded (flushed) freeing up buffer space for
+		// the new I-frame.	The discarded I-frame shall be marked as missing."
+		iSDUQueue.FlushOldestIncomingSdu();
+		}
+	LEAVEIFERRORL(iSDUQueue.PutIFramePDU(aIFrame));
+
+	iExpectedTxSeq = Mod64(txSeq + 1);
+
+	// SDU Q sucks out the content, so we can free the frame whether it's been consumed or dropped.
+	aIFrame.Free();
+	}
+
+TInt CL2CapStreamingController::HandleOutgoingIFrame(HIFramePDU* aIFrame)
+	{
+	LOG_FUNC
+	aIFrame->SetPDUCID(iRemoteCID);
+	aIFrame->SetTxSeqNumber(iNextTxSeq);
+	iNextTxSeq = Mod64(iNextTxSeq + 1);
+
+	// The Control field is zero-ed when the frame is created.
+
+	aIFrame->CalculateAndSetFCS();
+
+	return KErrNone;
+	}
+
+void CL2CapStreamingController::HandlePduSendComplete(HL2CapPDU& aPdu)
+	{
+	LOG_FUNC
+	delete &aPdu;
+	}
+
+void CL2CapStreamingController::HandlePduSendError(HL2CapPDU& aPdu)
+	{
+	LOG_FUNC
+	delete &aPdu;
+	}
+
+
+RL2CapErtmTimerManager::RL2CapErtmTimerManager(CL2CapEnhancedReTxController& aClient)
+ :	RL2CapRetransmissionModeTimerManager(aClient),
+ 	iLocalBusyDelayTimerRunning(EFalse)
+	{
+	LOG_FUNC
+	}
+
+void RL2CapErtmTimerManager::Close()
+	{
+	LOG_FUNC
+	StopLocalBusyDelayTimer();
+	RL2CapRetransmissionModeTimerManager::Close();
+	}
+
+void RL2CapErtmTimerManager::StartLocalBusyDelayTimer()
+	{
+	LOG_FUNC
+	// Shouldn't happen, but no big deal if it does.
+	__ASSERT_DEBUG(!iLocalBusyDelayTimerRunning, Panic(EL2CAPInvalidDataControllerTimerState));
+
+	if (!iLocalBusyDelayTimerRunning)
+		{
+		TCallBack cb(LocalBusyDelayTimerExpired, this);
+		iLocalBusyDelayTimerEntry.Set(cb);
+	
+		// Set the timeout. The value is in Milliseconds.
+		BTSocketTimer::Queue((iClient.PeerRetransmissionTimeout() * 1000) / KLocalBusyDelayTimerDenominator,
+							 iLocalBusyDelayTimerEntry);
+		iLocalBusyDelayTimerRunning = ETrue;
+		}
+	}
+
+void RL2CapErtmTimerManager::StopLocalBusyDelayTimer()
+	{
+	LOG_FUNC
+	if (iLocalBusyDelayTimerRunning)
+		{
+		BTSocketTimer::Remove(iLocalBusyDelayTimerEntry);
+		iLocalBusyDelayTimerRunning = EFalse;
+		}
+	}
+
+void RL2CapErtmTimerManager::HandleLocalBusyDelayTimerExpired()
+	{
+	LOG_FUNC
+	iLocalBusyDelayTimerRunning = EFalse;
+	static_cast<CL2CapEnhancedReTxController&>(iClient).LocalBusyDelayTimerExpired();
+	}
+
+/*static*/ TInt RL2CapErtmTimerManager::LocalBusyDelayTimerExpired(TAny* aTimerMan)
+	{
+	LOG_STATIC_FUNC
+	RL2CapErtmTimerManager* timerMan = reinterpret_cast<RL2CapErtmTimerManager*>(aTimerMan);
+	timerMan->HandleLocalBusyDelayTimerExpired();
+	return EFalse;
+	}
+
+
+
+RL2CapErtmUnacknowledgedIFrames::RL2CapErtmUnacknowledgedIFrames()
+ :	iFrameList(_FOFF(HIFramePDU, iLink))
+	{
+	LOG_FUNC
+	iFrameIndex.Reset();
+	}
+
+void RL2CapErtmUnacknowledgedIFrames::Close()
+	{
+	LOG_FUNC
+
+	// Some PDUs may hold a reference to this data controller.
+	TDblQueIter<HIFramePDU> unackedIter(iFrameList);
+	HIFramePDU* pdu;
+
+	while((pdu = unackedIter++) != NULL)
+		{
+		Remove(*pdu);
+		if (!pdu->IsAwaitingHciCompletion())
+			{
+			delete pdu;
+			}
+		else
+			{
+			// Will delete itself on HCI completion.
+			pdu->DeregisterPduOwner();
+			}
+		}
+	}
+
+
+CL2CapErtmDataTransmitter* CL2CapErtmDataTransmitter::NewL(CL2CapEnhancedReTxController& aController)
+	{
+	LOG_STATIC_FUNC
+	return new (ELeave) CL2CapErtmDataTransmitter(aController);
+	}
+
+CL2CapErtmDataTransmitter::~CL2CapErtmDataTransmitter()
+	{
+	LOG_FUNC
+	iUnackedIFrames.Close();
+	}
+
+CL2CapErtmDataTransmitter::CL2CapErtmDataTransmitter(CL2CapEnhancedReTxController& aController)
+ :	iController(aController),
+	iNextTxSeq(0),
+	iExpectedAckSeq(0),
+	iRemoteBusy(EFalse),
+	iSRejActioned(EFalse),
+	iSRejSaveReqSeq(0),
+	iWaitAckStatePending(EFalse),
+	iInWaitAckState(EFalse)
+	{
+	LOG_FUNC
+	}
+
+void CL2CapErtmDataTransmitter::HandleIncomingIFrame(RMBufChain& aIFrame)
+	{
+	LOG_FUNC
+	LOG2(_L("NextTxSeq=%d, ExpectedAckSeq = %d"), iNextTxSeq, iExpectedAckSeq)
+
+	const TUint8 reqSeq = HIFramePDU::ReqSeqNumber(aIFrame);
+	const TBool final = HIFramePDU::FinalBit(aIFrame);
+
+	HandleReqSeqAck(reqSeq);
+	if (final)
+		{
+		HandleFinalAck();
+		}
+	}
+
+void CL2CapErtmDataTransmitter::HandleIncomingSFrameL(RMBufChain& aSFrame)
+	{
+	LOG_FUNC
+	LOG2(_L("NextTxSeq=%d, ExpectedAckSeq = %d"), iNextTxSeq, iExpectedAckSeq)
+
+	const TUint8 reqSeq = HSFramePDU::ReqSeqNumber(aSFrame);
+	const TSupervisoryFunction function = HSFramePDU::SupervisoryFunction(aSFrame);
+	const TBool final = HSFramePDU::FinalBit(aSFrame);
+	const TBool poll = HSFramePDU::PollBit(aSFrame);
+
+	// An S-Frame [SREJ] with the POLL=0 does NOT acknowledge any I-Frames.
+	if (!(function == ESelectiveReject && !poll))
+		{
+		HandleReqSeqAck(reqSeq);
+		}
+	else // SREJ [P=0]
+		{
+		// The remote detected packet loss itself, so restart the retransmission timer -
+		// - SREJ -> I-Frame -> RR roundtrip time is too long and would almost always
+		// cause it to expire if we didn't (most probably unnecessarily).
+		if (TimerMan().IsAckTimerRunning())
+			{
+			TimerMan().StartAckTimer();
+			}
+		}
+
+	switch (function)
+		{
+		case EReceiverNotReady:
+			iRemoteBusy = ETrue;
+			TimerMan().StopAckTimer();
+			if (final && iInWaitAckState)
+				{
+				iInWaitAckState = EFalse;
+				// We'll start retransmitting when we get RR from the peer. 
+				}
+			break;
+	
+		case EReceiverReady:
+			if (final)
+				{
+				HandleFinalAck();
+				}
+			// iRemoteBusy cleared at the bottom of the function.
+			break;
+	
+		case EReject:
+			// A REJ should be ignored if we're in WAIT_ACK and the Final bit in the
+			// frame is 0. We'll receive a Final packet in a while anyway, and that
+			// packet is guaranteed to contain the same ReqSeq, because the remote
+			// can't acknowledge anything new until we retransmit, and we won't
+			// retransmit until we receive the Final ack.
+			if (!(iInWaitAckState && !final))
+				{
+				RetransmitUnackedIFrames();
+	
+				TimerMan().StopAckTimer();
+				// The timer will be started again when the first retransmitted I-Frame
+				// is completed.
+				}
+	
+			if (final)
+				{
+				HandleFinalAck();
+				}
+			// iRemoteBusy cleared at the bottom of the function.
+			break;
+			
+		case ESelectiveReject:
+			HandleIncomingSRejL(aSFrame);
+			// iRemoteBusy cleared at the bottom of the function.
+			break;
+		}
+
+	// Anything other than RNR means remote isn't busy anymore.
+	if (iRemoteBusy && function != EReceiverNotReady)
+		{
+		__ASSERT_DEBUG(!TimerMan().IsAckTimerRunning(), Panic(EL2CAPAckTimerRunningWhenRemoteBusy));
+		iRemoteBusy = EFalse;
+		if (!iUnackedIFrames.IsEmpty())
+			{
+			TimerMan().StartAckTimer();
+			}
+		LOG(_L("Exitted RemoteBusy condition"))
+		}
+	}
+
+void CL2CapErtmDataTransmitter::HandleIncomingSRejL(RMBufChain& aSRejFrame)
+	{
+	LOG_FUNC
+
+	const TUint8 reqSeq = HSFramePDU::ReqSeqNumber(aSRejFrame);
+	const TSupervisoryFunction function = HSFramePDU::SupervisoryFunction(aSRejFrame);
+	const TBool final = HSFramePDU::FinalBit(aSRejFrame);
+	const TBool poll = HSFramePDU::PollBit(aSRejFrame);
+
+	HIFramePDU* requestedIFrame = iUnackedIFrames[reqSeq];
+	// If the ReqSeq is invalid then it should've been caught earlier.
+	__ASSERT_ALWAYS(requestedIFrame != NULL, Panic(EL2CAPSRejReqSeqNotOnUnackedList));
+
+	if (poll)
+		{
+		// Set the Final bit directly here to make sure it's in the right frame.
+		requestedIFrame->SetFinalBit(ETrue);
+		SendOrPendL(*requestedIFrame);
+
+		if (iController.IsPollOutstanding())
+			{
+			iSRejActioned = ETrue;
+			iSRejSaveReqSeq = reqSeq;
+			// If Poll is set, then Final must be 0, so we don't have to check for it.
+			}
+		} // Poll = 1
+	else
+		{
+		// If peer is in SREJ_SENT when it receives the Poll, then it needs to respond
+		// with an SREJ[F=1]. This SREJ may contain a ReqSeq it has already sent in the
+		// previous SREJ[F=0], and we need to guard against this on our side, so that we
+		// don't retransmit a packet twice - it could cause the channel to be closed, as
+		// the second copy would be outside of peer's receive window and thus invalid.
+		TBool duplicateWithFinal = EFalse;
+
+		if (iController.IsPollOutstanding())
+			{
+			if (final)
+				{
+				if (iSRejActioned && iSRejSaveReqSeq == reqSeq)
+					{
+					// Peer has sent us a SREJ F=0 for the same frame before, need to
+					// ignore this SREJ.
+					duplicateWithFinal = ETrue;
+					LOG1(_L("Received a duplicate SREJ with ReqSeq=%d with Final=1, will drop it"),
+						 reqSeq)
+					}
+
+				if (!iUnackedIFrames.IsEmpty() && !iRemoteBusy)
+					{
+					TimerMan().StartAckTimer();
+					}
+
+				iSRejActioned = EFalse;
+				iInWaitAckState = EFalse;
+				// Don't go into ERetransmitAllUnackedFrames, even in WAIT_ACK:
+				// - an SREJ[P=0,F=1] frame doesn't carry an acking ReqSeq, so we
+				//   wouldn't know at which frame we should start the retransmission;
+				// - the peer has just SREJed some I-Frames so there's a chance
+				//   we're already retransmitting the stuff we timed out on.
+				//   If we're not (which is possible if acknowledgments from the
+				//   peer have been lost), then we'll need to wait for the
+				//   next Retransmission Timeout and then retransmit (unless we
+				//   we hit remote being in SREJ_SENT again, rinse-and-repeat then).
+				OutgoingQ().SendPendingRetransmitIFramesL();
+				}
+			else
+				{
+				iSRejActioned = ETrue;
+				iSRejSaveReqSeq = reqSeq;
+				}
+			} // waiting for final ack
+
+		if (!duplicateWithFinal)
+			{
+			// Clean the F-bit in case it was set during previous transmission.
+			requestedIFrame->SetFinalBit(EFalse);
+			SendOrPendL(*requestedIFrame);
+			}
+		} // Poll bit = 0
+	}
+
+void CL2CapErtmDataTransmitter::HciCompletedIFrame(HIFramePDU& aIFrame)
+	{
+	LOG_FUNC
+	if (aIFrame.Acked())
+		{
+		__ASSERT_DEBUG(iUnackedIFrames[aIFrame.TxSeqNumber()] == NULL,
+					   Panic(EL2CAPAckedTxSeqFoundOnUnackedList));
+
+		// This condition will happen extremely rarely or none at all on real hardware.
+		// It does happen on the emulator with USB HCTL.
+		LOG1(_L("I-Frame %d has been acknowledged before HCI completion"), aIFrame.TxSeqNumber())
+		// Frame has been already sent and acked - skip putting it on the Send queue, just delete.
+		delete &aIFrame;
+		// We'll start the retransmission timer the first time a frame that hasn't been acked is
+		// completed (the else branch below). 
+		}
+	else
+		{
+		__ASSERT_DEBUG(iUnackedIFrames[aIFrame.TxSeqNumber()] != NULL,
+					   Panic(EL2CAPUnackedTxSeqNotFoundOnUnackedList));
+
+		if (!TimerMan().IsAckTimerRunning() && !iRemoteBusy)
+			{
+			// It's the first frame sent out after a period of silence.
+			// ... or it's the first frame of a retransmission.
+			// ... or we can has a bug (LOL that's impossible, I coded this myself).
+			TimerMan().StartAckTimer();
+			}
+		}
+	}
+
+void CL2CapErtmDataTransmitter::AckTimerExpired()
+	{
+	LOG_FUNC
+	__ASSERT_DEBUG(!iUnackedIFrames.IsEmpty(), Panic(EL2CAPAckTimerExpiryWithoutPDUToSupervise));
+	// Signal to GetPdu that we need to enter WAIT_ACK.
+	iWaitAckStatePending = ETrue;
+	iController.NotifyMuxerOfPdusToSendIfHaveSome();
+	}
+
+HIFramePDU* CL2CapErtmDataTransmitter::GetIFrameToSendL()
+	{
+	LOG_FUNC
+	HIFramePDU* pduToSend = NULL;
+
+	// Can't send data in WAIT_ACK.
+	// We send data in RemoteBusy until the window fills up, but with the ack timer stopped.
+	if (!iInWaitAckState)
+		{
+		if (iUnackedIFrames[iNextTxSeq] != NULL)
+			{
+			// Rreceived a REJ or Ack timer expired.
+			pduToSend = iUnackedIFrames[iNextTxSeq];
+			if (pduToSend->IsAwaitingHciCompletion())
+				{
+				pduToSend = NULL;
+				}
+			else
+				{
+				// In case it was set for the previous transmission. 
+				pduToSend->SetFinalBit(EFalse);
+				}
+			}
+		else if (HaveSpaceInOutgoingWindow())
+			{
+			pduToSend = static_cast<HIFramePDU*>(iController.SDUQueue().GetPDU());
+			if (pduToSend != NULL)
+				{
+				pduToSend->SetTxSeqNumber(iNextTxSeq);
+				// It's a new frame that hasn't been sent before, so we need to append it to the
+				// unacked list. Once put on the list, it should never get delinked from it until
+				// acknowledged, even during retransmissions.
+				__ASSERT_DEBUG(iUnackedIFrames[iNextTxSeq] == NULL, Panic(EL2CAPNewTxSeqFoundOnUnackedList));
+				iUnackedIFrames.Append(*pduToSend);
+				}
+			}
+
+		if (pduToSend != NULL)
+			{
+			iNextTxSeq = Mod64(iNextTxSeq + 1);
+			}
+		} // state != WAIT_ACK (can transmit data)
+	return pduToSend;
+	}
+
+TBool CL2CapErtmDataTransmitter::HaveSpaceInOutgoingWindow() const
+	{
+	LOG_FUNC
+	TBool canSend = ETrue;
+	if (Mod64(iNextTxSeq - iExpectedAckSeq) >= iController.Config().TXWindowSize())
+		{
+		canSend = EFalse;
+		}
+	return canSend;
+	}
+
+void CL2CapErtmDataTransmitter::HandleFinalAck()
+	{
+	LOG_FUNC
+
+	if (iInWaitAckState)
+		{
+		iInWaitAckState = EFalse;
+		RetransmitUnackedIFrames();
+		}
+
+	// Whether it was WAIT_ACK or WAIT_F, Ack timer was stopped because Monitor timer
+	// was running.
+	if (!iUnackedIFrames.IsEmpty() && !iRemoteBusy)
+		{
+		TimerMan().StartAckTimer();
+		}
+	}
+
+void CL2CapErtmDataTransmitter::HandleReqSeqAck(TUint8 aReqSeq)
+	{
+	LOG_FUNC
+
+	__ASSERT_DEBUG((aReqSeq == iExpectedAckSeq) || !iUnackedIFrames.IsEmpty(),
+				   Panic(EL2CAPUnacknowledgedPdusMissingFromList));
+
+	// See if any I-Frames have been acknowledged by the peer, slide the send window,
+	// send new data if window space is now available, restart ack timer.
+
+	if (!iUnackedIFrames.IsEmpty() && aReqSeq != iExpectedAckSeq)
+		{
+		const TBool wasWindowFull = !HaveSpaceInOutgoingWindow();
+
+		TDblQueIter<HIFramePDU> iter(iUnackedIFrames.Iterator());
+		HIFramePDU* pduPtr;
+		TBool ackedPDUs = EFalse;
+
+		__ASSERT_DEBUG(iUnackedIFrames.First()->TxSeqNumber() == iExpectedAckSeq,
+					   Panic(EL2CAPOldestUnackedPduTxSeqNotMatchingExpectedAck));
+
+		while ((pduPtr = iter++) != NULL)
+			{
+			if (InWindow(pduPtr->TxSeqNumber(), iExpectedAckSeq, Mod64(aReqSeq - 1)))
+				{
+				iUnackedIFrames.Remove(*pduPtr);
+				pduPtr->SetAcked(ETrue);
+				ackedPDUs = ETrue;
+
+				if (!pduPtr->IsAwaitingHciCompletion())
+					{
+					delete pduPtr;
+					}
+				else
+					{
+					LOG1(_L("I-Frame %d has been acknowledged before HCI completion!"),
+						 pduPtr->TxSeqNumber())
+					// Will delete itself on HCI completion.
+					pduPtr->DeregisterPduOwner();
+					}
+				} // pdu in acked window
+			else
+				{
+				// The queue is sorted chronologically - the head is always the oldest PDU.
+				// First PDU outside of the acknowledged window means we can stop the loop.
+				break;
+				}
+			}
+
+		if (iUnackedIFrames.IsEmpty())
+			{
+			TimerMan().StopAckTimer();
+			}
+		else if (ackedPDUs && !iRemoteBusy)
+			{
+			// This will actually REstart the timer.
+			TimerMan().StartAckTimer();
+			}
+
+		iExpectedAckSeq = aReqSeq;
+		LOG1(_L("New ExpectedAckSeq=%d"), aReqSeq)
+
+		if (iController.IsOutgoingDataPathClosing() && iUnackedIFrames.IsEmpty())
+			{
+			iController.SignalOutgoingDataDeliveredToSduQ();
+			}
+		}
+	}
+
+void CL2CapErtmDataTransmitter::RetransmitUnackedIFrames()
+	{
+	LOG_FUNC
+	LOG1(_L("Setting NextTxSeq to ExpectedAckSeq = %d"), iExpectedAckSeq)
+	iNextTxSeq = iExpectedAckSeq;
+	// This is important when exiting WAIT_ACK: forget about all I-Frames SREJ-requested
+	// by the peer. We'll retransmit them as part of the whole bunch. And we can't transmit
+	// the same frame twice, which would happen had we left these hanging around.
+	OutgoingQ().CancelPendingRetransmitIFrames();
+	}
+
+
+TL2CapErtmMissingTxSeqs::TL2CapErtmMissingTxSeqs()
+ :	iNumMissingTxSeqs(0),
+	iExpectedRecvIdx(0),
+	iResendIdx(0)
+	{}
+
+TBool TL2CapErtmMissingTxSeqs::IsTxSeqOnTheList(TUint8 aTxSeq) const
+	{
+	TBool found = EFalse;
+	for (TInt i = 0; i < iNumMissingTxSeqs && !found; i++)
+		{
+		if (iMissingTxSeqs[i] == aTxSeq)
+			{
+			found = ETrue;
+			}
+		}
+	return found;
+	}
+
+TBool TL2CapErtmMissingTxSeqs::ReceivedTxSeq(TUint8 aTxSeq)
+	{
+	LOG_FUNC
+	TBool resendNeeded = EFalse;
+	if (iMissingTxSeqs[iExpectedRecvIdx] == aTxSeq)
+		{
+		// This is the only path possible with the default of max. 1 SREJ at a time.
+		iExpectedRecvIdx++;
+		}
+	else
+		{
+		// Should only fall here with the experimental KMaxSRejsInFlight > 1
+		__ASSERT_DEBUG(KMaxSRejsInFlight > 1, Panic(EL2CAPMultiSRejPathHitWhenNotSoConfigured));
+
+		LOG1(_L("SREJ resend path hit, TxSeq=%d"), aTxSeq);
+#ifdef __FLOG_ACTIVE
+		Log();
+#endif
+		resendNeeded = ETrue;
+
+		// General idea: reshuffle the array to maintain chronological ordering, in which
+		// we expect the requested I-Frames to come through. All I-Frames between iExpectedRecvIdx
+		// and the one we're processing now are considered to have been lost and will be
+		// re-requested, so we need to move them to the very end of the list.
+		//
+		// E.g.:
+		// 0. Current list is 6 elements (iNumMissingTxSeqs == 6):
+		//     32 33 | 20 21 45 46
+		//   32 and 33 have been received (iExpectedRecvIdx == 2), we're still waiting for the
+		//   rest.
+		// 1. 45 comes through, we fall here. We need to move it to right after 33 and increase
+		//   iExpectedRecvIdx:
+		//     32 33 45 | 20 21 46
+		// 2. But the order in the array implied that 20 & 21 were requested before 45 and
+		//   haven't come through, so they must have been lost (remote has to respond to SREJs
+		//   in order they are sent). Ergo, we need to resend the SREJs for 20 and 21, which in
+		//   turn means we're now expecting them after all other frames, so finally:
+		//     32 33 45 | 46 20 21
+
+		// Find the index of the received TxSeq.
+		TInt receivedIdx = iExpectedRecvIdx;
+		while (iMissingTxSeqs[receivedIdx] != aTxSeq && receivedIdx < iNumMissingTxSeqs)
+			{
+			receivedIdx++;
+			}
+
+		const TInt numToResend = receivedIdx - iExpectedRecvIdx;
+		LOG2(_L("Last received = %d, num SREJs to resend = %d"),
+			 (iExpectedRecvIdx > 0 ? iMissingTxSeqs[iExpectedRecvIdx] : -1), numToResend);
+
+		// To maintain the partitioning of the array we need to move the received
+		// TxSeq towards the < iExpectedRecvIdx half, over the to-be-SREJed again TxSeqs.
+		// (step 1 in the example)
+		while (receivedIdx > iExpectedRecvIdx)
+			{
+			TUint8 tmp = iMissingTxSeqs[receivedIdx - 1];
+			iMissingTxSeqs[receivedIdx - 1] = iMissingTxSeqs[receivedIdx];
+			iMissingTxSeqs[receivedIdx] = tmp;
+			receivedIdx--;
+			}
+		iExpectedRecvIdx++;
+
+		// Now move the TxSeqs to be SREJed again onto the end of the list to maintain correct
+		// ordering (step 2 in the example).
+		// Note: this could be optimized a bit at least theoretically down to O(1) by
+		// copying the TxSeqs to re-SREJ out to a temp array, then moving the ones currently
+		// at the end, and then copying the ones from the temp array back in at the end.
+		// Note 2: this is the first time I've used bubble sort in my carreer. Honest.
+		for (TInt i = 0; i < numToResend; i++)
+			{
+			for (TInt bubble = iExpectedRecvIdx; bubble < iNumMissingTxSeqs - 1; bubble++)
+				{
+				TUint8 tmp = iMissingTxSeqs[bubble];
+				iMissingTxSeqs[bubble] = iMissingTxSeqs[bubble + 1];
+				iMissingTxSeqs[bubble + 1] = tmp;
+				}
+			}
+
+		// Store the index of the beginning of the TxSeqs to re-request, they will be pulled
+		// in an outer loop until the index reaches iNumMissingTxSeqs.
+		iResendIdx = iNumMissingTxSeqs - numToResend;
+#ifdef __FLOG_ACTIVE
+		Log();
+#endif
+		}
+	return resendNeeded;
+	}
+
+#ifdef __FLOG_ACTIVE
+void TL2CapErtmMissingTxSeqs::Log()
+	{
+	LOG3(_L("ExpectedRecvIdx=%d, ResendIdx=%d, NumMissingTxSeqs=%d"),
+		 iExpectedRecvIdx, iResendIdx, iNumMissingTxSeqs);
+
+	TBuf<256> buf;
+	for (TInt i = 0; i < iNumMissingTxSeqs; i++)
+		{
+		buf.AppendFormat(_L("%d "), iMissingTxSeqs[i]);
+		}
+	LOG1(_L("%S"), &buf)
+	}
+#endif
+
+
+TL2CapErtmReceiverStateBase::TL2CapErtmReceiverStateBase(CL2CapErtmDataReceiver& aReceiver)
+ : iReceiver(aReceiver)
+	{
+	LOG_FUNC
+	}
+
+void TL2CapErtmReceiverStateBase::EnterL(RMBufChain* /*aIFrame*/)
+	{
+	LOG_FUNC
+	}
+
+void TL2CapErtmReceiverStateBase::ExitL()
+	{
+	LOG_FUNC
+	}
+
+TBool TL2CapErtmReceiverStateBase::IsLocalBusySupported() const
+	{
+	LOG_FUNC
+	return EFalse;
+	}
+
+
+TL2CapErtmReceiverStateRecv::TL2CapErtmReceiverStateRecv(CL2CapErtmDataReceiver& aReceiver)
+ : TL2CapErtmReceiverStateBase(aReceiver)
+	{
+	LOG_FUNC
+	}
+
+void TL2CapErtmReceiverStateRecv::EnterL(RMBufChain* /*aIFrame*/)
+	{
+	LOG_FUNC
+
+	if (iReceiver.IsIncomingSduQFull())
+		{
+		// We may have transitioned from REJ_SENT or SREJ_SENT, which don't allow LocalBusy.
+		// We can enter it now.
+		iReceiver.TimerMan().StopLocalBusyDelayTimer();
+		iReceiver.UpdateLocalBusyStatusL();
+		}
+
+	__ASSERT_DEBUG((iReceiver.IsIncomingSduQFull() && iReceiver.LocalBusy()) ||
+				   (!iReceiver.IsIncomingSduQFull() && !iReceiver.LocalBusy()) ||
+				   (iReceiver.IsIncomingSduQFull() && iReceiver.TimerMan().IsLocalBusyDelayTimerRunning()) ||
+				   (!iReceiver.IsIncomingSduQFull() && !iReceiver.TimerMan().IsLocalBusyDelayTimerRunning()),
+				   Panic(EL2CapSduQAndLocalBusyStateInconsistent));
+	}
+
+void TL2CapErtmReceiverStateRecv::HandleIncomingIFrameL(RMBufChain& aIFrame)
+	{
+	LOG_FUNC
+	__ASSERT_DEBUG((iReceiver.IsIncomingSduQFull() && iReceiver.LocalBusy()) ||
+				   (!iReceiver.IsIncomingSduQFull() && !iReceiver.LocalBusy()) ||
+				   (iReceiver.IsIncomingSduQFull() && iReceiver.TimerMan().IsLocalBusyDelayTimerRunning()) ||
+				   (!iReceiver.IsIncomingSduQFull() && !iReceiver.TimerMan().IsLocalBusyDelayTimerRunning()),
+				   Panic(EL2CapSduQAndLocalBusyStateInconsistent));
+
+	__ASSERT_DEBUG(!iReceiver.IsIncomingSduQFull() ||
+				   InWindow(iReceiver.TxSeqExpectedBySduQ(), iReceiver.BufferSeq(), iReceiver.ExpectedTxSeq()),
+				   Panic(EL2CAPTxSeqExpectedBySduQNotWithinBufferSeqAndExpectedTxSeq));
+
+	__ASSERT_DEBUG(iReceiver.IsIncomingSduQFull() ||
+				   (iReceiver.ExpectedTxSeq() == iReceiver.BufferSeq() &&
+					iReceiver.BufferSeq() == iReceiver.TxSeqExpectedBySduQ()),
+				   Panic(EL2CAPExpectedTxSeqAndBufferSeqAndTxSeqExpectedBySduQNotEqualWhenIncomingQEmptyAndInRecvState));
+
+	const TUint8 txSeq = HIFramePDU::TxSeqNumber(aIFrame);
+
+	TBool freeFrame = ETrue;
+
+	if (iReceiver.ExpectedTxSeq() == txSeq)
+		// With-Expected-TxSeq
+		{
+		iReceiver.IncExpectedTxSeq();
+		if (!iReceiver.IsIncomingSduQFull())
+			{
+			iReceiver.SetBufferSeq(iReceiver.ExpectedTxSeq());
+			}
+		iReceiver.PassToIncomingQL(aIFrame);
+		freeFrame = EFalse;
+		}
+	else if (!iReceiver.LocalBusy())
+		{
+		if (iReceiver.IsTxSeqUnexpected(txSeq))
+			// With-Unexpected-TxSeq
+			{
+			if (iReceiver.IsSRejPreferredToRej(iReceiver.ExpectedTxSeq(), txSeq))
+				{
+				iReceiver.SetStateSRejSentL(aIFrame);
+				freeFrame = EFalse;
+				}
+			else
+				{
+				if (iReceiver.BufferSeq() == iReceiver.ExpectedTxSeq())
+					{
+					iReceiver.SetStateRejSentL();
+					}
+				else
+					{
+					// BufferSeq != ExpectedTxSeq only when SDU Q is full or in SREJ_SENT.
+					__ASSERT_DEBUG(iReceiver.IsIncomingSduQFull(),
+								   Panic(EL2CAPBufferSeqNotEqualToExpectedTxSeqWhenInRecvAndSduQNotFull));
+
+					__ASSERT_DEBUG(iReceiver.TimerMan().IsLocalBusyDelayTimerRunning(),
+								   Panic(EL2CAPLocalBusyDelayTimerNotRunningWhenSduQFullButNotInLB));
+
+					iReceiver.TimerMan().StopLocalBusyDelayTimer();
+
+					iReceiver.UpdateLocalBusyStatusL();
+					}
+				}
+			} // Unexpected TxSeq
+			// else must be duplicate (With-Duplicate-TxSeq), in which case we just drop it
+		} // !LocalBusy
+	// LocalBusy and !ExpectedTxSeq - drop it - the remote will initiate a retransmission
+	// when we exit LocalBusy anyway.
+
+	if (freeFrame)
+		{
+		aIFrame.Free();
+		}
+	}
+
+void TL2CapErtmReceiverStateRecv::HandlePollL()
+	{
+	LOG_FUNC
+	if (iReceiver.IsIncomingSduQFull())
+		{
+		// If the Poll is a result of the peer's retransmission timer expiry, then the peer will
+		// start retransmitting I-Frames from the sequence number given here by us (our current
+		// BufferSeq). Basically, the point of a Poll in WAIT_ACK is the peer asking "what have
+		// you received, need to know where to start retransmitting from", and we're supposed
+		// to be honest.
+		// Otherwise, if our upper layer Read()s and we process buffered I-Frames between now
+		// and the moment the first retransmitted packet comes through, we will have moved
+		// BufferSeq and render that packet invalid (a duplicate effectively). Therefore we
+		// need to drop the buffered I-Frames to prevent duplicates and guarantee that BufferSeq
+		// won't move.
+		iReceiver.FlushBufferedIncomingIFrames();
+
+		// Incoming I-Frame buffer empty and state = RECV:
+		__ASSERT_DEBUG(// no buffered I-Frames
+					   iReceiver.ExpectedTxSeq() == iReceiver.BufferSeq() &&
+					   // most recent ReqSeq
+					   iReceiver.BufferSeq() == iReceiver.TxSeqExpectedBySduQ(),
+					   Panic(EL2CAPExpectedTxSeqAndBufferSeqAndTxSeqExpectedBySduQNotEqualWhenIncomingQEmptyAndInRecvState));
+		}
+	// Now respond with a data frame or an ack frame with the Final bit set.
+	iReceiver.Controller().SendIOrRrOrRnrL(ETrue);
+	}
+
+TBool TL2CapErtmReceiverStateRecv::IsLocalBusySupported() const
+	{
+	LOG_FUNC
+	return ETrue;
+	}
+
+void TL2CapErtmReceiverStateRecv::TxSeqExpectedBySduQChanged(TUint8 aTxSeq)
+	{
+	LOG_FUNC
+	LOG1(_L("Slipping BufferSeq to %d"), aTxSeq)
+	iReceiver.SetBufferSeq(aTxSeq);
+	__ASSERT_DEBUG(iReceiver.IsIncomingSduQFull() || iReceiver.BufferSeq() == iReceiver.ExpectedTxSeq(),
+				   Panic(EL2CAPBufferSeqNotEqualToExpectedTxSeqWhenInRecvAndSduQNotFull));
+	}
+
+
+TL2CapErtmReceiverStateRejSent::TL2CapErtmReceiverStateRejSent(CL2CapErtmDataReceiver& aReceiver)
+ :	TL2CapErtmReceiverStateBase(aReceiver)
+	{
+	LOG_FUNC
+	}
+
+void TL2CapErtmReceiverStateRejSent::EnterL(RMBufChain* /*aIFrame*/)
+	{
+	LOG_FUNC
+
+	// LocalBusy is only allowed in RECV.
+	__ASSERT_DEBUG(!iReceiver.LocalBusy(), Panic(EL2CAPInRejSentAndLocalBusy));
+
+	// Otherwise by sending the REJ we'd acknowledge frames between BufferSeq and ExpectedTxSeq
+	// (and if BufferSeq != ExpectedTxSeq and we're not in SREJ_SENT then incoming SDU Q is full
+	// and we can not acknowledge anything new).
+	__ASSERT_DEBUG(iReceiver.BufferSeq() == iReceiver.ExpectedTxSeq(),
+				   Panic(EL2CAPRejSentEnteredWithBufferedFrames));
+	
+	iReceiver.Controller().OutgoingQ().QueueNonAckingSFrame(*HSFramePDU::NewL(EReject), iReceiver.ExpectedTxSeq());
+	}
+
+void TL2CapErtmReceiverStateRejSent::HandleIncomingIFrameL(RMBufChain& aIFrame)
+	{
+	LOG_FUNC
+
+	__ASSERT_DEBUG(iReceiver.TxSeqExpectedBySduQ() == iReceiver.BufferSeq(),
+				   Panic(EL2CAPNextTxSeqExpectedBySduQNotEqualToBufferSeqWhenNotInSRejSent));
+
+	__ASSERT_DEBUG(InWindow(iReceiver.TxSeqExpectedBySduQ(), iReceiver.BufferSeq(), iReceiver.ExpectedTxSeq()),
+				   Panic(EL2CAPTxSeqExpectedBySduQNotWithinBufferSeqAndExpectedTxSeq));
+
+	__ASSERT_DEBUG(!iReceiver.LocalBusy(), Panic(EL2CAPInRejSentAndLocalBusy));
+
+	const TUint8 txSeq = HIFramePDU::TxSeqNumber(aIFrame);
+
+	if (iReceiver.ExpectedTxSeq() == txSeq)
+		// With-Expected-TxSeq
+		{
+		iReceiver.IncExpectedTxSeq();
+		if (!iReceiver.IsIncomingSduQFull())
+			{
+			iReceiver.SetBufferSeq(iReceiver.ExpectedTxSeq());
+			}
+
+		// Received the missing frame, clear REJ exception condition.
+		iReceiver.SetStateRecvL();
+
+		iReceiver.PassToIncomingQL(aIFrame);
+		}
+	else
+		{
+		// else it's a duplicate or unexpected frame:
+		// - drop the duplicate as usual,
+		// - drop the unexpected frame as we're already in a REJ exception condition
+		//   and aren't allowed to send a second REJ until the first one is cleared.
+		aIFrame.Free();
+		}
+	}
+
+void TL2CapErtmReceiverStateRejSent::HandlePollL()
+	{
+	LOG_FUNC
+	// General-case Poll handling: simply respond with a data frame or an ack frame with
+	// the Final bit set.
+	iReceiver.Controller().SendIOrRrOrRnrL(ETrue);
+	}
+
+void TL2CapErtmReceiverStateRejSent::TxSeqExpectedBySduQChanged(TUint8 aTxSeq)
+	{
+	LOG_FUNC
+	LOG1(_L("NextConsumedTxSeq moved: slipping BufferSeq to %d"), aTxSeq)
+	iReceiver.SetBufferSeq(aTxSeq);
+	__ASSERT_DEBUG(iReceiver.IsIncomingSduQFull() || iReceiver.BufferSeq() == iReceiver.ExpectedTxSeq(),
+				   Panic(EL2CAPBufferSeqNotEqualToExpectedTxSeqWhenInRecvAndSduQNotFull));
+	}
+
+
+TL2CapErtmReceiverStateSRejSent::TL2CapErtmReceiverStateSRejSent(CL2CapErtmDataReceiver& aReceiver)
+ :	TL2CapErtmReceiverStateBase(aReceiver),
+ 	iGoToRej(EFalse)
+	{
+	LOG_FUNC
+	}
+
+void TL2CapErtmReceiverStateSRejSent::EnterL(RMBufChain* aIFrame)
+	{
+	LOG_FUNC
+
+	__ASSERT_DEBUG(!iReceiver.LocalBusy(), Panic(EL2CAPInSRejSentAndLocalBusy));
+	__ASSERT_DEBUG(iMissingTxSeqs.IsEmpty(), Panic(EL2CAPNormalReceiveStateWhenMissingSRejFramesListIsNotEmpty));
+	__ASSERT_DEBUG(aIFrame != NULL, Panic(EL2CAPDataFrameNotPassedOnEntryToSRejSent));
+
+	iGoToRej = EFalse;
+	SendSRejsUpToReceivedIFrameL(*aIFrame);
+	}
+
+void TL2CapErtmReceiverStateSRejSent::ExitL()
+	{
+	LOG_FUNC
+	TL2CapErtmReceiverStateBase::ExitL();
+	
+	iMissingTxSeqs.Reset();
+
+	// BufferSeq was frozen once SREJ_SENT was entered. Now we can move it forward.
+	if (iReceiver.IsIncomingSduQFull())
+		{
+		// If incoming SDU Q filled up during SREJ_SENT, then we can only move BufferSeq up to
+		// the point at which that happened, not ExpectedTxSeq.
+		iReceiver.SetBufferSeq(iReceiver.TxSeqExpectedBySduQ());
+		}
+	else
+		{
+		// Bring BufferSeq back in sync with ExpectedTxSeq.
+		iReceiver.SetBufferSeq(iReceiver.ExpectedTxSeq());
+		}
+	LOG2(_L("On exit from SREJ_SENT[IsIncomingSduQFull = %d]: slipped BufferSeq to %d"),
+		 iReceiver.IsIncomingSduQFull(), iReceiver.BufferSeq())
+	}
+
+void TL2CapErtmReceiverStateSRejSent::HandleIncomingIFrameL(RMBufChain& aIFrame)
+	{
+	LOG_FUNC
+
+	__ASSERT_DEBUG(InWindow(iReceiver.TxSeqExpectedBySduQ(), iReceiver.BufferSeq(), iReceiver.ExpectedTxSeq()),
+				   Panic(EL2CAPTxSeqExpectedBySduQNotWithinBufferSeqAndExpectedTxSeq));
+
+	__ASSERT_DEBUG(!iReceiver.LocalBusy(), Panic(EL2CAPInSRejSentAndLocalBusy));
+
+	const TUint8 txSeq = HIFramePDU::TxSeqNumber(aIFrame);
+
+	TBool freeFrame = ETrue;
+	
+	if (iMissingTxSeqs.ExpectedSRejTxSeq() == txSeq)
+		// With-Expected-TxSeq-Srej
+		{
+		LOG1(_L("With-Expected-TxSeq-Srej: TxSeq=%d"), txSeq)
+		iReceiver.PassToIncomingQL(aIFrame);
+		freeFrame = EFalse;
+
+		TBool resendPreviousSRej = iMissingTxSeqs.ReceivedTxSeq(txSeq);
+		__ASSERT_ALWAYS(!resendPreviousSRej, Panic(EL2CAPHaveSRejsToResendEvenThoughReceivedFirstInOrder));
+
+		if (iMissingTxSeqs.AllRequestedFramesReceived())
+			{
+			if (iGoToRej)
+				{
+				if (iReceiver.IsIncomingSduQFull())
+					{
+					// We couldn't have entered LocalBusy in SREJ_SENT, but now we can transition
+					// to RECV with LocalBusy instead of REJ_SENT. The retransmission will be
+					// started when we exit LocalBusy anyway.
+					iReceiver.SetStateRecvL();
+					// We rely on this...
+					__ASSERT_DEBUG(iReceiver.LocalBusy(),
+								   Panic(EL2CAPNotInLocalBusyAfterTransitionToRecvFromSrejSentWithSduQFull));
+					}
+				else
+					{
+					iReceiver.SetStateRejSentL();
+					}
+				}
+			else // !iGoToRej
+				{
+				iReceiver.SetStateRecvL();
+				}
+			} // all SREJed frames received
+		} // With-Expected-TxSeq-Srej
+	else
+		{
+		if (iReceiver.ExpectedTxSeq() == txSeq)
+			{
+			// With-Expected-TxSeq
+			if (!iGoToRej)
+				{
+				LOG1(_L("With-Expected-TxSeq: TxSeq=%d"), txSeq)
+				iReceiver.IncExpectedTxSeq();
+				iReceiver.PassToIncomingQL(aIFrame);
+				freeFrame = EFalse;
+				}
+			}
+		else if (InWindow(txSeq, iReceiver.BufferSeq(), iReceiver.ExpectedTxSeq()))
+			{
+			if (iMissingTxSeqs.IsTxSeqOnTheList(txSeq))
+				// With-Unexpected-TxSeq-Srej
+				{
+				__ASSERT_DEBUG(TL2CapErtmMissingTxSeqs::KMaxSRejsInFlight > 1,
+							   Panic(EL2CAPMultiSRejPathHitWhenNotSoConfigured));
+
+				// Multiple SRej requests have been made and some of the retransmitted I-Frames
+				// were lost again. Send new SREJs for those frames.
+
+				__ASSERT_DEBUG(!iMissingTxSeqs.IsEmpty(), Panic(EL2CAPOldestSRejedFrameNotOnMissingList));
+				LOG2(_L("With-Unexpected-TxSeq-Srej: TxSeq=%d. Expected was=%d"),
+					 txSeq, iMissingTxSeqs.ExpectedSRejTxSeq())
+
+				TBool resendSRej = iMissingTxSeqs.ReceivedTxSeq(txSeq);
+				while (resendSRej)
+					{
+					TUint8 resendTxSeq;
+					resendSRej = iMissingTxSeqs.GetNextTxSeqForResend(resendTxSeq);
+					iReceiver.Controller().OutgoingQ().QueueNonAckingSFrame(*HSFramePDU::NewL(ESelectiveReject), resendTxSeq);
+					LOG1(_L("Resending SREJ for TxSeq=%d"), resendTxSeq)
+					}
+				iReceiver.PassToIncomingQL(aIFrame);
+				freeFrame = EFalse;
+				}
+			else // else must be a duplicate: With-duplicate-TxSeq-Srej
+				{
+				LOG1(_L("With-duplicate-TxSeq-Srej: TxSeq=%d"), txSeq)
+				}
+			}
+		else if (!iGoToRej) // With-Unexpected-TxSeq
+			{
+			LOG1(_L("With-Unexpected-TxSeq: TxSeq=%d."), txSeq)
+			if (iReceiver.IsSRejPreferredToRej(iReceiver.ExpectedTxSeq(), txSeq) &&
+				iMissingTxSeqs.HaveSpaceForNewTxSeqs(Mod64(txSeq - iReceiver.ExpectedTxSeq())))
+				{
+				__ASSERT_DEBUG(TL2CapErtmMissingTxSeqs::KMaxSRejsInFlight > 1,
+							   Panic(EL2CAPMultiSRejPathHitWhenNotSoConfigured));
+				SendSRejsUpToReceivedIFrameL(aIFrame);
+				freeFrame = EFalse;
+				}
+			else
+				{
+				// Don't send more SREJs. Wait for I-Frames requested so far to be
+				// retransmitted and go to REJ mode.
+				iGoToRej = ETrue;
+				LOG1(_L("NumMissingTxSeqs=%d, GoToRej=1"), iMissingTxSeqs.NumMissingTxSeqs())
+				}
+			}
+		} // !With-Expected-TxSeq-Srej
+
+	if (freeFrame)
+		{
+		aIFrame.Free();
+		}
+	}
+
+void TL2CapErtmReceiverStateSRejSent::HandlePollL()
+	{
+	LOG_FUNC
+	__ASSERT_DEBUG(!iMissingTxSeqs.IsEmpty(), Panic(EL2CAPCaughtInSRejSentWithNoMissingTxSeqs));
+
+	HSFramePDU* frame = HSFramePDU::NewL(ESelectiveReject);
+	frame->SetFinalBit(ETrue);
+	iReceiver.OutgoingQ().QueueNonAckingSFrame(*frame, iMissingTxSeqs.LastTxSeq());
+	}
+
+void TL2CapErtmReceiverStateSRejSent::TxSeqExpectedBySduQChanged(TUint8 /*aTxSeq*/)
+	{
+	LOG_FUNC
+	// Can't slip BufferSeq until SREJ_SENT is left.
+	}
+
+void TL2CapErtmReceiverStateSRejSent::SendSRejsUpToReceivedIFrameL(RMBufChain& aIFrame)
+	{
+	LOG_FUNC
+
+	const TUint8 receivedTxSeq = HIFramePDU::TxSeqNumber(aIFrame);
+
+	for (TUint8 sRejSeq = iReceiver.ExpectedTxSeq(); sRejSeq != receivedTxSeq; sRejSeq = Mod64(sRejSeq + 1))
+		{
+		iMissingTxSeqs.AppendTxSeq(sRejSeq);
+		iReceiver.OutgoingQ().QueueNonAckingSFrame(*HSFramePDU::NewL(ESelectiveReject), sRejSeq);
+		}
+
+	iReceiver.SetExpectedTxSeq(Mod64(receivedTxSeq + 1));
+	iReceiver.PassToIncomingQL(aIFrame);
+
+	LOG3(_L("ExpectedTxSeq = %d, First Expected SREJed TxSeq = %d, Last Expected SREJed TxSeq = %d"),
+		 iReceiver.ExpectedTxSeq(), iMissingTxSeqs.ExpectedSRejTxSeq(), iMissingTxSeqs.LastTxSeq())
+	}
+
+RL2CapErtmIncomingIFrameQueue::RL2CapErtmIncomingIFrameQueue()
+ : iTxSeqExpectedBySduQ(0)
+	{
+	LOG_FUNC
+	}
+
+void RL2CapErtmIncomingIFrameQueue::Close()
+	{
+	LOG_FUNC
+	if (!iQueue.IsEmpty())
+		{
+		LOG(_L("Incoming I-Frame queue not empty, freeing mbufs."))
+		iQueue.Free();
+		}
+	}
+
+
+void RL2CapErtmIncomingIFrameQueue::HandleIncomingIFrameL(RMBufChain& aIFrame, const CL2CapErtmDataReceiver& aReceiver)
+	{
+	LOG_FUNC
+	Insert(aIFrame);
+	if (!aReceiver.IsIncomingSduQFull())
+		{
+		ConsumeUpToFirstGapL(aReceiver);
+		}
+	}
+
+void RL2CapErtmIncomingIFrameQueue::Insert(RMBufChain& aIFrame)
+	{
+	LOG_FUNC
+	const TUint8 txSeq = HIFramePDU::TxSeqNumber(aIFrame);
+	LOG2(_L("TxSeq=%d, NextConsumedTxSeq=%d"), txSeq, iTxSeqExpectedBySduQ)
+
+	if (iQueue.IsEmpty() || !InWindow(txSeq, iTxSeqExpectedBySduQ, HIFramePDU::TxSeqNumber(iQueue.Last())))
+		// Fast path for the most common cases.
+		{
+		__ASSERT_DEBUG(iQueue.IsEmpty() || txSeq != HIFramePDU::TxSeqNumber(iQueue.Last()),
+					   Panic(EL2CapDuplicateIFramePassedToIncomingQ));
+		iQueue.Append(aIFrame);
+		LOG(_L("Appending on fast path"))
+		}
+	else
+		// Generic insertion algorithm.
+		// Depends on there not being duplicate TxSeqs on the queue, i.e. the queue mustn't contain
+		// acknowledged frames. It can only contain frames within the current receive window.
+		{
+		TBool inserted = EFalse;
+		for (TMBufPktQIter iter(iQueue); !inserted && iter.More(); iter++)
+			{
+			TUint8 currentTxSeq = HIFramePDU::TxSeqNumber(iter.Current());
+			__ASSERT_DEBUG(txSeq != currentTxSeq, Panic(EL2CapDuplicateIFramePassedToIncomingQ));
+
+			if (InWindow(txSeq, iTxSeqExpectedBySduQ, currentTxSeq))
+				{
+				LOG1(_L("Inserting before %d"), currentTxSeq)
+				iter.Insert(aIFrame);
+				inserted = ETrue;
+				}
+			}
+		if (!inserted)
+			{
+			// It's actually impossible to get here with the fast path in the beginning
+			// of the function, but it's here for testing of the loop. Might change to
+			// __ASSERT_DEBUG when the testing dust settles.
+			LOG(_L("Gone through the Q, appending"))
+			iQueue.Append(aIFrame);
+			}
+		}
+#ifdef _DEBUG
+	LogQ();
+#endif
+	}
+
+#ifdef _DEBUG
+void RL2CapErtmIncomingIFrameQueue::LogQ()
+	{
+	LOG_FUNC
+	TMBufPktQIter i(iQueue);
+	TBuf<512> buf;
+	while (i.More())
+		{
+		buf.AppendFormat(_L("%d "), HIFramePDU::TxSeqNumber(i++));
+		}
+	LOG1(_L("Q: %S"), &buf)
+	}
+#endif
+
+void RL2CapErtmIncomingIFrameQueue::ConsumeUpToFirstGapL(const CL2CapErtmDataReceiver& aReceiver)
+	{
+	LOG_FUNC
+	while (!iQueue.IsEmpty() && iTxSeqExpectedBySduQ == HIFramePDU::TxSeqNumber(iQueue.First())
+		   && !aReceiver.IsIncomingSduQFull())
+		{
+		RMBufChain frame;
+		//Check on return value of iQueue.Remove() is unnecessary, since we've checked iQueue.IsEmpty()
+		static_cast<void>(iQueue.Remove(frame));
+		iTxSeqExpectedBySduQ = Mod64(iTxSeqExpectedBySduQ + 1);
+		// This feeds the frame to the SDU Queue. This is also the place where we get notified
+		// if the queue gets full. If that happens, SetIncomingSduQFull is synchronously
+		// called by SDU Q here in the same stack frame - hence the guard in the loop condition.
+		aReceiver.PassToSduQL(frame);
+		}
+	LOG3(_L("On return from ConsumeUpToFirstGap: Queue empty=%d, NextConsumedTxSeq=%d, IsIncomingSduQFull=%d"),
+		 iQueue.IsEmpty(), iTxSeqExpectedBySduQ, aReceiver.IsIncomingSduQFull())
+
+#ifdef _DEBUG
+	LogQ();
+#endif
+	}
+
+
+CL2CapErtmDataReceiver* CL2CapErtmDataReceiver::NewL(CL2CapEnhancedReTxController& aController)
+	{
+	LOG_STATIC_FUNC
+	CL2CapErtmDataReceiver* receiver = new (ELeave) CL2CapErtmDataReceiver(aController);
+	CleanupStack::PushL(receiver);
+	receiver->ConstructL();
+	CleanupStack::Pop(receiver);
+	return receiver;
+	}
+
+void CL2CapErtmDataReceiver::ConstructL()
+	{
+	LOG_FUNC
+	iReceiveState = &iStateRecv;
+	iReceiveState->EnterL(NULL);
+	}
+
+CL2CapErtmDataReceiver::CL2CapErtmDataReceiver(CL2CapEnhancedReTxController& aController)
+ :	iController(aController),
+	iExpectedTxSeq(0),
+	iBufferSeq(0),
+	iLastAckReqSeq(0),
+	iIncomingSduQFull(EFalse),
+	iLocalBusy(EFalse),
+	iInWaitFState(EFalse),
+	iWaitFStatePending(EFalse),
+	iSendAck(EFalse),
+	iStateRecv(*this),
+	iStateRejSent(*this),
+	iStateSRejSent(*this)
+	{
+	LOG_FUNC
+	}
+
+CL2CapErtmDataReceiver::~CL2CapErtmDataReceiver()
+	{
+	LOG_FUNC
+	iIncomingIFrameQ.Close();
+	}
+
+void CL2CapErtmDataReceiver::HandleIncomingIFrameL(RMBufChain& aIFrame)
+	{
+	LOG_FUNC
+	LOG3(_L("ExpectedTxSeq=%d, BufferSeq=%d, LastAckReqSeq=%d"),
+		 iExpectedTxSeq, iBufferSeq, iLastAckReqSeq)
+
+	if (HIFramePDU::FinalBit(aIFrame))
+		{
+		// It's important that this is executed first, we wouldn't like to drop an I-Frame that
+		// carries the Final Ack if we're in WAIT_F. If the remote has set F=1 then it must have
+		// received the Poll and is therefore aware of the ReqSeq we've sent in RR[P=1].
+		// So if the remote is a correct implementation, then this I-Frame is the one we're
+		// expecting and we shouldn't drop it.
+		HandleFinalAckL();
+		}
+
+	if (// While we're in WAIT_F, peer may be still sending us I-Frames that are newer than those
+		// acknowledged by the RR[P=1] frame. They will be retransmitted when it receives the RR,
+		// so we can't accept the original transmissions, as that would move ExpectedAckSeq and
+		// thus cause the retransmissions to be invalid - we'd close the connection.
+		!iInWaitFState &&
+		// ... also need to drop I-Frames when waiting to enter WAIT_F - if we're in WAIT_ACK,
+		// then iWaitFStatePending persists for an extended period of time during which we may
+		// have sent an RR due to some other unrelated reason. The peer started retransmitting
+		// when it got that RR, which makes the situation identical to being in WAIT_F.
+		// Ideally the spec would say that RR[P=1] makes the peer start retransmitting from
+		// the given ReqSeq and RR[P=0] just makes it pick up where it left it, but it's not
+		// the case - both restart the transmission, so both must be treated equally.
+		!iWaitFStatePending)
+		{
+		const TUint8 bufferSeqBefore = iBufferSeq;
+
+		iReceiveState->HandleIncomingIFrameL(aIFrame);
+
+		if (iBufferSeq != bufferSeqBefore	// anything new to ack ?
+			&& !iSendAck)					// an ack already pending ?
+			{
+			iSendAck = IsEndOfReceiveWindowApproaching();
+			if (!iSendAck)
+				{
+				// Note that we'll never fall here if PeerTxWin <= KReceiveWinFreeSpaceLeftToTriggerAck
+				if (!TimerMan().IsSendPeerAckTimerRunning())
+					{
+					if (!TimerMan().StartSendPeerAckTimer())
+						{
+						// The timer could not be started. Send an ack immediately.
+						iSendAck = ETrue;
+						}
+					}
+				}
+			}
+		} // !WAIT_F
+	else
+		{
+		aIFrame.Free();
+		}
+	}
+
+void CL2CapErtmDataReceiver::HandleIncomingSFrameL(RMBufChain& aSFrame)
+	{
+	LOG_FUNC
+	LOG3(_L("ExpectedTxSeq=%d, BufferSeq=%d, LastAckReqSeq=%d"),
+		 iExpectedTxSeq, iBufferSeq, iLastAckReqSeq)
+
+	const TBool poll = HSFramePDU::PollBit(aSFrame);
+	const TBool final = HSFramePDU::FinalBit(aSFrame);
+	const TSupervisoryFunction function = HSFramePDU::SupervisoryFunction(aSFrame);
+
+	if (poll && final)
+		{
+		LOG(_L("Incoming S-Frame has both P and F bits set!"))
+		LEAVEL(KErrL2CAPIllegalRemoteBehavior);
+		}
+
+	if (final)
+		{
+		HandleFinalAckL();
+		}
+	
+	if (poll && function != ESelectiveReject) // SREJ[P=1] is handled by the transmitter.
+		{
+		iReceiveState->HandlePollL();
+		}
+	}
+
+void CL2CapErtmDataReceiver::HandleFinalAckL()
+	{
+	LOG_FUNC
+	if (iInWaitFState)
+		{
+		iInWaitFState = EFalse;
+		if (iIncomingSduQFull)
+			{
+			// Incoming SDU Q filled up again while we were exiting the previous LB condition.
+			UpdateLocalBusyStatusL();
+			}
+		}
+	}
+
+void CL2CapErtmDataReceiver::SetIncomingSduQFullL(TBool aIncomingSduQFull)
+	{
+	LOG_FUNC
+	LOG2(_L("Incoming SDU Q full %d -> %d"), iIncomingSduQFull, aIncomingSduQFull)
+
+	if (!iIncomingSduQFull && aIncomingSduQFull)
+		{
+		// Don't enter LocalBusy even if we can, start the delay timer instead.
+		// If the queue remains full until its expiry, we'll enter LocalBusy then.
+		// LocalBusy means we inform the peer of our busy condition by sending RNRs
+		// instead of RRs.
+		// We refrain from entering LocalBusy right off because exiting it means
+		// that the peer will have to restart the transmission from a seqnum given by us
+		// in the RR exiting the LocalBusy condition, which in turn means some frames
+		// which are already in transit (if there are any) will be lost. So if the full
+		// SDU Q is just a momentary hiccup, we hide this from the peer and just buffer
+		// the incoming frames and anchor BufferSeq at its current value to prevent the
+		// receive window from moving.
+		TimerMan().StartLocalBusyDelayTimer();
+		}
+	else if (iIncomingSduQFull && !aIncomingSduQFull)
+		{
+		TimerMan().StopLocalBusyDelayTimer();
+		}
+
+	iIncomingSduQFull = aIncomingSduQFull;
+	UpdateLocalBusyStatusL();
+	}
+
+// This routine manages the LocalBusy status by evaluating the necessary conditions:
+// - whether the incoming SDU queue is full at the moment,
+// - whether LocalBusy delay timer is still running,
+// - whether we're exiting a previous LocalBusy condition,
+// - whether we're able to enter LB in current receive state (LB is only allowed in RECV).
+void CL2CapErtmDataReceiver::UpdateLocalBusyStatusL()
+	{
+	LOG_FUNC
+	LOG5(_L("iIncomingSduQFull=%d, iLocalBusy=%d, iWaitF=%d, iWaitFPending=%d, recv state = 0x%08x"),
+		 iIncomingSduQFull, iLocalBusy, iInWaitFState, iWaitFStatePending, iReceiveState)
+
+	if (iIncomingSduQFull)
+		{
+		if (!iLocalBusy && !TimerMan().IsLocalBusyDelayTimerRunning())
+			{
+			if (!iInWaitFState && !iWaitFStatePending // have to wait until previous LB is properly finished
+				&& iReceiveState->IsLocalBusySupported()) // only RECV
+				{
+				EnterLocalBusyL();
+				}
+			}
+		// Once we enter LB the receive state is locked onto RECV so don't have to check whether
+		// it's changed.
+		}
+	else // !iIncomingSduQFull
+		{
+		// First we need to consume the frames on the incoming Q, which may cause the SDU Q to
+		// fill up again.
+		iIncomingIFrameQ.ConsumeUpToFirstGapL(*this);
+
+		if (!iIncomingSduQFull)
+			// Incoming SDU Q didn't fill up again in ConsumeUpToFirstGapL().
+			{
+			if (iLocalBusy)
+				{
+				// iLocalBusy is only possible when iInWaitFState && iWaitFStatePending are false
+				// and the only receive state in LB is RECV, so we can exit it anytime.
+				__ASSERT_DEBUG(iReceiveState == &iStateRecv && !iInWaitFState && !iWaitFStatePending,
+							   Panic(EL2CAPLocalBusyUnderIllegalConditions));
+				ExitLocalBusy();
+				}
+			}
+		else // Incoming SDU Q filled up again in ConsumeUpToFirstGapL().
+			{
+			// Some frames have been consumed and we've restarted the LB delay timer,
+			// so it's a good idea to ack now. 
+			iSendAck = ETrue;
+			}
+
+		// Give the receive state a chance to sync BufferSeq with TxSeqExpectedBySduQ.
+		// Must keep them in sync with if we can - there're reasons for that - see
+		// TL2CapErtmReceiverStateRecv::HandlePollL. We don't do that in SREJ_SENT though
+		// as BufferSeq can't change in that state but it's cool because our response to
+		// a Poll in SREJ_SENT will be SREJ which doesn't trigger retransmission of all
+		// unacked I-Frames and there's a separate mechanism for the peer to protect
+		// itself against a duplicate SREJ[F=1].
+		// This is asserted through the code.
+		iReceiveState->TxSeqExpectedBySduQChanged(iIncomingIFrameQ.TxSeqExpectedBySduQ());
+
+		__ASSERT_DEBUG(iIncomingSduQFull ||
+					   ((iReceiveState != &iStateSRejSent &&
+						 iIncomingIFrameQ.TxSeqExpectedBySduQ() == iBufferSeq &&
+						 iBufferSeq == iExpectedTxSeq) ||
+					    (iReceiveState == &iStateSRejSent)),
+					   Panic(EL2CAPWindowInformationInconsistentWhenExitingSduQFull));
+
+		__ASSERT_DEBUG(!iIncomingSduQFull ||
+					   ((iReceiveState != &iStateSRejSent &&
+						 iIncomingIFrameQ.TxSeqExpectedBySduQ() == iBufferSeq) ||
+						(iReceiveState == &iStateSRejSent)),
+					   Panic(EL2CAPWindowInformationInconsistentAfterMovingBufferSeqWhenSduQFull));
+		} // !iIncomingSduQFull
+	}
+
+void CL2CapErtmDataReceiver::EnterLocalBusyL()
+	{
+	LOG_FUNC
+	__ASSERT_DEBUG(!TimerMan().IsLocalBusyDelayTimerRunning(),
+				   Panic(EL2CAPEnterLocalBusyCalledWhileDelayTimerStillRunning));
+
+	iLocalBusy = ETrue;
+	// Send an RNR immediately.
+	iSendAck = ETrue;
+	iController.NotifyMuxerOfPdusToSendIfHaveSome();
+	}
+
+void CL2CapErtmDataReceiver::ExitLocalBusy()
+	{
+	LOG_FUNC
+	iLocalBusy = EFalse;
+	// Signal to GetPdu that we need to enter WAIT_F.
+	iWaitFStatePending = ETrue;
+	iController.NotifyMuxerOfPdusToSendIfHaveSome();
+	}
+
+template<typename FrameType>
+void CL2CapErtmDataReceiver::StampWithReqSeq(FrameType& aFrame)
+	{
+	LOG_FUNC
+	__ASSERT_DEBUG(!aFrame.IsQueuedForSend(), Panic(EL2CAPReqSeqSetOnFrameAlreadyQueuedForSend));
+
+	aFrame.SetReqSeqNumber(iLastAckReqSeq = iBufferSeq);
+	TimerMan().StopSendPeerAckTimer();
+	iSendAck = EFalse;
+	}
+
+TBool CL2CapErtmDataReceiver::IsSRejPreferredToRej(TUint8 aExpectedTxSeq, TUint8 aReceivedTxSeq)
+	{
+	LOG_FUNC
+	return InWindow(aReceivedTxSeq, aExpectedTxSeq, Mod64(aExpectedTxSeq + KSRejMissingFrameThreshold));
+	}
+
+void CL2CapErtmDataReceiver::LocalBusyDelayTimerExpired()
+	{
+	LOG_FUNC
+
+	// This guards from the classic state-changed-between-timer-expiry-and-handler-entry
+	// race condition.
+	if (iIncomingSduQFull)
+		{
+		TRAPD(err, UpdateLocalBusyStatusL());
+		if (err != KErrNone)
+			{
+			iController.ErrorD(err);
+			// Just joined the majority.
+			}
+		}
+	}
+
+TBool CL2CapErtmDataReceiver::IsEndOfReceiveWindowApproaching() const
+	{
+	LOG_FUNC
+	TBool ackNeeded = EFalse;
+
+	const TUint8 numFramesReceivedSinceLastAck = Mod64(iExpectedTxSeq - iLastAckReqSeq);
+	const TInt numFramesReceivedBeforeWantToSendAck = iController.Config().PeerTXWindowSize() - KReceiveWinFreeSpaceLeftToTriggerAck; 
+	if (numFramesReceivedSinceLastAck >= numFramesReceivedBeforeWantToSendAck)
+		{
+		LOG2(_L("LastAckReqSeq = %d, ExpectedTxSeq = %d. Reaching end of window, will send an ack."),
+			 iLastAckReqSeq, iExpectedTxSeq)
+		ackNeeded = ETrue;
+		}
+	return ackNeeded;
+	}
+
+void CL2CapErtmDataReceiver::SendPeerAckTimerExpiredL()
+	{
+	LOG_FUNC
+	if (iBufferSeq != iLastAckReqSeq)
+		{
+		iSendAck = ETrue;
+		iController.NotifyMuxerOfPdusToSendIfHaveSome();
+		}
+	}
+
+HSFramePDU* CL2CapErtmDataReceiver::GetAckFrameL(TBool aFinal)
+	{
+	HSFramePDU* frame = HSFramePDU::NewL(iLocalBusy ? EReceiverNotReady : EReceiverReady);
+	frame->SetFinalBit(aFinal);
+	return frame;
+	}
+
+void CL2CapErtmDataReceiver::SetStateL(TL2CapErtmReceiverStateBase& aState, RMBufChain* aIFrame)
+	{
+	LOG_FUNC
+	iReceiveState->ExitL();
+	iReceiveState = &aState;
+	iReceiveState->EnterL(aIFrame);
+	}
+
+
+RL2CapErtmOutgoingQueue::RL2CapErtmOutgoingQueue(CL2CapEnhancedReTxController& aController)
+ :	iController(aController),
+	iOutgoingQ(_FOFF(HL2CapPDU, iDataControllerInternalQLink)),
+	iPendRetransmitIFrameQ(_FOFF(HL2CapPDU, iDataControllerInternalQLink))
+	{
+	LOG_FUNC
+	}
+
+void RL2CapErtmOutgoingQueue::Close()
+	{
+	LOG_FUNC
+	DeleteAllFrames();
+	}
+
+void RL2CapErtmOutgoingQueue::QueueIFrameL(HIFramePDU& aIFrame)
+	{
+	LOG_FUNC
+	if (!TRetransmissionAndFlowControlOption::EnhancedMaxTransmitLessOrEqual(aIFrame.TransmissionCount() + 1,
+																			 iController.Config().MaxTransmit()))
+		{
+		LOG2(_L("MaxTransmit=%d exceeded by I-Frame no %d"),
+			 iController.Config().MaxTransmit(), aIFrame.TxSeqNumber())
+		LEAVEL(KErrL2CAPMaxTransmitExceeded);
+		}
+	else if (aIFrame.IsQueuedForSend())
+		{
+		// This is either due to the I-Frame being SREJ-requested twice or a bug
+		// in our code. We don't have enough information to tell between the two,
+		// so have to assume it's the former.
+		LOG1(_L("I-Frame no %d already queued for send"), aIFrame.TxSeqNumber())
+		LEAVEL(KErrL2CAPIllegalRemoteBehavior);
+		}
+
+	iController.StampWithReqSeq(aIFrame);
+	iOutgoingQ.AddLast(aIFrame);
+	aIFrame.SetQueuedForSend(ETrue);
+	LOG3(_L("Queued I-Frame for send, TxSeq=%d, ReqSeq=%d, F=%d"),
+		 aIFrame.TxSeqNumber(), aIFrame.ReqSeqNumber(), aIFrame.FinalBit())
+	}
+
+void RL2CapErtmOutgoingQueue::QueueAckingSFrame(HSFramePDU& aSFrame)
+	{
+	LOG_FUNC
+	// This method is meant only for those S-Frames which carry an acknowledgement number.
+	__ASSERT_DEBUG(aSFrame.SupervisoryFunction() != ESelectiveReject || aSFrame.PollBit(),
+				   Panic(EL2CAPSendSFrameWithAckCalledForSRejP0));
+	// S-Frames are fire-n-forget, no healthy S-Frame gets queued twice!
+	__ASSERT_DEBUG(!aSFrame.IsQueuedForSend(), Panic(EL2CAPSFrameQueuedForSendTwice));
+
+	iController.StampWithReqSeq(aSFrame);
+	iOutgoingQ.AddLast(aSFrame);
+	aSFrame.SetQueuedForSend(ETrue);
+	LOG4(_L("Queued S-Frame for send, S=%d, ReqSeq=%d, P=%d, F=%d"),
+		 aSFrame.SupervisoryFunction(), aSFrame.ReqSeqNumber(), aSFrame.PollBit(), aSFrame.FinalBit())
+	}
+
+void RL2CapErtmOutgoingQueue::QueueNonAckingSFrame(HSFramePDU& aSFrame, TUint8 aReqSeq)
+	{
+	LOG_FUNC
+	// S-Frames are fire-n-forget, no healthy S-Frame gets queued twice!
+	__ASSERT_DEBUG(!aSFrame.IsQueuedForSend(), Panic(EL2CAPSFrameQueuedForSendTwice));
+
+	aSFrame.SetReqSeqNumber(aReqSeq);
+	iOutgoingQ.AddLast(aSFrame);
+	aSFrame.SetQueuedForSend(ETrue);
+	LOG4(_L("Queued S-Frame for send, S=%d, ReqSeq=%d, P=%d, F=%d"),
+		 aSFrame.SupervisoryFunction(), aSFrame.ReqSeqNumber(), aSFrame.PollBit(), aSFrame.FinalBit())
+	}
+
+void RL2CapErtmOutgoingQueue::PendRetransmitIFrameL(HIFramePDU& aIFrame)
+	{
+	LOG_FUNC
+	if (aIFrame.IsQueuedForSend())
+		{
+		// This is either due to the I-Frame being SREJ-requested twice or a bug
+		// in our code. We don't have enough information to tell between the two,
+		// so have to assume it's the former.
+		LOG1(_L("I-Frame no %d already queued for send!"), aIFrame.TxSeqNumber())
+		LEAVEL(KErrL2CAPIllegalRemoteBehavior);
+		}
+	iPendRetransmitIFrameQ.AddLast(aIFrame);
+	aIFrame.SetQueuedForSend(ETrue);
+	LOG3(_L("Queued I-Frame pending retransmission, TxSeq=%d, ReqSeq=%d, F=%d"),
+		 aIFrame.TxSeqNumber(), aIFrame.ReqSeqNumber(), aIFrame.FinalBit())
+	}
+
+void RL2CapErtmOutgoingQueue::SendPendingRetransmitIFramesL()
+	{
+	LOG_FUNC
+	if (!iPendRetransmitIFrameQ.IsEmpty())
+		{
+		TSglQueIter<HIFramePDU> iter(iPendRetransmitIFrameQ);
+		while (HIFramePDU* eyeFrame = iter++)
+			{
+			eyeFrame->SetQueuedForSend(EFalse);	// QueueIFrameL would leave if this flag was set.
+			QueueIFrameL(*eyeFrame);
+			}
+		iPendRetransmitIFrameQ.Reset();
+		}
+	}
+
+void RL2CapErtmOutgoingQueue::CancelPendingRetransmitIFrames()
+	{
+	LOG_FUNC
+	if (!iPendRetransmitIFrameQ.IsEmpty())
+		{
+		TSglQueIter<HIFramePDU> iter(iPendRetransmitIFrameQ);
+		while (HIFramePDU* eyeFrame = iter++)
+			{
+			LOG1(_L("Cancelling SREJ-requested pending retransmission of I-Frame %d"),
+				 eyeFrame->TxSeqNumber())
+			eyeFrame->SetQueuedForSend(EFalse);
+			}
+		iPendRetransmitIFrameQ.Reset();
+		}
+	}
+
+void RL2CapErtmOutgoingQueue::DeleteAllFrames()
+	{
+	LOG_FUNC
+	if (!iPendRetransmitIFrameQ.IsEmpty())
+		{
+		TSglQueIter<HIFramePDU> iter(iPendRetransmitIFrameQ);
+		while (HIFramePDU* eyeFrame = iter++)
+			{
+			delete eyeFrame;
+			}
+		iPendRetransmitIFrameQ.Reset();
+		}
+
+	if (!iOutgoingQ.IsEmpty())
+		{
+		TSglQueIter<HL2CapPDU> iter(iOutgoingQ);
+		while (HL2CapPDU* pdu = iter++)
+			{
+			delete pdu;
+			}
+		iOutgoingQ.Reset();
+		}	
+	}
+
+HL2CapPDU* RL2CapErtmOutgoingQueue::DequeueNextToSend()
+	{
+	LOG_FUNC
+	HL2CapPDU* pdu = NULL;
+	if (!iOutgoingQ.IsEmpty())
+		{
+		pdu = iOutgoingQ.First();
+		if (!pdu->IsAwaitingHciCompletion())
+			{
+			iOutgoingQ.Remove(*pdu);
+			pdu->SetQueuedForSend(EFalse);
+			}
+		else
+			{
+			LOG(_L("OutgoingQ stalled pending HCI Completion of the previous transmission of the next PDU"))
+			pdu = NULL;
+			}
+		}
+	return pdu;
+	}
+
+
+CL2CapEnhancedReTxController* CL2CapEnhancedReTxController::NewL(TL2CAPPort aLocalCID,
+																 TL2CAPPort aRemoteCID,
+																 CL2CAPMux& aMuxer,
+																 CL2CapSDUQueue& aSDUQueue,
+																		TL2CapDataControllerConfig* aConfig)
+	{
+	LOG_STATIC_FUNC
+	CL2CapEnhancedReTxController* controller = new (ELeave) CL2CapEnhancedReTxController(aLocalCID, aRemoteCID, aMuxer, aSDUQueue, aConfig);
+	CleanupStack::PushL(controller);
+	controller->ConstructL();
+	CleanupStack::Pop(controller);
+	return controller;
+	}
+
+void CL2CapEnhancedReTxController::ConstructL()
+	{
+	LOG_FUNC
+	iTransmitter = CL2CapErtmDataTransmitter::NewL(*this);
+	iReceiver = CL2CapErtmDataReceiver::NewL(*this);
+	}
+
+// ***** CL2CapEnhancedReTxController Implementation
+CL2CapEnhancedReTxController::CL2CapEnhancedReTxController(TL2CAPPort aLocalCID,
+														   TL2CAPPort aRemoteCID,
+														   CL2CAPMux& aMuxer,
+														   CL2CapSDUQueue& aSDUQueue,
+														   TL2CapDataControllerConfig* aConfig)
+ :	CL2CapBasicDataController(aLocalCID, aRemoteCID, aMuxer, aSDUQueue, aConfig, EFalse),
+	iDeliverOutgoingDataAndSignalToSduQWhenDone(EFalse),
+	iOutgoingQ(*this),
+ 	iTimerMan(*this)
+	{
+	LOG_FUNC
+	LOG2(_L("CL2CapEnhancedReTxController: local CID = %d, remote CID = %d"), aLocalCID, aRemoteCID)
+	}
+
+CL2CapEnhancedReTxController::~CL2CapEnhancedReTxController()
+	{
+	LOG_FUNC
+	iTimerMan.Close();
+	iOutgoingQ.Close();
+	delete iTransmitter;
+	delete iReceiver;
+	}
+
+void CL2CapEnhancedReTxController::HandleIncomingIFrameL(RMBufChain& aIFrame)
+	{
+	LOG_FUNC
+	const TUint8 txSeq = HIFramePDU::TxSeqNumber(aIFrame);
+	const TUint8 reqSeq = HIFramePDU::ReqSeqNumber(aIFrame);
+	const TBool final = HIFramePDU::FinalBit(aIFrame);
+
+	LOG3(_L("Incoming I-Frame: TxSeq = %d, F = %d, ReqSeq = %d"), 
+		 txSeq, final, reqSeq)
+	
+	if (iReceiver->IsTxSeqInvalid(txSeq))
+		{
+		LOG3(_L("Received invalid TxSeq num %d, [BufferSeq = %d, Receive TxWin = %d]"),
+			 txSeq, iReceiver->BufferSeq(), iConfig->PeerTXWindowSize())
+		LEAVEL(KErrL2CAPInvalidPacketSequenceNumber);
+		}
+	else if (!iTransmitter->IsReqSeqValid(reqSeq))
+		{
+		LOG3(_L("Received invalid ReqSeq num %d, [ExpectedAckSeq = %d, NextTxSeq = %d]"),
+			 reqSeq, iTransmitter->ExpectedAckSeq(), iTransmitter->NextTxSeq())
+		LEAVEL(KErrL2CAPInvalidAcknowledgementNumber);
+		}
+	else if (IsFBitValid(final)) // ... and TxSeq and ReqSeq are valid
+		{
+		// ReqSeq & FBit valid: use this information whatever TxSeq value is, as long as it's
+		// within window.
+
+		if (final)
+			{
+			HandleFinalAck();
+			}
+
+		iTransmitter->HandleIncomingIFrame(aIFrame);
+		iReceiver->HandleIncomingIFrameL(aIFrame);
+
+		NotifyMuxerOfPdusToSendIfHaveSome();
+		} // ReqSeq & Final Valid, TxSeq within window
+	else
+		{
+		LOG(_L("F-bit invalid, dropping the I-Frame"))
+		aIFrame.Free();
+		}
+	}
+
+void CL2CapEnhancedReTxController::HandleIncomingSFrameL(RMBufChain& aSFrame)
+	{
+	LOG_FUNC
+
+	const TUint8 reqSeq = HSFramePDU::ReqSeqNumber(aSFrame);
+	const TBool poll = HSFramePDU::PollBit(aSFrame);
+	const TBool final = HSFramePDU::FinalBit(aSFrame);
+
+	LOG4(_L("Incoming S-Frame: S = %d, P = %d, F = %d, ReqSeq = %d"),
+		 HSFramePDU::SupervisoryFunction(aSFrame), poll, final, reqSeq)
+
+	if (!iTransmitter->IsReqSeqValid(reqSeq))
+		{
+		LOG3(_L("Received invalid ReqSeq num %d, [ExpectedAckSeq = %d, NextTxSeq = %d]"),
+			 reqSeq, iTransmitter->ExpectedAckSeq(), iTransmitter->NextTxSeq())
+		LEAVEL(KErrL2CAPInvalidAcknowledgementNumber);
+		}
+	else if (IsFBitValid(final)) // ... and TxSeq and ReqSeq are valid
+		{
+		// ReqSeq & FBit valid: use this information whatever TxSeq value is, as long as it's
+		// within window.
+
+		if (final)
+			{
+			HandleFinalAck();
+			}
+
+		iTransmitter->HandleIncomingSFrameL(aSFrame);
+		iReceiver->HandleIncomingSFrameL(aSFrame);
+
+		NotifyMuxerOfPdusToSendIfHaveSome();
+		}
+	else
+		{
+		LOG(_L("F-bit invalid, dropping the S-Frame"))
+		aSFrame.Free();
+		}
+	}
+
+void CL2CapEnhancedReTxController::HandleFinalAck()
+	{
+	LOG_FUNC
+	// Just do housekeeping here. Transmitter/Receiver maintain their respective WAIT_* states
+	// and know what to do with the final ack.
+	iTimerMan.StopMonitorTimer();
+
+	iPollSFrameTransmitCount = 0;
+	}
+
+void CL2CapEnhancedReTxController::SendIOrRrOrRnrL(TBool aFinal)
+	{
+	LOG_FUNC
+	// Try piggybacking on an I-Frame first...
+	HIFramePDU* eyeFrame = iTransmitter->GetIFrameToSendL();
+	if (eyeFrame)
+		{
+		eyeFrame->SetFinalBit(aFinal);
+		iOutgoingQ.QueueIFrameL(*eyeFrame);
+		}
+	else
+		{
+		// ... no I-Frame to send or can't send one.
+		iOutgoingQ.QueueAckingSFrame(*iReceiver->GetAckFrameL(aFinal));
+		}
+	}
+
+HL2CapPDU* CL2CapEnhancedReTxController::GetPduL()
+	{
+	LOG_FUNC
+	// Note that:
+	// - the queue contains S-Frames (naturally) and I-Frames (if they're SREJ-requested by
+	// the peer or a Poll request was received and we had a data frame to piggyback the Final
+	// response on);
+	// - once a packet is put on the Q, it should be virtually considered to be on the air from
+	// a logical POV.
+	if (!iOutgoingQ.HaveFramesToTransmit())
+		{
+		// Check for async events to handle.
+		// Note: polls are only sent when the OutgoingQ is empty.
+		// Reason:
+		// 1. The frames on the queue may be I-Frames queued in response to SREJs from the peer.
+		// Sending the Poll before responding to the SREJs would lead to a race condition
+		// (in WAIT_F) - the SREJs were received when XMIT state = Normal and so SRejActioned was
+		// not set to True. So if they're not responded to before the Poll, we'll receive a
+		// duplicate SREJ with the Final ack and respond to it with a duplicate I-Frame, which
+		// causes disconnect.
+		//
+		// 2. Even though this may increase the latency of handling an Ack timer expiry if
+		// there're packets on the queue, it actually bets on the assumption that these packets are
+		// I-Frames SREJ-requested by the peer (same one(s) that caused us to time out) and
+		// sending them before the Poll packet still gives the SREJ chance to work and hence
+		// minimizes the amount of stuff we'll have to retransmit once we receive the final ack.]
+		if ((iReceiver->IsWaitFStatePending() || iTransmitter->IsWaitAckStatePending()) &&
+			!IsPollOutstanding())
+			// ^- this serializes poll cycles - only one poll can be outstanding at a time
+			// but the conditions for WAIT_F and WAIT_ACK are independent and can occur
+			// simultaneously.
+			{
+			// Start a new Poll cycle.
+			__ASSERT_DEBUG(iPollSFrameTransmitCount == 0, Panic(EL2CAPPollFrameNumberTransmitIsNotZero));
+
+			HSFramePDU* sFrame = iReceiver->GetAckFrameL();
+			sFrame->SetPollBit(ETrue);
+
+			// Give WAIT_ACK preference in case both are outstanding.
+			if (iTransmitter->IsWaitAckStatePending())
+				{
+				LOG(_L("Entering WaitAck"))
+				iTransmitter->EnterWaitAckState();
+				}
+			else
+				{
+				LOG(_L("Entering WaitF"))
+				iReceiver->EnterWaitFState();
+				}
+
+			iPollSFrameTransmitCount++;
+			iTimerMan.StartMonitorTimer();
+
+			iOutgoingQ.QueueAckingSFrame(*sFrame);
+			}
+		else // outgoing Q empty & no async events outstanding, can send data
+			{
+			HIFramePDU* eyeFrame = iTransmitter->GetIFrameToSendL();
+			if (eyeFrame)
+				{
+				iOutgoingQ.QueueIFrameL(*eyeFrame);
+				}
+			}
+		}
+
+	// If an acking frame was queued in the previous step, this flag has been cleared.
+	if (iReceiver->SendAck())
+		{
+		SendIOrRrOrRnrL(EFalse);
+		}
+
+	// If there's anything ready to send it's on the queue by now.
+	HL2CapPDU* pduToSend = iOutgoingQ.DequeueNextToSend();
+	if (pduToSend)
+		{
+		pduToSend->DeliverOutgoingPDU(*this);
+		NotifyMuxerOfPdusToSendIfHaveSome();
+		}
+
+	return pduToSend;
+	}
+
+void CL2CapEnhancedReTxController::HandlePduSendComplete(HL2CapPDU& aPdu)
+	{
+	LOG_FUNC
+	// We only claim ownership of I-Frames.
+	iTransmitter->HciCompletedIFrame(static_cast<HIFramePDU&>(aPdu));
+    // May be waiting for the current I-Frame to complete if it's already been
+    // requested for retransmission.
+    NotifyMuxerOfPdusToSendIfHaveSome();
+	}
+
+void CL2CapEnhancedReTxController::HandlePduSendError(HL2CapPDU& /*aPdu*/)
+	{
+	LOG_FUNC
+	// We use protocol-level retransmissions for I-Frames.
+	}
+
+TInt CL2CapEnhancedReTxController::HandleOutgoingIFrame(HIFramePDU* aIFrame)
+	{
+	LOG_FUNC
+	__ASSERT_DEBUG(!iTransmitter->InWaitAckState(), Panic(EL2CAPIFrameSentInWaitAck));
+
+	aIFrame->SetPDUCID(iRemoteCID);
+	aIFrame->CalculateAndSetFCS();
+
+	aIFrame->SetPduOwner(this);
+
+	LOG3(_L("Outgoing I-Frame: TxSeq = %d, F = %d, ReqSeq = %d"), 
+		 aIFrame->TxSeqNumber(), aIFrame->FinalBit(), aIFrame->ReqSeqNumber())
+
+	return KErrNone;
+	}
+
+TInt CL2CapEnhancedReTxController::HandleOutgoingSFrame(HSFramePDU* aSFrame)
+	{
+	LOG_FUNC
+	aSFrame->SetPDUCID(iRemoteCID);
+	aSFrame->CalculateAndSetFCS();
+
+	LOG4(_L("Outgoing S-Frame: S = %d, P = %d, F = %d, ReqSeq = %d"),
+		 aSFrame->SupervisoryFunction(), aSFrame->PollBit(), aSFrame->FinalBit(), aSFrame->ReqSeqNumber())
+
+	return KErrNone;
+	}
+
+void CL2CapEnhancedReTxController::SendPeerAckTimerExpired()
+	{
+	LOG_FUNC
+	TRAPD(err, iReceiver->SendPeerAckTimerExpiredL());
+	if (err != KErrNone)
+		{
+		ErrorD(err);
+		// RIP
+		}
+	}
+
+void CL2CapEnhancedReTxController::AckTimerExpired()
+	{
+	LOG_FUNC
+	// Reset the POLL transmit count (it will be incremented when the SFrame[POLL] is pulled for sending
+	iPollSFrameTransmitCount = 0;
+	iTransmitter->AckTimerExpired();
+	}
+
+void CL2CapEnhancedReTxController::LocalBusyDelayTimerExpired()
+	{
+	LOG_FUNC
+	iReceiver->LocalBusyDelayTimerExpired();
+	}
+
+void CL2CapEnhancedReTxController::MonitorTimerExpired()
+	{
+	LOG_FUNC
+	__ASSERT_DEBUG(IsPollOutstanding(), Panic(EL2CAPUnexpectedMonitorTimeout));
+	__ASSERT_DEBUG(iPollSFrameTransmitCount > 0, Panic(EL2CAPPollFrameNumberTransmitIsZero));
+
+	TInt err = KErrNone;
+
+	if (!TRetransmissionAndFlowControlOption::EnhancedMaxTransmitLessOrEqual(iPollSFrameTransmitCount + 1,
+																			 iConfig->MaxTransmit()))
+		{
+		LOG1(_L("MaxTransmit=%d exceeded by a Poll S-Frame"), iConfig->MaxTransmit())
+		err = KErrL2CAPMaxTransmitExceeded;
+		}
+	else
+		{
+		HSFramePDU* frame = NULL;
+		TRAP(err, frame = iReceiver->GetAckFrameL());
+		if (err == KErrNone)
+			{
+			frame->SetPollBit(ETrue);
+			iOutgoingQ.QueueAckingSFrame(*frame);
+
+			iPollSFrameTransmitCount++;
+			iTimerMan.StartMonitorTimer();
+
+			NotifyMuxerOfPdusToSendIfHaveSome();
+			}
+		}
+	if (err != KErrNone)
+		{
+		ErrorD(err);
+		// RIP
+		}
+	}
+
+TUint16 CL2CapEnhancedReTxController::MonitorTimeout()
+	{
+	return iConfig->MonitorTimeout();
+	}
+
+TUint16 CL2CapEnhancedReTxController::RetransmissionTimeout()
+	{
+	return iConfig->RetransmissionTimeout();
+	}
+
+TUint16 CL2CapEnhancedReTxController::PeerRetransmissionTimeout()
+	{
+	return iConfig->PeerRetransmissionTimeout();
+	}
+
+void CL2CapEnhancedReTxController::OutgoingPduAvailableOnSduQ()
+	{
+	LOG_FUNC
+	NotifyMuxerOfPdusToSendIfHaveSome();
+	}
+
+void CL2CapEnhancedReTxController::SetIncomingSduQFull(TBool aIncomingSduQFull)
+	{
+	LOG_FUNC
+	TRAPD(err, iReceiver->SetIncomingSduQFullL(aIncomingSduQFull));
+	if (err != KErrNone)
+		{
+		ErrorD(err);
+		// RIP
+		}
+	}
+
+TBool CL2CapEnhancedReTxController::DeliverOutgoingDataAndSignalToSduQWhenDone()
+	{
+	LOG_FUNC
+	iDeliverOutgoingDataAndSignalToSduQWhenDone = ETrue;
+	// Returning true means we don't have any outstanding data to deliver and
+	// hence can be deleted immediately.
+	return !iTransmitter->HaveUnackedIFrames();
+	}
+
+inline void CL2CapEnhancedReTxController::NotifyMuxerOfPdusToSendIfHaveSome()
+    {
+    LOG_FUNC
+    // This is intended to be your one-stop kick-the-muxer routine.
+    // It should evaluate all conditions that may cause us to want to send
+    // a PDU and should be called at the end of handling of every event which
+    // may cause us to want to send stuff.
+    //
+    // The conditions are:
+    // - if there's something on the OutgoingQ, then always transmit it ASAP,
+    //   Outgoing Q is "the air" from the protocols point of view;
+    // - if we're not in WAIT_ACK (now called WAIT_F in the spec), then send
+    //   data:
+    //      - I-Frames requested for retransmission (with REJ or ack timer expiry,
+    //        SREJ-requested ones are put on the OutgoingQ),
+    //      - new I-Frames from SDUs on the SDU Q.
+    // - if there's some signalling to be send that hasn't been directly put
+    //   on the OutgoingQ - send it:
+    //      - need to go through WAIT_ACK and can enter it (WAIT_F not in progress),
+    //      - need to go through WAIT_F and can enter it (WAIT_ACK not in progress);
+    //      - time to send an acknowledgement to the peer.
+
+    if (iOutgoingQ.HaveFramesToTransmit()  ||  // various stuff already on the transmit Q,
+        // ... or data:
+        (!iTransmitter->InWaitAckState() &&
+            // ... in need of retransmission ...
+            ((iTransmitter->IsRetransmittingUnackedIFrames() && !iTransmitter->IsNextUnackedIFrameAwaitingHciCompletion()) ||
+            // ... or waiting to be pulled from SDU Q,
+            (!iTransmitter->IsRetransmittingUnackedIFrames() && iSDUQueue.HavePDUToSend() && iTransmitter->HaveSpaceInOutgoingWindow()))
+        ) ||
+        // or some boring signalling:
+        // ... want to enter WAIT_ACK and not already in WAIT_F ...
+        (iTransmitter->IsWaitAckStatePending() && !iReceiver->InWaitFState()) ||
+        // ... want to enter WAIT_F and not already in WAIT_ACK ...
+        (iReceiver->IsWaitFStatePending()      && !iTransmitter->InWaitAckState()) ||
+        // ... time to send an acknowledgement.
+        iReceiver->SendAck())
+        {
+        iMuxer.PDUAvailable();
+        }
+
+    #ifdef __L2CAP_ERTM_GETPDU_DEBUG
+    LOG1(_L("iOutgoingQ.HaveFramesToTransmit(): %d"), iOutgoingQ.HaveFramesToTransmit())
+    LOG1(_L("iTransmitter->HaveSpaceInOutgoingWindow(): %d"), iTransmitter->HaveSpaceInOutgoingWindow())
+    LOG1(_L("iTransmitter->InWaitAckState(): %d"), iTransmitter->InWaitAckState())
+    LOG1(_L("iTransmitter->IsRetransmittingUnackedIFrames(): %d"), iTransmitter->IsRetransmittingUnackedIFrames())
+    if (iTransmitter->IsRetransmittingUnackedIFrames())
+        {
+        LOG1(_L("iTransmitter->IsNextUnackedIFrameAwaitingHciCompletion(): %d"), iTransmitter->IsNextUnackedIFrameAwaitingHciCompletion())
+        }
+    LOG1(_L("iChannel.DataQueue().HavePDUToSend(): %d"), iSDUQueue.HavePDUToSend())
+    LOG1(_L("iTransmitter->IsWaitAckStatePending(): %d"), iTransmitter->IsWaitAckStatePending())
+    LOG1(_L("iReceiver->InWaitFState(): %d"), iReceiver->InWaitFState())
+    LOG1(_L("iReceiver->IsWaitFStatePending(): %d"), iReceiver->IsWaitFStatePending())
+    LOG1(_L("iTransmitter->InWaitAckState(): %d"), iTransmitter->InWaitAckState())
+    LOG1(_L("iReceiver->SendAck(): %d"), iReceiver->SendAck())
+    #endif
+    }