diff -r 000000000000 -r 29b1cd4cb562 bluetooth/btstack/l2cap/L2CapEnhancedDataController.cpp --- /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 +#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(iClient).LocalBusyDelayTimerExpired(); + } + +/*static*/ TInt RL2CapErtmTimerManager::LocalBusyDelayTimerExpired(TAny* aTimerMan) + { + LOG_STATIC_FUNC + RL2CapErtmTimerManager* timerMan = reinterpret_cast(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 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(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 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(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 +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 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 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 iter(iPendRetransmitIFrameQ); + while (HIFramePDU* eyeFrame = iter++) + { + delete eyeFrame; + } + iPendRetransmitIFrameQ.Reset(); + } + + if (!iOutgoingQ.IsEmpty()) + { + TSglQueIter 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(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 + }