bluetooth/btstack/l2cap/L2CapDataController.cpp
changeset 0 29b1cd4cb562
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bluetooth/btstack/l2cap/L2CapDataController.cpp	Fri Jan 15 08:13:17 2010 +0200
@@ -0,0 +1,1338 @@
+// Copyright (c) 2004-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 <e32base.h>
+
+#include "L2CapDataController.h"
+#include "L2CapEnhancedDataController.h"
+
+#include "L2CapSDUQueue.h"
+#include "l2signalmgr.h"
+
+#include "btsockettimer.h"
+ 
+#include "l2util.h"
+#include "L2CapDebugControlInterface.h"
+
+#include "l2constants.h"	
+
+#ifdef __FLOG_ACTIVE
+_LIT8(KLogComponent, LOG_COMPONENT_L2CAP_DATA_CONTROLLER);
+#endif
+
+using namespace L2CapDataUtils;
+
+// ***** CL2CapBasicDataController Implementation
+/*static*/ CL2CapBasicDataController* CL2CapBasicDataController::NewL(TL2CAPPort aLocalCID, TL2CAPPort aRemoteCID, CL2CAPMux& aMuxer, CL2CapSDUQueue& aSDUQueue, TL2CapDataControllerConfig* aConfig)
+	{
+	LOG_STATIC_FUNC
+	// Create the required data controller. 
+	CL2CapBasicDataController* controller = NULL;
+
+	switch(aConfig->LinkMode())
+		{
+		case EL2CAPBasicMode:
+			LOG(_L("FEC L2CapBasicDataController Basic Mode"))
+			controller = new(ELeave) CL2CapBasicDataController(aLocalCID, aRemoteCID, aMuxer, aSDUQueue, aConfig, ETrue);
+			break;
+			
+		case EL2CAPRetransmissionMode:
+			LOG(_L("FEC L2CapBasicDataController Retransmission Mode"))
+			controller = new(ELeave) CL2CapDataReTxController(aLocalCID, aRemoteCID, aMuxer, aSDUQueue, aConfig);		
+			break;
+			
+		case EL2CAPFlowControlMode:
+			LOG(_L("FEC L2CapBasicDataController Flow Control Mode"))
+			controller = new(ELeave) CL2CapDataFlowController(aLocalCID, aRemoteCID, aMuxer, aSDUQueue, aConfig);		
+			break;
+
+		case EL2CAPStreamingMode:
+			LOG(_L("FEC L2CapBasicDataController Streaming Mode"))
+			controller = new(ELeave) CL2CapStreamingController(aLocalCID, aRemoteCID, aMuxer, aSDUQueue, aConfig);		
+			break;
+
+		case EL2CAPEnhancedRetransmissionMode:
+			LOG(_L("FEC L2CapBasicDataController Enhnaced Retransmission Mode"))
+			controller = CL2CapEnhancedReTxController::NewL(aLocalCID, aRemoteCID, aMuxer, aSDUQueue, aConfig);		
+			break;
+		
+		default:
+			Panic(EL2CAPInvalidLinkMode);
+			break;
+		};
+	
+	return controller;
+	}
+
+CL2CapBasicDataController::CL2CapBasicDataController(TL2CAPPort aLocalCID, TL2CAPPort aRemoteCID, CL2CAPMux& aMuxer, CL2CapSDUQueue& aSDUQueue, TL2CapDataControllerConfig* aConfig, TBool aIsBasicDataVersion)
+ : iSDUQueue(aSDUQueue),
+   iMuxer(aMuxer),
+   iLocalCID(aLocalCID),
+   iRemoteCID(aRemoteCID),
+   iConfig(aConfig),
+   iIsBasicDataVersion(aIsBasicDataVersion)
+	{
+	LOG_FUNC
+	// Register with the Mux.
+	iMuxer.RegisterDataPDUHandler(*this);
+
+	L2CAP_DEBUG(ObjectAllocation(L2capDebugInfo::EDataPlanes,
+	                             L2capDebugInfo::EAllocated));	
+	}
+
+CL2CapBasicDataController::~CL2CapBasicDataController()
+	{
+	LOG_FUNC
+	delete iConfig;
+
+	L2CAP_DEBUG(ObjectAllocation(L2capDebugInfo::EDataPlanes,
+	                             L2capDebugInfo::EDeleted));	
+	}	
+
+HL2CapPDU* CL2CapBasicDataController::GetPdu()
+	{
+	LOG_FUNC
+	HL2CapPDU* pdu = NULL;
+	TRAPD(err, pdu = GetPduL());
+	if (err != KErrNone)
+		{
+		// The protocol here is that all leaves propagated up to here cause
+		// us to ErrorD() ourselves, which will destruct data plane objects
+		// (the data controller, SDU queue and the whole shebang) and error
+		// the socket with the leave error code.
+		ErrorD(err);
+		}
+	return pdu;
+	}
+
+HL2CapPDU* CL2CapBasicDataController::GetPduL()
+	{
+	LOG_FUNC
+	HL2CapPDU* pduToSend = NULL;
+	if((pduToSend = iSDUQueue.GetPDU()) != NULL)
+		{
+		pduToSend->SetPDUCID(iRemoteCID);
+		}
+	return pduToSend;
+	}
+
+void CL2CapBasicDataController::ProcessFlushTimerExpiry()
+	{
+	LOG_FUNC
+	iMuxer.ProcessFlushTimerExpiry();
+	}
+
+void CL2CapBasicDataController::OutgoingPduAvailableOnSduQ()
+	{
+	LOG_FUNC
+	iMuxer.PDUAvailable();
+	}
+
+void CL2CapBasicDataController::UpdateConfig(TL2CapDataControllerConfig* aConfig)
+	{
+	LOG_FUNC
+	TBool channelPriorityUpdated = (iConfig->ChannelPriority() != aConfig->ChannelPriority());
+	// Delete the existing config, and replace it with the new one.
+	delete iConfig;
+	iConfig = aConfig;
+
+	if(channelPriorityUpdated)
+		{
+		iMuxer.ChannelPriorityUpdated(*this);
+		}
+	}
+
+void CL2CapBasicDataController::UpdateChannelPriority(TUint8 aNewPriority)
+	{
+	LOG_FUNC
+	if(iConfig->ChannelPriority() != aNewPriority)
+		{
+		iConfig->SetChannelPriority(aNewPriority);
+		iMuxer.ChannelPriorityUpdated(*this);
+		}
+	}
+	
+	
+void CL2CapBasicDataController::ErrorD(TInt aError)
+	{
+	LOG_FUNC
+	LOG1(_L("FEC CL2CapBasicDataController::Error %d"), aError)
+	iDataPlaneErrored = ETrue;
+	iSDUQueue.ErrorD(aError);
+	}
+
+void CL2CapBasicDataController::SetIncomingSduQFull(TBool /*aIncomingSduQFull*/)
+	{
+	LOG_FUNC
+	// No action can be taken in basic mode.
+	}
+
+TBool CL2CapBasicDataController::DeliverOutgoingDataAndSignalToSduQWhenDone()
+	{
+	LOG_FUNC
+	// We can be deleted right away.
+	return ETrue;
+	}
+
+void CL2CapBasicDataController::DeregisterFromMuxer()
+	{
+	LOG_FUNC
+	iMuxer.DataChannelRemoved(this);
+	}
+
+#ifdef _DEBUG
+TInt CL2CapBasicDataController::GetDataPlaneConfig(TL2DataPlaneConfig& conf) const
+	{
+	LOG_FUNC
+	TInt rerr = KErrNone;
+	// Populate the debug structure.
+	if(iConfig)
+		{
+		conf.iPriority = iConfig->ChannelPriority();
+		conf.iLinkMode = iConfig->LinkMode();
+		conf.iTxWindowSize = iConfig->TXWindowSize();
+		conf.iMaxTransmit = iConfig->MaxTransmit();
+		conf.iRetransmissionTimeout = iConfig->RetransmissionTimeout();
+		conf.iMonitorTimeout = iConfig->MonitorTimeout();
+		}
+	else
+		{
+		rerr = KErrNotReady;
+		}
+	return rerr;
+	}
+#endif
+
+TBool CL2CapBasicDataController::HandleIncomingDataFrame(RMBufChain& aDataFrame)
+	{
+	LOG_FUNC
+	TBool packetDelivered = EFalse;
+	TRAPD(err, packetDelivered = HandleIncomingDataFrameL(aDataFrame));
+	if (err != KErrNone)
+		{
+		// The protocol here is that all leaves propagated up to here cause
+		// us to ErrorD() ourselves, which will destruct data plane objects
+		// (the data controller, SDU queue and the whole shebang) and error
+		// the socket with the leave error code.
+		ErrorD(err);
+
+		if (aDataFrame.IsEmpty())
+			{
+			packetDelivered = ETrue;
+			}
+		// else frame will be offered to other data controllers
+		}
+	return packetDelivered;
+	}
+
+void CL2CapBasicDataController::HandleIncomingIFrameL(RMBufChain& /*aDataFrame*/)
+	{
+	LOG_FUNC
+	Panic(EL2CAPIFrameOrSFrameHandledByBasicController);
+	}
+
+void CL2CapBasicDataController::HandleIncomingBFrameL(RMBufChain& aDataFrame)
+	{
+	LOG_FUNC
+	iSDUQueue.PutBFramePDU(aDataFrame);
+	}
+
+void CL2CapBasicDataController::HandleIncomingSFrameL(RMBufChain& /*aDataFrame*/)
+	{
+	LOG_FUNC
+	Panic(EL2CAPIFrameOrSFrameHandledByBasicController);
+	}
+
+TBool CL2CapBasicDataController::HandleIncomingDataFrameL(RMBufChain& aDataFrame)
+	{
+	LOG_FUNC
+	TBool PDUProcessed = EFalse;
+	TInt err = KErrNone;
+	
+	if(HL2CapPDU::PDUCID(aDataFrame) == iLocalCID)
+		{
+		if(iIsBasicDataVersion)
+			{
+			// This can only be a B-Frame.
+			if(HBFramePDU::CheckDecode(aDataFrame) == KErrNone)
+			   	{
+			   	HandleIncomingBFrameL(aDataFrame);
+			   	}
+			else
+				{
+				// Invalid frame.  Drop it.
+				aDataFrame.Free();
+				LOG(_L("FEC L2CapBasicDataController B-Frame Dropped"))
+				}
+			}
+		else
+			{
+			if(HIFramePDU::IFrameIdentifier(aDataFrame))
+				{
+				// This is an I-Frame
+
+				// Check stuff that causes us to drop frames first:
+				// - FCS (2.1 Core Spec Addendum 1: 3.3.7 Invalid Frame Detection Algorithm pt. 2)
+				// - length field matching the actual payload length (funnily 3.3.7 doesn't say
+				//   what to do with it, pt. 7.2.2 Recombination Of L2CAP PDUs mentions it in
+				//   passing).
+				// Note: the frame is dropped when length is incorrect (instead of disconnecting)
+				// because we don't know where FCS is (it's at the end of the packet and we don't
+				// know where that is), so have to assume that the length field problem is due to
+				// transmission corruption and not the remote misbehaving.
+				if (HIFramePDU::CheckPayloadDecode(aDataFrame) != KErrNone ||
+					HL2CapPDU::CheckDecode(aDataFrame) != KErrNone)
+					{
+					aDataFrame.Free();
+					LOG(_L("FEC L2CapBasicDataController I-Frame Dropped"))
+					}
+				else
+					{
+					// Now check stuff that causes us to disconnect (per CSA1 3.3.7).
+					err = HIFramePDU::CheckLengthWithinLimits(aDataFrame, iConfig->IncomingMps());
+					if (err != KErrNone)
+						{
+						LOG(_L("FEC L2CapBasicDataController I-Frame invalid"))
+
+						aDataFrame.Free();
+						// This will cause the channel to be closed.
+						LEAVEL(err);
+						}
+					else
+						{
+						HandleIncomingIFrameL(aDataFrame);
+						}
+					}
+				}
+			else
+				{
+				// S-Frame
+
+				// Check stuff that causes the frame to be dropped first.
+				if (HSFramePDU::CheckPayloadDecode(aDataFrame) != KErrNone ||
+					HL2CapPDU::CheckDecode(aDataFrame) != KErrNone)
+					{
+					LOG(_L("FEC L2CapBasicDataController S-Frame Dropped"))
+					}
+				else
+					{
+					// Now check stuff that causes us to disconnect (per CSA1 3.3.7).
+					err = HSFramePDU::CheckLengthField(aDataFrame);
+					if (err != KErrNone)
+						{
+						LOG(_L("FEC L2CapBasicDataController S-Frame Invalid"))
+
+						aDataFrame.Free();
+						// This will cause the channel to be closed.
+						LEAVEL(err);
+						}
+					else
+						{
+						HandleIncomingSFrameL(aDataFrame);
+					  	}
+					}
+				// Finished with the S-Frame.
+				aDataFrame.Free();
+				}
+			}
+		PDUProcessed = ETrue;
+		}
+	return PDUProcessed;
+	}
+
+// Outgoing PDU handler.
+TInt CL2CapBasicDataController::HandleOutgoingIFrame(HIFramePDU* /*aIFrame*/)
+	{
+	LOG_FUNC
+	return KErrNone;
+	}
+
+TInt CL2CapBasicDataController::HandleOutgoingBFrame(HBFramePDU* /*aBFrame*/)
+	{
+	LOG_FUNC
+	return KErrNone;
+	}
+
+TInt CL2CapBasicDataController::HandleOutgoingGFrame(HGFramePDU* /*aGFrame*/)
+	{
+	LOG_FUNC
+	return KErrNone;
+	}
+
+TInt CL2CapBasicDataController::HandleOutgoingCFrame(HCFramePDU* /*aCFrame*/)
+	{
+	LOG_FUNC
+	return KErrNone;
+	}
+
+TInt CL2CapBasicDataController::HandleOutgoingSFrame(HSFramePDU* /*aSFrame*/)
+	{
+	LOG_FUNC
+	return KErrNone;
+	}
+
+// ***** CL2CapDataFlowController Implementation
+CL2CapDataFlowController::CL2CapDataFlowController(TL2CAPPort aLocalCID, TL2CAPPort aRemoteCID, CL2CAPMux& aMuxer, CL2CapSDUQueue& aSDUQueue, TL2CapDataControllerConfig* aConfig)
+ : CL2CapBasicDataController(aLocalCID, aRemoteCID, aMuxer, aSDUQueue, aConfig, EFalse),
+   iPendingSentPDUs(_FOFF(HIFramePDU, iLink)),
+   iSentPDUs(_FOFF(HIFramePDU, iLink)),
+   iTimerMan(*this),
+   iIncomingSduQFull(EFalse)
+	{
+	LOG_FUNC
+	}
+
+CL2CapDataFlowController::~CL2CapDataFlowController()
+	{
+	LOG_FUNC
+	// Some PDUs may hold a reference to this data controller.
+	TDblQueIter<HIFramePDU> pendingIter(iPendingSentPDUs);
+	TDblQueIter<HIFramePDU> sentIter(iSentPDUs);
+	HIFramePDU* pduPtr;
+	
+	while((pduPtr = pendingIter++) != NULL)
+		{
+		LOG1(_L("Deregistering TxSeq = %d"), pduPtr->TxSeqNumber());
+		pduPtr->DeregisterPduOwner();
+		}
+
+	// Delete any PDUs that have already been sent.
+	while((pduPtr = sentIter++) != NULL)
+		{
+		delete pduPtr;
+		}
+
+	// Cancel any outstanding timers.
+	iTimerMan.Close();
+	}
+
+
+void CL2CapDataFlowController::ProcessFlushTimerExpiry()
+	{
+	LOG_FUNC
+	iMuxer.ProcessFlushTimerExpiry();
+	PDUAvailable();
+
+	if(!iSentPDUs.IsEmpty())
+		{
+		iNextTxSeq = Mod64(iSentPDUs.Last()->TxSeqNumber() + 1);
+		}
+	else
+		{
+		iNextTxSeq = iExpectedAckSeq;
+		}
+	}
+
+void CL2CapDataFlowController::HandleIncomingIFrameL(RMBufChain& aDataFrame)
+	{
+	LOG_FUNC
+	// Check if flow control is active for incoming data.
+	if(iIncomingSduQFull)
+		{
+		// Check that the PDU sequence number is within the expected range.
+		if(!iSenderTxWindowClosed && Mod64(HIFramePDU::TxSeqNumber(aDataFrame) - iLastAckSentRxSeqNum) >= iConfig->PeerTXWindowSize())
+			{
+			// The number is not in range.  No more I-Frames will be processed
+			// until flow control is deactivated.
+			iSenderTxWindowClosed = ETrue;
+			}
+		}
+
+	LOG(_L("FEC CL2CapDataFlowController: HandleIncomingIFrame"))
+	LOG1(_L("\tTxSeqNumber = %d"), HIFramePDU::TxSeqNumber(aDataFrame))
+	LOG1(_L("\tLastAckSentRxSeqNum = %d"), iLastAckSentRxSeqNum)
+	
+	if(!iSenderTxWindowClosed)
+		{
+		ProcessIFrameL(aDataFrame);
+		}
+	else
+		{
+		// Drop the frame.
+		aDataFrame.Free();
+		LOG(_L("FEC CL2CapDataFlowController: Incoming PDU seq number of I-Frame is not in range!"))
+		}
+	}
+
+void CL2CapDataFlowController::ProcessIFrameL(RMBufChain& aDataFrame)
+	{
+	LOG_FUNC
+	iExpectedTxSeq = Mod64(iExpectedTxSeq + 1); 
+	iExpectedAckSeq = HIFramePDU::ReqSeqNumber(aDataFrame);
+	
+	LEAVEIFERRORL(iSDUQueue.PutIFramePDU(aDataFrame));
+
+	// Check if the acknowledged frame(s) has created
+	// space in the window.	
+	RemoveAckedPDUsFromSentQueue();
+
+	// Only send an ack if incoming data is not currently flow controlled off.
+	if(!iIncomingSduQFull)
+		{
+		// Check if the peer frames need to be acknowledged now.
+		// Check the peer TxWindow.  This check is
+		// (Number of I-Frames recv since last Ack was sent > (TxWindow - Constant)
+		if(Mod64(iExpectedTxSeq - iLastAckSentRxSeqNum) > (iConfig->PeerTXWindowSize() - KTxWinAckThresholdOffset))
+			{
+			// Send an ack.
+			iSendAckToPeer = ETrue;
+			}
+		else
+			{
+			// If the timer is not running, start it.
+			if(!iTimerMan.IsSendPeerAckTimerRunning())
+				{
+				if(!iTimerMan.StartSendPeerAckTimer())
+					{
+					// A timer could not be started.  Send an ack immediately.
+					iSendAckToPeer = ETrue;
+					}
+				}
+			}
+		}
+	// Check if any outbound data can be sent.
+	PDUAvailable();
+	}
+
+void CL2CapDataFlowController::HandleIncomingSFrameL(RMBufChain& aDataFrame)
+	{
+	LOG_FUNC
+	// Check that the function is RR.  If not this S-Frame is not valid for
+	// flow control mode.
+	if(HSFramePDU::SupervisoryFunction(aDataFrame) == EReceiverReady)
+		{
+		iExpectedAckSeq = HSFramePDU::ReqSeqNumber(aDataFrame);
+		// Check if the acknowledged frame(s) has created
+		// space in the window.	
+		RemoveAckedPDUsFromSentQueue();
+		PDUAvailable();				
+		LOG(_L("FEC CL2CapDataFlowController: Incoming S-Frame: RR"))				
+		LOG1(_L("\tiLastAckedFrameNum = %d"),  iExpectedAckSeq)
+		}
+	}
+
+TInt CL2CapDataFlowController::HandleOutgoingIFrame(HIFramePDU* aIFrame)
+	{
+	LOG_FUNC
+	aIFrame->SetPDUCID(iRemoteCID);
+	aIFrame->SetTxSeqNumber(iNextTxSeq);
+	LOG(_L("FEC CL2CapDataFlowController: HandleOutgoingIFrame"))
+	LOG1(_L("\tTxSeqNumber = %d"), iNextTxSeq)
+	iNextTxSeq = Mod64(iNextTxSeq + 1);
+
+	// If flow controlled off - don't acknowledge any new I-Frames
+	aIFrame->SetReqSeqNumber(iIncomingSduQFull ? iLastAckSentRxSeqNum : iExpectedTxSeq);
+
+	// Set the retransmission disable bit if flow control is on.
+	aIFrame->SetRetransmitDisable(iIncomingSduQFull);
+	aIFrame->CalculateAndSetFCS();
+
+	aIFrame->iLink.Deque(); // just in case, it shouldn't be on any list at this point
+	aIFrame->SetPduOwner(this);
+	iPendingSentPDUs.AddLast(*aIFrame);
+	
+	// Cancel the peer ack timer.
+	iTimerMan.StopSendPeerAckTimer();
+	iSendAckToPeer = EFalse;
+
+	// Store the last acknowledged frame index.
+	if(!iIncomingSduQFull)
+		{
+		iLastAckSentRxSeqNum = iExpectedTxSeq;
+		}
+	LOG1(_L("\tLastAckSentRxSeqNum = %d"), iLastAckSentRxSeqNum)
+	
+	return KErrNone;
+	}
+
+TInt CL2CapDataFlowController::HandleOutgoingSFrame(HSFramePDU* aSFrame)
+	{
+	LOG_FUNC
+	aSFrame->SetPDUCID(iRemoteCID);
+	// If flow controlled off - don't acknowledge any new I-Frames
+	aSFrame->SetReqSeqNumber(iIncomingSduQFull ? iLastAckSentRxSeqNum : iExpectedTxSeq);
+	LOG(_L("FEC CL2CapDataFlowController: HandleOutgoingSFrame"))		
+	LOG1(_L("\tReqSeqNUmber = %d"), iIncomingSduQFull ? iLastAckSentRxSeqNum : iExpectedTxSeq)
+	// Set the retransmission disable bit if flow control is on.
+	aSFrame->SetRetransmitDisable(iIncomingSduQFull);
+	aSFrame->CalculateAndSetFCS();
+
+	// If the monitor timer is currently running.  Re-start it.
+	if(!iTimerMan.IsAckTimerRunning())
+		{
+		iTimerMan.StartMonitorTimer();
+		}
+
+	// Store the last acknowledged frame index.
+	if(!iIncomingSduQFull)
+		{
+		iLastAckSentRxSeqNum = iExpectedTxSeq;
+		}
+		
+	// Cancel the peer ack timer.
+	iTimerMan.StopSendPeerAckTimer();
+	iSendAckToPeer = EFalse;
+	LOG1(_L("\tLastAckSentRxSeqNum = %d"), iLastAckSentRxSeqNum)
+	return KErrNone;
+	}
+
+TBool CL2CapDataFlowController::CanSendPDU()
+	{
+	LOG_FUNC
+	TBool canSend = ETrue;
+
+	// This condition is (V(S) - V(A))mod64 >= TxWindow
+	if(Mod64(iNextTxSeq - iExpectedAckSeq) >= iConfig->TXWindowSize())
+		{
+		canSend = EFalse;
+		}	
+	return canSend;
+	}
+
+					
+HL2CapPDU* CL2CapDataFlowController::GetPduL()
+	{
+	LOG_FUNC
+	// This is called from the signal manager.
+	HL2CapPDU* pduToSend = NULL;
+
+	if(CanSendPDU())
+		{	
+		pduToSend = iSDUQueue.GetPDU();
+		}
+
+	if(pduToSend)
+		{
+		pduToSend->DeliverOutgoingPDU(*this);
+		}
+
+	// Note:  If pduToSend has a valid pointer then the call to 
+	// pduToSend->DeliverOutgoingPDU(*this) (above) will clear the 
+	// iSendAckToPeer flag.
+	if(iSendAckToPeer)
+		{
+		pduToSend = HSFramePDU::New(EReceiverReady);
+		if(pduToSend)
+			{
+			pduToSend->DeliverOutgoingPDU(*this);
+			}
+		else
+			{
+			ErrorD(KErrNoMemory);
+			// We've been deleted!
+			}
+		}
+	return pduToSend;
+	}
+
+
+void CL2CapDataFlowController::PDUAvailable()
+	{
+	LOG_FUNC
+
+	// Note that CanSend only checks whether there's available outgoing window space,
+	// not whether there really is some outstanding data on SDU Q. GetPduL will check
+	// that and do nothing if there isn't.
+	if(CanSendPDU() || iSendAckToPeer)
+		{
+		iMuxer.PDUAvailable();
+		}
+	}
+
+void CL2CapDataFlowController::RemoveAckedPDUsFromSentQueue()
+	{
+	LOG_FUNC
+	TDblQueIter<HIFramePDU> iter(iSentPDUs);
+	HIFramePDU* pduPtr;
+	TInt ackedPDUs = 0;
+	TInt count = 0;
+
+	TUint8 frameSeqNumber = Mod64(iExpectedAckSeq - 1);
+
+	// Find out how many PDU's have been ack'd
+	while((pduPtr = iter++) != NULL)
+		{
+		if(pduPtr->TxSeqNumber() == frameSeqNumber)
+			{
+			// Increment the count by one and exit the loop.
+			ackedPDUs = count + 1;
+			break;
+			}
+		count++;
+		}
+
+	// Remove all ack'd PDUs
+	iter.SetToFirst();
+	for(TInt i=0;i<ackedPDUs;i++)
+		{
+		pduPtr = iter++;
+		delete pduPtr;
+		}
+
+	// Some PDUs have been ack'd.
+	if(ackedPDUs)
+		{
+		if(iSentPDUs.IsEmpty())
+			{
+			iTimerMan.StartMonitorTimer();
+			}
+		else
+			{
+			iTimerMan.StopMonitorTimer();
+			iTimerMan.StartAckTimer();
+			}
+		}
+	}
+
+void CL2CapDataFlowController::MonitorTimerExpired()
+	{
+	LOG_FUNC
+	// The monitor timer has expired.  Send a S-Frame to the peer by setting
+	// the ack outstanding flag and calling PDUAvailable.  This method will
+	// call this back at which point the S-Frame will be created.
+	LOG(_L("FEC CL2CapDataFlowController::MonitorTimerExpired"))
+	iSendAckToPeer = ETrue;
+	PDUAvailable();
+	}
+	
+	
+void CL2CapDataFlowController::AckTimerExpired()
+	{
+	LOG_FUNC
+	// Remove the PDU that the timer is supervising.  This can
+	// only be the first Sent PDU.
+	LOG(_L("FEC CL2CapDataFlowController::AckTimerExpired"))
+	__ASSERT_DEBUG(!iSentPDUs.IsEmpty(), Panic(EL2CAPAckTimerExpiryWithoutPDUToSupervise));
+	
+	HL2CapPDU* pdu = iSentPDUs.First();
+	delete pdu;
+
+	iExpectedAckSeq = Mod64(iExpectedAckSeq + 1);
+
+	if(iSentPDUs.IsEmpty())
+		{
+		iTimerMan.StartMonitorTimer();
+		}
+	else
+		{
+		iTimerMan.StopMonitorTimer();
+		iTimerMan.StartAckTimer();
+		}
+	PDUAvailable();	
+	}
+
+void CL2CapDataFlowController::SendPeerAckTimerExpired()
+	{
+	LOG_FUNC
+
+	// Indicate that an ack should be sent to the peer to
+	// acknowledge receipt of I-Frames.
+	iSendAckToPeer = ETrue;
+	PDUAvailable();	
+	}
+
+TUint16 CL2CapDataFlowController::MonitorTimeout()
+	{
+	return iConfig->MonitorTimeout();
+	}
+
+TUint16 CL2CapDataFlowController::RetransmissionTimeout()
+	{
+	return iConfig->RetransmissionTimeout();
+	}
+
+TUint16 CL2CapDataFlowController::PeerRetransmissionTimeout()
+	{
+	return iConfig->PeerRetransmissionTimeout();
+	}
+
+
+void CL2CapDataFlowController::HandlePduSendComplete(HL2CapPDU& aPdu)
+	{
+	LOG_FUNC
+
+	// We only claim ownership of I-Frames.
+	HIFramePDU& IFrame = static_cast<HIFramePDU&>(aPdu);
+
+	// Remove the PDU from the pending send queue.
+	IFrame.iLink.Deque();
+	
+	// If there are outstanding (i.e., un-ack'ed) PDU's, the
+	// Ack timer will already be running.  Otherwise it should
+	// be started.
+	if(iSentPDUs.IsEmpty())
+		{
+		iTimerMan.StartAckTimer();
+		}
+
+	// It's waiting for acknowledgement now.
+	iSentPDUs.AddLast(IFrame);
+	PDUAvailable();
+	}
+
+void CL2CapDataFlowController::HandlePduSendError(HL2CapPDU& /*aPdu*/)
+	{
+	LOG_FUNC
+	// In FC mode the frame will get deleted when we get an acknowledgement.
+	// In RTM, protocol-level retransmissions will be run when peer rejects or
+	// our ack timer expires.
+	}
+
+
+void CL2CapDataFlowController::SetIncomingSduQFull(TBool aIncomingSduQFull)
+	{
+	LOG_FUNC
+	// Check that the FC status has changed
+	if(iIncomingSduQFull != aIncomingSduQFull)
+		{
+		iIncomingSduQFull = aIncomingSduQFull;
+		iSenderTxWindowClosed = EFalse;
+
+		// Send an acknowledgement to the peer indicate the new
+		// flow control status.
+		iSendAckToPeer = ETrue;
+		PDUAvailable();
+		}
+	}
+
+
+// ***** CL2CapDataReTxController Implementation
+CL2CapDataReTxController::CL2CapDataReTxController(TL2CAPPort aLocalCID, TL2CAPPort aRemoteCID, CL2CAPMux& aMuxer, CL2CapSDUQueue& aSDUQueue, TL2CapDataControllerConfig* aConfig)
+ : CL2CapDataFlowController(aLocalCID, aRemoteCID, aMuxer, aSDUQueue, aConfig),
+   iRejectPDU(0),
+   iRetransmitSentPDUs(EFalse),
+   iRejectSent(EFalse),
+   iRetransmissionDisabled(EFalse),
+   iRestartAckTimer(EFalse),
+   iRetransTxVal(iNextTxSeq)
+	{
+	LOG_FUNC
+	}
+	
+void CL2CapDataReTxController::HandleIncomingIFrameL(RMBufChain& aDataFrame)
+	{
+	LOG_FUNC
+	TBool newRtxDisable = HIFramePDU::RetransmitDisable(aDataFrame);
+	if(iExpectedTxSeq == HIFramePDU::TxSeqNumber(aDataFrame))
+		{
+		LOG(_L("FEC CL2CapDataReTxController: HandleIncomingIFrame"))
+		LOG1(_L("\tTxSeqNumber = %d"), HIFramePDU::TxSeqNumber(aDataFrame))	
+		ProcessIFrameL(aDataFrame);
+		iRejectSent = EFalse;
+		}
+	else
+		{
+		// This I-Frame has an invalid sequence number.
+		// Check that a Reject Exception is not already being
+		// handled.
+		if(!iRejectSent)
+			{
+			iRejectPDU = HSFramePDU::NewL(EReject);
+			iRejectPDU->DeliverOutgoingPDU(*this);
+			// Send the Reject PDU.
+			iMuxer.PDUAvailable();
+			iRejectSent = ETrue;
+			}
+		aDataFrame.Free();
+		LOG(_L("FEC CL2CapDataReTxController: Incoming PDU seq number of I-Frame is not in range!"))
+		}
+	
+	// Check if the re-transmission disable bit has transitioned.
+	if(iRetransmissionDisabled != newRtxDisable)
+		{
+		if(newRtxDisable)
+			{
+			// Re-transmission has been disabled.
+			iTimerMan.StartMonitorTimer();
+			}
+		else
+			{
+			// Re-transmission has been enabled.
+			if(!iSentPDUs.IsEmpty())
+				{
+				iTimerMan.StopMonitorTimer();
+				iTimerMan.StartAckTimer();
+				}
+			}
+		iRetransmissionDisabled = newRtxDisable;
+		}
+	}
+
+void CL2CapDataReTxController::HandleIncomingSFrameL(RMBufChain& aDataFrame)
+	{
+	LOG_FUNC
+	if(HSFramePDU::SupervisoryFunction(aDataFrame) == EReject)
+		{
+		iRetransmitSentPDUs = ETrue;
+		iRetransTxVal = HSFramePDU::ReqSeqNumber(aDataFrame);
+		LOG(_L("FEC CL2CapDataReTxController: Incoming S-Frame: Reject"))		
+		}
+	iExpectedAckSeq = HSFramePDU::ReqSeqNumber(aDataFrame);
+	LOG(_L("FEC CL2CapDataReTxController: Incoming S-Frame"))
+	LOG1(_L("\tLastAckedFrameNum = %d"), iExpectedAckSeq)
+
+	TBool newRtxDisable = HSFramePDU::RetransmitDisable(aDataFrame);
+
+	// Check if the acknowledged frame(s) has created
+	// space in the window.	
+	RemoveAckedPDUsFromSentQueue();
+	PDUAvailable();				
+	
+	// Check if the re-transmission disable bit has transitioned.
+	if(iRetransmissionDisabled != newRtxDisable)
+		{
+		if(newRtxDisable)
+			{
+			// Re-transmission has been disabled.
+			iTimerMan.StartMonitorTimer();
+			}
+		else
+			{
+			// Re-transmission has been enabled.
+			if(!iSentPDUs.IsEmpty())
+				{
+				iTimerMan.StopMonitorTimer();
+				iTimerMan.StartAckTimer();
+				}
+			}
+		iRetransmissionDisabled = newRtxDisable;
+		}
+	}
+
+HL2CapPDU* CL2CapDataReTxController::GetPduL()
+	{
+	LOG_FUNC
+	// This is called from the signal manager.
+	HL2CapPDU* pduToSend = NULL;
+
+	// Check the re-transmission disabled flag.  If its set
+	// then only S-Frames can be sent.
+	if(!iRetransmissionDisabled && !iDataPlaneErrored)
+		{
+		if(iRejectPDU)
+			{
+			pduToSend = iRejectPDU;
+			iRejectPDU = NULL;
+			}
+		else
+			{
+			if(iRetransmitSentPDUs)
+				{
+				pduToSend = RetransmitSentPDU();
+				}	
+
+			if(!pduToSend && CanSendPDU())
+				{	
+				pduToSend = iSDUQueue.GetPDU();
+				}
+			}
+		}
+
+	if(pduToSend)
+		{
+		pduToSend->DeliverOutgoingPDU(*this);
+		}
+
+	// Note:  If pduToSend has a valid pointer then the call to 
+	// pduToSend->DeliverOutgoingPDU(*this) (above) will clear the 
+	// iSendAckToPeer flag.
+	if(iSendAckToPeer)
+		{
+		pduToSend = HSFramePDU::New(EReceiverReady);
+		if(pduToSend)
+			{
+			pduToSend->DeliverOutgoingPDU(*this);
+			}
+		else
+			{
+			ErrorD(KErrNoMemory);
+			// We've been deleted!
+			}
+		}
+	return pduToSend;
+	}
+
+void CL2CapDataReTxController::PDUAvailable()
+	{
+	LOG_FUNC
+
+	// Note that CanSend only checks whether there's available outgoing window space,
+	// not whether there really is some outstanding data on SDU Q. GetPduL will check
+	// that and do nothing if there isn't.
+	if(CanSendPDU() || iSendAckToPeer || iRetransmitSentPDUs)
+		{
+		iMuxer.PDUAvailable();
+		}
+	}
+
+void CL2CapDataReTxController::AckTimerExpired()
+	{
+	LOG_FUNC
+	__ASSERT_DEBUG(!iSentPDUs.IsEmpty(), Panic(EL2CAPAckTimerExpiryWithoutPDUToSupervise));
+
+	// Check if the PDU can be re-sent (i.e., the Max.
+	// transmit has not been exceeded.)
+	
+    HIFramePDU* pdu = iSentPDUs.First();
+        
+	if(pdu->CanTransmit(iConfig->MaxTransmit()))
+		{
+		// Set the next frame to be transmitted to the PDU 
+		// whose timer expired.
+		iRetransTxVal = pdu->TxSeqNumber();
+		iRetransmitSentPDUs = ETrue;
+		iRestartAckTimer = ETrue;
+
+		// The frame will be re-transmitted when GetPDU is called.
+		// This will also re-start the Retransmission timer.
+		PDUAvailable();	
+		}
+	else
+		{
+		// The channel can not support the requested quality.
+		// i.e., the number of retransmissions before the channel is
+		// disconnected has been exceeded.
+		// Error the SAP to disconnect the channel.
+		ErrorD(KErrL2CAPMaxTransmitExceeded);
+		LOG(_L("FEC CL2CapDataReTxController::AckTimerExpired - KErrL2CAPMaxTransmitExceeded"))	
+		}
+	}
+
+HL2CapPDU* CL2CapDataReTxController::RetransmitSentPDU()
+	{
+	LOG_FUNC
+	HIFramePDU* pduToSend = NULL;
+	TDblQueIter<HIFramePDU> iter(iSentPDUs);
+	HIFramePDU* pduPtr;
+	
+	while((pduPtr = iter++) != NULL)
+		{
+		if(pduPtr->TxSeqNumber() == iRetransTxVal)
+			{
+			pduToSend = pduPtr;
+			break;
+			}
+		}
+
+	if(!pduToSend)
+		{
+		iRetransmitSentPDUs = EFalse;
+		}
+	return pduToSend;
+	}
+
+void CL2CapDataReTxController::HandlePduSendComplete(HL2CapPDU& aPdu)
+	{
+	LOG_FUNC
+
+	// We only claim ownership of I-Frames.
+	HIFramePDU& IFrame = static_cast<HIFramePDU&>(aPdu);
+
+	// Remove the PDU from the pending send queue.
+	IFrame.iLink.Deque();
+	
+	// If there are outstanding (i.e., un-ack'ed) PDU's, the
+	// Ack timer will already be running.  Otherwise it should
+	// be started.
+	if(iSentPDUs.IsEmpty() || iRestartAckTimer)
+		{
+		iTimerMan.StopMonitorTimer();
+		iRestartAckTimer = EFalse;
+		iTimerMan.StartAckTimer();
+		}
+
+	iSentPDUs.AddLast(IFrame);
+	
+	// When operating in lossy conditions such as when running 
+	// concurrently with a name inquiry it is possible for an 
+	// acknowledgement to arrive before the sent completion code is called
+	// for the relevant PDU.  As a result aIFrame may have already been 
+	// acknowledged.  Now that we have ownership of the PDU we can 
+	// remove it and cancel the Ack timer if necessary.
+	
+	// N.B. Here we have assumed that the delay in the notification 
+	// that a PDU has been sent (thereby moving it from the pending sent
+	// queue to the sent queue) is not so large that in the mean time we
+	// have sent so many new PDUs that the transmission window has wrapped
+	// so far around that an old I-Frame from the last cycle is 
+	// considered an unacknowledged one because the Tx Seq value is once again 
+	// in the valid transmission window.  This would require BT to be much
+	// faster than it currently is (11/04/2007). 
+	RemoveAckedPDUsFromSentQueue();
+	PDUAvailable();
+	}
+	
+void CL2CapDataReTxController::RemoveAckedPDUsFromSentQueue()
+	{
+	LOG_FUNC
+	TDblQueIter<HIFramePDU> iter(iSentPDUs);
+	HIFramePDU* pduPtr;
+	TBool ackedPDUs = EFalse;
+
+	// Remove PDUs which have been previously acknowledged.
+	// The queue may be out of order due to retransmissions 
+	// being appended at the end of the sent queue.
+	while((pduPtr = iter++) != NULL)
+		{
+		// If the PDU's Tx Seq number is not in the current valid transmission window it is considered a previously acknowledged I-Frame and is removed.
+		if (!(((pduPtr->TxSeqNumber()>= iExpectedAckSeq) && (pduPtr->TxSeqNumber()< (iExpectedAckSeq + iConfig->TXWindowSize()))) || (((iExpectedAckSeq + iConfig->TXWindowSize()) > KL2CapTxSeqValues) && (pduPtr->TxSeqNumber()< Mod64((iExpectedAckSeq + iConfig->TXWindowSize()))))))
+			{
+			ackedPDUs = ETrue;
+			delete pduPtr;
+			}
+		}
+
+	// Some PDUs have been ack'd.
+	if(ackedPDUs)
+		{
+		if(iSentPDUs.IsEmpty())
+			{
+			if (iDeliverOutgoingDataAndSignalToSduQWhenDone && iPendingSentPDUs.IsEmpty())
+				{
+				iSDUQueue.DataControllerDeliveredOutgoingData();
+				}
+			else
+				{
+				iTimerMan.StartMonitorTimer();
+				}
+			}
+		else
+			{
+			iTimerMan.StopMonitorTimer();
+			iTimerMan.StartAckTimer();
+			}
+		}
+	}
+
+TInt CL2CapDataReTxController::HandleOutgoingIFrame(HIFramePDU* aIFrame)
+	{
+	LOG_FUNC
+	aIFrame->SetPDUCID(iRemoteCID);
+	
+	// When sending a retransmission iRetransmitSentPDUs = ETrue.
+	// When doing a retransmission the I-Frame has already had its Tx Seq
+	// value set and iRetransTxVal is incremented so that we will continue
+	// iterating through the sent PDUs for retransmission.
+	// When sending a new I-Frame iNextTxSeq is used to get the next
+	// brand new Tx Seq value (given the limitations of wrapping round the number
+	// space).
+	if (!iRetransmitSentPDUs)
+		{
+		aIFrame->SetTxSeqNumber(iNextTxSeq);
+		iNextTxSeq = Mod64(iNextTxSeq + 1);
+		}
+	else
+		{
+		++iRetransTxVal;
+		}
+	
+	LOG(_L("FEC CL2CapDataReTxController::HandleOutgoingIFrame"))		
+	LOG1(_L("\tTxSeqNumber = %d"), (aIFrame->TxSeqNumber()))
+
+	// If flow controlled off - don't acknowledge any new I-Frames
+	aIFrame->SetReqSeqNumber(iIncomingSduQFull ? iLastAckSentRxSeqNum : iExpectedTxSeq);
+
+	// Set the retransmission disable bit if flow control is on.
+	aIFrame->SetRetransmitDisable(iIncomingSduQFull);
+	aIFrame->CalculateAndSetFCS();
+
+	aIFrame->iLink.Deque(); // may be on Sent PDU list if it's a retransmission
+	aIFrame->SetPduOwner(this);
+	iPendingSentPDUs.AddLast(*aIFrame);
+	if ((iSentPDUs.IsEmpty()) && iTimerMan.IsAckTimerRunning())
+		{
+		iRestartAckTimer = EFalse;
+		iTimerMan.StartMonitorTimer();
+		}
+
+	// Cancel the peer ack timer.
+	iTimerMan.StopSendPeerAckTimer();
+	iSendAckToPeer = EFalse;
+
+	// Store the last acknowledged frame index.
+	if(!iIncomingSduQFull)
+		{
+		iLastAckSentRxSeqNum = iExpectedTxSeq;
+		}
+	LOG1(_L("\tLastAckSentRxSeqNum = %d"), iLastAckSentRxSeqNum)
+
+	return KErrNone;
+	}
+
+TBool CL2CapDataReTxController::DeliverOutgoingDataAndSignalToSduQWhenDone()
+	{
+	LOG_FUNC
+	iDeliverOutgoingDataAndSignalToSduQWhenDone = ETrue;
+	// Returning true means we don't have any outstanding data to deliver and
+	// hence can be deleted immediately.
+	return iPendingSentPDUs.IsEmpty() && iSentPDUs.IsEmpty();
+	}
+
+
+RL2CapRetransmissionModeTimerManager::RL2CapRetransmissionModeTimerManager(MRetransmissionModeTimerClient& aClient)
+ :	iClient(aClient) 
+	{
+	LOG_FUNC
+	}
+
+void RL2CapRetransmissionModeTimerManager::Close()
+	{
+	LOG_FUNC
+	CancelFECTimer();
+	StopSendPeerAckTimer();
+	}
+
+void RL2CapRetransmissionModeTimerManager::StartMonitorTimer()
+	{
+	LOG_FUNC
+	// Monitor timer preempts the Ack timer, so we can stop whichever we have running.
+	CancelFECTimer();
+	
+	TCallBack cb(FECTimerExpired, this);
+	iFECTimerEntry.Set(cb);
+	
+	// Set the timeout.  The value is in Milliseconds.
+	BTSocketTimer::Queue(iClient.MonitorTimeout()*1000, iFECTimerEntry);
+	iFECTimerState = EMonitorTimerRunning;
+	}
+
+void RL2CapRetransmissionModeTimerManager::StartAckTimer()
+	{
+	LOG_FUNC
+
+	if (iFECTimerState != EMonitorTimerRunning)
+		// Ack timer can't be run when the Monitor timer is running.
+		{
+		CancelFECTimer();
+	
+		TCallBack cb(FECTimerExpired, this);
+		iFECTimerEntry.Set(cb);
+	
+		// Set the timeout.  The value is in Milliseconds.
+		BTSocketTimer::Queue(iClient.RetransmissionTimeout()*1000, iFECTimerEntry);
+		iFECTimerState = EAckTimerRunning;
+		}
+	}
+
+void RL2CapRetransmissionModeTimerManager::StopMonitorTimer()
+	{
+	LOG_FUNC
+	if (iFECTimerState == EMonitorTimerRunning)
+		{
+		CancelFECTimer();
+		}
+	}
+
+void RL2CapRetransmissionModeTimerManager::StopAckTimer()
+	{
+	LOG_FUNC
+	if (iFECTimerState == EAckTimerRunning)
+		{
+		CancelFECTimer();
+		}
+	}
+
+void RL2CapRetransmissionModeTimerManager::CancelFECTimer()
+	{
+	LOG_FUNC
+	// Cancel either the monitor or ack timer.
+	if(iFECTimerState != EFECTimerIdle)
+		{
+		// Cancel the current outgoing timer.
+		BTSocketTimer::Remove(iFECTimerEntry);
+		iFECTimerState = EFECTimerIdle;
+		}
+	}
+
+void RL2CapRetransmissionModeTimerManager::HandleFECTimerExpired()
+	{
+	LOG_FUNC
+	// Call the appropriate method to handle this expiry.
+	switch(iFECTimerState)
+		{
+		case EFECTimerIdle:
+			Panic(EL2CAPInvalidDataControllerTimerState);
+			break;
+			
+		case EMonitorTimerRunning:
+			iFECTimerState = EFECTimerIdle;
+			iClient.MonitorTimerExpired();
+			break;
+			
+		case EAckTimerRunning:
+			iFECTimerState = EFECTimerIdle;
+			iClient.AckTimerExpired();
+			break;
+
+		default:
+			Panic(EL2CAPInvalidDataControllerTimerState);
+			break;
+		}
+	}
+
+void RL2CapRetransmissionModeTimerManager::HandleSendPeerAckTimerExpired()
+	{
+	LOG_FUNC
+	__ASSERT_ALWAYS(iSendPeerAckTimerRunning, Panic(EL2CAPInvalidDataControllerTimerState));
+	iSendPeerAckTimerRunning = EFalse;
+	iClient.SendPeerAckTimerExpired();
+	}
+
+TBool RL2CapRetransmissionModeTimerManager::StartSendPeerAckTimer()
+	{
+	LOG_FUNC
+	// This timer should only be started if the peer is using a long re-transmission timer.
+	__ASSERT_ALWAYS(!iSendPeerAckTimerRunning, Panic(EL2CAPInvalidDataControllerTimerState));
+	
+	TInt peerAckTimer = iClient.PeerRetransmissionTimeout() - KAveTimeToTransmitAckToPeer;
+	if(peerAckTimer > KMinimumPeerAckTimeout)
+		{
+		TCallBack cb(SendPeerAckTimerExpired, this);
+		iSendPeerAckTimerEntry.Set(cb);
+
+		// Set the timeout.  The value os in Milliseconds.
+		BTSocketTimer::Queue(peerAckTimer*1000, iSendPeerAckTimerEntry);
+		iSendPeerAckTimerRunning = ETrue;
+		}
+	return iSendPeerAckTimerRunning;
+	}
+
+void RL2CapRetransmissionModeTimerManager::StopSendPeerAckTimer()
+	{
+	LOG_FUNC
+	if (iSendPeerAckTimerRunning)
+		{
+		BTSocketTimer::Remove(iSendPeerAckTimerEntry);
+		iSendPeerAckTimerRunning = EFalse;
+		}
+	}
+
+/*static*/ TInt RL2CapRetransmissionModeTimerManager::SendPeerAckTimerExpired(TAny* aTimerMan)
+	{
+	LOG_STATIC_FUNC
+	RL2CapRetransmissionModeTimerManager* timerMan = reinterpret_cast<RL2CapRetransmissionModeTimerManager*>(aTimerMan);
+	timerMan->HandleSendPeerAckTimerExpired();
+	return EFalse;
+	}
+
+/*static*/ TInt RL2CapRetransmissionModeTimerManager::FECTimerExpired(TAny* aTimeMan)
+	{
+	LOG_STATIC_FUNC
+	RL2CapRetransmissionModeTimerManager* timeMan = reinterpret_cast<RL2CapRetransmissionModeTimerManager*>(aTimeMan);
+	timeMan->HandleFECTimerExpired();
+	return EFalse;
+	}