From ae9af5d73485713a20845629b4a894daa4b8f7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Sun, 16 Aug 2026 20:28:07 +0200 Subject: [PATCH 01/14] Fix IEEE 802.11 ADDBA transaction handling --- examples/wireless/qos/omnetpp.ini | 23 + .../blockack/OriginatorBlockAckAgreement.h | 24 +- .../OriginatorBlockAckAgreementHandler.cc | 82 ++- .../OriginatorBlockAckAgreementHandler.h | 11 +- .../OriginatorBlockAckAgreementPolicy.cc | 11 +- .../OriginatorBlockAckAgreementPolicy.ned | 2 +- .../RecipientBlockAckAgreementHandler.cc | 112 +++-- .../RecipientBlockAckAgreementHandler.h | 21 +- .../IBlockAckAgreementHandlerCallback.h | 2 +- .../IOriginatorBlockAckAgreementHandler.h | 7 +- .../mac/contract/IOriginatorMacDataService.h | 9 +- .../IRecipientBlockAckAgreementHandler.h | 3 +- .../ieee80211/mac/coordinationfunction/Hcf.cc | 87 +++- .../ieee80211/mac/coordinationfunction/Hcf.h | 8 +- .../originator/OriginatorMacDataService.cc | 27 +- .../mac/originator/OriginatorMacDataService.h | 5 +- .../originator/OriginatorQosMacDataService.cc | 37 +- .../originator/OriginatorQosMacDataService.h | 5 +- .../ieee80211/mac/queue/InProgressFrames.cc | 7 +- tests/fingerprint/examples.csv | 1 + tests/unit/Ieee80211AddbaTransaction_1.test | 468 ++++++++++++++++++ 21 files changed, 836 insertions(+), 116 deletions(-) create mode 100644 tests/unit/Ieee80211AddbaTransaction_1.test diff --git a/examples/wireless/qos/omnetpp.ini b/examples/wireless/qos/omnetpp.ini index 887e0683fc6..1810255a502 100644 --- a/examples/wireless/qos/omnetpp.ini +++ b/examples/wireless/qos/omnetpp.ini @@ -83,5 +83,28 @@ sim-time-limit = 10s extends = MacQos **.isBlockAckSupported = true +[Config MacQosWithTransactionalBlockAck] +description = "Exercises successful and timed-out ADDBA transactions" +extends = MacQosWithoutAggregation +sim-time-limit = 3s + +# Use one voice flow so each wireless hop has a single peer/TID data flow. +*.cliHost.numApps = 1 +*.cliHost.app[0].destPort = 5000 +*.cliHost.app[0].packetName = "TransactionalBlockAck" +*.cliHost.app[0].startTime = 1s +*.cliHost.app[0].stopTime = 2s +*.cliHost.app[0].sendInterval = 10ms +*.srvHost.numApps = 1 +*.srvHost.app[0].localPort = 5000 + +# The client-to-AP transaction succeeds. The AP-to-server transaction times +# out, because the server deliberately does not support Block Ack. +*.cliHost.wlan[0].mac.hcf.isBlockAckSupported = true +*.ap.wlan[0].mac.hcf.isBlockAckSupported = true +*.srvHost.wlan[0].mac.hcf.isBlockAckSupported = false +**.mac.hcf.originatorAckPolicy.blockAckReqThreshold = 2 +**.mac.hcf.originatorBlockAckAgreementPolicy.addbaFailureTimeout = 250ms + # radio medium *.radioMedium.sameTransmissionStartTimeCheck = "ignore" diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h index a516527bc34..a024f5f817e 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h @@ -15,6 +15,12 @@ namespace ieee80211 { class OriginatorBlockAckAgreementHandler; +enum class OriginatorBlockAckAgreementState +{ + PENDING, + ESTABLISHED, +}; + class INET_API OriginatorBlockAckAgreement : public cObject { protected: @@ -25,19 +31,22 @@ class INET_API OriginatorBlockAckAgreement : public cObject int bufferSize = -1; bool isAMsduSupported = false; bool isDelayedBlockAckPolicySupported = false; - bool isAddbaResponseReceived = false; + OriginatorBlockAckAgreementState state = OriginatorBlockAckAgreementState::PENDING; bool isAddbaRequestSent = false; + uint8_t dialogToken = 0; + simtime_t addbaResponseDeadline = -1; simtime_t blockAckTimeoutValue = -1; simtime_t expirationTime = -1; public: - OriginatorBlockAckAgreement(MacAddress receiverAddr, Tid tid, SequenceNumberCyclic startingSequenceNumber, int bufferSize, bool isAMsduSupported, bool isDelayedBlockAckPolicySupported) : + OriginatorBlockAckAgreement(MacAddress receiverAddr, Tid tid, SequenceNumberCyclic startingSequenceNumber, int bufferSize, bool isAMsduSupported, bool isDelayedBlockAckPolicySupported, uint8_t dialogToken) : receiverAddr(receiverAddr), tid(tid), startingSequenceNumber(startingSequenceNumber), bufferSize(bufferSize), isAMsduSupported(isAMsduSupported), - isDelayedBlockAckPolicySupported(isDelayedBlockAckPolicySupported) + isDelayedBlockAckPolicySupported(isDelayedBlockAckPolicySupported), + dialogToken(dialogToken) { } @@ -46,7 +55,8 @@ class INET_API OriginatorBlockAckAgreement : public cObject virtual int getBufferSize() const { return bufferSize; } virtual SequenceNumberCyclic getStartingSequenceNumber() { return startingSequenceNumber; } virtual void setStartingSequenceNumber(SequenceNumberCyclic sequenceNumber) { startingSequenceNumber = sequenceNumber; } - virtual bool getIsAddbaResponseReceived() const { return isAddbaResponseReceived; } + virtual bool getIsAddbaResponseReceived() const { return state == OriginatorBlockAckAgreementState::ESTABLISHED; } + virtual bool isPending() const { return state == OriginatorBlockAckAgreementState::PENDING; } virtual bool getIsAddbaRequestSent() const { return isAddbaRequestSent; } virtual bool getIsAMsduSupported() const { return isAMsduSupported; } virtual bool getIsDelayedBlockAckPolicySupported() const { return isDelayedBlockAckPolicySupported; } @@ -54,13 +64,16 @@ class INET_API OriginatorBlockAckAgreement : public cObject virtual Tid getTid() const { return tid; } virtual const simtime_t getBlockAckTimeoutValue() const { return blockAckTimeoutValue; } virtual int getNumSentBaPolicyFrames() const { return numSentBaPolicyFrames; } + virtual uint8_t getDialogToken() const { return dialogToken; } + virtual simtime_t getAddbaResponseDeadline() const { return addbaResponseDeadline; } virtual void setBufferSize(int bufferSize) { this->bufferSize = bufferSize; } - virtual void setIsAddbaResponseReceived(bool isAddbaResponseReceived) { this->isAddbaResponseReceived = isAddbaResponseReceived; } + virtual void setIsAddbaResponseReceived(bool isAddbaResponseReceived) { state = isAddbaResponseReceived ? OriginatorBlockAckAgreementState::ESTABLISHED : OriginatorBlockAckAgreementState::PENDING; } virtual void setIsAddbaRequestSent(bool isAddbaRequestSent) { this->isAddbaRequestSent = isAddbaRequestSent; } virtual void setIsAMsduSupported(bool isAMsduSupported) { this->isAMsduSupported = isAMsduSupported; } virtual void setIsDelayedBlockAckPolicySupported(bool isDelayedBlockAckPolicySupported) { this->isDelayedBlockAckPolicySupported = isDelayedBlockAckPolicySupported; } virtual void setBlockAckTimeoutValue(const simtime_t blockAckTimeoutValue) { this->blockAckTimeoutValue = blockAckTimeoutValue; } + virtual void setAddbaResponseDeadline(simtime_t addbaResponseDeadline) { this->addbaResponseDeadline = addbaResponseDeadline; } virtual void baPolicyFrameSent() { numSentBaPolicyFrames++; } virtual void calculateExpirationTime() { expirationTime = blockAckTimeoutValue == 0 ? SIMTIME_MAX : simTime() + blockAckTimeoutValue; } @@ -71,4 +84,3 @@ class INET_API OriginatorBlockAckAgreement : public cObject } /* namespace inet */ #endif - diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc index 2f11dc365db..889387ff61b 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc @@ -14,11 +14,19 @@ namespace ieee80211 { void OriginatorBlockAckAgreementHandler::createAgreement(const Ptr& addbaRequest) { - OriginatorBlockAckAgreement *blockAckAgreement = new OriginatorBlockAckAgreement(addbaRequest->getReceiverAddress(), addbaRequest->getTid(), addbaRequest->getStartingSequenceNumber(), addbaRequest->getBufferSize(), addbaRequest->getAMsduSupported(), addbaRequest->getBlockAckPolicy() == 0); + ASSERT(addbaRequest->getDialogToken() != 0); + OriginatorBlockAckAgreement *blockAckAgreement = new OriginatorBlockAckAgreement(addbaRequest->getReceiverAddress(), addbaRequest->getTid(), addbaRequest->getStartingSequenceNumber(), addbaRequest->getBufferSize(), addbaRequest->getAMsduSupported(), addbaRequest->getBlockAckPolicy() == 0, addbaRequest->getDialogToken()); auto agreementId = std::make_pair(addbaRequest->getReceiverAddress(), addbaRequest->getTid()); blockAckAgreements[agreementId] = blockAckAgreement; } +uint8_t OriginatorBlockAckAgreementHandler::allocateDialogToken() +{ + auto dialogToken = nextDialogToken; + nextDialogToken = nextDialogToken == 255 ? 1 : nextDialogToken + 1; + return dialogToken; +} + simtime_t OriginatorBlockAckAgreementHandler::computeEarliestExpirationTime() { simtime_t earliestTime = SIMTIME_MAX; @@ -33,6 +41,35 @@ simtime_t OriginatorBlockAckAgreementHandler::computeEarliestExpirationTime() return earliestTime; } +simtime_t OriginatorBlockAckAgreementHandler::computeEarliestAddbaResponseDeadline() const +{ + simtime_t earliestDeadline = SIMTIME_MAX; + for (const auto& entry : blockAckAgreements) { + auto agreement = entry.second; + if (agreement->isPending() && agreement->getIsAddbaRequestSent()) { + ASSERT(agreement->getAddbaResponseDeadline() >= 0); + earliestDeadline = std::min(earliestDeadline, agreement->getAddbaResponseDeadline()); + } + } + return earliestDeadline; +} + +void OriginatorBlockAckAgreementHandler::addbaResponseTimeoutExpired(IBlockAckAgreementHandlerCallback *callback) +{ + auto now = simTime(); + for (auto it = blockAckAgreements.begin(); it != blockAckAgreements.end();) { + auto agreement = it->second; + if (agreement->isPending() && agreement->getIsAddbaRequestSent() && agreement->getAddbaResponseDeadline() <= now) { + EV_INFO << "ADDBA Response timeout for receiver=" << agreement->getReceiverAddr() << " tid=" << (int)agreement->getTid() << endl; + it = blockAckAgreements.erase(it); + delete agreement; + } + else + it++; + } + scheduleAddbaResponseTimer(callback); +} + void OriginatorBlockAckAgreementHandler::blockAckAgreementExpired(IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) { // When a timeout of BlockAckTimeout is detected, the STA shall send a DELBA frame to the @@ -57,6 +94,9 @@ const Ptr OriginatorBlockAckAgreementHandler::buildAddbaR { auto addbaRequest = makeShared(); addbaRequest->setReceiverAddress(receiverAddr); + // IEEE Std 802.11-2024, 9.6.4.2: a solicited ADDBA Request uses a nonzero + // Dialog Token, and the corresponding response copies that token. + addbaRequest->setDialogToken(allocateDialogToken()); addbaRequest->setTid(tid); addbaRequest->setAMsduSupported(blockAckAgreementPolicy->isMsduSupported()); addbaRequest->setBlockAckTimeoutValue(blockAckAgreementPolicy->getBlockAckTimeoutValue()); @@ -92,6 +132,11 @@ void OriginatorBlockAckAgreementHandler::scheduleInactivityTimer(IBlockAckAgreem callback->scheduleInactivityTimer(earliestExpirationTime); } +void OriginatorBlockAckAgreementHandler::scheduleAddbaResponseTimer(IBlockAckAgreementHandlerCallback *callback) +{ + callback->scheduleAddbaResponseTimer(computeEarliestAddbaResponseDeadline()); +} + OriginatorBlockAckAgreement *OriginatorBlockAckAgreementHandler::getAgreement(MacAddress receiverAddr, Tid tid) { auto agreementId = std::make_pair(receiverAddr, tid); @@ -99,6 +144,12 @@ OriginatorBlockAckAgreement *OriginatorBlockAckAgreementHandler::getAgreement(Ma return it != blockAckAgreements.end() ? it->second : nullptr; } +bool OriginatorBlockAckAgreementHandler::isAddbaResponsePending(MacAddress receiverAddr, Tid tid) const +{ + auto it = blockAckAgreements.find(std::make_pair(receiverAddr, tid)); + return it != blockAckAgreements.end() && it->second->isPending(); +} + const Ptr OriginatorBlockAckAgreementHandler::buildDelba(MacAddress receiverAddr, Tid tid, int reasonCode) { auto delba = makeShared(); @@ -132,15 +183,23 @@ void OriginatorBlockAckAgreementHandler::processTransmittedDataFrame(Packet *pac } } -void OriginatorBlockAckAgreementHandler::processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) +OriginatorBlockAckAgreement *OriginatorBlockAckAgreementHandler::processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) { auto agreement = getAgreement(addbaResp->getTransmitterAddress(), addbaResp->getTid()); - if (blockAckAgreementPolicy->isAddbaReqAccepted(addbaResp, agreement)) { + // IEEE Std 802.11-2024, 11.5.2.2: only a successful response matching the + // outstanding peer, TID, and Dialog Token establishes the agreement. + if (agreement == nullptr || !agreement->isPending() || !agreement->getIsAddbaRequestSent() || agreement->getDialogToken() != addbaResp->getDialogToken()) + return nullptr; + if (addbaResp->getStatusCode() == 0 && blockAckAgreementPolicy->isAddbaReqAccepted(addbaResp, agreement)) { updateAgreement(agreement, addbaResp); scheduleInactivityTimer(callback); + scheduleAddbaResponseTimer(callback); + return agreement; } else { - // TODO send a new one? + terminateAgreement(addbaResp->getTransmitterAddress(), addbaResp->getTid()); + scheduleAddbaResponseTimer(callback); + return nullptr; } } @@ -152,13 +211,21 @@ void OriginatorBlockAckAgreementHandler::updateAgreement(OriginatorBlockAckAgree agreement->calculateExpirationTime(); } -void OriginatorBlockAckAgreementHandler::processTransmittedAddbaReq(const Ptr& addbaReq) +void OriginatorBlockAckAgreementHandler::processTransmittedAddbaReq(const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) { auto agreement = getAgreement(addbaReq->getReceiverAddress(), addbaReq->getTid()); - if (agreement) + if (agreement && agreement->isPending() && agreement->getDialogToken() == addbaReq->getDialogToken()) { + if (agreement->getAddbaResponseDeadline() < 0) { + auto addbaFailureTimeout = blockAckAgreementPolicy->computeAddbaFailureTimeout(); + if (addbaFailureTimeout <= 0) + throw cRuntimeError("ADDBA failure timeout must be greater than zero"); + agreement->setAddbaResponseDeadline(simTime() + addbaFailureTimeout); + } agreement->setIsAddbaRequestSent(true); + scheduleAddbaResponseTimer(callback); + } else - throw cRuntimeError("Block Ack Agreement should have already been added"); + EV_WARN << "Ignoring stale transmitted ADDBA Request for receiver=" << addbaReq->getReceiverAddress() << " tid=" << (int)addbaReq->getTid() << " dialogToken=" << (int)addbaReq->getDialogToken() << endl; } void OriginatorBlockAckAgreementHandler::processTransmittedDelba(const Ptr& delba) @@ -180,4 +247,3 @@ OriginatorBlockAckAgreementHandler::~OriginatorBlockAckAgreementHandler() } // namespace ieee80211 } // namespace inet - diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h index 4a449d17da3..b7ec1e9c43c 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h @@ -21,31 +21,36 @@ class INET_API OriginatorBlockAckAgreementHandler : public IOriginatorBlockAckAg { protected: std::map, OriginatorBlockAckAgreement *> blockAckAgreements; + uint8_t nextDialogToken = 1; protected: virtual const Ptr buildAddbaRequest(MacAddress receiverAddr, Tid tid, SequenceNumberCyclic startingSequenceNumber, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy); + virtual uint8_t allocateDialogToken(); virtual void createAgreement(const Ptr& addbaRequest); virtual void updateAgreement(OriginatorBlockAckAgreement *agreement, const Ptr& addbaResp); virtual void terminateAgreement(MacAddress originatorAddr, Tid tid); virtual const Ptr buildDelba(MacAddress receiverAddr, Tid tid, int reasonCode); virtual simtime_t computeEarliestExpirationTime(); + virtual simtime_t computeEarliestAddbaResponseDeadline() const; virtual void scheduleInactivityTimer(IBlockAckAgreementHandlerCallback *callback); + virtual void scheduleAddbaResponseTimer(IBlockAckAgreementHandlerCallback *callback); public: virtual ~OriginatorBlockAckAgreementHandler(); - virtual void processTransmittedAddbaReq(const Ptr& addbaReq) override; + virtual void processTransmittedAddbaReq(const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; virtual void processTransmittedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *callback) override; virtual void processReceivedBlockAck(const Ptr& blockAck, IBlockAckAgreementHandlerCallback *callback) override; - virtual void processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; + virtual OriginatorBlockAckAgreement *processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; virtual void processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy) override; virtual void processTransmittedDelba(const Ptr& delba) override; virtual void blockAckAgreementExpired(IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) override; + virtual void addbaResponseTimeoutExpired(IBlockAckAgreementHandlerCallback *callback) override; virtual OriginatorBlockAckAgreement *getAgreement(MacAddress receiverAddr, Tid tid) override; + virtual bool isAddbaResponsePending(MacAddress receiverAddr, Tid tid) const override; }; } // namespace ieee80211 } // namespace inet #endif - diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc index 44b4c0874fa..50a98dd646f 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc @@ -24,15 +24,16 @@ void OriginatorBlockAckAgreementPolicy::initialize(int stage) aMsduSupported = par("aMsduSupported"); maximumAllowedBufferSize = par("maximumAllowedBufferSize"); blockAckTimeoutValue = par("blockAckTimeoutValue"); - // TODO addbaFailureTimeout = par("addbaFailureTimeout"); + addbaFailureTimeout = par("addbaFailureTimeout"); + if (addbaFailureTimeout <= 0) + throw cRuntimeError("addbaFailureTimeout must be greater than zero"); WATCH(blockAckReqThreshold); } } simtime_t OriginatorBlockAckAgreementPolicy::computeAddbaFailureTimeout() const { - // TODO ADDBAFailureTimeout -- 6.3.29.2.2 Semantics of the service primitive - throw cRuntimeError("Unimplemented"); + return addbaFailureTimeout; } bool OriginatorBlockAckAgreementPolicy::isAddbaReqNeeded(Packet *packet, const Ptr& header) @@ -42,8 +43,7 @@ bool OriginatorBlockAckAgreementPolicy::isAddbaReqNeeded(Packet *packet, const P bool OriginatorBlockAckAgreementPolicy::isAddbaReqAccepted(const Ptr& addbaResp, OriginatorBlockAckAgreement *agreement) { - ASSERT(agreement); - return true; + return agreement != nullptr && addbaResp->getStatusCode() == 0; } bool OriginatorBlockAckAgreementPolicy::isDelbaAccepted(const Ptr& delba) @@ -53,4 +53,3 @@ bool OriginatorBlockAckAgreementPolicy::isDelbaAccepted(const Ptr + #include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h" namespace inet { @@ -65,26 +67,56 @@ void RecipientBlockAckAgreementHandler::blockAckAgreementExpired(IProcedureCallb } // -// An originator that intends to use the Block Ack mechanism for the transmission of QoS data frames to an -// intended recipient should first check whether the intended recipient STA is capable of participating in Block -// Ack mechanism by discovering and examining its Delayed Block Ack and Immediate Block Ack capability -// bits. If the intended recipient STA is capable of participating, the originator sends an ADDBA Request frame -// indicating the TID for which the Block Ack is being set up. +// Keep the accepted parameters staged until the exact corresponding successful +// response packet is actually transmitted. Packet identity survives mutable +// header copy-on-write during sequence-number assignment and keeps overlapping +// transactions independent even when the originator reuses a Dialog Token. // -RecipientBlockAckAgreement *RecipientBlockAckAgreementHandler::addAgreement(const Ptr& addbaReq) +void RecipientBlockAckAgreementHandler::clearPendingAgreements(MacAddress originatorAddr, Tid tid) { - MacAddress originatorAddr = addbaReq->getTransmitterAddress(); - auto id = std::make_pair(originatorAddr, addbaReq->getTid()); - auto it = blockAckAgreements.find(id); - if (it == blockAckAgreements.end()) { - RecipientBlockAckAgreement *agreement = new RecipientBlockAckAgreement(originatorAddr, addbaReq->getTid(), addbaReq->getStartingSequenceNumber(), addbaReq->getBufferSize(), addbaReq->getBlockAckTimeoutValue()); - blockAckAgreements[id] = agreement; - EV_DETAIL << "Block Ack Agreement is added with the following parameters: " << *agreement << endl; - return agreement; + for (auto it = pendingBlockAckAgreements.begin(); it != pendingBlockAckAgreements.end();) { + if (it->originatorAddress == originatorAddr && it->tid == tid) { + delete it->agreement; + it = pendingBlockAckAgreements.erase(it); + } + else + it++; + } +} + +void RecipientBlockAckAgreementHandler::stageAgreement(Packet *addbaResponsePacket, const Ptr& addbaRequest, const Ptr& addbaResponse) +{ + auto originatorAddr = addbaRequest->getTransmitterAddress(); + Tid tid = addbaRequest->getTid(); + auto agreement = new RecipientBlockAckAgreement(originatorAddr, tid, addbaRequest->getStartingSequenceNumber(), addbaResponse->getBufferSize(), addbaResponse->getBlockAckTimeoutValue()); + pendingBlockAckAgreements.push_back({ addbaResponsePacket->getId(), originatorAddr, tid, addbaRequest->getDialogToken(), agreement }); + EV_DETAIL << "Block Ack Agreement is staged with the following parameters: " << *agreement << endl; +} + +RecipientBlockAckAgreement *RecipientBlockAckAgreementHandler::activateAgreement(Packet *addbaResponsePacket, const Ptr& addbaResponse) +{ + auto pendingIt = std::find_if(pendingBlockAckAgreements.begin(), pendingBlockAckAgreements.end(), [addbaResponsePacket](const PendingAgreement& pendingAgreement) { + return pendingAgreement.addbaResponsePacketId == addbaResponsePacket->getId(); + }); + if (pendingIt == pendingBlockAckAgreements.end()) + return nullptr; + if (addbaResponse->getReceiverAddress() != pendingIt->originatorAddress || addbaResponse->getTid() != pendingIt->tid || addbaResponse->getDialogToken() != pendingIt->dialogToken || addbaResponse->getStatusCode() != 0) { + EV_WARN << "Ignoring transmitted ADDBA Response whose fields do not match its staged transaction" << endl; + return nullptr; + } + auto agreement = pendingIt->agreement; + pendingBlockAckAgreements.erase(pendingIt); + auto id = std::make_pair(addbaResponse->getReceiverAddress(), addbaResponse->getTid()); + auto activeIt = blockAckAgreements.find(id); + if (activeIt != blockAckAgreements.end()) { + delete activeIt->second; + activeIt->second = agreement; } else - // TODO update? - return it->second; + blockAckAgreements[id] = agreement; + agreement->addbaResposneSent(); + agreement->calculateExpirationTime(); + return agreement; } // @@ -102,10 +134,14 @@ const Ptr RecipientBlockAckAgreementHandler::buildDelba(MacAddre return delba; } -const Ptr RecipientBlockAckAgreementHandler::buildAddbaResponse(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy) +const Ptr RecipientBlockAckAgreementHandler::buildAddbaResponse(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, bool accepted) { auto addbaResponse = makeShared(); addbaResponse->setReceiverAddress(addbaRequest->getTransmitterAddress()); + // IEEE Std 802.11-2024, 9.6.4.2 and 11.5.2.3: the response copies the + // request's Dialog Token and reports whether the agreement was accepted. + addbaResponse->setDialogToken(addbaRequest->getDialogToken()); + addbaResponse->setStatusCode(accepted ? 0 : 1); // 1: REFUSED_REASON_UNSPECIFIED // The Block Ack Policy subfield is set to 1 for immediate Block Ack and 0 for delayed Block Ack. Tid tid = addbaRequest->getTid(); addbaResponse->setTid(tid); @@ -116,21 +152,10 @@ const Ptr RecipientBlockAckAgreementHandler::buildAddbaR return addbaResponse; } -void RecipientBlockAckAgreementHandler::updateAgreement(const Ptr& addbaResponse) -{ - auto id = std::make_pair(addbaResponse->getReceiverAddress(), addbaResponse->getTid()); - auto it = blockAckAgreements.find(id); - if (it != blockAckAgreements.end()) { - RecipientBlockAckAgreement *agreement = it->second; - agreement->addbaResposneSent(); - } - else - throw cRuntimeError("Agreement is not found"); -} - void RecipientBlockAckAgreementHandler::terminateAgreement(MacAddress originatorAddr, Tid tid) { auto agreementId = std::make_pair(originatorAddr, tid); + clearPendingAgreements(originatorAddr, tid); auto it = blockAckAgreements.find(agreementId); if (it != blockAckAgreements.end()) { RecipientBlockAckAgreement *agreement = it->second; @@ -146,24 +171,26 @@ RecipientBlockAckAgreement *RecipientBlockAckAgreementHandler::getAgreement(Tid return it != blockAckAgreements.end() ? it->second : nullptr; } -void RecipientBlockAckAgreementHandler::processTransmittedAddbaResp(const Ptr& addbaResp, IBlockAckAgreementHandlerCallback *callback) +RecipientBlockAckAgreement *RecipientBlockAckAgreementHandler::processTransmittedAddbaResp(Packet *addbaRespPacket, const Ptr& addbaResp, IBlockAckAgreementHandlerCallback *callback) { - updateAgreement(addbaResp); - scheduleInactivityTimer(callback); + // IEEE Std 802.11-2024, 11.5.2.3: the recipient agreement becomes active + // only when the matching successful ADDBA Response is transmitted. + auto agreement = activateAgreement(addbaRespPacket, addbaResp); + if (agreement != nullptr) + scheduleInactivityTimer(callback); + return agreement; } void RecipientBlockAckAgreementHandler::processReceivedAddbaRequest(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *callback) { EV_INFO << "Processing Addba Request from " << addbaRequest->getTransmitterAddress() << endl; - if (blockAckAgreementPolicy->isAddbaReqAccepted(addbaRequest)) { - EV_DETAIL << "Addba Request has been accepted. Creating a new Block Ack Agreement." << endl; - auto agreement = addAgreement(addbaRequest); - EV_DETAIL << "Agreement is added with the following parameters: " << *agreement << endl; - EV_DETAIL << "Building Addba Response" << endl; - auto addbaResponse = buildAddbaResponse(addbaRequest, blockAckAgreementPolicy); - auto addbaResponsePacket = new Packet("AddbaResponse", addbaResponse); - callback->processMgmtFrame(addbaResponsePacket, addbaResponse); - } + bool accepted = addbaRequest->getDialogToken() != 0 && blockAckAgreementPolicy->isAddbaReqAccepted(addbaRequest); + EV_DETAIL << "Building Addba Response" << endl; + auto addbaResponse = buildAddbaResponse(addbaRequest, blockAckAgreementPolicy, accepted); + auto addbaResponsePacket = new Packet("AddbaResponse", addbaResponse); + if (accepted) + stageAgreement(addbaResponsePacket, addbaRequest, addbaResponse); + callback->processMgmtFrame(addbaResponsePacket, addbaResponse); } void RecipientBlockAckAgreementHandler::processTransmittedDelba(const Ptr& delba) @@ -181,8 +208,9 @@ RecipientBlockAckAgreementHandler::~RecipientBlockAckAgreementHandler() { for (auto it : blockAckAgreements) delete it.second; + for (auto it : pendingBlockAckAgreements) + delete it.agreement; } } // namespace ieee80211 } // namespace inet - diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h index 1e65a1557ef..56a66bb1dc0 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h @@ -8,6 +8,8 @@ #ifndef __INET_RECIPIENTBLOCKACKAGREEMENTHANDLER_H #define __INET_RECIPIENTBLOCKACKAGREEMENTHANDLER_H +#include + #include "inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h" #include "inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h" @@ -25,20 +27,30 @@ class RecipientBlockAckAgreement; class INET_API RecipientBlockAckAgreementHandler : public IRecipientBlockAckAgreementHandler { protected: + struct PendingAgreement { + int64_t addbaResponsePacketId; + MacAddress originatorAddress; + Tid tid; + uint8_t dialogToken; + RecipientBlockAckAgreement *agreement; + }; + std::map, RecipientBlockAckAgreement *> blockAckAgreements; + std::vector pendingBlockAckAgreements; protected: virtual void terminateAgreement(MacAddress originatorAddr, Tid tid); - virtual RecipientBlockAckAgreement *addAgreement(const Ptr& addbaReq); - virtual void updateAgreement(const Ptr& addbaResponse); - virtual const Ptr buildAddbaResponse(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy); + virtual void clearPendingAgreements(MacAddress originatorAddr, Tid tid); + virtual void stageAgreement(Packet *addbaResponsePacket, const Ptr& addbaRequest, const Ptr& addbaResponse); + virtual RecipientBlockAckAgreement *activateAgreement(Packet *addbaResponsePacket, const Ptr& addbaResponse); + virtual const Ptr buildAddbaResponse(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, bool accepted); virtual const Ptr buildDelba(MacAddress receiverAddr, Tid tid, int reasonCode); virtual simtime_t computeEarliestExpirationTime(); virtual void scheduleInactivityTimer(IBlockAckAgreementHandlerCallback *callback); public: virtual ~RecipientBlockAckAgreementHandler(); - virtual void processTransmittedAddbaResp(const Ptr& addbaResp, IBlockAckAgreementHandlerCallback *callback) override; + virtual RecipientBlockAckAgreement *processTransmittedAddbaResp(Packet *addbaRespPacket, const Ptr& addbaResp, IBlockAckAgreementHandlerCallback *callback) override; virtual void processReceivedAddbaRequest(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *callback) override; virtual void processReceivedDelba(const Ptr& delba, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy) override; virtual void qosFrameReceived(const Ptr& qosHeader, IBlockAckAgreementHandlerCallback *callback) override; @@ -52,4 +64,3 @@ class INET_API RecipientBlockAckAgreementHandler : public IRecipientBlockAckAgre } // namespace inet #endif - diff --git a/src/inet/linklayer/ieee80211/mac/contract/IBlockAckAgreementHandlerCallback.h b/src/inet/linklayer/ieee80211/mac/contract/IBlockAckAgreementHandlerCallback.h index cc4a7b86b92..06ea78814c9 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IBlockAckAgreementHandlerCallback.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IBlockAckAgreementHandlerCallback.h @@ -19,10 +19,10 @@ class INET_API IBlockAckAgreementHandlerCallback virtual ~IBlockAckAgreementHandlerCallback() {} virtual void scheduleInactivityTimer(simtime_t timeout) = 0; + virtual void scheduleAddbaResponseTimer(simtime_t deadline) = 0; }; } // namespace ieee80211 } // namespace inet #endif - diff --git a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h index 4a01e89b6c3..420a34e10c1 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h @@ -25,18 +25,19 @@ class INET_API IOriginatorBlockAckAgreementHandler virtual ~IOriginatorBlockAckAgreementHandler() {} virtual void processReceivedBlockAck(const Ptr& blockAck, IBlockAckAgreementHandlerCallback *callback) = 0; - virtual void processTransmittedAddbaReq(const Ptr& addbaReq) = 0; + virtual void processTransmittedAddbaReq(const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; virtual void processTransmittedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *callback) = 0; - virtual void processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; + virtual OriginatorBlockAckAgreement *processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; virtual void processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy) = 0; virtual void processTransmittedDelba(const Ptr& delba) = 0; virtual void blockAckAgreementExpired(IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) = 0; + virtual void addbaResponseTimeoutExpired(IBlockAckAgreementHandlerCallback *callback) = 0; virtual OriginatorBlockAckAgreement *getAgreement(MacAddress receiverAddr, Tid tid) = 0; + virtual bool isAddbaResponsePending(MacAddress receiverAddr, Tid tid) const = 0; }; } // namespace ieee80211 } // namespace inet #endif - diff --git a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorMacDataService.h b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorMacDataService.h index 4e66ec6c190..9f0f8aad18a 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorMacDataService.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorMacDataService.h @@ -8,6 +8,8 @@ #ifndef __INET_IORIGINATORMACDATASERVICE_H #define __INET_IORIGINATORMACDATASERVICE_H +#include + #include "inet/queueing/contract/IPacketQueue.h" namespace inet { @@ -15,6 +17,9 @@ namespace ieee80211 { class INET_API IOriginatorMacDataService { + public: + using FrameEligibilityFunction = std::function; + public: static simsignal_t packetFragmentedSignal; static simsignal_t packetAggregatedSignal; @@ -22,6 +27,9 @@ class INET_API IOriginatorMacDataService public: virtual ~IOriginatorMacDataService() {} + virtual void setFrameEligibilityFunction(const FrameEligibilityFunction& frameEligibilityFunction) = 0; + virtual bool isFrameEligible(const Packet *packet) const = 0; + virtual bool hasEligibleFrame(queueing::IPacketQueue *pendingQueue) const = 0; virtual std::vector *extractFramesToTransmit(queueing::IPacketQueue *pendingQueue) = 0; }; @@ -29,4 +37,3 @@ class INET_API IOriginatorMacDataService } // namespace inet #endif - diff --git a/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h index 357745c3ddb..d07f8d71e8c 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h @@ -24,7 +24,7 @@ class INET_API IRecipientBlockAckAgreementHandler public: virtual ~IRecipientBlockAckAgreementHandler() {} - virtual void processTransmittedAddbaResp(const Ptr& addbaResp, IBlockAckAgreementHandlerCallback *callback) = 0; + virtual RecipientBlockAckAgreement *processTransmittedAddbaResp(Packet *addbaRespPacket, const Ptr& addbaResp, IBlockAckAgreementHandlerCallback *callback) = 0; virtual void processReceivedAddbaRequest(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *callback) = 0; virtual void processReceivedDelba(const Ptr& delba, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy) = 0; virtual void processTransmittedDelba(const Ptr& delba) = 0; @@ -38,4 +38,3 @@ class INET_API IRecipientBlockAckAgreementHandler } // namespace inet #endif - diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc index b3d11467343..2646b3bcf8d 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc @@ -34,6 +34,7 @@ void Hcf::initialize(int stage) mac = check_and_cast(getContainingNicModule(this)->getSubmodule("mac")); startRxTimer = new cMessage("startRxTimeout"); inactivityTimer = new cMessage("blockAckInactivityTimer"); + addbaResponseTimer = new cMessage("addbaResponseTimer"); edca = check_and_cast(getSubmodule("edca")); hcca = check_and_cast(getSubmodule("hcca")); tx = check_and_cast(getModuleByPath(par("txModule"))); @@ -59,6 +60,10 @@ void Hcf::initialize(int stage) originatorBlockAckAgreementHandler = new OriginatorBlockAckAgreementHandler(); originatorBlockAckProcedure = new OriginatorBlockAckProcedure(); recipientBlockAckProcedure = new RecipientBlockAckProcedure(); + originatorDataService->setFrameEligibilityFunction([this](const Packet *packet) { + auto dataHeader = dynamicPtrCast(packet->peekAtFront()); + return dataHeader == nullptr || dataHeader->getType() != ST_DATA_WITH_QOS || !originatorBlockAckAgreementHandler->isAddbaResponsePending(dataHeader->getReceiverAddress(), dataHeader->getTid()); + }); } } } @@ -97,6 +102,14 @@ void Hcf::handleMessage(cMessage *msg) else throw cRuntimeError("Unknown event"); } + else if (msg == addbaResponseTimer) { + if (originatorBlockAckAgreementHandler) { + originatorBlockAckAgreementHandler->addbaResponseTimeoutExpired(this); + resumeEligibleChannelAccess(); + } + else + throw cRuntimeError("Unknown event"); + } else throw cRuntimeError("Unknown msg type"); } @@ -135,7 +148,7 @@ void Hcf::processUpperFrame(Packet *packet, const PtrgetEdcaf(ac)->getPendingQueue(); pendingQueue->enqueuePacket(packet); - if (!pendingQueue->isEmpty()) { + if (hasFrameToTransmit(ac)) { auto edcaf = edca->getChannelOwner(); if (edcaf == nullptr || edcaf->getAccessCategory() != ac) { EV_DETAIL << "Requesting channel for access category " << printAccessCategory(ac) << endl; @@ -156,6 +169,17 @@ void Hcf::scheduleInactivityTimer(simtime_t timeout) rescheduleAfter(timeout, inactivityTimer); } +void Hcf::scheduleAddbaResponseTimer(simtime_t deadline) +{ + Enter_Method("scheduleAddbaResponseTimer"); + if (deadline == SIMTIME_MAX) { + if (addbaResponseTimer->isScheduled()) + cancelEvent(addbaResponseTimer); + } + else + rescheduleAt(deadline, addbaResponseTimer); +} + void Hcf::processLowerFrame(Packet *packet, const Ptr& header) { Enter_Method("processLowerFrame(%s)", packet->getName()); @@ -200,6 +224,11 @@ void Hcf::channelGranted(IChannelAccess *channelAccess) if (edcaf) { AccessCategory ac = edcaf->getAccessCategory(); EV_DETAIL << "Channel access granted to the " << printAccessCategory(ac) << " queue" << std::endl; + if (!hasFrameToTransmit(ac)) { + EV_DETAIL << "Releasing channel because no eligible frame is available.\n"; + edcaf->releaseChannel(this); + return; + } edcaf->getTxopProcedure()->startTxop(ac); auto internallyCollidedEdcafs = edca->getInternallyCollidedEdcafs(); if (internallyCollidedEdcafs.size() > 0) { @@ -280,12 +309,12 @@ void Hcf::frameSequenceFinished() emit(IFrameSequenceHandler::frameSequenceFinishedSignal, frameSequenceHandler->getContext()); auto edcaf = edca->getChannelOwner(); if (edcaf) { - bool startContention = hasFrameToTransmit(); // TODO outstanding frame edcaf->releaseChannel(this); mac->sendDownPendingRadioConfigMsg(); // TODO review edcaf->getTxopProcedure()->endTxop(); - if (startContention) - edcaf->requestChannel(this); + // FrameSequenceHandler clears its running state after this callback, + // so request all eligible ACs here without the idle-state guard. + requestEligibleChannelAccess(); } else if (hcca->isOwning()) { hcca->releaseChannel(this); @@ -337,23 +366,24 @@ void Hcf::recipientProcessReceivedManagementFrame(const Ptr(header)) { recipientBlockAckAgreementHandler->processReceivedAddbaRequest(addbaRequest, recipientBlockAckAgreementPolicy, this); - auto agreement = recipientBlockAckAgreementHandler->getAgreement(addbaRequest->getTid(), addbaRequest->getTransmitterAddress()); - emit(blockAckAgreementAddedSignal, agreement); } else if (auto addbaResp = dynamicPtrCast(header)) { - originatorBlockAckAgreementHandler->processReceivedAddbaResp(addbaResp, originatorBlockAckAgreementPolicy, this); - auto agreement = originatorBlockAckAgreementHandler->getAgreement(addbaResp->getTransmitterAddress(), addbaResp->getTid()); - emit(blockAckAgreementAddedSignal, agreement); + auto establishedAgreement = originatorBlockAckAgreementHandler->processReceivedAddbaResp(addbaResp, originatorBlockAckAgreementPolicy, this); + if (establishedAgreement != nullptr) + emit(blockAckAgreementAddedSignal, establishedAgreement); + resumeEligibleChannelAccess(); } else if (auto delba = dynamicPtrCast(header)) { if (delba->getInitiator()) { auto agreement = recipientBlockAckAgreementHandler->getAgreement(delba->getTid(), delba->getReceiverAddress()); - emit(blockAckAgreementDeletedSignal, agreement); + if (agreement != nullptr) + emit(blockAckAgreementDeletedSignal, agreement); recipientBlockAckAgreementHandler->processReceivedDelba(delba, recipientBlockAckAgreementPolicy); } else { auto agreement = originatorBlockAckAgreementHandler->getAgreement(delba->getReceiverAddress(), delba->getTid()); - emit(blockAckAgreementDeletedSignal, agreement); + if (agreement != nullptr) + emit(blockAckAgreementDeletedSignal, agreement); originatorBlockAckAgreementHandler->processReceivedDelba(delba, originatorBlockAckAgreementPolicy); } } @@ -432,7 +462,7 @@ void Hcf::originatorProcessTransmittedFrame(Packet *packet) else if (auto dataHeader = dynamicPtrCast(transmittedHeader)) originatorProcessTransmittedDataFrame(packet, dataHeader, ac); else if (auto mgmtHeader = dynamicPtrCast(transmittedHeader)) - originatorProcessTransmittedManagementFrame(mgmtHeader, ac); + originatorProcessTransmittedManagementFrame(packet, mgmtHeader, ac); else // TODO Ieee80211ControlFrame originatorProcessTransmittedControlFrame(transmittedHeader, ac); } @@ -452,17 +482,21 @@ void Hcf::originatorProcessTransmittedDataFrame(Packet *packet, const PtrgetInProgressFrames()->dropFrame(packet); } -void Hcf::originatorProcessTransmittedManagementFrame(const Ptr& mgmtHeader, AccessCategory ac) +void Hcf::originatorProcessTransmittedManagementFrame(Packet *packet, const Ptr& mgmtHeader, AccessCategory ac) { auto edcaf = edca->getEdcaf(ac); if (originatorAckPolicy->isAckNeeded(mgmtHeader)) edcaf->getAckHandler()->processTransmittedDataOrMgmtFrame(mgmtHeader); if (auto addbaReq = dynamicPtrCast(mgmtHeader)) { if (originatorBlockAckAgreementHandler) - originatorBlockAckAgreementHandler->processTransmittedAddbaReq(addbaReq); + originatorBlockAckAgreementHandler->processTransmittedAddbaReq(addbaReq, originatorBlockAckAgreementPolicy, this); + } + else if (auto addbaResp = dynamicPtrCast(mgmtHeader)) { + bool hadAgreement = recipientBlockAckAgreementHandler->getAgreement(addbaResp->getTid(), addbaResp->getReceiverAddress()) != nullptr; + auto activatedAgreement = recipientBlockAckAgreementHandler->processTransmittedAddbaResp(packet, addbaResp, this); + if (!hadAgreement && activatedAgreement != nullptr) + emit(blockAckAgreementAddedSignal, activatedAgreement); } - else if (auto addbaResp = dynamicPtrCast(mgmtHeader)) - recipientBlockAckAgreementHandler->processTransmittedAddbaResp(addbaResp, this); else if (auto delba = dynamicPtrCast(mgmtHeader)) { if (delba->getInitiator()) originatorBlockAckAgreementHandler->processTransmittedDelba(delba); @@ -630,7 +664,7 @@ bool Hcf::hasFrameToTransmit(AccessCategory ac) { auto edcaf = edca->getEdcaf(ac); if (edcaf) - return !edcaf->getPendingQueue()->isEmpty() || edcaf->getInProgressFrames()->hasInProgressFrames(); + return originatorDataService->hasEligibleFrame(edcaf->getPendingQueue()) || edcaf->getInProgressFrames()->hasInProgressFrames(); else throw cRuntimeError("Hcca is unimplemented"); } @@ -639,11 +673,26 @@ bool Hcf::hasFrameToTransmit() { auto edcaf = edca->getChannelOwner(); if (edcaf) - return !edcaf->getPendingQueue()->isEmpty() || edcaf->getInProgressFrames()->hasInProgressFrames(); + return originatorDataService->hasEligibleFrame(edcaf->getPendingQueue()) || edcaf->getInProgressFrames()->hasInProgressFrames(); else throw cRuntimeError("Hcca is unimplemented"); } +void Hcf::requestEligibleChannelAccess() +{ + for (int ac = 0; ac < AC_NUMCATEGORIES; ac++) { + auto accessCategory = AccessCategory(ac); + if (hasFrameToTransmit(accessCategory)) + edca->requestChannelAccess(accessCategory, this); + } +} + +void Hcf::resumeEligibleChannelAccess() +{ + if (edca->getChannelOwner() == nullptr && !frameSequenceHandler->isSequenceRunning()) + requestEligibleChannelAccess(); +} + void Hcf::sendUp(const std::vector& completeFrames) { for (auto frame : completeFrames) @@ -772,6 +821,7 @@ Hcf::~Hcf() { cancelAndDelete(startRxTimer); cancelAndDelete(inactivityTimer); + cancelAndDelete(addbaResponseTimer); delete recipientAckProcedure; delete ctsProcedure; delete rtsProcedure; @@ -784,4 +834,3 @@ Hcf::~Hcf() } // namespace ieee80211 } // namespace inet - diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h index 2060a4e3d2c..cb0d4ee16d2 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h @@ -29,7 +29,6 @@ #include "inet/linklayer/ieee80211/mac/contract/ITx.h" #include "inet/linklayer/ieee80211/mac/framesequence/FrameSequenceContext.h" #include "inet/linklayer/ieee80211/mac/framesequence/FrameSequenceHandler.h" -#include "inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.h" #include "inet/linklayer/ieee80211/mac/originator/QosAckHandler.h" #include "inet/linklayer/ieee80211/mac/originator/QosRecoveryProcedure.h" #include "inet/linklayer/ieee80211/mac/originator/TxopProcedure.h" @@ -58,6 +57,7 @@ class INET_API Hcf : public ICoordinationFunction, public IFrameSequenceHandler: cMessage *startRxTimer = nullptr; cMessage *inactivityTimer = nullptr; + cMessage *addbaResponseTimer = nullptr; // Transmission and Reception IRx *rx = nullptr; @@ -116,6 +116,8 @@ class INET_API Hcf : public ICoordinationFunction, public IFrameSequenceHandler: FrameSequenceContext *buildContext(AccessCategory ac); virtual bool hasFrameToTransmit(); virtual bool hasFrameToTransmit(AccessCategory ac); + virtual void requestEligibleChannelAccess(); + virtual void resumeEligibleChannelAccess(); virtual bool isReceptionInProgress(); // Recipient @@ -125,7 +127,7 @@ class INET_API Hcf : public ICoordinationFunction, public IFrameSequenceHandler: virtual void recipientProcessTransmittedControlResponseFrame(Packet *packet, const Ptr& header); // Originator - virtual void originatorProcessTransmittedManagementFrame(const Ptr& mgmtHeader, AccessCategory ac); + virtual void originatorProcessTransmittedManagementFrame(Packet *packet, const Ptr& mgmtHeader, AccessCategory ac); virtual void originatorProcessTransmittedControlFrame(const Ptr& controlHeader, AccessCategory ac); virtual void originatorProcessTransmittedDataFrame(Packet *packet, const Ptr& dataHeader, AccessCategory ac); virtual void originatorProcessReceivedManagementFrame(const Ptr& header, const Ptr& lastTransmittedHeader, AccessCategory ac); @@ -158,6 +160,7 @@ class INET_API Hcf : public ICoordinationFunction, public IFrameSequenceHandler: // IProcedureCallback virtual void scheduleInactivityTimer(simtime_t timeout) override; + virtual void scheduleAddbaResponseTimer(simtime_t deadline) override; std::string getFrameSequenceInfo() const; @@ -174,4 +177,3 @@ class INET_API Hcf : public ICoordinationFunction, public IFrameSequenceHandler: } /* namespace inet */ #endif - diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.cc b/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.cc index 12d6c5cd21e..dbefc758f03 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.cc @@ -38,15 +38,37 @@ std::vector *OriginatorMacDataService::fragmentIfNeeded(Packet *frame) return nullptr; } +bool OriginatorMacDataService::isFrameEligible(const Packet *packet) const +{ + return !frameEligibilityFunction || frameEligibilityFunction(packet); +} + +bool OriginatorMacDataService::hasEligibleFrame(queueing::IPacketQueue *pendingQueue) const +{ + for (int i = 0; i < pendingQueue->getNumPackets(); i++) + if (isFrameEligible(pendingQueue->getPacket(i))) + return true; + return false; +} + std::vector *OriginatorMacDataService::extractFramesToTransmit(queueing::IPacketQueue *pendingQueue) { Enter_Method("extractFramesToTransmit"); - if (pendingQueue->isEmpty()) + if (!hasEligibleFrame(pendingQueue)) return nullptr; else { // if (msduRateLimiting) // txRateLimitingIfNeeded(); - Packet *packet = pendingQueue->dequeuePacket(); + Packet *packet = nullptr; + for (int i = 0; i < pendingQueue->getNumPackets(); i++) { + auto candidate = pendingQueue->getPacket(i); + if (isFrameEligible(candidate)) { + pendingQueue->removePacket(candidate); + packet = candidate; + break; + } + } + ASSERT(packet != nullptr); take(packet); if (sequenceNumberAssignment) { auto frame = packet->removeAtFront(); @@ -76,4 +98,3 @@ OriginatorMacDataService::~OriginatorMacDataService() } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.h b/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.h index 2a75735b598..7bf62fa66ad 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.h +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.h @@ -30,6 +30,7 @@ class INET_API OriginatorMacDataService : public IOriginatorMacDataService, publ // MsduIntegrityAndProtection *msduIntegrityAndProtection = nullptr; IFragmentationPolicy *fragmentationPolicy = nullptr; IFragmentation *fragmentation = nullptr; + FrameEligibilityFunction frameEligibilityFunction; // MpduEncryptionAndIntegrity *mpduEncryptionAndIntegrity = nullptr; // MpduHeaderPlusFcs *mpduHeaderPlusFcs = nullptr; @@ -42,6 +43,9 @@ class INET_API OriginatorMacDataService : public IOriginatorMacDataService, publ public: virtual ~OriginatorMacDataService(); + virtual void setFrameEligibilityFunction(const FrameEligibilityFunction& frameEligibilityFunction) override { this->frameEligibilityFunction = frameEligibilityFunction; } + virtual bool isFrameEligible(const Packet *packet) const override; + virtual bool hasEligibleFrame(queueing::IPacketQueue *pendingQueue) const override; virtual std::vector *extractFramesToTransmit(queueing::IPacketQueue *pendingQueue) override; }; @@ -49,4 +53,3 @@ class INET_API OriginatorMacDataService : public IOriginatorMacDataService, publ } /* namespace inet */ #endif - diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc index f374452e933..933bcc859ad 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc @@ -78,21 +78,45 @@ std::vector *OriginatorQosMacDataService::fragmentIfNeeded(Packet *fra return nullptr; } +bool OriginatorQosMacDataService::isFrameEligible(const Packet *packet) const +{ + return !frameEligibilityFunction || frameEligibilityFunction(packet); +} + +bool OriginatorQosMacDataService::hasEligibleFrame(queueing::IPacketQueue *pendingQueue) const +{ + for (int i = 0; i < pendingQueue->getNumPackets(); i++) + if (isFrameEligible(pendingQueue->getPacket(i))) + return true; + return false; +} + std::vector *OriginatorQosMacDataService::extractFramesToTransmit(queueing::IPacketQueue *pendingQueue) { Enter_Method("extractFramesToTransmit"); - if (pendingQueue->isEmpty()) + if (!hasEligibleFrame(pendingQueue)) return nullptr; else { // if (msduRateLimiting) // txRateLimitingIfNeeded(); Packet *packet = nullptr; - if (aMsduAggregationPolicy) - packet = aMsduAggregateIfNeeded(pendingQueue); - if (!packet) { - packet = pendingQueue->dequeuePacket(); - take(packet); + for (int i = 0; i < pendingQueue->getNumPackets(); i++) { + auto candidate = pendingQueue->getPacket(i); + if (!isFrameEligible(candidate)) + continue; + // The current A-MSDU policy only aggregates the queue head with + // frames for the same receiver and TID, so the head eligibility + // decision also applies to every selected subframe. + if (aMsduAggregationPolicy && i == 0) + packet = aMsduAggregateIfNeeded(pendingQueue); + if (!packet) { + pendingQueue->removePacket(candidate); + packet = candidate; + take(packet); + } + break; } + ASSERT(packet != nullptr); // PS Defer Queueing if (sequenceNumberAssignment) { auto header = packet->removeAtFront(); @@ -129,4 +153,3 @@ OriginatorQosMacDataService::~OriginatorQosMacDataService() } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.h b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.h index 0d162c213be..eb3258fe545 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.h +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.h @@ -38,6 +38,7 @@ class INET_API OriginatorQosMacDataService : public IOriginatorMacDataService, p // PsDeferQueueing *psDeferQueueing = nullptr; IMpduAggregationPolicy *aMpduAggregationPolicy = nullptr; IMpduAggregation *aMpduAggregation = nullptr; + FrameEligibilityFunction frameEligibilityFunction; protected: virtual void initialize() override; @@ -50,6 +51,9 @@ class INET_API OriginatorQosMacDataService : public IOriginatorMacDataService, p public: virtual ~OriginatorQosMacDataService(); + virtual void setFrameEligibilityFunction(const FrameEligibilityFunction& frameEligibilityFunction) override { this->frameEligibilityFunction = frameEligibilityFunction; } + virtual bool isFrameEligible(const Packet *packet) const override; + virtual bool hasEligibleFrame(queueing::IPacketQueue *pendingQueue) const override; virtual std::vector *extractFramesToTransmit(queueing::IPacketQueue *pendingQueue) override; }; @@ -57,4 +61,3 @@ class INET_API OriginatorQosMacDataService : public IOriginatorMacDataService, p } /* namespace inet */ #endif - diff --git a/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.cc b/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.cc index c1bb688f218..6a1b37b9af7 100644 --- a/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.cc +++ b/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.cc @@ -49,7 +49,7 @@ void InProgressFrames::forEachChild(cVisitor *v) bool InProgressFrames::hasEligibleFrameToTransmit() { for (auto frame : inProgressFrames) { - if (ackHandler->isEligibleToTransmit(frame->peekAtFront())) + if (dataService->isFrameEligible(frame) && ackHandler->isEligibleToTransmit(frame->peekAtFront())) return true; } return false; @@ -82,7 +82,7 @@ Packet *InProgressFrames::getFrameToTransmit() { ensureHasFrameToTransmit(); for (auto frame : inProgressFrames) { - if (ackHandler->isEligibleToTransmit(frame->peekAtFront())) + if (dataService->isFrameEligible(frame) && ackHandler->isEligibleToTransmit(frame->peekAtFront())) return frame; } return nullptr; @@ -95,7 +95,7 @@ Packet *InProgressFrames::getPendingFrameFor(Packet *frame) return frameToTransmit; else { for (auto frame : inProgressFrames) { - if (ackHandler->isEligibleToTransmit(frame->peekAtFront()) && frameToTransmit != frame) + if (dataService->isFrameEligible(frame) && ackHandler->isEligibleToTransmit(frame->peekAtFront()) && frameToTransmit != frame) return frame; } auto frames = dataService->extractFramesToTransmit(pendingQueue); @@ -175,4 +175,3 @@ InProgressFrames::~InProgressFrames() } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/tests/fingerprint/examples.csv b/tests/fingerprint/examples.csv index 283915ab865..694329231ea 100644 --- a/tests/fingerprint/examples.csv +++ b/tests/fingerprint/examples.csv @@ -659,6 +659,7 @@ /examples/wireless/qos/, -f omnetpp.ini -c MacQosWithoutAggregation -r 0, 10s, 4119-7162/tplx;3dfe-cedf/~tNl;b9bb-5046/~tND;a4c3-19bf/tyf, PASS, wireless Ipv4 /examples/wireless/qos/, -f omnetpp.ini -c MacQosWithRtsCts -r 0, 10s, b762-75c3/tplx;6644-8a9a/~tNl;d52a-b71c/~tND;b2d6-1329/tyf, PASS, wireless Ipv4 /examples/wireless/qos/, -f omnetpp.ini -c MacQosWithBlockAck -r 0, 10s, 8306-3cd3/tplx;26d1-9165/~tNl;e0fc-7553/~tND;08b5-d005/tyf, PASS, wireless Ipv4 +/examples/wireless/qos/, -f omnetpp.ini -c MacQosWithTransactionalBlockAck -r 0, 3s, c4ff-d71f/tplx;68ba-2827/~tNl;9a98-cc02/~tND, PASS, wireless Ipv4 /examples/wireless/ratecontrol/, -f omnetpp.ini -c Mac -r 0, 100s, bf30-2f13/tplx;7b2f-653d/~tNl;6e1e-3b7b/~tND;19fe-8b0e/tyf, PASS, wireless diff --git a/tests/unit/Ieee80211AddbaTransaction_1.test b/tests/unit/Ieee80211AddbaTransaction_1.test new file mode 100644 index 00000000000..f6512680414 --- /dev/null +++ b/tests/unit/Ieee80211AddbaTransaction_1.test @@ -0,0 +1,468 @@ +%description: +Test IEEE 802.11 ADDBA transaction identity, response handling, timeout cleanup, SSN wrap-around, and frame eligibility. + +%includes: +#include + +#include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h" +#include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h" +#include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h" +#include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h" +#include "inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementPolicy.h" +#include "inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementPolicy.h" +#include "inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.h" +#include "inet/linklayer/ieee80211/mac/queue/InProgressFrames.h" +#include "inet/linklayer/ieee80211/mac/sequencenumberassignment/QoSSequenceNumberAssignment.h" + +%global: +using namespace inet; +using namespace inet::ieee80211; + +class TestOriginatorPolicy : public IOriginatorBlockAckAgreementPolicy +{ + public: + simtime_t addbaFailureTimeout = 1; + + virtual bool isAddbaReqNeeded(Packet *, const Ptr&) override { return true; } + virtual bool isAddbaReqAccepted(const Ptr&, OriginatorBlockAckAgreement *) override { return true; } + virtual bool isDelbaAccepted(const Ptr&) override { return true; } + virtual bool isMsduSupported() const override { return true; } + virtual simtime_t computeAddbaFailureTimeout() const override { return addbaFailureTimeout; } + virtual simtime_t getBlockAckTimeoutValue() const override { return 0; } + virtual bool isDelayedAckPolicySupported() const override { return false; } + virtual int getMaximumAllowedBufferSize() const override { return 64; } +}; + +class TestRecipientPolicy : public IRecipientBlockAckAgreementPolicy +{ + public: + bool accepted = true; + + virtual bool isAddbaReqAccepted(const Ptr&) override { return accepted; } + virtual bool isDelbaAccepted(const Ptr&) override { return true; } + virtual simtime_t getBlockAckTimeoutValue() const override { return 0; } + virtual bool aMsduSupported() const override { return true; } + virtual bool delayedBlockAckPolicySupported() const override { return false; } + virtual int getMaximumAllowedBufferSize() const override { return 64; } +}; + +class TestCallback : public IProcedureCallback, public IBlockAckAgreementHandlerCallback +{ + public: + Packet *managementPacket = nullptr; + std::vector managementPackets; + Ptr managementHeader; + simtime_t addbaDeadline = SIMTIME_MAX; + + virtual ~TestCallback() { for (auto packet : managementPackets) delete packet; } + virtual void transmitControlResponseFrame(Packet *, const Ptr&, Packet *, const Ptr&) override { throw cRuntimeError("Unexpected control response"); } + virtual void processMgmtFrame(Packet *packet, const Ptr& header) override { + managementPacket = packet; + managementPackets.push_back(packet); + managementHeader = header; + } + virtual void scheduleInactivityTimer(simtime_t) override {} + virtual void scheduleAddbaResponseTimer(simtime_t deadline) override { addbaDeadline = deadline; } +}; + +class TestOriginatorHandler : public OriginatorBlockAckAgreementHandler +{ + public: + using OriginatorBlockAckAgreementHandler::buildAddbaRequest; + void setNextDialogToken(uint8_t token) { nextDialogToken = token; } + void expireNow(MacAddress receiverAddress, Tid tid) { getAgreement(receiverAddress, tid)->setAddbaResponseDeadline(simTime()); } +}; + +class TestPacketQueue : public queueing::IPacketQueue +{ + public: + std::vector packets; + + virtual int getMaxNumPackets() const override { return -1; } + virtual int getNumPackets() const override { return packets.size(); } + virtual b getMaxTotalLength() const override { return b(-1); } + virtual b getTotalLength() const override { return b(0); } + virtual Packet *getPacket(int index) const override { return packets.at(index); } + virtual bool isEmpty() const override { return packets.empty(); } + virtual void removePacket(Packet *packet) override { packets.erase(std::find(packets.begin(), packets.end(), packet)); } + virtual void removeAllPackets() override { packets.clear(); } + virtual void enqueuePacket(Packet *packet) override { packets.push_back(packet); } + virtual Packet *dequeuePacket() override { auto packet = packets.front(); packets.erase(packets.begin()); return packet; } + virtual bool canPullSomePacket(const cGate *) const override { return !packets.empty(); } + virtual Packet *canPullPacket(const cGate *) const override { return packets.empty() ? nullptr : packets.front(); } + virtual Packet *pullPacket(const cGate *) override { return dequeuePacket(); } + virtual Packet *pullPacketStart(const cGate *, bps) override { throw cRuntimeError("Unsupported"); } + virtual Packet *pullPacketEnd(const cGate *) override { throw cRuntimeError("Unsupported"); } + virtual Packet *pullPacketProgress(const cGate *, bps, b, b) override { throw cRuntimeError("Unsupported"); } + virtual bool canPushSomePacket(const cGate *) const override { return true; } + virtual bool canPushPacket(Packet *, const cGate *) const override { return true; } + virtual void pushPacket(Packet *packet, const cGate *) override { enqueuePacket(packet); } + virtual void pushPacketStart(Packet *, const cGate *, bps) override { throw cRuntimeError("Unsupported"); } + virtual void pushPacketEnd(Packet *, const cGate *) override { throw cRuntimeError("Unsupported"); } + virtual void pushPacketProgress(Packet *, const cGate *, bps, b, b) override { throw cRuntimeError("Unsupported"); } +}; + +class TestAckHandler : public IAckHandler +{ + public: + virtual bool isEligibleToTransmit(const Ptr&) override { return true; } + virtual bool isOutstandingFrame(const Ptr&) override { return false; } + virtual void frameGotInProgress(const Ptr&) override {} +}; + +class TestInProgressFrames : public InProgressFrames +{ + public: + void configure(IOriginatorMacDataService *dataService, IAckHandler *ackHandler) { + this->dataService = dataService; + this->ackHandler = ackHandler; + } + void addFrame(Packet *packet) { inProgressFrames.push_back(packet); } + bool hasEligibleFrame() { return hasEligibleFrameToTransmit(); } +}; + +class TestOriginatorQosMacDataService : public OriginatorQosMacDataService +{ + public: + void enableSequenceNumberAssignment() { sequenceNumberAssignment = new QoSSequenceNumberAssignment(); } + void release(Packet *packet) { drop(packet); } +}; + +static Ptr makeQosHeader(MacAddress receiverAddress, Tid tid, SequenceNumberCyclic sequenceNumber) +{ + auto header = makeShared(); + header->setType(ST_DATA_WITH_QOS); + header->setReceiverAddress(receiverAddress); + header->setTid(tid); + header->setSequenceNumber(sequenceNumber); + return header; +} + +static Ptr makeResponse(MacAddress transmitterAddress, Tid tid, uint8_t dialogToken, uint16_t statusCode) +{ + auto response = makeShared(); + response->setTransmitterAddress(transmitterAddress); + response->setTid(tid); + response->setDialogToken(dialogToken); + response->setStatusCode(statusCode); + response->setBufferSize(32); + response->setBlockAckTimeoutValue(0); + return response; +} + +static Ptr makeRequest(MacAddress transmitterAddress, Tid tid, uint8_t dialogToken, SequenceNumberCyclic startingSequenceNumber, int bufferSize) +{ + auto request = makeShared(); + request->setTransmitterAddress(transmitterAddress); + request->setTid(tid); + request->setDialogToken(dialogToken); + request->setStartingSequenceNumber(startingSequenceNumber); + request->setBufferSize(bufferSize); + request->setBlockAckPolicy(1); + request->setBlockAckTimeoutValue(0); + return request; +} + +%activity: +// IEEE Std 802.11-2024, 9.6.4.2, 10.25.2, and 11.5.2.2. +TestOriginatorPolicy originatorPolicy; +TestCallback callback; +MacAddress peer1("00:00:00:00:00:01"); +MacAddress peer2("00:00:00:00:00:02"); +MacAddress peer3("00:00:00:00:00:03"); + +{ + TestOriginatorHandler tokenHandler; + tokenHandler.setNextDialogToken(255); + auto request255 = tokenHandler.buildAddbaRequest(peer1, 0, SequenceNumberCyclic(0), &originatorPolicy); + auto request1 = tokenHandler.buildAddbaRequest(peer1, 0, SequenceNumberCyclic(0), &originatorPolicy); + ASSERT(request255->getDialogToken() == 255); + ASSERT(request1->getDialogToken() == 1); +} + +TestOriginatorHandler handler; +Packet triggerPacket("trigger"); +auto triggerHeader = makeQosHeader(peer1, 3, SequenceNumberCyclic(4095)); +handler.processTransmittedDataFrame(&triggerPacket, triggerHeader, &originatorPolicy, &callback); +auto request = dynamicPtrCast(callback.managementHeader); +ASSERT(request != nullptr); +ASSERT(request->getDialogToken() != 0); +ASSERT(request->getStartingSequenceNumber() == SequenceNumberCyclic(0)); +handler.processTransmittedAddbaReq(request, &originatorPolicy, &callback); +ASSERT(callback.addbaDeadline == simTime() + 1); + +auto wrongToken = makeResponse(peer1, 3, request->getDialogToken() + 1, 0); +ASSERT(handler.processReceivedAddbaResp(wrongToken, &originatorPolicy, &callback) == nullptr); +ASSERT(handler.isAddbaResponsePending(peer1, 3)); +auto wrongTid = makeResponse(peer1, 4, request->getDialogToken(), 0); +ASSERT(handler.processReceivedAddbaResp(wrongTid, &originatorPolicy, &callback) == nullptr); +auto wrongPeer = makeResponse(peer2, 3, request->getDialogToken(), 0); +ASSERT(handler.processReceivedAddbaResp(wrongPeer, &originatorPolicy, &callback) == nullptr); + +auto success = makeResponse(peer1, 3, request->getDialogToken(), 0); +auto established = handler.processReceivedAddbaResp(success, &originatorPolicy, &callback); +ASSERT(established != nullptr); +ASSERT(established->getStartingSequenceNumber() == SequenceNumberCyclic(0)); +ASSERT(!handler.isAddbaResponsePending(peer1, 3)); +ASSERT(handler.processReceivedAddbaResp(success, &originatorPolicy, &callback) == nullptr); + +Packet rejectedTrigger("rejectedTrigger"); +auto rejectedHeader = makeQosHeader(peer2, 4, SequenceNumberCyclic(10)); +handler.processTransmittedDataFrame(&rejectedTrigger, rejectedHeader, &originatorPolicy, &callback); +auto rejectedRequest = dynamicPtrCast(callback.managementHeader); +handler.processTransmittedAddbaReq(rejectedRequest, &originatorPolicy, &callback); +auto rejection = makeResponse(peer2, 4, rejectedRequest->getDialogToken(), 1); +ASSERT(handler.processReceivedAddbaResp(rejection, &originatorPolicy, &callback) == nullptr); +ASSERT(handler.getAgreement(peer2, 4) == nullptr); + +Packet timeoutTrigger("timeoutTrigger"); +auto timeoutHeader = makeQosHeader(peer3, 5, SequenceNumberCyclic(20)); +handler.processTransmittedDataFrame(&timeoutTrigger, timeoutHeader, &originatorPolicy, &callback); +auto timedRequest = dynamicPtrCast(callback.managementHeader); +handler.processTransmittedAddbaReq(timedRequest, &originatorPolicy, &callback); +handler.expireNow(peer3, 5); +handler.addbaResponseTimeoutExpired(&callback); +ASSERT(handler.getAgreement(peer3, 5) == nullptr); +handler.processTransmittedAddbaReq(timedRequest, &originatorPolicy, &callback); +ASSERT(handler.getAgreement(peer3, 5) == nullptr); +auto staleResponse = makeResponse(peer3, 5, timedRequest->getDialogToken(), 0); +ASSERT(handler.processReceivedAddbaResp(staleResponse, &originatorPolicy, &callback) == nullptr); +handler.processTransmittedDataFrame(&timeoutTrigger, timeoutHeader, &originatorPolicy, &callback); +ASSERT(handler.isAddbaResponsePending(peer3, 5)); + +{ + TestOriginatorHandler invalidTimeoutHandler; + TestOriginatorPolicy invalidTimeoutPolicy; + TestCallback invalidTimeoutCallback; + invalidTimeoutPolicy.addbaFailureTimeout = 0; + Packet invalidTimeoutTrigger("invalidTimeoutTrigger"); + auto invalidTimeoutHeader = makeQosHeader(peer2, 1, SequenceNumberCyclic(30)); + invalidTimeoutHandler.processTransmittedDataFrame(&invalidTimeoutTrigger, invalidTimeoutHeader, &invalidTimeoutPolicy, &invalidTimeoutCallback); + auto invalidTimeoutRequest = dynamicPtrCast(invalidTimeoutCallback.managementHeader); + bool threw = false; + try { + invalidTimeoutHandler.processTransmittedAddbaReq(invalidTimeoutRequest, &invalidTimeoutPolicy, &invalidTimeoutCallback); + } + catch (cRuntimeError&) { + threw = true; + } + ASSERT(threw); +} + +// IEEE Std 802.11-2024, 9.6.4.2, 10.25.6.6.1, and 11.5.2.3. +RecipientBlockAckAgreementHandler recipientHandler; +TestRecipientPolicy recipientPolicy; +TestCallback recipientCallback; +auto recipientRequest = makeShared(); +recipientRequest->setTransmitterAddress(peer1); +recipientRequest->setTid(6); +recipientRequest->setDialogToken(77); +recipientRequest->setStartingSequenceNumber(SequenceNumberCyclic(0)); +recipientRequest->setBufferSize(64); +recipientRequest->setBlockAckPolicy(1); +recipientRequest->setBlockAckTimeoutValue(0); +recipientHandler.processReceivedAddbaRequest(recipientRequest, &recipientPolicy, &recipientCallback); +auto acceptedResponsePacket = recipientCallback.managementPacket; +auto acceptedResponse = dynamicPtrCast(recipientCallback.managementHeader); +ASSERT(acceptedResponse->getDialogToken() == 77); +ASSERT(acceptedResponse->getTid() == 6); +ASSERT(acceptedResponse->getStatusCode() == 0); +ASSERT(recipientHandler.getAgreement(6, peer1) == nullptr); +auto wrongRecipientTokenResponse = makeShared(); +wrongRecipientTokenResponse->setReceiverAddress(peer1); +wrongRecipientTokenResponse->setTid(6); +wrongRecipientTokenResponse->setDialogToken(78); +wrongRecipientTokenResponse->setStatusCode(0); +ASSERT(recipientHandler.processTransmittedAddbaResp(acceptedResponsePacket, wrongRecipientTokenResponse, &recipientCallback) == nullptr); +ASSERT(recipientHandler.getAgreement(6, peer1) == nullptr); + +// Exercise the real management-frame extraction and sequence-assignment path: +// the mutable header is copied, but the Packet/message identity is preserved. +TestOriginatorQosMacDataService responseDataService; +responseDataService.enableSequenceNumberAssignment(); +TestPacketQueue responseQueue; +responseQueue.packets = { acceptedResponsePacket }; +auto extractedResponses = responseDataService.extractFramesToTransmit(&responseQueue); +ASSERT(extractedResponses->size() == 1); +auto transmittedResponsePacket = extractedResponses->at(0); +auto transmittedResponse = dynamicPtrCast(transmittedResponsePacket->peekAtFront()); +ASSERT(transmittedResponsePacket == acceptedResponsePacket); +ASSERT(transmittedResponse != acceptedResponse); +auto firstRecipientAgreement = recipientHandler.processTransmittedAddbaResp(transmittedResponsePacket, transmittedResponse, &recipientCallback); +delete extractedResponses; +ASSERT(firstRecipientAgreement != nullptr); +ASSERT(firstRecipientAgreement->getStartingSequenceNumber() == SequenceNumberCyclic(0)); +ASSERT(recipientHandler.processTransmittedAddbaResp(transmittedResponsePacket, transmittedResponse, &recipientCallback) == nullptr); +responseDataService.release(transmittedResponsePacket); + +recipientRequest->setDialogToken(79); +recipientRequest->setStartingSequenceNumber(SequenceNumberCyclic(9)); +recipientHandler.processReceivedAddbaRequest(recipientRequest, &recipientPolicy, &recipientCallback); +auto replacementResponsePacket = recipientCallback.managementPacket; +auto replacementResponse = dynamicPtrCast(recipientCallback.managementHeader); +ASSERT(replacementResponse->getDialogToken() == 79); +ASSERT(recipientHandler.getAgreement(6, peer1) == firstRecipientAgreement); +ASSERT(recipientHandler.getAgreement(6, peer1)->getStartingSequenceNumber() == SequenceNumberCyclic(0)); +auto replacementAgreement = recipientHandler.processTransmittedAddbaResp(replacementResponsePacket, replacementResponse, &recipientCallback); +ASSERT(replacementAgreement != nullptr); +ASSERT(replacementAgreement != firstRecipientAgreement); +ASSERT(recipientHandler.getAgreement(6, peer1) == replacementAgreement); +ASSERT(replacementAgreement->getStartingSequenceNumber() == SequenceNumberCyclic(9)); + +recipientPolicy.accepted = false; +recipientRequest->setDialogToken(80); +recipientRequest->setStartingSequenceNumber(SequenceNumberCyclic(12)); +recipientHandler.processReceivedAddbaRequest(recipientRequest, &recipientPolicy, &recipientCallback); +auto refusedResponsePacket = recipientCallback.managementPacket; +auto refusedResponse = dynamicPtrCast(recipientCallback.managementHeader); +ASSERT(refusedResponse->getDialogToken() == 80); +ASSERT(refusedResponse->getTid() == 6); +ASSERT(refusedResponse->getStatusCode() == 1); +ASSERT(recipientHandler.getAgreement(6, peer1) == replacementAgreement); +ASSERT(recipientHandler.processTransmittedAddbaResp(refusedResponsePacket, refusedResponse, &recipientCallback) == nullptr); +ASSERT(recipientHandler.getAgreement(6, peer1) == replacementAgreement); + +recipientPolicy.accepted = true; +recipientRequest->setTransmitterAddress(peer3); +recipientRequest->setTid(2); +recipientRequest->setDialogToken(0); +recipientHandler.processReceivedAddbaRequest(recipientRequest, &recipientPolicy, &recipientCallback); +auto zeroTokenResponse = dynamicPtrCast(recipientCallback.managementHeader); +ASSERT(zeroTokenResponse->getDialogToken() == 0); +ASSERT(zeroTokenResponse->getStatusCode() == 1); +ASSERT(recipientHandler.getAgreement(2, peer3) == nullptr); + +// Overlapping accepted responses retain their own negotiated parameters. The +// exact response object, rather than peer/TID/token alone, owns staged state. +{ + RecipientBlockAckAgreementHandler overlapHandler; + TestRecipientPolicy overlapPolicy; + TestCallback overlapCallback; + auto requestA = makeRequest(peer1, 1, 90, SequenceNumberCyclic(100), 16); + auto requestB = makeRequest(peer1, 1, 91, SequenceNumberCyclic(200), 32); + overlapHandler.processReceivedAddbaRequest(requestA, &overlapPolicy, &overlapCallback); + auto responsePacketA = overlapCallback.managementPacket; + auto responseA = dynamicPtrCast(overlapCallback.managementHeader); + overlapHandler.processReceivedAddbaRequest(requestB, &overlapPolicy, &overlapCallback); + auto responsePacketB = overlapCallback.managementPacket; + auto responseB = dynamicPtrCast(overlapCallback.managementHeader); + ASSERT(overlapHandler.getAgreement(1, peer1) == nullptr); + auto agreementA = overlapHandler.processTransmittedAddbaResp(responsePacketA, responseA, &overlapCallback); + ASSERT(agreementA->getStartingSequenceNumber() == SequenceNumberCyclic(100)); + ASSERT(agreementA->getBufferSize() == 16); + auto agreementB = overlapHandler.processTransmittedAddbaResp(responsePacketB, responseB, &overlapCallback); + ASSERT(agreementB->getStartingSequenceNumber() == SequenceNumberCyclic(200)); + ASSERT(agreementB->getBufferSize() == 32); + ASSERT(overlapHandler.getAgreement(1, peer1) == agreementB); + ASSERT(overlapHandler.processTransmittedAddbaResp(responsePacketA, responseA, &overlapCallback) == nullptr); +} + +{ + RecipientBlockAckAgreementHandler overlapHandler; + TestRecipientPolicy overlapPolicy; + TestCallback overlapCallback; + // Reusing a token cannot make one queued response activate the other's SSN. + auto requestA = makeRequest(peer1, 1, 92, SequenceNumberCyclic(300), 24); + auto requestB = makeRequest(peer1, 1, 92, SequenceNumberCyclic(400), 40); + overlapHandler.processReceivedAddbaRequest(requestA, &overlapPolicy, &overlapCallback); + auto responsePacketA = overlapCallback.managementPacket; + auto responseA = dynamicPtrCast(overlapCallback.managementHeader); + overlapHandler.processReceivedAddbaRequest(requestB, &overlapPolicy, &overlapCallback); + auto responsePacketB = overlapCallback.managementPacket; + auto responseB = dynamicPtrCast(overlapCallback.managementHeader); + auto agreementB = overlapHandler.processTransmittedAddbaResp(responsePacketB, responseB, &overlapCallback); + ASSERT(agreementB->getStartingSequenceNumber() == SequenceNumberCyclic(400)); + ASSERT(agreementB->getBufferSize() == 40); + auto agreementA = overlapHandler.processTransmittedAddbaResp(responsePacketA, responseA, &overlapCallback); + ASSERT(agreementA->getStartingSequenceNumber() == SequenceNumberCyclic(300)); + ASSERT(agreementA->getBufferSize() == 24); + ASSERT(overlapHandler.getAgreement(1, peer1) == agreementA); + ASSERT(overlapHandler.processTransmittedAddbaResp(responsePacketB, responseB, &overlapCallback) == nullptr); +} + +// A rejected overlapping request creates no staged agreement and does not +// discard either the active agreement or an earlier accepted transaction. +{ + RecipientBlockAckAgreementHandler overlapHandler; + TestRecipientPolicy overlapPolicy; + TestCallback overlapCallback; + auto initialRequest = makeRequest(peer2, 2, 93, SequenceNumberCyclic(10), 8); + overlapHandler.processReceivedAddbaRequest(initialRequest, &overlapPolicy, &overlapCallback); + auto initialResponsePacket = overlapCallback.managementPacket; + auto initialResponse = dynamicPtrCast(overlapCallback.managementHeader); + auto initialAgreement = overlapHandler.processTransmittedAddbaResp(initialResponsePacket, initialResponse, &overlapCallback); + auto requestA = makeRequest(peer2, 2, 94, SequenceNumberCyclic(500), 48); + overlapHandler.processReceivedAddbaRequest(requestA, &overlapPolicy, &overlapCallback); + auto responsePacketA = overlapCallback.managementPacket; + auto responseA = dynamicPtrCast(overlapCallback.managementHeader); + overlapPolicy.accepted = false; + auto requestB = makeRequest(peer2, 2, 95, SequenceNumberCyclic(600), 56); + overlapHandler.processReceivedAddbaRequest(requestB, &overlapPolicy, &overlapCallback); + auto responsePacketB = overlapCallback.managementPacket; + auto responseB = dynamicPtrCast(overlapCallback.managementHeader); + ASSERT(overlapHandler.getAgreement(2, peer2) == initialAgreement); + ASSERT(overlapHandler.processTransmittedAddbaResp(responsePacketB, responseB, &overlapCallback) == nullptr); + ASSERT(overlapHandler.getAgreement(2, peer2) == initialAgreement); + auto agreementA = overlapHandler.processTransmittedAddbaResp(responsePacketA, responseA, &overlapCallback); + ASSERT(agreementA->getStartingSequenceNumber() == SequenceNumberCyclic(500)); + ASSERT(agreementA->getBufferSize() == 48); + ASSERT(overlapHandler.getAgreement(2, peer2) == agreementA); +} + +{ + RecipientBlockAckAgreementHandler overlapHandler; + TestRecipientPolicy overlapPolicy; + TestCallback overlapCallback; + auto requestA = makeRequest(peer2, 2, 96, SequenceNumberCyclic(700), 20); + overlapHandler.processReceivedAddbaRequest(requestA, &overlapPolicy, &overlapCallback); + auto responsePacketA = overlapCallback.managementPacket; + auto responseA = dynamicPtrCast(overlapCallback.managementHeader); + overlapPolicy.accepted = false; + auto requestB = makeRequest(peer2, 2, 97, SequenceNumberCyclic(800), 28); + overlapHandler.processReceivedAddbaRequest(requestB, &overlapPolicy, &overlapCallback); + auto responsePacketB = overlapCallback.managementPacket; + auto responseB = dynamicPtrCast(overlapCallback.managementHeader); + auto agreementA = overlapHandler.processTransmittedAddbaResp(responsePacketA, responseA, &overlapCallback); + ASSERT(agreementA->getStartingSequenceNumber() == SequenceNumberCyclic(700)); + ASSERT(agreementA->getBufferSize() == 20); + ASSERT(overlapHandler.processTransmittedAddbaResp(responsePacketB, responseB, &overlapCallback) == nullptr); + ASSERT(overlapHandler.getAgreement(2, peer2) == agreementA); +} + +OriginatorQosMacDataService dataService; +dataService.setFrameEligibilityFunction([&handler](const Packet *packet) { + auto header = dynamicPtrCast(packet->peekAtFront()); + return header == nullptr || header->getType() != ST_DATA_WITH_QOS || !handler.isAddbaResponsePending(header->getReceiverAddress(), header->getTid()); +}); +Packet heldFrame("heldFrame", makeQosHeader(peer3, 5, SequenceNumberCyclic(0))); +Packet otherTidFrame("otherTidFrame", makeQosHeader(peer3, 4, SequenceNumberCyclic(0))); +Packet otherPeerFrame("otherPeerFrame", makeQosHeader(peer2, 5, SequenceNumberCyclic(0))); +auto managementHeader = makeShared(); +managementHeader->setType(ST_ACTION); +Packet managementFrame("managementFrame", managementHeader); +TestPacketQueue mixedQueue; +mixedQueue.packets = { &heldFrame, &otherTidFrame, &otherPeerFrame, &managementFrame }; +ASSERT(!dataService.isFrameEligible(&heldFrame)); +ASSERT(dataService.isFrameEligible(&otherTidFrame)); +ASSERT(dataService.isFrameEligible(&otherPeerFrame)); +ASSERT(dataService.isFrameEligible(&managementFrame)); +ASSERT(dataService.hasEligibleFrame(&mixedQueue)); +mixedQueue.packets = { &heldFrame }; +ASSERT(!dataService.hasEligibleFrame(&mixedQueue)); + +// A frame prefetched for duration calculation already has an SSN, but it must +// remain in progress without being selected while the same transaction is pending. +TestAckHandler ackHandler; +TestInProgressFrames inProgressFrames; +inProgressFrames.configure(&dataService, &ackHandler); +auto prefetchedHeldFrame = new Packet("prefetchedHeldFrame", makeQosHeader(peer3, 5, SequenceNumberCyclic(21))); +auto prefetchedOtherTidFrame = new Packet("prefetchedOtherTidFrame", makeQosHeader(peer3, 4, SequenceNumberCyclic(1))); +inProgressFrames.addFrame(prefetchedHeldFrame); +inProgressFrames.addFrame(prefetchedOtherTidFrame); +ASSERT(inProgressFrames.hasEligibleFrame()); +ASSERT(inProgressFrames.getFrameToTransmit() == prefetchedOtherTidFrame); + +EV << "ADDBA transaction and negotiated SSN integrity checks passed.\n"; + +%contains: stdout +ADDBA transaction and negotiated SSN integrity checks passed. From 2d09b22b3df136b0a8b5f6059b04c777cbf4d159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Mon, 17 Aug 2026 00:26:35 +0200 Subject: [PATCH 02/14] Fix IEEE 802.11 ADDBA transaction lifecycle Track each originator ADDBA setup with an exact local transaction ID, start the response timeout only after the complete request is transmitted, and apply a separate retry backoff after timeout, refusal, or discard. Cancel all queued and in-progress fragments belonging to failed or completed transactions so stale requests cannot block or later re-establish state. Establish recipient Block Ack agreements when a request is accepted, remove response-packet staging, reset reorder buffers on renegotiation, and publish distinct agreement-change observations. Preserve internal-collision recovery and materialize frames only at DCF grants and HCF TXOP continuation boundaries so availability queries remain side-effect free. Add typed queue drop callbacks and arbitrary-packet dequeue propagation through queues, flows, and schedulers. Restore QueueingTimeTag, PEK_QUEUED, and packetPulled accounting exactly once while keeping cancellation on removal/drop semantics. Extend the focused regression with real fragmentation and transaction cancellation, reorder reset, queue accounting and overflow callbacks, HCF continuation, and legacy DCF channel-grant coverage. Validation: release and debug builds; focused Ieee80211AddbaTransaction unit test; MacDcf and MacEdca smoke simulations; architecture and WLAN reviews; 22 related fingerprints with 15 unchanged and 7 explained Block Ack trajectory changes. Fingerprint baselines are intentionally unchanged. --- .../blockack/Ieee80211AddbaTransactionTag.msg | 16 + .../blockack/OriginatorBlockAckAgreement.h | 7 +- .../OriginatorBlockAckAgreementHandler.cc | 87 ++- .../OriginatorBlockAckAgreementHandler.h | 14 +- .../OriginatorBlockAckAgreementPolicy.cc | 8 +- .../OriginatorBlockAckAgreementPolicy.h | 5 +- .../OriginatorBlockAckAgreementPolicy.ned | 3 +- .../RecipientBlockAckAgreementHandler.cc | 91 +-- .../RecipientBlockAckAgreementHandler.h | 17 +- .../blockackreordering/BlockAckReordering.cc | 10 +- .../blockackreordering/BlockAckReordering.h | 2 +- .../IBlockAckAgreementHandlerCallback.h | 4 + .../IOriginatorBlockAckAgreementHandler.h | 8 +- .../IOriginatorBlockAckAgreementPolicy.h | 4 +- .../IRecipientBlockAckAgreementHandler.h | 3 +- .../contract/IRecipientQosMacDataService.h | 2 +- .../ieee80211/mac/coordinationfunction/Dcf.cc | 6 +- .../ieee80211/mac/coordinationfunction/Hcf.cc | 99 ++- .../ieee80211/mac/coordinationfunction/Hcf.h | 6 +- .../mac/coordinationfunction/Hcf.ned | 3 +- .../ieee80211/mac/framesequence/HcfFs.cc | 7 +- .../originator/OriginatorMacDataService.cc | 10 +- .../originator/OriginatorQosMacDataService.cc | 10 +- .../ieee80211/mac/queue/InProgressFrames.h | 3 +- .../recipient/RecipientQosMacDataService.cc | 7 +- .../recipient/RecipientQosMacDataService.h | 2 +- src/inet/queueing/base/PacketFlowBase.cc | 16 +- src/inet/queueing/base/PacketFlowBase.h | 6 +- src/inet/queueing/base/PacketQueueBase.cc | 33 +- src/inet/queueing/base/PacketQueueBase.h | 6 +- src/inet/queueing/contract/IPacketExtractor.h | 26 + src/inet/queueing/contract/IPacketQueue.h | 19 +- .../queueing/queue/CompoundPacketQueueBase.cc | 28 +- .../queueing/queue/CompoundPacketQueueBase.h | 9 +- src/inet/queueing/queue/PacketQueue.cc | 23 +- src/inet/queueing/queue/PacketQueue.h | 4 +- src/inet/queueing/scheduler/LabelScheduler.cc | 38 +- src/inet/queueing/scheduler/LabelScheduler.h | 5 +- .../queueing/scheduler/PriorityScheduler.cc | 20 +- .../queueing/scheduler/PriorityScheduler.h | 5 +- src/inet/queueing/scheduler/WrrScheduler.cc | 44 +- src/inet/queueing/scheduler/WrrScheduler.h | 9 +- tests/unit/Ieee80211AddbaTransaction_1.test | 672 +++++++++++++----- 43 files changed, 1021 insertions(+), 376 deletions(-) create mode 100644 src/inet/linklayer/ieee80211/mac/blockack/Ieee80211AddbaTransactionTag.msg create mode 100644 src/inet/queueing/contract/IPacketExtractor.h diff --git a/src/inet/linklayer/ieee80211/mac/blockack/Ieee80211AddbaTransactionTag.msg b/src/inet/linklayer/ieee80211/mac/blockack/Ieee80211AddbaTransactionTag.msg new file mode 100644 index 00000000000..43d9033c743 --- /dev/null +++ b/src/inet/linklayer/ieee80211/mac/blockack/Ieee80211AddbaTransactionTag.msg @@ -0,0 +1,16 @@ +// +// Copyright (C) 2026 OpenSim Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +import inet.common.INETDefs; +import inet.common.TagBase; + +namespace inet::ieee80211; + +// Identifies one local originator ADDBA transaction across packet transformations. +class Ieee80211AddbaTransactionTag extends TagBase +{ + uint64_t transactionId; +} diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h index a024f5f817e..8099d7c1bdb 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h @@ -34,19 +34,21 @@ class INET_API OriginatorBlockAckAgreement : public cObject OriginatorBlockAckAgreementState state = OriginatorBlockAckAgreementState::PENDING; bool isAddbaRequestSent = false; uint8_t dialogToken = 0; + uint64_t transactionId = 0; simtime_t addbaResponseDeadline = -1; simtime_t blockAckTimeoutValue = -1; simtime_t expirationTime = -1; public: - OriginatorBlockAckAgreement(MacAddress receiverAddr, Tid tid, SequenceNumberCyclic startingSequenceNumber, int bufferSize, bool isAMsduSupported, bool isDelayedBlockAckPolicySupported, uint8_t dialogToken) : + OriginatorBlockAckAgreement(MacAddress receiverAddr, Tid tid, SequenceNumberCyclic startingSequenceNumber, int bufferSize, bool isAMsduSupported, bool isDelayedBlockAckPolicySupported, uint8_t dialogToken, uint64_t transactionId) : receiverAddr(receiverAddr), tid(tid), startingSequenceNumber(startingSequenceNumber), bufferSize(bufferSize), isAMsduSupported(isAMsduSupported), isDelayedBlockAckPolicySupported(isDelayedBlockAckPolicySupported), - dialogToken(dialogToken) + dialogToken(dialogToken), + transactionId(transactionId) { } @@ -65,6 +67,7 @@ class INET_API OriginatorBlockAckAgreement : public cObject virtual const simtime_t getBlockAckTimeoutValue() const { return blockAckTimeoutValue; } virtual int getNumSentBaPolicyFrames() const { return numSentBaPolicyFrames; } virtual uint8_t getDialogToken() const { return dialogToken; } + virtual uint64_t getTransactionId() const { return transactionId; } virtual simtime_t getAddbaResponseDeadline() const { return addbaResponseDeadline; } virtual void setBufferSize(int bufferSize) { this->bufferSize = bufferSize; } diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc index 889387ff61b..d21d8398167 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc @@ -8,14 +8,15 @@ #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h" #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h" +#include "inet/linklayer/ieee80211/mac/blockack/Ieee80211AddbaTransactionTag_m.h" namespace inet { namespace ieee80211 { -void OriginatorBlockAckAgreementHandler::createAgreement(const Ptr& addbaRequest) +void OriginatorBlockAckAgreementHandler::createAgreement(const Ptr& addbaRequest, uint64_t transactionId) { ASSERT(addbaRequest->getDialogToken() != 0); - OriginatorBlockAckAgreement *blockAckAgreement = new OriginatorBlockAckAgreement(addbaRequest->getReceiverAddress(), addbaRequest->getTid(), addbaRequest->getStartingSequenceNumber(), addbaRequest->getBufferSize(), addbaRequest->getAMsduSupported(), addbaRequest->getBlockAckPolicy() == 0, addbaRequest->getDialogToken()); + OriginatorBlockAckAgreement *blockAckAgreement = new OriginatorBlockAckAgreement(addbaRequest->getReceiverAddress(), addbaRequest->getTid(), addbaRequest->getStartingSequenceNumber(), addbaRequest->getBufferSize(), addbaRequest->getAMsduSupported(), addbaRequest->getBlockAckPolicy() == 0, addbaRequest->getDialogToken(), transactionId); auto agreementId = std::make_pair(addbaRequest->getReceiverAddress(), addbaRequest->getTid()); blockAckAgreements[agreementId] = blockAckAgreement; } @@ -54,15 +55,34 @@ simtime_t OriginatorBlockAckAgreementHandler::computeEarliestAddbaResponseDeadli return earliestDeadline; } -void OriginatorBlockAckAgreementHandler::addbaResponseTimeoutExpired(IBlockAckAgreementHandlerCallback *callback) +simtime_t OriginatorBlockAckAgreementHandler::getAddbaResponseTimeout(IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy) const +{ + auto timeout = blockAckAgreementPolicy->getAddbaResponseTimeout(); + if (timeout <= 0) + throw cRuntimeError("ADDBA response timeout must be greater than zero"); + return timeout; +} + +void OriginatorBlockAckAgreementHandler::recordAddbaFailure(MacAddress receiverAddr, Tid tid, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy) +{ + auto retryBackoff = blockAckAgreementPolicy->computeAddbaRetryBackoff(); + if (retryBackoff < 0) + throw cRuntimeError("ADDBA retry backoff must not be negative"); + addbaRetryDeadlines[std::make_pair(receiverAddr, tid)] = simTime() + retryBackoff; +} + +void OriginatorBlockAckAgreementHandler::addbaResponseTimeoutExpired(IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) { auto now = simTime(); for (auto it = blockAckAgreements.begin(); it != blockAckAgreements.end();) { auto agreement = it->second; if (agreement->isPending() && agreement->getIsAddbaRequestSent() && agreement->getAddbaResponseDeadline() <= now) { - EV_INFO << "ADDBA Response timeout for receiver=" << agreement->getReceiverAddr() << " tid=" << (int)agreement->getTid() << endl; + EV_INFO << "ADDBA transaction timeout for receiver=" << agreement->getReceiverAddr() << " tid=" << (int)agreement->getTid() << endl; + auto transactionId = agreement->getTransactionId(); + recordAddbaFailure(agreement->getReceiverAddr(), agreement->getTid(), blockAckAgreementPolicy); it = blockAckAgreements.erase(it); delete agreement; + callback->cancelAddbaTransaction(transactionId, nullptr); } else it++; @@ -172,14 +192,23 @@ void OriginatorBlockAckAgreementHandler::terminateAgreement(MacAddress originato } } -void OriginatorBlockAckAgreementHandler::processTransmittedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *callback) +void OriginatorBlockAckAgreementHandler::processTransmittedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) { - auto agreement = getAgreement(dataHeader->getReceiverAddress(), dataHeader->getTid()); - if (blockAckAgreementPolicy->isAddbaReqNeeded(packet, dataHeader) && agreement == nullptr) { - auto addbaReq = buildAddbaRequest(dataHeader->getReceiverAddress(), dataHeader->getTid(), dataHeader->getSequenceNumber() + 1, blockAckAgreementPolicy); - createAgreement(addbaReq); + auto receiverAddr = dataHeader->getReceiverAddress(); + auto tid = dataHeader->getTid(); + auto agreementId = std::make_pair(receiverAddr, tid); + auto agreement = getAgreement(receiverAddr, tid); + auto retryIt = addbaRetryDeadlines.find(agreementId); + bool retryAllowed = retryIt == addbaRetryDeadlines.end() || retryIt->second <= simTime(); + if (blockAckAgreementPolicy->isAddbaReqNeeded(packet, dataHeader) && agreement == nullptr && retryAllowed) { + if (retryIt != addbaRetryDeadlines.end()) + addbaRetryDeadlines.erase(retryIt); + auto addbaReq = buildAddbaRequest(receiverAddr, tid, dataHeader->getSequenceNumber() + 1, blockAckAgreementPolicy); + auto transactionId = nextTransactionId++; + createAgreement(addbaReq, transactionId); auto addbaPacket = new Packet("AddbaReq", addbaReq); - callback->processMgmtFrame(addbaPacket, addbaReq); + addbaPacket->addTag()->setTransactionId(transactionId); + procedureCallback->processMgmtFrame(addbaPacket, addbaReq); } } @@ -191,14 +220,20 @@ OriginatorBlockAckAgreement *OriginatorBlockAckAgreementHandler::processReceived if (agreement == nullptr || !agreement->isPending() || !agreement->getIsAddbaRequestSent() || agreement->getDialogToken() != addbaResp->getDialogToken()) return nullptr; if (addbaResp->getStatusCode() == 0 && blockAckAgreementPolicy->isAddbaReqAccepted(addbaResp, agreement)) { + auto transactionId = agreement->getTransactionId(); updateAgreement(agreement, addbaResp); + addbaRetryDeadlines.erase(std::make_pair(addbaResp->getTransmitterAddress(), addbaResp->getTid())); scheduleInactivityTimer(callback); scheduleAddbaResponseTimer(callback); + callback->cancelAddbaTransaction(transactionId, nullptr); return agreement; } else { + auto transactionId = agreement->getTransactionId(); + recordAddbaFailure(addbaResp->getTransmitterAddress(), addbaResp->getTid(), blockAckAgreementPolicy); terminateAgreement(addbaResp->getTransmitterAddress(), addbaResp->getTid()); scheduleAddbaResponseTimer(callback); + callback->cancelAddbaTransaction(transactionId, nullptr); return nullptr; } } @@ -211,23 +246,37 @@ void OriginatorBlockAckAgreementHandler::updateAgreement(OriginatorBlockAckAgree agreement->calculateExpirationTime(); } -void OriginatorBlockAckAgreementHandler::processTransmittedAddbaReq(const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) +bool OriginatorBlockAckAgreementHandler::isAddbaRequestPending(const Packet *packet, const Ptr& addbaReq) const +{ + auto it = blockAckAgreements.find(std::make_pair(addbaReq->getReceiverAddress(), addbaReq->getTid())); + auto transactionTag = packet->findTag(); + return it != blockAckAgreements.end() && it->second->isPending() && transactionTag != nullptr && + it->second->getDialogToken() == addbaReq->getDialogToken() && it->second->getTransactionId() == transactionTag->getTransactionId(); +} + +void OriginatorBlockAckAgreementHandler::processTransmittedAddbaReq(Packet *packet, const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) { auto agreement = getAgreement(addbaReq->getReceiverAddress(), addbaReq->getTid()); - if (agreement && agreement->isPending() && agreement->getDialogToken() == addbaReq->getDialogToken()) { - if (agreement->getAddbaResponseDeadline() < 0) { - auto addbaFailureTimeout = blockAckAgreementPolicy->computeAddbaFailureTimeout(); - if (addbaFailureTimeout <= 0) - throw cRuntimeError("ADDBA failure timeout must be greater than zero"); - agreement->setAddbaResponseDeadline(simTime() + addbaFailureTimeout); - } + if (isAddbaRequestPending(packet, addbaReq) && !addbaReq->getMoreFragments() && !agreement->getIsAddbaRequestSent()) { + agreement->setAddbaResponseDeadline(simTime() + getAddbaResponseTimeout(blockAckAgreementPolicy)); agreement->setIsAddbaRequestSent(true); scheduleAddbaResponseTimer(callback); } - else + else if (!isAddbaRequestPending(packet, addbaReq)) EV_WARN << "Ignoring stale transmitted ADDBA Request for receiver=" << addbaReq->getReceiverAddress() << " tid=" << (int)addbaReq->getTid() << " dialogToken=" << (int)addbaReq->getDialogToken() << endl; } +void OriginatorBlockAckAgreementHandler::processDroppedAddbaReq(Packet *packet, const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) +{ + if (isAddbaRequestPending(packet, addbaReq)) { + auto transactionId = packet->getTag()->getTransactionId(); + recordAddbaFailure(addbaReq->getReceiverAddress(), addbaReq->getTid(), blockAckAgreementPolicy); + terminateAgreement(addbaReq->getReceiverAddress(), addbaReq->getTid()); + scheduleAddbaResponseTimer(callback); + callback->cancelAddbaTransaction(transactionId, packet); + } +} + void OriginatorBlockAckAgreementHandler::processTransmittedDelba(const Ptr& delba) { terminateAgreement(delba->getReceiverAddress(), delba->getTid()); diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h index b7ec1e9c43c..4d212cae9cf 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h @@ -21,12 +21,14 @@ class INET_API OriginatorBlockAckAgreementHandler : public IOriginatorBlockAckAg { protected: std::map, OriginatorBlockAckAgreement *> blockAckAgreements; + std::map, simtime_t> addbaRetryDeadlines; uint8_t nextDialogToken = 1; + uint64_t nextTransactionId = 1; protected: virtual const Ptr buildAddbaRequest(MacAddress receiverAddr, Tid tid, SequenceNumberCyclic startingSequenceNumber, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy); virtual uint8_t allocateDialogToken(); - virtual void createAgreement(const Ptr& addbaRequest); + virtual void createAgreement(const Ptr& addbaRequest, uint64_t transactionId); virtual void updateAgreement(OriginatorBlockAckAgreement *agreement, const Ptr& addbaResp); virtual void terminateAgreement(MacAddress originatorAddr, Tid tid); virtual const Ptr buildDelba(MacAddress receiverAddr, Tid tid, int reasonCode); @@ -34,20 +36,24 @@ class INET_API OriginatorBlockAckAgreementHandler : public IOriginatorBlockAckAg virtual simtime_t computeEarliestAddbaResponseDeadline() const; virtual void scheduleInactivityTimer(IBlockAckAgreementHandlerCallback *callback); virtual void scheduleAddbaResponseTimer(IBlockAckAgreementHandlerCallback *callback); + virtual simtime_t getAddbaResponseTimeout(IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy) const; + virtual void recordAddbaFailure(MacAddress receiverAddr, Tid tid, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy); public: virtual ~OriginatorBlockAckAgreementHandler(); - virtual void processTransmittedAddbaReq(const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; - virtual void processTransmittedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *callback) override; + virtual void processTransmittedAddbaReq(Packet *packet, const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; + virtual void processDroppedAddbaReq(Packet *packet, const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; + virtual void processTransmittedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) override; virtual void processReceivedBlockAck(const Ptr& blockAck, IBlockAckAgreementHandlerCallback *callback) override; virtual OriginatorBlockAckAgreement *processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; virtual void processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy) override; virtual void processTransmittedDelba(const Ptr& delba) override; virtual void blockAckAgreementExpired(IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) override; - virtual void addbaResponseTimeoutExpired(IBlockAckAgreementHandlerCallback *callback) override; + virtual void addbaResponseTimeoutExpired(IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; virtual OriginatorBlockAckAgreement *getAgreement(MacAddress receiverAddr, Tid tid) override; virtual bool isAddbaResponsePending(MacAddress receiverAddr, Tid tid) const override; + virtual bool isAddbaRequestPending(const Packet *packet, const Ptr& addbaReq) const override; }; } // namespace ieee80211 diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc index 50a98dd646f..0bd101f3949 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc @@ -25,17 +25,15 @@ void OriginatorBlockAckAgreementPolicy::initialize(int stage) maximumAllowedBufferSize = par("maximumAllowedBufferSize"); blockAckTimeoutValue = par("blockAckTimeoutValue"); addbaFailureTimeout = par("addbaFailureTimeout"); + addbaRetryBackoff = par("addbaRetryBackoff"); if (addbaFailureTimeout <= 0) throw cRuntimeError("addbaFailureTimeout must be greater than zero"); + if (addbaRetryBackoff < 0) + throw cRuntimeError("addbaRetryBackoff must not be negative"); WATCH(blockAckReqThreshold); } } -simtime_t OriginatorBlockAckAgreementPolicy::computeAddbaFailureTimeout() const -{ - return addbaFailureTimeout; -} - bool OriginatorBlockAckAgreementPolicy::isAddbaReqNeeded(Packet *packet, const Ptr& header) { return ackPolicy->isBlockAckPolicyEligibleFrame(packet, header); diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.h index b762cc29828..9cacbc9ec78 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.h @@ -26,6 +26,7 @@ class INET_API OriginatorBlockAckAgreementPolicy : public ModeSetListener, publi int maximumAllowedBufferSize = -1; simtime_t blockAckTimeoutValue = -1; simtime_t addbaFailureTimeout = -1; + simtime_t addbaRetryBackoff = -1; protected: virtual int numInitStages() const override { return NUM_INIT_STAGES; } @@ -36,7 +37,8 @@ class INET_API OriginatorBlockAckAgreementPolicy : public ModeSetListener, publi virtual bool isAddbaReqAccepted(const Ptr& addbaResp, OriginatorBlockAckAgreement *agreement) override; virtual bool isDelbaAccepted(const Ptr& delba) override; - virtual simtime_t computeAddbaFailureTimeout() const override; + virtual simtime_t getAddbaResponseTimeout() const override { return addbaFailureTimeout; } + virtual simtime_t computeAddbaRetryBackoff() const override { return addbaRetryBackoff; } virtual bool isMsduSupported() const override { return aMsduSupported; } virtual simtime_t getBlockAckTimeoutValue() const override { return blockAckTimeoutValue; } @@ -48,4 +50,3 @@ class INET_API OriginatorBlockAckAgreementPolicy : public ModeSetListener, publi } /* namespace inet */ #endif - diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.ned b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.ned index 121d522355f..6163b66238a 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.ned +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.ned @@ -23,6 +23,7 @@ simple OriginatorBlockAckAgreementPolicy extends SimpleModule like IOriginatorBl bool aMsduSupported = default(true); int maximumAllowedBufferSize = default(64); double blockAckTimeoutValue @unit(s) = default(0s); // 0 means that it depends on the originator - double addbaFailureTimeout @unit(s) = default(1s); + double addbaFailureTimeout @unit(s) = default(1s); // Model response-wait timeout, retained under its legacy parameter name for configuration compatibility + double addbaRetryBackoff @unit(s) = default(1s); // Model retry suppression after a failed or discarded ADDBA transaction; not an IEEE 802.11 timer @display("i=block/control"); } diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc index df06384284f..2f87f5e8e8c 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc @@ -7,8 +7,6 @@ #include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h" -#include - #include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h" namespace inet { @@ -66,59 +64,6 @@ void RecipientBlockAckAgreementHandler::blockAckAgreementExpired(IProcedureCallb scheduleInactivityTimer(agreementHandlerCallback); } -// -// Keep the accepted parameters staged until the exact corresponding successful -// response packet is actually transmitted. Packet identity survives mutable -// header copy-on-write during sequence-number assignment and keeps overlapping -// transactions independent even when the originator reuses a Dialog Token. -// -void RecipientBlockAckAgreementHandler::clearPendingAgreements(MacAddress originatorAddr, Tid tid) -{ - for (auto it = pendingBlockAckAgreements.begin(); it != pendingBlockAckAgreements.end();) { - if (it->originatorAddress == originatorAddr && it->tid == tid) { - delete it->agreement; - it = pendingBlockAckAgreements.erase(it); - } - else - it++; - } -} - -void RecipientBlockAckAgreementHandler::stageAgreement(Packet *addbaResponsePacket, const Ptr& addbaRequest, const Ptr& addbaResponse) -{ - auto originatorAddr = addbaRequest->getTransmitterAddress(); - Tid tid = addbaRequest->getTid(); - auto agreement = new RecipientBlockAckAgreement(originatorAddr, tid, addbaRequest->getStartingSequenceNumber(), addbaResponse->getBufferSize(), addbaResponse->getBlockAckTimeoutValue()); - pendingBlockAckAgreements.push_back({ addbaResponsePacket->getId(), originatorAddr, tid, addbaRequest->getDialogToken(), agreement }); - EV_DETAIL << "Block Ack Agreement is staged with the following parameters: " << *agreement << endl; -} - -RecipientBlockAckAgreement *RecipientBlockAckAgreementHandler::activateAgreement(Packet *addbaResponsePacket, const Ptr& addbaResponse) -{ - auto pendingIt = std::find_if(pendingBlockAckAgreements.begin(), pendingBlockAckAgreements.end(), [addbaResponsePacket](const PendingAgreement& pendingAgreement) { - return pendingAgreement.addbaResponsePacketId == addbaResponsePacket->getId(); - }); - if (pendingIt == pendingBlockAckAgreements.end()) - return nullptr; - if (addbaResponse->getReceiverAddress() != pendingIt->originatorAddress || addbaResponse->getTid() != pendingIt->tid || addbaResponse->getDialogToken() != pendingIt->dialogToken || addbaResponse->getStatusCode() != 0) { - EV_WARN << "Ignoring transmitted ADDBA Response whose fields do not match its staged transaction" << endl; - return nullptr; - } - auto agreement = pendingIt->agreement; - pendingBlockAckAgreements.erase(pendingIt); - auto id = std::make_pair(addbaResponse->getReceiverAddress(), addbaResponse->getTid()); - auto activeIt = blockAckAgreements.find(id); - if (activeIt != blockAckAgreements.end()) { - delete activeIt->second; - activeIt->second = agreement; - } - else - blockAckAgreements[id] = agreement; - agreement->addbaResposneSent(); - agreement->calculateExpirationTime(); - return agreement; -} - // // When a timeout of BlockAckTimeout is detected, the STA shall send a DELBA frame to the peer STA with the Reason Code // field set to TIMEOUT and shall issue a MLME-DELBA.indication primitive with the ReasonCode @@ -155,7 +100,6 @@ const Ptr RecipientBlockAckAgreementHandler::buildAddbaR void RecipientBlockAckAgreementHandler::terminateAgreement(MacAddress originatorAddr, Tid tid) { auto agreementId = std::make_pair(originatorAddr, tid); - clearPendingAgreements(originatorAddr, tid); auto it = blockAckAgreements.find(agreementId); if (it != blockAckAgreements.end()) { RecipientBlockAckAgreement *agreement = it->second; @@ -171,26 +115,31 @@ RecipientBlockAckAgreement *RecipientBlockAckAgreementHandler::getAgreement(Tid return it != blockAckAgreements.end() ? it->second : nullptr; } -RecipientBlockAckAgreement *RecipientBlockAckAgreementHandler::processTransmittedAddbaResp(Packet *addbaRespPacket, const Ptr& addbaResp, IBlockAckAgreementHandlerCallback *callback) -{ - // IEEE Std 802.11-2024, 11.5.2.3: the recipient agreement becomes active - // only when the matching successful ADDBA Response is transmitted. - auto agreement = activateAgreement(addbaRespPacket, addbaResp); - if (agreement != nullptr) - scheduleInactivityTimer(callback); - return agreement; -} - -void RecipientBlockAckAgreementHandler::processReceivedAddbaRequest(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *callback) +RecipientBlockAckAgreement *RecipientBlockAckAgreementHandler::processReceivedAddbaRequest(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) { EV_INFO << "Processing Addba Request from " << addbaRequest->getTransmitterAddress() << endl; bool accepted = addbaRequest->getDialogToken() != 0 && blockAckAgreementPolicy->isAddbaReqAccepted(addbaRequest); EV_DETAIL << "Building Addba Response" << endl; auto addbaResponse = buildAddbaResponse(addbaRequest, blockAckAgreementPolicy, accepted); auto addbaResponsePacket = new Packet("AddbaResponse", addbaResponse); - if (accepted) - stageAgreement(addbaResponsePacket, addbaRequest, addbaResponse); - callback->processMgmtFrame(addbaResponsePacket, addbaResponse); + RecipientBlockAckAgreement *agreement = nullptr; + if (accepted) { + // IEEE Std 802.11-2024, 10.25.2 and 11.5.2.3: accepting the + // request establishes or modifies the recipient agreement when the + // successful response is formed; transmission is not a state gate. + auto id = std::make_pair(addbaRequest->getTransmitterAddress(), addbaRequest->getTid()); + agreement = new RecipientBlockAckAgreement(addbaRequest->getTransmitterAddress(), addbaRequest->getTid(), addbaRequest->getStartingSequenceNumber(), addbaResponse->getBufferSize(), addbaResponse->getBlockAckTimeoutValue()); + auto it = blockAckAgreements.find(id); + if (it != blockAckAgreements.end()) { + delete it->second; + it->second = agreement; + } + else + blockAckAgreements[id] = agreement; + scheduleInactivityTimer(agreementHandlerCallback); + } + procedureCallback->processMgmtFrame(addbaResponsePacket, addbaResponse); + return agreement; } void RecipientBlockAckAgreementHandler::processTransmittedDelba(const Ptr& delba) @@ -208,8 +157,6 @@ RecipientBlockAckAgreementHandler::~RecipientBlockAckAgreementHandler() { for (auto it : blockAckAgreements) delete it.second; - for (auto it : pendingBlockAckAgreements) - delete it.agreement; } } // namespace ieee80211 diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h index 56a66bb1dc0..717f046dddd 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h @@ -8,8 +8,6 @@ #ifndef __INET_RECIPIENTBLOCKACKAGREEMENTHANDLER_H #define __INET_RECIPIENTBLOCKACKAGREEMENTHANDLER_H -#include - #include "inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h" #include "inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h" @@ -27,22 +25,10 @@ class RecipientBlockAckAgreement; class INET_API RecipientBlockAckAgreementHandler : public IRecipientBlockAckAgreementHandler { protected: - struct PendingAgreement { - int64_t addbaResponsePacketId; - MacAddress originatorAddress; - Tid tid; - uint8_t dialogToken; - RecipientBlockAckAgreement *agreement; - }; - std::map, RecipientBlockAckAgreement *> blockAckAgreements; - std::vector pendingBlockAckAgreements; protected: virtual void terminateAgreement(MacAddress originatorAddr, Tid tid); - virtual void clearPendingAgreements(MacAddress originatorAddr, Tid tid); - virtual void stageAgreement(Packet *addbaResponsePacket, const Ptr& addbaRequest, const Ptr& addbaResponse); - virtual RecipientBlockAckAgreement *activateAgreement(Packet *addbaResponsePacket, const Ptr& addbaResponse); virtual const Ptr buildAddbaResponse(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, bool accepted); virtual const Ptr buildDelba(MacAddress receiverAddr, Tid tid, int reasonCode); virtual simtime_t computeEarliestExpirationTime(); @@ -50,8 +36,7 @@ class INET_API RecipientBlockAckAgreementHandler : public IRecipientBlockAckAgre public: virtual ~RecipientBlockAckAgreementHandler(); - virtual RecipientBlockAckAgreement *processTransmittedAddbaResp(Packet *addbaRespPacket, const Ptr& addbaResp, IBlockAckAgreementHandlerCallback *callback) override; - virtual void processReceivedAddbaRequest(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *callback) override; + virtual RecipientBlockAckAgreement *processReceivedAddbaRequest(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) override; virtual void processReceivedDelba(const Ptr& delba, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy) override; virtual void qosFrameReceived(const Ptr& qosHeader, IBlockAckAgreementHandlerCallback *callback) override; virtual void processTransmittedDelba(const Ptr& delba) override; diff --git a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc index 47837ead38a..0ac0cb679dc 100644 --- a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc +++ b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc @@ -187,16 +187,17 @@ ReceiveBuffer *BlockAckReordering::createReceiveBufferIfNecessary(RecipientBlock void BlockAckReordering::processReceivedDelba(const Ptr& delba) { - Tid tid = delba->getTid(); - MacAddress originatorAddr = delba->getTransmitterAddress(); + resetReceiveBuffer(delba->getTid(), delba->getTransmitterAddress()); +} + +void BlockAckReordering::resetReceiveBuffer(Tid tid, MacAddress originatorAddr) +{ auto id = std::make_pair(tid, originatorAddr); auto it = receiveBuffers.find(id); if (it != receiveBuffers.end()) { delete it->second; receiveBuffers.erase(it); } - else - EV_DETAIL << "Receive buffer is not found" << endl; } void BlockAckReordering::passedUp(RecipientBlockAckAgreement *agreement, ReceiveBuffer *receiveBuffer, SequenceNumberCyclic sequenceNumber) @@ -244,4 +245,3 @@ BlockAckReordering::~BlockAckReordering() } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h index a50606f8a53..91578f1b9ac 100644 --- a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h +++ b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h @@ -44,6 +44,7 @@ class INET_API BlockAckReordering virtual ~BlockAckReordering(); void processReceivedDelba(const Ptr& delba); + void resetReceiveBuffer(Tid tid, MacAddress originatorAddr); ReorderBuffer processReceivedQoSFrame(RecipientBlockAckAgreement *agreement, Packet *dataPacket, const Ptr& dataHeader); ReorderBuffer processReceivedBlockAckReq(RecipientBlockAckAgreement *agreement, const Ptr& blockAckReq); }; @@ -52,4 +53,3 @@ class INET_API BlockAckReordering } /* namespace inet */ #endif - diff --git a/src/inet/linklayer/ieee80211/mac/contract/IBlockAckAgreementHandlerCallback.h b/src/inet/linklayer/ieee80211/mac/contract/IBlockAckAgreementHandlerCallback.h index 06ea78814c9..09abd395fd3 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IBlockAckAgreementHandlerCallback.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IBlockAckAgreementHandlerCallback.h @@ -11,6 +11,9 @@ #include "inet/common/INETDefs.h" namespace inet { + +class Packet; + namespace ieee80211 { class INET_API IBlockAckAgreementHandlerCallback @@ -20,6 +23,7 @@ class INET_API IBlockAckAgreementHandlerCallback virtual void scheduleInactivityTimer(simtime_t timeout) = 0; virtual void scheduleAddbaResponseTimer(simtime_t deadline) = 0; + virtual void cancelAddbaTransaction(uint64_t transactionId, Packet *excludedPacket) = 0; }; } // namespace ieee80211 diff --git a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h index 420a34e10c1..2b3231d03b2 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h @@ -25,16 +25,18 @@ class INET_API IOriginatorBlockAckAgreementHandler virtual ~IOriginatorBlockAckAgreementHandler() {} virtual void processReceivedBlockAck(const Ptr& blockAck, IBlockAckAgreementHandlerCallback *callback) = 0; - virtual void processTransmittedAddbaReq(const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; - virtual void processTransmittedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *callback) = 0; + virtual void processTransmittedAddbaReq(Packet *packet, const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; + virtual void processDroppedAddbaReq(Packet *packet, const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; + virtual void processTransmittedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) = 0; virtual OriginatorBlockAckAgreement *processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; virtual void processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy) = 0; virtual void processTransmittedDelba(const Ptr& delba) = 0; virtual void blockAckAgreementExpired(IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) = 0; - virtual void addbaResponseTimeoutExpired(IBlockAckAgreementHandlerCallback *callback) = 0; + virtual void addbaResponseTimeoutExpired(IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; virtual OriginatorBlockAckAgreement *getAgreement(MacAddress receiverAddr, Tid tid) = 0; virtual bool isAddbaResponsePending(MacAddress receiverAddr, Tid tid) const = 0; + virtual bool isAddbaRequestPending(const Packet *packet, const Ptr& addbaReq) const = 0; }; } // namespace ieee80211 diff --git a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementPolicy.h b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementPolicy.h index 40ea82aa95e..11a5f26dfe6 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementPolicy.h @@ -26,7 +26,8 @@ class INET_API IOriginatorBlockAckAgreementPolicy virtual bool isDelbaAccepted(const Ptr& delba) = 0; virtual bool isMsduSupported() const = 0; - virtual simtime_t computeAddbaFailureTimeout() const = 0; + virtual simtime_t getAddbaResponseTimeout() const = 0; + virtual simtime_t computeAddbaRetryBackoff() const = 0; virtual simtime_t getBlockAckTimeoutValue() const = 0; virtual bool isDelayedAckPolicySupported() const = 0; virtual int getMaximumAllowedBufferSize() const = 0; @@ -36,4 +37,3 @@ class INET_API IOriginatorBlockAckAgreementPolicy } // namespace inet #endif - diff --git a/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h index d07f8d71e8c..21b96cf3aee 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h @@ -24,8 +24,7 @@ class INET_API IRecipientBlockAckAgreementHandler public: virtual ~IRecipientBlockAckAgreementHandler() {} - virtual RecipientBlockAckAgreement *processTransmittedAddbaResp(Packet *addbaRespPacket, const Ptr& addbaResp, IBlockAckAgreementHandlerCallback *callback) = 0; - virtual void processReceivedAddbaRequest(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *callback) = 0; + virtual RecipientBlockAckAgreement *processReceivedAddbaRequest(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) = 0; virtual void processReceivedDelba(const Ptr& delba, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy) = 0; virtual void processTransmittedDelba(const Ptr& delba) = 0; virtual void qosFrameReceived(const Ptr& qosHeader, IBlockAckAgreementHandlerCallback *callback) = 0; diff --git a/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosMacDataService.h b/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosMacDataService.h index 8a3ff4c0b7c..f401256e355 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosMacDataService.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosMacDataService.h @@ -27,10 +27,10 @@ class INET_API IRecipientQosMacDataService virtual std::vector dataFrameReceived(Packet *dataPacket, const Ptr& dataHeader, IRecipientBlockAckAgreementHandler *blockAckAgreementHandler) = 0; virtual std::vector controlFrameReceived(Packet *controlPacket, const Ptr& controlHeader, IRecipientBlockAckAgreementHandler *blockAckAgreementHandler) = 0; virtual std::vector managementFrameReceived(Packet *mgmtPacket, const Ptr& mgmtHeader) = 0; + virtual void resetBlockAckReordering(Tid tid, MacAddress originatorAddr) = 0; }; } // namespace ieee80211 } // namespace inet #endif - diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc index 0da41104ace..dd07a97ee0e 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc @@ -72,6 +72,11 @@ void Dcf::channelGranted(IChannelAccess *channelAccess) Enter_Method("channelGranted"); ASSERT(this->channelAccess == channelAccess); if (!frameSequenceHandler->isSequenceRunning()) { + if (this->channelAccess->getInProgressFrames()->getFrameToTransmit() == nullptr) { + EV_DETAIL << "Releasing channel because no frame is available.\n"; + channelAccess->releaseChannel(this); + return; + } frameSequenceHandler->startFrameSequence(new DcfFs(), buildContext(), this); emit(IFrameSequenceHandler::frameSequenceStartedSignal, frameSequenceHandler->getContext()); } @@ -391,4 +396,3 @@ Dcf::~Dcf() } // namespace ieee80211 } // namespace inet - diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc index 2646b3bcf8d..4cfe74f766b 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc @@ -9,6 +9,7 @@ #include "inet/common/ModuleAccess.h" #include "inet/linklayer/ieee80211/mac/Ieee80211Mac.h" +#include "inet/linklayer/ieee80211/mac/blockack/Ieee80211AddbaTransactionTag_m.h" #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h" #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckProcedure.h" #include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h" @@ -24,6 +25,7 @@ using namespace inet::physicallayer; simsignal_t Hcf::edcaCollisionDetectedSignal = cComponent::registerSignal("edcaCollisionDetected"); simsignal_t Hcf::blockAckAgreementAddedSignal = cComponent::registerSignal("blockAckAgreementAdded"); simsignal_t Hcf::blockAckAgreementDeletedSignal = cComponent::registerSignal("blockAckAgreementDeleted"); +simsignal_t Hcf::blockAckAgreementChangedSignal = cComponent::registerSignal("blockAckAgreementChanged"); Define_Module(Hcf); @@ -61,11 +63,32 @@ void Hcf::initialize(int stage) originatorBlockAckProcedure = new OriginatorBlockAckProcedure(); recipientBlockAckProcedure = new RecipientBlockAckProcedure(); originatorDataService->setFrameEligibilityFunction([this](const Packet *packet) { + if (auto addbaReq = dynamicPtrCast(packet->peekAtFront())) + return originatorBlockAckAgreementHandler->isAddbaRequestPending(packet, addbaReq); auto dataHeader = dynamicPtrCast(packet->peekAtFront()); return dataHeader == nullptr || dataHeader->getType() != ST_DATA_WITH_QOS || !originatorBlockAckAgreementHandler->isAddbaResponsePending(dataHeader->getReceiverAddress(), dataHeader->getTid()); }); } } + else if (stage == INITSTAGE_LAST) { + for (int ac = 0; ac < AC_NUMCATEGORIES; ac++) + edca->getEdcaf(AccessCategory(ac))->getPendingQueue()->addPacketDropCallback(this); + } +} + +void Hcf::processDroppedBlockAckSetupFrame(Packet *packet) +{ + if (originatorBlockAckAgreementHandler) { + auto addbaReq = dynamicPtrCast(packet->peekAtFront()); + if (addbaReq != nullptr) + originatorBlockAckAgreementHandler->processDroppedAddbaReq(packet, addbaReq, originatorBlockAckAgreementPolicy, this); + } +} + +void Hcf::handlePacketDropped(Packet *packet) +{ + Enter_Method("handlePacketDropped"); + processDroppedBlockAckSetupFrame(packet); } std::string Hcf::getFrameSequenceInfo() const @@ -104,7 +127,7 @@ void Hcf::handleMessage(cMessage *msg) } else if (msg == addbaResponseTimer) { if (originatorBlockAckAgreementHandler) { - originatorBlockAckAgreementHandler->addbaResponseTimeoutExpired(this); + originatorBlockAckAgreementHandler->addbaResponseTimeoutExpired(originatorBlockAckAgreementPolicy, this); resumeEligibleChannelAccess(); } else @@ -180,6 +203,42 @@ void Hcf::scheduleAddbaResponseTimer(simtime_t deadline) rescheduleAt(deadline, addbaResponseTimer); } +void Hcf::cancelAddbaTransaction(uint64_t transactionId, Packet *excludedPacket) +{ + Enter_Method("cancelAddbaTransaction"); + auto belongsToTransaction = [transactionId, excludedPacket](Packet *packet) { + auto transactionTag = packet->findTag(); + return packet != excludedPacket && transactionTag != nullptr && transactionTag->getTransactionId() == transactionId; + }; + for (int ac = 0; ac < AC_NUMCATEGORIES; ac++) { + auto edcaf = edca->getEdcaf(AccessCategory(ac)); + auto pendingQueue = edcaf->getPendingQueue(); + for (int i = pendingQueue->getNumPackets() - 1; i >= 0; i--) { + auto packet = pendingQueue->getPacket(i); + if (belongsToTransaction(packet)) { + pendingQueue->removePacket(packet); + take(packet); + PacketDropDetails details; + details.setReason(OTHER_PACKET_DROP); + emit(packetDroppedSignal, packet, &details); + delete packet; + } + } + auto inProgressFrames = edcaf->getInProgressFrames(); + for (int i = inProgressFrames->getLength() - 1; i >= 0; i--) { + auto packet = inProgressFrames->getFrames(i); + if (belongsToTransaction(packet)) { + auto header = packet->peekAtFront(); + inProgressFrames->dropFrame(packet); + edcaf->getAckHandler()->dropFrame(header); + PacketDropDetails details; + details.setReason(OTHER_PACKET_DROP); + emit(packetDroppedSignal, packet, &details); + } + } + } +} + void Hcf::processLowerFrame(Packet *packet, const Ptr& header) { Enter_Method("processLowerFrame(%s)", packet->getName()); @@ -224,18 +283,18 @@ void Hcf::channelGranted(IChannelAccess *channelAccess) if (edcaf) { AccessCategory ac = edcaf->getAccessCategory(); EV_DETAIL << "Channel access granted to the " << printAccessCategory(ac) << " queue" << std::endl; - if (!hasFrameToTransmit(ac)) { - EV_DETAIL << "Releasing channel because no eligible frame is available.\n"; - edcaf->releaseChannel(this); - return; - } - edcaf->getTxopProcedure()->startTxop(ac); auto internallyCollidedEdcafs = edca->getInternallyCollidedEdcafs(); if (internallyCollidedEdcafs.size() > 0) { EV_INFO << "Internal collision happened with the following queues:" << std::endl; handleInternalCollision(internallyCollidedEdcafs); emit(edcaCollisionDetectedSignal, (unsigned long)internallyCollidedEdcafs.size()); } + if (!hasFrameToTransmit(ac)) { + EV_DETAIL << "Releasing channel because no eligible frame is available.\n"; + edcaf->releaseChannel(this); + return; + } + edcaf->getTxopProcedure()->startTxop(ac); startFrameSequence(ac); } else @@ -283,6 +342,7 @@ void Hcf::handleInternalCollision(std::vector internallyCollidedEdcafs) edca->getMgmtAndNonQoSRecoveryProcedure()->retryLimitReached(internallyCollidedFrame, mgmtHeader); else ; // TODO + NonQoSDataFrame edcaf->getInProgressFrames()->dropFrame(internallyCollidedFrame); + processDroppedBlockAckSetupFrame(internallyCollidedFrame); edcaf->getAckHandler()->dropFrame(internallyCollidedHeader); PacketDropDetails details; details.setReason(RETRY_LIMIT_REACHED); @@ -365,7 +425,16 @@ void Hcf::recipientProcessReceivedManagementFrame(const Ptr(header)) { - recipientBlockAckAgreementHandler->processReceivedAddbaRequest(addbaRequest, recipientBlockAckAgreementPolicy, this); + bool hadAgreement = recipientBlockAckAgreementHandler->getAgreement(addbaRequest->getTid(), addbaRequest->getTransmitterAddress()) != nullptr; + auto agreement = recipientBlockAckAgreementHandler->processReceivedAddbaRequest(addbaRequest, recipientBlockAckAgreementPolicy, this, this); + if (agreement != nullptr) { + if (hadAgreement) { + recipientDataService->resetBlockAckReordering(addbaRequest->getTid(), addbaRequest->getTransmitterAddress()); + emit(blockAckAgreementChangedSignal, agreement); + } + else + emit(blockAckAgreementAddedSignal, agreement); + } } else if (auto addbaResp = dynamicPtrCast(header)) { auto establishedAgreement = originatorBlockAckAgreementHandler->processReceivedAddbaResp(addbaResp, originatorBlockAckAgreementPolicy, this); @@ -432,6 +501,7 @@ void Hcf::originatorProcessRtsProtectionFailed(Packet *packet) edca->getMgmtAndNonQoSRecoveryProcedure()->retryLimitReached(packet, mgmtHeader); else ; // TODO nonqos data edcaf->getInProgressFrames()->dropFrame(packet); + processDroppedBlockAckSetupFrame(packet); edcaf->getAckHandler()->dropFrame(protectedHeader); EV_INFO << "Dropping RTS/CTS protected frame " << packet->getName() << ", because retry limit is reached.\n"; PacketDropDetails details; @@ -477,7 +547,7 @@ void Hcf::originatorProcessTransmittedDataFrame(Packet *packet, const PtrgetEdcaf(ac); edcaf->getAckHandler()->processTransmittedDataOrMgmtFrame(dataHeader); if (originatorBlockAckAgreementHandler) - originatorBlockAckAgreementHandler->processTransmittedDataFrame(packet, dataHeader, originatorBlockAckAgreementPolicy, this); + originatorBlockAckAgreementHandler->processTransmittedDataFrame(packet, dataHeader, originatorBlockAckAgreementPolicy, this, this); if (dataHeader->getAckPolicy() == NO_ACK) edcaf->getInProgressFrames()->dropFrame(packet); } @@ -489,14 +559,10 @@ void Hcf::originatorProcessTransmittedManagementFrame(Packet *packet, const Ptr< edcaf->getAckHandler()->processTransmittedDataOrMgmtFrame(mgmtHeader); if (auto addbaReq = dynamicPtrCast(mgmtHeader)) { if (originatorBlockAckAgreementHandler) - originatorBlockAckAgreementHandler->processTransmittedAddbaReq(addbaReq, originatorBlockAckAgreementPolicy, this); - } - else if (auto addbaResp = dynamicPtrCast(mgmtHeader)) { - bool hadAgreement = recipientBlockAckAgreementHandler->getAgreement(addbaResp->getTid(), addbaResp->getReceiverAddress()) != nullptr; - auto activatedAgreement = recipientBlockAckAgreementHandler->processTransmittedAddbaResp(packet, addbaResp, this); - if (!hadAgreement && activatedAgreement != nullptr) - emit(blockAckAgreementAddedSignal, activatedAgreement); + originatorBlockAckAgreementHandler->processTransmittedAddbaReq(packet, addbaReq, originatorBlockAckAgreementPolicy, this); } + else if (dynamicPtrCast(mgmtHeader)) + ; // Recipient agreement was established when the successful response was formed. else if (auto delba = dynamicPtrCast(mgmtHeader)) { if (delba->getInitiator()) originatorBlockAckAgreementHandler->processTransmittedDelba(delba); @@ -557,6 +623,7 @@ void Hcf::originatorProcessFailedFrame(Packet *failedPacket) else if (auto mgmtHeader = dynamicPtrCast(failedHeader)) edca->getMgmtAndNonQoSRecoveryProcedure()->retryLimitReached(failedPacket, mgmtHeader); edcaf->getInProgressFrames()->dropFrame(failedPacket); + processDroppedBlockAckSetupFrame(failedPacket); edcaf->getAckHandler()->dropFrame(dynamicPtrCast(failedHeader)); EV_INFO << "Dropping frame " << failedPacket->getName() << ", because retry limit is reached.\n"; PacketDropDetails details; diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h index cb0d4ee16d2..6f0eae4747d 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h @@ -44,12 +44,13 @@ class Ieee80211Mac; /** * Implements IEEE 802.11 Hybrid Coordination Function. */ -class INET_API Hcf : public ICoordinationFunction, public IFrameSequenceHandler::ICallback, public IChannelAccess::ICallback, public ITx::ICallback, public IProcedureCallback, public IBlockAckAgreementHandlerCallback, public ModeSetListener +class INET_API Hcf : public ICoordinationFunction, public IFrameSequenceHandler::ICallback, public IChannelAccess::ICallback, public ITx::ICallback, public IProcedureCallback, public IBlockAckAgreementHandlerCallback, public queueing::IPacketQueue::ICallback, public ModeSetListener { public: static simsignal_t edcaCollisionDetectedSignal; static simsignal_t blockAckAgreementAddedSignal; static simsignal_t blockAckAgreementDeletedSignal; + static simsignal_t blockAckAgreementChangedSignal; protected: Ieee80211Mac *mac = nullptr; @@ -118,6 +119,8 @@ class INET_API Hcf : public ICoordinationFunction, public IFrameSequenceHandler: virtual bool hasFrameToTransmit(AccessCategory ac); virtual void requestEligibleChannelAccess(); virtual void resumeEligibleChannelAccess(); + virtual void processDroppedBlockAckSetupFrame(Packet *packet); + virtual void handlePacketDropped(Packet *packet) override; virtual bool isReceptionInProgress(); // Recipient @@ -161,6 +164,7 @@ class INET_API Hcf : public ICoordinationFunction, public IFrameSequenceHandler: // IProcedureCallback virtual void scheduleInactivityTimer(simtime_t timeout) override; virtual void scheduleAddbaResponseTimer(simtime_t deadline) override; + virtual void cancelAddbaTransaction(uint64_t transactionId, Packet *excludedPacket) override; std::string getFrameSequenceInfo() const; diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.ned b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.ned index 165edc93513..1bb1fd4481f 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.ned +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.ned @@ -51,6 +51,7 @@ module Hcf extends Module like IHcf @signal[datarateSelected](type=double); @signal[blockAckAgreementAdded]; @signal[blockAckAgreementDeleted]; + @signal[blockAckAgreementChanged]; @statistic[packetSentToPeer](title="packets sent"; record=count,sum(packetBytes),vector(packetBytes); interpolationmode=none); @statistic[packetSentToPeerUnicast](title="packets sent: unicast"; source=ieee80211Unicast(packetSentToPeer); record=count,sum(packetBytes),vector(packetBytes); interpolationmode=none); @statistic[packetSentToPeerMulticast](title="packets sent: multicast"; source=ieee80211Multicast(packetSentToPeer); record=count,sum(packetBytes),vector(packetBytes); interpolationmode=none); @@ -76,6 +77,7 @@ module Hcf extends Module like IHcf @statistic[datarateSelected](title="datarates selected"; record=vector; interpolationmode=none); @statistic[blockAckAgreementAdded](title="added block ack agreements"; record=count); @statistic[blockAckAgreementDeleted](title="deleted block ack agreements"; record=count); + @statistic[blockAckAgreementChanged](title="changed block ack agreements"; record=count); @statistic[blockAckAgreementActive](title="active block ack agreements"; source=warmup(count(blockAckAgreementAdded)-count(blockAckAgreementDeleted)); record=vector; interpolationmode=sample-hold; autoWarmupFilter=false); submodules: @@ -134,4 +136,3 @@ module Hcf extends Module like IHcf @display("p=550,600"); } } - diff --git a/src/inet/linklayer/ieee80211/mac/framesequence/HcfFs.cc b/src/inet/linklayer/ieee80211/mac/framesequence/HcfFs.cc index fc81d4d863c..af6b96ea350 100644 --- a/src/inet/linklayer/ieee80211/mac/framesequence/HcfFs.cc +++ b/src/inet/linklayer/ieee80211/mac/framesequence/HcfFs.cc @@ -58,9 +58,10 @@ bool HcfFs::isSelfCtsNeeded(OptionalFs *frameSequence, FrameSequenceContext *con bool HcfFs::hasMoreTxOps(RepeatingFs *frameSequence, FrameSequenceContext *context) { - bool hasFrameToTransmit = context->getInProgressFrames()->hasInProgressFrames(); - if (hasFrameToTransmit) { - auto nextFrameToTransmit = context->getInProgressFrames()->getFrameToTransmit(); + // This predicate is the TXOP continuation boundary, so it may materialize + // an eligible pending frame. Availability queries elsewhere remain pure. + auto nextFrameToTransmit = context->getInProgressFrames()->getFrameToTransmit(); + if (nextFrameToTransmit != nullptr) { const auto& nextHeader = nextFrameToTransmit->peekAtFront(); return frameSequence->getCount() == 0 || (!nextHeader->getReceiverAddress().isMulticast() && context->getQoSContext()->txopProcedure->getRemaining() > 0); } diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.cc b/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.cc index dbefc758f03..3a984705c4e 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.cc @@ -7,6 +7,7 @@ #include "inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.h" +#include "inet/linklayer/ieee80211/mac/blockack/Ieee80211AddbaTransactionTag_m.h" #include "inet/linklayer/ieee80211/mac/fragmentation/Fragmentation.h" #include "inet/linklayer/ieee80211/mac/sequencenumberassignment/NonQoSSequenceNumberAssignment.h" @@ -32,7 +33,13 @@ std::vector *OriginatorMacDataService::fragmentIfNeeded(Packet *frame) auto fragmentSizes = fragmentationPolicy->computeFragmentSizes(frame); if (fragmentSizes.size() != 0) { emit(packetFragmentedSignal, frame); + auto transactionTag = frame->findTag(); + bool hasTransactionTag = transactionTag != nullptr; + auto transactionId = hasTransactionTag ? transactionTag->getTransactionId() : 0; auto fragmentFrames = fragmentation->fragmentFrame(frame, fragmentSizes); + if (hasTransactionTag) + for (auto fragment : *fragmentFrames) + fragment->addTag()->setTransactionId(transactionId); return fragmentFrames; } return nullptr; @@ -63,8 +70,7 @@ std::vector *OriginatorMacDataService::extractFramesToTransmit(queuein for (int i = 0; i < pendingQueue->getNumPackets(); i++) { auto candidate = pendingQueue->getPacket(i); if (isFrameEligible(candidate)) { - pendingQueue->removePacket(candidate); - packet = candidate; + packet = pendingQueue->dequeuePacket(candidate); break; } } diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc index 933bcc859ad..a58823f3d59 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc @@ -11,6 +11,7 @@ #include "inet/linklayer/ieee80211/mac/aggregation/MpduAggregation.h" #include "inet/linklayer/ieee80211/mac/aggregation/MsduAggregation.h" +#include "inet/linklayer/ieee80211/mac/blockack/Ieee80211AddbaTransactionTag_m.h" #include "inet/linklayer/ieee80211/mac/fragmentation/Fragmentation.h" #include "inet/linklayer/ieee80211/mac/sequencenumberassignment/QoSSequenceNumberAssignment.h" @@ -72,7 +73,13 @@ std::vector *OriginatorQosMacDataService::fragmentIfNeeded(Packet *fra auto fragmentSizes = fragmentationPolicy->computeFragmentSizes(frame); if (fragmentSizes.size() != 0) { emit(packetFragmentedSignal, frame); + auto transactionTag = frame->findTag(); + bool hasTransactionTag = transactionTag != nullptr; + auto transactionId = hasTransactionTag ? transactionTag->getTransactionId() : 0; auto fragmentFrames = fragmentation->fragmentFrame(frame, fragmentSizes); + if (hasTransactionTag) + for (auto fragment : *fragmentFrames) + fragment->addTag()->setTransactionId(transactionId); return fragmentFrames; } return nullptr; @@ -110,8 +117,7 @@ std::vector *OriginatorQosMacDataService::extractFramesToTransmit(queu if (aMsduAggregationPolicy && i == 0) packet = aMsduAggregateIfNeeded(pendingQueue); if (!packet) { - pendingQueue->removePacket(candidate); - packet = candidate; + packet = pendingQueue->dequeuePacket(candidate); take(packet); } break; diff --git a/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.h b/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.h index 91c4a10cca8..0efea3f23e8 100644 --- a/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.h +++ b/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.h @@ -49,7 +49,7 @@ class INET_API InProgressFrames : public SimpleModule virtual void dropFrame(Packet *packet); virtual void dropFrames(std::set>> seqAndFragNums); - virtual bool hasInProgressFrames() { ensureHasFrameToTransmit(); return hasEligibleFrameToTransmit(); } + virtual bool hasInProgressFrames() { return hasEligibleFrameToTransmit(); } virtual std::vector getOutstandingFrames(); virtual void clearDroppedFrames(); @@ -59,4 +59,3 @@ class INET_API InProgressFrames : public SimpleModule } /* namespace inet */ #endif - diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc index 77e677bb147..8e8b052d0f2 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc @@ -30,6 +30,12 @@ void RecipientQosMacDataService::initialize() blockAckReordering = new BlockAckReordering(); } +void RecipientQosMacDataService::resetBlockAckReordering(Tid tid, MacAddress originatorAddr) +{ + if (blockAckReordering) + blockAckReordering->resetReceiveBuffer(tid, originatorAddr); +} + Packet *RecipientQosMacDataService::defragment(std::vector completeFragments) { for (auto fragment : completeFragments) { @@ -196,4 +202,3 @@ RecipientQosMacDataService::~RecipientQosMacDataService() } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.h b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.h index e4b8ee28865..91a245645fc 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.h +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.h @@ -49,10 +49,10 @@ class INET_API RecipientQosMacDataService : public IRecipientQosMacDataService, virtual std::vector dataFrameReceived(Packet *dataPacket, const Ptr& dataHeader, IRecipientBlockAckAgreementHandler *blockAckAgreementHandler) override; virtual std::vector controlFrameReceived(Packet *controlPacket, const Ptr& controlHeader, IRecipientBlockAckAgreementHandler *blockAckAgreementHandler) override; virtual std::vector managementFrameReceived(Packet *mgmtPacket, const Ptr& mgmtHeader) override; + virtual void resetBlockAckReordering(Tid tid, MacAddress originatorAddr) override; }; } /* namespace ieee80211 */ } /* namespace inet */ #endif - diff --git a/src/inet/queueing/base/PacketFlowBase.cc b/src/inet/queueing/base/PacketFlowBase.cc index 3ccc24cb908..66b0e60e638 100644 --- a/src/inet/queueing/base/PacketFlowBase.cc +++ b/src/inet/queueing/base/PacketFlowBase.cc @@ -24,6 +24,7 @@ void PacketFlowBase::initialize(int stage) provider.reference(inputGate, false); collector.reference(outputGate, false); collection.reference(inputGate, false); + packetExtractor.reference(inputGate, false); WATCH(inProgressStreamId); } else if (stage == INITSTAGE_QUEUEING) { @@ -168,6 +169,20 @@ Packet *PacketFlowBase::pullPacket(const cGate *gate) return packet; } +Packet *PacketFlowBase::dequeuePacket(Packet *packet) +{ + Enter_Method("dequeuePacket"); + checkPacketStreaming(nullptr); + packet = packetExtractor->dequeuePacket(packet); + take(packet); + emit(packetPulledInSignal, packet); + processPacket(packet); + handlePacketProcessed(packet); + emit(packetPulledOutSignal, packet); + drop(packet); + return packet; +} + Packet *PacketFlowBase::pullPacketStart(const cGate *gate, bps datarate) { Enter_Method("pullPacketStart"); @@ -234,4 +249,3 @@ void PacketFlowBase::handlePullPacketProcessed(Packet *packet, const cGate *gate } // namespace queueing } // namespace inet - diff --git a/src/inet/queueing/base/PacketFlowBase.h b/src/inet/queueing/base/PacketFlowBase.h index d0725d5e173..7a1c9a8c777 100644 --- a/src/inet/queueing/base/PacketFlowBase.h +++ b/src/inet/queueing/base/PacketFlowBase.h @@ -16,17 +16,19 @@ #include "inet/queueing/common/PassivePacketSourceRef.h" #include "inet/queueing/contract/IPacketCollection.h" #include "inet/queueing/contract/IPacketFlow.h" +#include "inet/queueing/contract/IPacketExtractor.h" namespace inet { namespace queueing { -class INET_API PacketFlowBase : public PacketProcessorBase, public virtual IPacketFlow, public virtual IPacketCollection +class INET_API PacketFlowBase : public PacketProcessorBase, public virtual IPacketFlow, public virtual IPacketCollection, public virtual IPacketExtractor { protected: cGate *inputGate = nullptr; ActivePacketSourceRef producer; PassivePacketSourceRef provider; ModuleRef collection; + ModuleRef packetExtractor; cGate *outputGate = nullptr; PassivePacketSinkRef consumer; @@ -82,6 +84,7 @@ class INET_API PacketFlowBase : public PacketProcessorBase, public virtual IPack virtual Packet *getPacket(int index) const override { return collection->getPacket(index); } virtual bool isEmpty() const override { return collection->isEmpty(); } virtual void removePacket(Packet *packet) override { collection->removePacket(packet); } + virtual Packet *dequeuePacket(Packet *packet) override; virtual void removeAllPackets() override { collection->removeAllPackets(); } }; @@ -89,4 +92,3 @@ class INET_API PacketFlowBase : public PacketProcessorBase, public virtual IPack } // namespace inet #endif - diff --git a/src/inet/queueing/base/PacketQueueBase.cc b/src/inet/queueing/base/PacketQueueBase.cc index 4780fa44fb7..ecf7a39e573 100644 --- a/src/inet/queueing/base/PacketQueueBase.cc +++ b/src/inet/queueing/base/PacketQueueBase.cc @@ -7,8 +7,12 @@ #include "inet/queueing/base/PacketQueueBase.h" +#include + #include "inet/common/Simsignals.h" +#include "inet/common/PacketEventTag.h" #include "inet/common/StringFormat.h" +#include "inet/common/TimeTag.h" namespace inet { namespace queueing { @@ -53,6 +57,34 @@ Packet *PacketQueueBase::dequeuePacket() return packet; } +void PacketQueueBase::addPacketDropCallback(IPacketQueue::ICallback *callback) +{ + Enter_Method("addPacketDropCallback"); + if (std::find(packetDropCallbacks.begin(), packetDropCallbacks.end(), callback) == packetDropCallbacks.end()) + packetDropCallbacks.push_back(callback); +} + +void PacketQueueBase::removePacketDropCallback(IPacketQueue::ICallback *callback) +{ + Enter_Method("removePacketDropCallback"); + packetDropCallbacks.erase(std::remove(packetDropCallbacks.begin(), packetDropCallbacks.end(), callback), packetDropCallbacks.end()); +} + +void PacketQueueBase::notifyPacketDropped(Packet *packet) +{ + for (auto callback : packetDropCallbacks) + callback->handlePacketDropped(packet); +} + +void PacketQueueBase::recordPacketDequeued(Packet *packet) +{ + auto queueingTime = simTime() - packet->getArrivalTime(); + auto packetEvent = new PacketEvent(); + insertPacketEvent(this, packet, PEK_QUEUED, 0, queueingTime, packetEvent); + increaseTimeTag(packet, queueingTime, queueingTime); + emit(packetPulledSignal, packet); +} + void PacketQueueBase::emit(simsignal_t signal, cObject *object, cObject *details) { if (signal == packetPushedSignal || signal == packetPushStartedSignal) @@ -92,4 +124,3 @@ std::string PacketQueueBase::resolveDirective(char directive) const } // namespace queueing } // namespace inet - diff --git a/src/inet/queueing/base/PacketQueueBase.h b/src/inet/queueing/base/PacketQueueBase.h index c82b2155cc5..c7415c3d751 100644 --- a/src/inet/queueing/base/PacketQueueBase.h +++ b/src/inet/queueing/base/PacketQueueBase.h @@ -25,12 +25,15 @@ class INET_API PacketQueueBase : public PacketProcessorBase, public virtual IPac cGate *inputGate = nullptr; cGate *outputGate = nullptr; + std::vector packetDropCallbacks; protected: virtual void initialize(int stage) override; virtual void handleMessage(cMessage *message) override; virtual void emit(simsignal_t signal, cObject *object, cObject *details = nullptr) override; + virtual void recordPacketDequeued(Packet *packet); + virtual void notifyPacketDropped(Packet *packet); virtual std::string resolveDirective(char directive) const override; @@ -40,6 +43,8 @@ class INET_API PacketQueueBase : public PacketProcessorBase, public virtual IPac virtual void enqueuePacket(Packet *packet) override; virtual Packet *dequeuePacket() override; + virtual void addPacketDropCallback(IPacketQueue::ICallback *callback) override; + virtual void removePacketDropCallback(IPacketQueue::ICallback *callback) override; virtual void pushPacketStart(Packet *packet, const cGate *gate, bps datarate) override { throw cRuntimeError("Invalid operation"); } virtual void pushPacketEnd(Packet *packet, const cGate *gate) override { throw cRuntimeError("Invalid operation"); } @@ -54,4 +59,3 @@ class INET_API PacketQueueBase : public PacketProcessorBase, public virtual IPac } // namespace inet #endif - diff --git a/src/inet/queueing/contract/IPacketExtractor.h b/src/inet/queueing/contract/IPacketExtractor.h new file mode 100644 index 00000000000..f53b822717c --- /dev/null +++ b/src/inet/queueing/contract/IPacketExtractor.h @@ -0,0 +1,26 @@ +// +// Copyright (C) 2026 OpenSim Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +#ifndef __INET_IPACKETEXTRACTOR_H +#define __INET_IPACKETEXTRACTOR_H + +#include "inet/common/packet/Packet.h" + +namespace inet { +namespace queueing { + +/** Provides semantic pull accounting while extracting a selected packet. */ +class INET_API IPacketExtractor +{ + public: + virtual ~IPacketExtractor() {} + virtual Packet *dequeuePacket(Packet *packet) = 0; +}; + +} // namespace queueing +} // namespace inet + +#endif diff --git a/src/inet/queueing/contract/IPacketQueue.h b/src/inet/queueing/contract/IPacketQueue.h index 159fbb9fb91..cf35bc6f00d 100644 --- a/src/inet/queueing/contract/IPacketQueue.h +++ b/src/inet/queueing/contract/IPacketQueue.h @@ -9,6 +9,7 @@ #define __INET_IPACKETQUEUE_H #include "inet/queueing/contract/IPacketCollection.h" +#include "inet/queueing/contract/IPacketExtractor.h" #include "inet/queueing/contract/IPassivePacketSink.h" #include "inet/queueing/contract/IPassivePacketSource.h" @@ -18,9 +19,15 @@ namespace queueing { /** * This class defines the interface for packet queues. */ -class INET_API IPacketQueue : public virtual IPacketCollection, public virtual IPassivePacketSink, public virtual IPassivePacketSource +class INET_API IPacketQueue : public virtual IPacketCollection, public virtual IPacketExtractor, public virtual IPassivePacketSink, public virtual IPassivePacketSource { public: + class INET_API ICallback { + public: + virtual ~ICallback() {} + virtual void handlePacketDropped(Packet *packet) = 0; + }; + /** * Enqueues the packet into the packet queue. The onwership of the packet * is transferred from the caller to the queue. @@ -36,10 +43,18 @@ class INET_API IPacketQueue : public virtual IPacketCollection, public virtual I * The queue must not be empty. The returned packet must not be nullptr. */ virtual Packet *dequeuePacket() = 0; + + /** + * Dequeues the specified packet with the same accounting as dequeuePacket(). + * The queue must contain the packet. Ownership is transferred to the caller. + */ + virtual Packet *dequeuePacket(Packet *packet) = 0; + + virtual void addPacketDropCallback(ICallback *callback) = 0; + virtual void removePacketDropCallback(ICallback *callback) = 0; }; } // namespace queueing } // namespace inet #endif - diff --git a/src/inet/queueing/queue/CompoundPacketQueueBase.cc b/src/inet/queueing/queue/CompoundPacketQueueBase.cc index d52d3842a69..4f3332cd230 100644 --- a/src/inet/queueing/queue/CompoundPacketQueueBase.cc +++ b/src/inet/queueing/queue/CompoundPacketQueueBase.cc @@ -23,6 +23,14 @@ void CompoundPacketQueueBase::initialize(int stage) consumer.reference(inputGate, true, 1); provider.reference(outputGate, true, -1); collection = check_and_cast(provider.get()); + packetExtractor = check_and_cast(provider.get()); + for (cModule::SubmoduleIterator it(this); !it.end(); it++) { + auto childQueue = dynamic_cast(*it); + if (childQueue != nullptr) { + childQueues.push_back(childQueue); + childQueue->addPacketDropCallback(this); + } + } packetDropperFunction = createDropperFunction(par("dropperClass")); subscribe(packetDroppedSignal, this); subscribe(packetCreatedSignal, this); @@ -62,6 +70,7 @@ void CompoundPacketQueueBase::pushPacket(Packet *packet, const cGate *gate) auto packet = packetDropperFunction->selectPacket(this); EV_INFO << "Dropping packet" << EV_FIELD(packet) << EV_ENDL; removePacket(packet); + notifyPacketDropped(packet); dropPacket(packet, QUEUE_OVERFLOW); } } @@ -86,6 +95,18 @@ void CompoundPacketQueueBase::removePacket(Packet *packet) emit(packetRemovedSignal, packet); } +Packet *CompoundPacketQueueBase::dequeuePacket(Packet *packet) +{ + Enter_Method("dequeuePacket"); + packet = packetExtractor->dequeuePacket(packet); + take(packet); + // The owning leaf/provider has already recorded queue residence. The + // compound boundary mirrors pullPacket() and emits only its pull event. + emit(packetPulledSignal, packet); + drop(packet); + return packet; +} + void CompoundPacketQueueBase::removeAllPackets() { Enter_Method("removeAllPacket"); @@ -125,6 +146,11 @@ void CompoundPacketQueueBase::receiveSignal(cComponent *source, simsignal_t sign throw cRuntimeError("Unknown signal"); } +void CompoundPacketQueueBase::handlePacketDropped(Packet *packet) +{ + Enter_Method("handlePacketDropped"); + notifyPacketDropped(packet); +} + } // namespace queueing } // namespace inet - diff --git a/src/inet/queueing/queue/CompoundPacketQueueBase.h b/src/inet/queueing/queue/CompoundPacketQueueBase.h index 706d9fb32c5..bc00b5b4ad1 100644 --- a/src/inet/queueing/queue/CompoundPacketQueueBase.h +++ b/src/inet/queueing/queue/CompoundPacketQueueBase.h @@ -17,7 +17,7 @@ namespace inet { namespace queueing { -class INET_API CompoundPacketQueueBase : public PacketQueueBase, public cListener +class INET_API CompoundPacketQueueBase : public PacketQueueBase, public cListener, public IPacketQueue::ICallback { protected: int packetCapacity = -1; @@ -26,6 +26,8 @@ class INET_API CompoundPacketQueueBase : public PacketQueueBase, public cListene PassivePacketSinkRef consumer; PassivePacketSourceRef provider; IPacketCollection *collection = nullptr; + IPacketExtractor *packetExtractor = nullptr; + std::vector childQueues; IPacketDropperFunction *packetDropperFunction = nullptr; @@ -37,6 +39,8 @@ class INET_API CompoundPacketQueueBase : public PacketQueueBase, public cListene virtual bool isOverloaded() const; public: + using PacketQueueBase::dequeuePacket; + virtual ~CompoundPacketQueueBase() { delete packetDropperFunction; } virtual int getMaxNumPackets() const override { return packetCapacity; } @@ -48,6 +52,7 @@ class INET_API CompoundPacketQueueBase : public PacketQueueBase, public cListene virtual bool isEmpty() const override { return collection->isEmpty(); } virtual Packet *getPacket(int index) const override { return collection->getPacket(index); } virtual void removePacket(Packet *packet) override; + virtual Packet *dequeuePacket(Packet *packet) override; virtual void removeAllPackets() override; virtual bool supportsPacketPushing(const cGate *gate) const override { return inputGate == gate; } @@ -61,10 +66,10 @@ class INET_API CompoundPacketQueueBase : public PacketQueueBase, public cListene virtual Packet *pullPacket(const cGate *gate) override; virtual void receiveSignal(cComponent *source, simsignal_t signal, cObject *object, cObject *details) override; + virtual void handlePacketDropped(Packet *packet) override; }; } // namespace queueing } // namespace inet #endif - diff --git a/src/inet/queueing/queue/PacketQueue.cc b/src/inet/queueing/queue/PacketQueue.cc index 5cb89c3a638..5eb7d35dd98 100644 --- a/src/inet/queueing/queue/PacketQueue.cc +++ b/src/inet/queueing/queue/PacketQueue.cc @@ -8,9 +8,7 @@ #include "inet/queueing/queue/PacketQueue.h" #include "inet/common/ModuleAccess.h" -#include "inet/common/PacketEventTag.h" #include "inet/common/Simsignals.h" -#include "inet/common/TimeTag.h" #include "inet/queueing/function/PacketComparatorFunction.h" #include "inet/queueing/function/PacketDropperFunction.h" @@ -104,6 +102,7 @@ void PacketQueue::pushPacket(Packet *packet, const cGate *gate) auto packet = packetDropperFunction->selectPacket(this); EV_INFO << "Dropping packet" << EV_FIELD(packet) << EV_ENDL; queue.remove(packet); + notifyPacketDropped(packet); dropPacket(packet, QUEUE_OVERFLOW); } } @@ -126,16 +125,24 @@ Packet *PacketQueue::pullPacket(const cGate *gate) } else queue.pop(); - auto queueingTime = simTime() - packet->getArrivalTime(); - auto packetEvent = new PacketEvent(); - insertPacketEvent(this, packet, PEK_QUEUED, 0, queueingTime, packetEvent); - increaseTimeTag(packet, queueingTime, queueingTime); - emit(packetPulledSignal, packet); + recordPacketDequeued(packet); if (collector != nullptr) animatePullPacket(packet, outputGate, collector.getReferencedGate()); return packet; } +Packet *PacketQueue::dequeuePacket(Packet *packet) +{ + Enter_Method("dequeuePacket"); + EV_INFO << "Dequeuing packet" << EV_FIELD(packet) << EV_ENDL; + queue.remove(packet); + if (buffer != nullptr) + buffer->removePacket(packet); + recordPacketDequeued(packet); + drop(packet); + return packet; +} + void PacketQueue::removePacket(Packet *packet) { Enter_Method("removePacket"); @@ -189,10 +196,10 @@ void PacketQueue::handlePacketRemoved(Packet *packet) if (queue.contains(packet)) { EV_INFO << "Removing packet" << EV_FIELD(packet) << EV_ENDL; queue.remove(packet); + notifyPacketDropped(packet); emit(packetRemovedSignal, packet); } } } // namespace queueing } // namespace inet - diff --git a/src/inet/queueing/queue/PacketQueue.h b/src/inet/queueing/queue/PacketQueue.h index 4debf8541ec..a92a358e4b9 100644 --- a/src/inet/queueing/queue/PacketQueue.h +++ b/src/inet/queueing/queue/PacketQueue.h @@ -45,6 +45,8 @@ class INET_API PacketQueue : public PacketQueueBase, public IPacketBuffer::ICall virtual bool isOverloaded() const; public: + using PacketQueueBase::dequeuePacket; + virtual ~PacketQueue() { delete packetDropperFunction; } virtual cGate *getRegistrationForwardingGate(cGate *gate) override; @@ -58,6 +60,7 @@ class INET_API PacketQueue : public PacketQueueBase, public IPacketBuffer::ICall virtual bool isEmpty() const override { return getNumPackets() == 0; } virtual Packet *getPacket(int index) const override; virtual void removePacket(Packet *packet) override; + virtual Packet *dequeuePacket(Packet *packet) override; virtual void removeAllPackets() override; virtual bool supportsPacketPushing(const cGate *gate) const override { return inputGate == gate; } @@ -77,4 +80,3 @@ class INET_API PacketQueue : public PacketQueueBase, public IPacketBuffer::ICall } // namespace inet #endif - diff --git a/src/inet/queueing/scheduler/LabelScheduler.cc b/src/inet/queueing/scheduler/LabelScheduler.cc index af30138c225..da7c97cc45d 100644 --- a/src/inet/queueing/scheduler/LabelScheduler.cc +++ b/src/inet/queueing/scheduler/LabelScheduler.cc @@ -43,12 +43,45 @@ b LabelScheduler::getTotalLength() const Packet *LabelScheduler::getPacket(int index) const { - throw cRuntimeError("TODO"); + int originalIndex = index; + for (auto collection : collections) { + auto numPackets = collection->getNumPackets(); + if (index < numPackets) + return collection->getPacket(index); + index -= numPackets; + } + throw cRuntimeError("Index %i out of range", originalIndex); } void LabelScheduler::removePacket(Packet *packet) { - throw cRuntimeError("TODO"); + for (auto collection : collections) { + for (int i = 0; i < collection->getNumPackets(); i++) { + if (collection->getPacket(i) == packet) { + collection->removePacket(packet); + return; + } + } + } + throw cRuntimeError("Cannot find packet"); +} + +Packet *LabelScheduler::dequeuePacket(Packet *packet) +{ + Enter_Method("dequeuePacket"); + for (auto collection : collections) { + for (int i = 0; i < collection->getNumPackets(); i++) { + if (collection->getPacket(i) == packet) { + packet = check_and_cast(collection)->dequeuePacket(packet); + take(packet); + handlePacketProcessed(packet); + emit(packetPulledSignal, packet); + drop(packet); + return packet; + } + } + } + throw cRuntimeError("Cannot find packet"); } void LabelScheduler::removeAllPackets() @@ -76,4 +109,3 @@ int LabelScheduler::schedulePacket() } // namespace queueing } // namespace inet - diff --git a/src/inet/queueing/scheduler/LabelScheduler.h b/src/inet/queueing/scheduler/LabelScheduler.h index bb03cd04af6..e94dd4461cc 100644 --- a/src/inet/queueing/scheduler/LabelScheduler.h +++ b/src/inet/queueing/scheduler/LabelScheduler.h @@ -10,11 +10,12 @@ #include "inet/queueing/base/PacketSchedulerBase.h" #include "inet/queueing/contract/IPacketCollection.h" +#include "inet/queueing/contract/IPacketExtractor.h" namespace inet { namespace queueing { -class INET_API LabelScheduler : public PacketSchedulerBase, public virtual IPacketCollection +class INET_API LabelScheduler : public PacketSchedulerBase, public virtual IPacketCollection, public virtual IPacketExtractor { protected: int defaultGateIndex = -1; @@ -35,6 +36,7 @@ class INET_API LabelScheduler : public PacketSchedulerBase, public virtual IPack virtual bool isEmpty() const override { return getNumPackets() == 0; } virtual Packet *getPacket(int index) const override; virtual void removePacket(Packet *packet) override; + virtual Packet *dequeuePacket(Packet *packet) override; virtual void removeAllPackets() override; }; @@ -42,4 +44,3 @@ class INET_API LabelScheduler : public PacketSchedulerBase, public virtual IPack } // namespace inet #endif - diff --git a/src/inet/queueing/scheduler/PriorityScheduler.cc b/src/inet/queueing/scheduler/PriorityScheduler.cc index 346d2b313c2..22d7625b778 100644 --- a/src/inet/queueing/scheduler/PriorityScheduler.cc +++ b/src/inet/queueing/scheduler/PriorityScheduler.cc @@ -71,6 +71,25 @@ void PriorityScheduler::removePacket(Packet *packet) throw cRuntimeError("Cannot find packet"); } +Packet *PriorityScheduler::dequeuePacket(Packet *packet) +{ + Enter_Method("dequeuePacket"); + for (auto collection : collections) { + for (int i = 0; i < collection->getNumPackets(); i++) { + if (collection->getPacket(i) == packet) { + auto extractor = check_and_cast(collection); + packet = extractor->dequeuePacket(packet); + take(packet); + handlePacketProcessed(packet); + emit(packetPulledSignal, packet); + drop(packet); + return packet; + } + } + } + throw cRuntimeError("Cannot find packet"); +} + void PriorityScheduler::removeAllPackets() { Enter_Method("removeAllPackets"); @@ -107,4 +126,3 @@ void PriorityScheduler::handleCanPullPacketChanged(const cGate *gate) } // namespace queueing } // namespace inet - diff --git a/src/inet/queueing/scheduler/PriorityScheduler.h b/src/inet/queueing/scheduler/PriorityScheduler.h index 09d6af3ffb9..81123e0cef4 100644 --- a/src/inet/queueing/scheduler/PriorityScheduler.h +++ b/src/inet/queueing/scheduler/PriorityScheduler.h @@ -10,11 +10,12 @@ #include "inet/queueing/base/PacketSchedulerBase.h" #include "inet/queueing/contract/IPacketCollection.h" +#include "inet/queueing/contract/IPacketExtractor.h" namespace inet { namespace queueing { -class INET_API PriorityScheduler : public PacketSchedulerBase, public virtual IPacketCollection +class INET_API PriorityScheduler : public PacketSchedulerBase, public virtual IPacketCollection, public virtual IPacketExtractor { protected: std::vector collections; @@ -33,6 +34,7 @@ class INET_API PriorityScheduler : public PacketSchedulerBase, public virtual IP virtual bool isEmpty() const override { return getNumPackets() == 0; } virtual Packet *getPacket(int index) const override; virtual void removePacket(Packet *packet) override; + virtual Packet *dequeuePacket(Packet *packet) override; virtual void removeAllPackets() override; virtual void handleCanPullPacketChanged(const cGate *gate) override; @@ -42,4 +44,3 @@ class INET_API PriorityScheduler : public PacketSchedulerBase, public virtual IP } // namespace inet #endif - diff --git a/src/inet/queueing/scheduler/WrrScheduler.cc b/src/inet/queueing/scheduler/WrrScheduler.cc index a551bb69526..152ba3de94e 100644 --- a/src/inet/queueing/scheduler/WrrScheduler.cc +++ b/src/inet/queueing/scheduler/WrrScheduler.cc @@ -58,6 +58,49 @@ b WrrScheduler::getTotalLength() const return totalLength; } +Packet *WrrScheduler::getPacket(int index) const +{ + int originalIndex = index; + for (auto collection : collections) { + auto numPackets = collection->getNumPackets(); + if (index < numPackets) + return collection->getPacket(index); + index -= numPackets; + } + throw cRuntimeError("Index %i out of range", originalIndex); +} + +void WrrScheduler::removePacket(Packet *packet) +{ + for (auto collection : collections) { + for (int i = 0; i < collection->getNumPackets(); i++) { + if (collection->getPacket(i) == packet) { + collection->removePacket(packet); + return; + } + } + } + throw cRuntimeError("Cannot find packet"); +} + +Packet *WrrScheduler::dequeuePacket(Packet *packet) +{ + Enter_Method("dequeuePacket"); + for (auto collection : collections) { + for (int i = 0; i < collection->getNumPackets(); i++) { + if (collection->getPacket(i) == packet) { + packet = check_and_cast(collection)->dequeuePacket(packet); + take(packet); + handlePacketProcessed(packet); + emit(packetPulledSignal, packet); + drop(packet); + return packet; + } + } + } + throw cRuntimeError("Cannot find packet"); +} + void WrrScheduler::removeAllPackets() { Enter_Method("removeAllPackets"); @@ -97,4 +140,3 @@ int WrrScheduler::schedulePacket() } // namespace queueing } // namespace inet - diff --git a/src/inet/queueing/scheduler/WrrScheduler.h b/src/inet/queueing/scheduler/WrrScheduler.h index d14f19b6b78..a763b66899d 100644 --- a/src/inet/queueing/scheduler/WrrScheduler.h +++ b/src/inet/queueing/scheduler/WrrScheduler.h @@ -10,6 +10,7 @@ #include "inet/queueing/base/PacketSchedulerBase.h" #include "inet/queueing/contract/IPacketCollection.h" +#include "inet/queueing/contract/IPacketExtractor.h" namespace inet { namespace queueing { @@ -17,7 +18,7 @@ namespace queueing { /** * This module implements a Weighted Round Robin Scheduler. */ -class INET_API WrrScheduler : public PacketSchedulerBase, public virtual IPacketCollection +class INET_API WrrScheduler : public PacketSchedulerBase, public virtual IPacketCollection, public virtual IPacketExtractor { protected: unsigned int *weights = nullptr; // array of weights (has numInputs elements) @@ -39,8 +40,9 @@ class INET_API WrrScheduler : public PacketSchedulerBase, public virtual IPacket virtual b getTotalLength() const override; virtual bool isEmpty() const override { return getNumPackets() == 0; } - virtual Packet *getPacket(int index) const override { throw cRuntimeError("Invalid operation"); } - virtual void removePacket(Packet *packet) override { throw cRuntimeError("Invalid operation"); } + virtual Packet *getPacket(int index) const override; + virtual void removePacket(Packet *packet) override; + virtual Packet *dequeuePacket(Packet *packet) override; virtual void removeAllPackets() override; }; @@ -48,4 +50,3 @@ class INET_API WrrScheduler : public PacketSchedulerBase, public virtual IPacket } // namespace inet #endif - diff --git a/tests/unit/Ieee80211AddbaTransaction_1.test b/tests/unit/Ieee80211AddbaTransaction_1.test index f6512680414..36564546b7f 100644 --- a/tests/unit/Ieee80211AddbaTransaction_1.test +++ b/tests/unit/Ieee80211AddbaTransaction_1.test @@ -1,18 +1,30 @@ %description: -Test IEEE 802.11 ADDBA transaction identity, response handling, timeout cleanup, SSN wrap-around, and frame eligibility. +Test IEEE 802.11 ADDBA transactions, reordering reset, fragmentation cleanup, DCF/HCF continuation, and queue accounting. %includes: #include +#include "inet/common/PacketEventTag.h" +#include "inet/common/Simsignals.h" +#include "inet/common/TimeTag_m.h" +#include "inet/common/packet/chunk/ByteCountChunk.h" #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h" #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h" +#include "inet/linklayer/ieee80211/mac/blockack/Ieee80211AddbaTransactionTag_m.h" #include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h" #include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h" +#include "inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h" #include "inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementPolicy.h" #include "inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementPolicy.h" +#include "inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.h" +#include "inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h" +#include "inet/linklayer/ieee80211/mac/fragmentation/Fragmentation.h" +#include "inet/linklayer/ieee80211/mac/framesequence/HcfFs.h" #include "inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.h" #include "inet/linklayer/ieee80211/mac/queue/InProgressFrames.h" #include "inet/linklayer/ieee80211/mac/sequencenumberassignment/QoSSequenceNumberAssignment.h" +#include "inet/queueing/queue/CompoundPacketQueueBase.h" +#include "inet/queueing/queue/PacketQueue.h" %global: using namespace inet; @@ -21,13 +33,15 @@ using namespace inet::ieee80211; class TestOriginatorPolicy : public IOriginatorBlockAckAgreementPolicy { public: - simtime_t addbaFailureTimeout = 1; + simtime_t addbaResponseTimeout = 1; + simtime_t addbaRetryBackoff = 2; virtual bool isAddbaReqNeeded(Packet *, const Ptr&) override { return true; } virtual bool isAddbaReqAccepted(const Ptr&, OriginatorBlockAckAgreement *) override { return true; } virtual bool isDelbaAccepted(const Ptr&) override { return true; } virtual bool isMsduSupported() const override { return true; } - virtual simtime_t computeAddbaFailureTimeout() const override { return addbaFailureTimeout; } + virtual simtime_t getAddbaResponseTimeout() const override { return addbaResponseTimeout; } + virtual simtime_t computeAddbaRetryBackoff() const override { return addbaRetryBackoff; } virtual simtime_t getBlockAckTimeoutValue() const override { return 0; } virtual bool isDelayedAckPolicySupported() const override { return false; } virtual int getMaximumAllowedBufferSize() const override { return 64; } @@ -53,8 +67,11 @@ class TestCallback : public IProcedureCallback, public IBlockAckAgreementHandler std::vector managementPackets; Ptr managementHeader; simtime_t addbaDeadline = SIMTIME_MAX; + std::vector cancelledTransactionIds; + std::vector excludedPackets; virtual ~TestCallback() { for (auto packet : managementPackets) delete packet; } + void forgetManagementPacket(Packet *packet) { managementPackets.erase(std::remove(managementPackets.begin(), managementPackets.end(), packet), managementPackets.end()); } virtual void transmitControlResponseFrame(Packet *, const Ptr&, Packet *, const Ptr&) override { throw cRuntimeError("Unexpected control response"); } virtual void processMgmtFrame(Packet *packet, const Ptr& header) override { managementPacket = packet; @@ -63,6 +80,10 @@ class TestCallback : public IProcedureCallback, public IBlockAckAgreementHandler } virtual void scheduleInactivityTimer(simtime_t) override {} virtual void scheduleAddbaResponseTimer(simtime_t deadline) override { addbaDeadline = deadline; } + virtual void cancelAddbaTransaction(uint64_t transactionId, Packet *excludedPacket) override { + cancelledTransactionIds.push_back(transactionId); + excludedPackets.push_back(excludedPacket); + } }; class TestOriginatorHandler : public OriginatorBlockAckAgreementHandler @@ -71,12 +92,24 @@ class TestOriginatorHandler : public OriginatorBlockAckAgreementHandler using OriginatorBlockAckAgreementHandler::buildAddbaRequest; void setNextDialogToken(uint8_t token) { nextDialogToken = token; } void expireNow(MacAddress receiverAddress, Tid tid) { getAgreement(receiverAddress, tid)->setAddbaResponseDeadline(simTime()); } + void allowRetryNow(MacAddress receiverAddress, Tid tid) { addbaRetryDeadlines[std::make_pair(receiverAddress, tid)] = simTime(); } +}; + +class TestBlockAckReordering : public BlockAckReordering +{ + public: + int getNumReceiveBuffers() const { return receiveBuffers.size(); } + ReceiveBuffer *getReceiveBuffer(Tid tid, MacAddress originatorAddress) const { + auto it = receiveBuffers.find(std::make_pair(tid, originatorAddress)); + return it == receiveBuffers.end() ? nullptr : it->second; + } }; class TestPacketQueue : public queueing::IPacketQueue { public: std::vector packets; + int numSelectedDequeues = 0; virtual int getMaxNumPackets() const override { return -1; } virtual int getNumPackets() const override { return packets.size(); } @@ -88,6 +121,9 @@ class TestPacketQueue : public queueing::IPacketQueue virtual void removeAllPackets() override { packets.clear(); } virtual void enqueuePacket(Packet *packet) override { packets.push_back(packet); } virtual Packet *dequeuePacket() override { auto packet = packets.front(); packets.erase(packets.begin()); return packet; } + virtual Packet *dequeuePacket(Packet *packet) override { numSelectedDequeues++; removePacket(packet); return packet; } + virtual void addPacketDropCallback(ICallback *) override {} + virtual void removePacketDropCallback(ICallback *) override {} virtual bool canPullSomePacket(const cGate *) const override { return !packets.empty(); } virtual Packet *canPullPacket(const cGate *) const override { return packets.empty() ? nullptr : packets.front(); } virtual Packet *pullPacket(const cGate *) override { return dequeuePacket(); } @@ -102,6 +138,75 @@ class TestPacketQueue : public queueing::IPacketQueue virtual void pushPacketProgress(Packet *, const cGate *, bps, b, b) override { throw cRuntimeError("Unsupported"); } }; +class TestCompoundPacketQueue : public queueing::CompoundPacketQueueBase +{ + public: + void configure(queueing::IPacketCollection *collection, queueing::IPacketExtractor *packetExtractor) { + this->collection = collection; + this->packetExtractor = packetExtractor; + } + void release(Packet *packet) { + take(packet); + drop(packet); + } +}; + +class TestTailDropper : public queueing::IPacketDropperFunction +{ + public: + virtual Packet *selectPacket(queueing::IPacketCollection *collection) const override { return collection->getPacket(collection->getNumPackets() - 1); } +}; + +class TestRealPacketQueue : public queueing::PacketQueue +{ + public: + void insertPacket(Packet *packet, simtime_t arrivalTime) { + // Stack-constructed test modules don't get the normal OMNeT++ member + // ownership setup, so keep packets owned by the module itself. + queue.setTakeOwnership(false); + take(packet); + packet->setArrivalTime(arrivalTime); + queue.insert(packet); + } + void configureTailDrop(int capacity) { + queue.setTakeOwnership(false); + packetCapacity = capacity; + packetDropperFunction = new TestTailDropper(); + } + void release(Packet *packet) { + take(packet); + drop(packet); + } +}; + +class TestPacketDropCallback : public queueing::IPacketQueue::ICallback +{ + public: + int numDroppedPackets = 0; + int64_t lastDroppedPacketId = -1; + + virtual void handlePacketDropped(Packet *packet) override { + numDroppedPackets++; + lastDroppedPacketId = packet->getId(); + } +}; + +class TestPacketPulledListener : public cListener +{ + public: + cComponent *leaf = nullptr; + cComponent *compound = nullptr; + int numLeafPulls = 0; + int numCompoundPulls = 0; + + virtual void receiveSignal(cComponent *source, simsignal_t, cObject *, cObject *) override { + if (source == leaf) + numLeafPulls++; + else if (source == compound) + numCompoundPulls++; + } +}; + class TestAckHandler : public IAckHandler { public: @@ -113,21 +218,137 @@ class TestAckHandler : public IAckHandler class TestInProgressFrames : public InProgressFrames { public: - void configure(IOriginatorMacDataService *dataService, IAckHandler *ackHandler) { + void configure(IOriginatorMacDataService *dataService, IAckHandler *ackHandler, queueing::IPacketQueue *pendingQueue = nullptr) { this->dataService = dataService; this->ackHandler = ackHandler; + this->pendingQueue = pendingQueue; } void addFrame(Packet *packet) { inProgressFrames.push_back(packet); } bool hasEligibleFrame() { return hasEligibleFrameToTransmit(); } + std::vector releaseFrames() { + std::vector frames = inProgressFrames; + frames.insert(frames.end(), droppedFrames.begin(), droppedFrames.end()); + for (auto frame : frames) + if (frame->getOwner() == this) + drop(frame); + inProgressFrames.clear(); + droppedFrames.clear(); + return frames; + } +}; + +class TestEdcaf : public Edcaf +{ + public: + queueing::IPacketQueue *pendingQueue = nullptr; + InProgressFrames *inProgressFrames = nullptr; + + virtual queueing::IPacketQueue *getPendingQueue() const override { return pendingQueue; } + virtual InProgressFrames *getInProgressFrames() const override { return inProgressFrames; } +}; + +class TestEdca : public Edca +{ + public: + Edcaf *edcaf = nullptr; + + virtual Edcaf *getEdcaf(AccessCategory) const override { return edcaf; } +}; + +class TestHcf : public Hcf +{ + public: + void configure(Edca *edca) { this->edca = edca; } + + protected: + virtual void scheduleAddbaResponseTimer(simtime_t) override {} +}; + +class TestTxopProcedure : public TxopProcedure +{ + public: + virtual simtime_t getRemaining() const override { return 1; } +}; + +class TestEmptyFrameSequence : public IFrameSequence +{ + public: + virtual void startSequence(FrameSequenceContext *, int) override {} + virtual IFrameSequenceStep *prepareStep(FrameSequenceContext *) override { return nullptr; } + virtual bool completeStep(FrameSequenceContext *) override { return true; } + virtual std::string getHistory() const override { return ""; } +}; + +class TestFragmentationPolicy : public IFragmentationPolicy +{ + public: + virtual std::vector computeFragmentSizes(Packet *) override { return { 2, 2 }; } }; class TestOriginatorQosMacDataService : public OriginatorQosMacDataService { public: void enableSequenceNumberAssignment() { sequenceNumberAssignment = new QoSSequenceNumberAssignment(); } + void enableFragmentation(IFragmentationPolicy *policy) { + fragmentationPolicy = policy; + fragmentation = new Fragmentation(); + } + std::vector *fragment(Packet *packet) { return fragmentIfNeeded(packet); } void release(Packet *packet) { drop(packet); } }; +class TestFrameSequenceHandler : public IFrameSequenceHandler +{ + public: + IFrameSequence *frameSequence = nullptr; + FrameSequenceContext *context = nullptr; + bool running = false; + int numStartedSequences = 0; + + virtual ~TestFrameSequenceHandler() { + delete frameSequence; + delete context; + } + virtual const FrameSequenceContext *getContext() const override { return context; } + virtual const IFrameSequence *getFrameSequence() const override { return frameSequence; } + virtual void startFrameSequence(IFrameSequence *frameSequence, FrameSequenceContext *context, ICallback *) override { + this->frameSequence = frameSequence; + this->context = context; + running = true; + numStartedSequences++; + } + virtual void processResponse(Packet *) override {} + virtual void transmissionComplete() override {} + virtual bool isSequenceRunning() override { return running; } + virtual void handleStartRxTimeout() override {} +}; + +class TestDcaf : public Dcaf +{ + public: + int numReleasedChannels = 0; + + void configure(InProgressFrames *inProgressFrames) { this->inProgressFrames = inProgressFrames; } + virtual void releaseChannel(IChannelAccess::ICallback *) override { numReleasedChannels++; } +}; + +class TestDcf : public Dcf +{ + public: + TestFrameSequenceHandler *configure(TestDcaf *channelAccess) { + this->channelAccess = channelAccess; + auto handler = new TestFrameSequenceHandler(); + frameSequenceHandler = handler; + return handler; + } + void grant(TestDcaf *channelAccess) { channelGranted(channelAccess); } + + protected: + virtual FrameSequenceContext *buildContext() override { + return new FrameSequenceContext(MacAddress::UNSPECIFIED_ADDRESS, nullptr, channelAccess->getInProgressFrames(), nullptr, nullptr, new NonQoSContext(nullptr), nullptr); + } +}; + static Ptr makeQosHeader(MacAddress receiverAddress, Tid tid, SequenceNumberCyclic sequenceNumber) { auto header = makeShared(); @@ -183,13 +404,14 @@ MacAddress peer3("00:00:00:00:00:03"); TestOriginatorHandler handler; Packet triggerPacket("trigger"); auto triggerHeader = makeQosHeader(peer1, 3, SequenceNumberCyclic(4095)); -handler.processTransmittedDataFrame(&triggerPacket, triggerHeader, &originatorPolicy, &callback); +handler.processTransmittedDataFrame(&triggerPacket, triggerHeader, &originatorPolicy, &callback, &callback); auto request = dynamicPtrCast(callback.managementHeader); ASSERT(request != nullptr); ASSERT(request->getDialogToken() != 0); ASSERT(request->getStartingSequenceNumber() == SequenceNumberCyclic(0)); -handler.processTransmittedAddbaReq(request, &originatorPolicy, &callback); +handler.processTransmittedAddbaReq(callback.managementPacket, request, &originatorPolicy, &callback); ASSERT(callback.addbaDeadline == simTime() + 1); +auto requestTransactionId = callback.managementPacket->getTag()->getTransactionId(); auto wrongToken = makeResponse(peer1, 3, request->getDialogToken() + 1, 0); ASSERT(handler.processReceivedAddbaResp(wrongToken, &originatorPolicy, &callback) == nullptr); @@ -202,46 +424,124 @@ ASSERT(handler.processReceivedAddbaResp(wrongPeer, &originatorPolicy, &callback) auto success = makeResponse(peer1, 3, request->getDialogToken(), 0); auto established = handler.processReceivedAddbaResp(success, &originatorPolicy, &callback); ASSERT(established != nullptr); +ASSERT(callback.cancelledTransactionIds.back() == requestTransactionId); +ASSERT(callback.excludedPackets.back() == nullptr); ASSERT(established->getStartingSequenceNumber() == SequenceNumberCyclic(0)); ASSERT(!handler.isAddbaResponsePending(peer1, 3)); ASSERT(handler.processReceivedAddbaResp(success, &originatorPolicy, &callback) == nullptr); Packet rejectedTrigger("rejectedTrigger"); auto rejectedHeader = makeQosHeader(peer2, 4, SequenceNumberCyclic(10)); -handler.processTransmittedDataFrame(&rejectedTrigger, rejectedHeader, &originatorPolicy, &callback); +handler.processTransmittedDataFrame(&rejectedTrigger, rejectedHeader, &originatorPolicy, &callback, &callback); auto rejectedRequest = dynamicPtrCast(callback.managementHeader); -handler.processTransmittedAddbaReq(rejectedRequest, &originatorPolicy, &callback); +handler.processTransmittedAddbaReq(callback.managementPacket, rejectedRequest, &originatorPolicy, &callback); auto rejection = makeResponse(peer2, 4, rejectedRequest->getDialogToken(), 1); ASSERT(handler.processReceivedAddbaResp(rejection, &originatorPolicy, &callback) == nullptr); ASSERT(handler.getAgreement(peer2, 4) == nullptr); +handler.processTransmittedDataFrame(&rejectedTrigger, rejectedHeader, &originatorPolicy, &callback, &callback); +ASSERT(handler.getAgreement(peer2, 4) == nullptr); +handler.allowRetryNow(peer2, 4); +handler.processTransmittedDataFrame(&rejectedTrigger, rejectedHeader, &originatorPolicy, &callback, &callback); +ASSERT(handler.isAddbaResponsePending(peer2, 4)); Packet timeoutTrigger("timeoutTrigger"); auto timeoutHeader = makeQosHeader(peer3, 5, SequenceNumberCyclic(20)); -handler.processTransmittedDataFrame(&timeoutTrigger, timeoutHeader, &originatorPolicy, &callback); +handler.processTransmittedDataFrame(&timeoutTrigger, timeoutHeader, &originatorPolicy, &callback, &callback); auto timedRequest = dynamicPtrCast(callback.managementHeader); -handler.processTransmittedAddbaReq(timedRequest, &originatorPolicy, &callback); +auto timedRequestPacket = callback.managementPacket; +auto timedTransactionId = timedRequestPacket->getTag()->getTransactionId(); +handler.processTransmittedAddbaReq(timedRequestPacket, timedRequest, &originatorPolicy, &callback); handler.expireNow(peer3, 5); -handler.addbaResponseTimeoutExpired(&callback); +handler.addbaResponseTimeoutExpired(&originatorPolicy, &callback); ASSERT(handler.getAgreement(peer3, 5) == nullptr); -handler.processTransmittedAddbaReq(timedRequest, &originatorPolicy, &callback); +ASSERT(callback.cancelledTransactionIds.back() == timedTransactionId); +ASSERT(callback.excludedPackets.back() == nullptr); +handler.processTransmittedAddbaReq(timedRequestPacket, timedRequest, &originatorPolicy, &callback); ASSERT(handler.getAgreement(peer3, 5) == nullptr); auto staleResponse = makeResponse(peer3, 5, timedRequest->getDialogToken(), 0); ASSERT(handler.processReceivedAddbaResp(staleResponse, &originatorPolicy, &callback) == nullptr); -handler.processTransmittedDataFrame(&timeoutTrigger, timeoutHeader, &originatorPolicy, &callback); +handler.processTransmittedDataFrame(&timeoutTrigger, timeoutHeader, &originatorPolicy, &callback, &callback); +ASSERT(handler.getAgreement(peer3, 5) == nullptr); +handler.allowRetryNow(peer3, 5); +handler.processTransmittedDataFrame(&timeoutTrigger, timeoutHeader, &originatorPolicy, &callback, &callback); ASSERT(handler.isAddbaResponsePending(peer3, 5)); +// An unsent transaction has no response deadline and is retired by a typed drop callback. +Packet unsentTrigger("unsentTrigger"); +auto unsentHeader = makeQosHeader(peer2, 5, SequenceNumberCyclic(40)); +handler.processTransmittedDataFrame(&unsentTrigger, unsentHeader, &originatorPolicy, &callback, &callback); +ASSERT(handler.isAddbaResponsePending(peer2, 5)); +ASSERT(callback.addbaDeadline == SIMTIME_MAX); +auto unsentRequest = dynamicPtrCast(callback.managementHeader); +auto unsentRequestPacket = callback.managementPacket; +auto unsentTransactionId = unsentRequestPacket->getTag()->getTransactionId(); +handler.processDroppedAddbaReq(unsentRequestPacket, unsentRequest, &originatorPolicy, &callback); +ASSERT(handler.getAgreement(peer2, 5) == nullptr); +ASSERT(callback.cancelledTransactionIds.back() == unsentTransactionId); +ASSERT(callback.excludedPackets.back() == unsentRequestPacket); + +// Fragmentation preserves the local transaction identity on every MPDU, and +// typed cancellation removes all exact siblings while retaining the dropped +// fragment and an unrelated transaction. +{ + TestOriginatorHandler fragmentedHandler; + TestCallback fragmentedCallback; + Packet fragmentedTrigger("fragmentedTrigger"); + auto fragmentedTriggerHeader = makeQosHeader(peer1, 7, SequenceNumberCyclic(50)); + fragmentedHandler.processTransmittedDataFrame(&fragmentedTrigger, fragmentedTriggerHeader, &originatorPolicy, &fragmentedCallback, &fragmentedCallback); + auto unfragmentedRequestPacket = fragmentedCallback.managementPacket; + auto fragmentedTransactionId = unfragmentedRequestPacket->getTag()->getTransactionId(); + fragmentedCallback.forgetManagementPacket(unfragmentedRequestPacket); + unfragmentedRequestPacket->insertAtBack(makeShared(B(4))); + unfragmentedRequestPacket->insertAtBack(makeShared()); + TestFragmentationPolicy fragmentationPolicy; + TestOriginatorQosMacDataService fragmentationDataService; + fragmentationDataService.enableFragmentation(&fragmentationPolicy); + auto fragments = fragmentationDataService.fragment(unfragmentedRequestPacket); + ASSERT(fragments != nullptr); + ASSERT(fragments->size() == 2); + for (auto fragment : *fragments) + ASSERT(fragment->getTag()->getTransactionId() == fragmentedTransactionId); + auto unrelatedPacket = new Packet("unrelatedTransaction", makeShared(B(1))); + unrelatedPacket->addTag()->setTransactionId(fragmentedTransactionId + 1); + auto droppedFragment = fragments->at(0); + auto siblingFragment = fragments->at(1); + TestPacketQueue fragmentedPendingQueue; + fragmentedPendingQueue.packets = { droppedFragment, siblingFragment, unrelatedPacket }; + TestInProgressFrames fragmentedInProgressFrames; + TestEdcaf fragmentedEdcaf; + fragmentedEdcaf.pendingQueue = &fragmentedPendingQueue; + fragmentedEdcaf.inProgressFrames = &fragmentedInProgressFrames; + TestEdca fragmentedEdca; + fragmentedEdca.edcaf = &fragmentedEdcaf; + TestHcf fragmentedHcf; + fragmentedHcf.configure(&fragmentedEdca); + auto droppedRequest = droppedFragment->peekAtFront(); + fragmentedHandler.processDroppedAddbaReq(droppedFragment, droppedRequest, &originatorPolicy, &fragmentedHcf); + ASSERT(fragmentedHandler.getAgreement(peer1, 7) == nullptr); + ASSERT(fragmentedPendingQueue.getNumPackets() == 2); + ASSERT(std::find(fragmentedPendingQueue.packets.begin(), fragmentedPendingQueue.packets.end(), droppedFragment) != fragmentedPendingQueue.packets.end()); + ASSERT(std::find(fragmentedPendingQueue.packets.begin(), fragmentedPendingQueue.packets.end(), siblingFragment) == fragmentedPendingQueue.packets.end()); + ASSERT(std::find(fragmentedPendingQueue.packets.begin(), fragmentedPendingQueue.packets.end(), unrelatedPacket) != fragmentedPendingQueue.packets.end()); + fragmentedPendingQueue.removePacket(droppedFragment); + fragmentedPendingQueue.removePacket(unrelatedPacket); + delete droppedFragment; + delete fragments; + delete unrelatedPacket; +} + { TestOriginatorHandler invalidTimeoutHandler; TestOriginatorPolicy invalidTimeoutPolicy; TestCallback invalidTimeoutCallback; - invalidTimeoutPolicy.addbaFailureTimeout = 0; + invalidTimeoutPolicy.addbaResponseTimeout = 0; Packet invalidTimeoutTrigger("invalidTimeoutTrigger"); auto invalidTimeoutHeader = makeQosHeader(peer2, 1, SequenceNumberCyclic(30)); - invalidTimeoutHandler.processTransmittedDataFrame(&invalidTimeoutTrigger, invalidTimeoutHeader, &invalidTimeoutPolicy, &invalidTimeoutCallback); - auto invalidTimeoutRequest = dynamicPtrCast(invalidTimeoutCallback.managementHeader); bool threw = false; try { - invalidTimeoutHandler.processTransmittedAddbaReq(invalidTimeoutRequest, &invalidTimeoutPolicy, &invalidTimeoutCallback); + invalidTimeoutHandler.processTransmittedDataFrame(&invalidTimeoutTrigger, invalidTimeoutHeader, &invalidTimeoutPolicy, &invalidTimeoutCallback, &invalidTimeoutCallback); + auto invalidRequest = dynamicPtrCast(invalidTimeoutCallback.managementHeader); + invalidTimeoutHandler.processTransmittedAddbaReq(invalidTimeoutCallback.managementPacket, invalidRequest, &invalidTimeoutPolicy, &invalidTimeoutCallback); } catch (cRuntimeError&) { threw = true; @@ -249,8 +549,8 @@ ASSERT(handler.isAddbaResponsePending(peer3, 5)); ASSERT(threw); } -// IEEE Std 802.11-2024, 9.6.4.2, 10.25.6.6.1, and 11.5.2.3. -RecipientBlockAckAgreementHandler recipientHandler; +// IEEE Std 802.11-2024, 9.6.4.2, 10.25.2, and 11.5.2.3. +RecipientBlockAckAgreementHandler immediateRecipientHandler; TestRecipientPolicy recipientPolicy; TestCallback recipientCallback; auto recipientRequest = makeShared(); @@ -261,173 +561,46 @@ recipientRequest->setStartingSequenceNumber(SequenceNumberCyclic(0)); recipientRequest->setBufferSize(64); recipientRequest->setBlockAckPolicy(1); recipientRequest->setBlockAckTimeoutValue(0); -recipientHandler.processReceivedAddbaRequest(recipientRequest, &recipientPolicy, &recipientCallback); -auto acceptedResponsePacket = recipientCallback.managementPacket; +auto firstImmediateAgreement = immediateRecipientHandler.processReceivedAddbaRequest(recipientRequest, &recipientPolicy, &recipientCallback, &recipientCallback); +ASSERT(firstImmediateAgreement != nullptr); +ASSERT(immediateRecipientHandler.getAgreement(6, peer1) == firstImmediateAgreement); +ASSERT(firstImmediateAgreement->getStartingSequenceNumber() == SequenceNumberCyclic(0)); auto acceptedResponse = dynamicPtrCast(recipientCallback.managementHeader); ASSERT(acceptedResponse->getDialogToken() == 77); -ASSERT(acceptedResponse->getTid() == 6); ASSERT(acceptedResponse->getStatusCode() == 0); -ASSERT(recipientHandler.getAgreement(6, peer1) == nullptr); -auto wrongRecipientTokenResponse = makeShared(); -wrongRecipientTokenResponse->setReceiverAddress(peer1); -wrongRecipientTokenResponse->setTid(6); -wrongRecipientTokenResponse->setDialogToken(78); -wrongRecipientTokenResponse->setStatusCode(0); -ASSERT(recipientHandler.processTransmittedAddbaResp(acceptedResponsePacket, wrongRecipientTokenResponse, &recipientCallback) == nullptr); -ASSERT(recipientHandler.getAgreement(6, peer1) == nullptr); - -// Exercise the real management-frame extraction and sequence-assignment path: -// the mutable header is copied, but the Packet/message identity is preserved. -TestOriginatorQosMacDataService responseDataService; -responseDataService.enableSequenceNumberAssignment(); -TestPacketQueue responseQueue; -responseQueue.packets = { acceptedResponsePacket }; -auto extractedResponses = responseDataService.extractFramesToTransmit(&responseQueue); -ASSERT(extractedResponses->size() == 1); -auto transmittedResponsePacket = extractedResponses->at(0); -auto transmittedResponse = dynamicPtrCast(transmittedResponsePacket->peekAtFront()); -ASSERT(transmittedResponsePacket == acceptedResponsePacket); -ASSERT(transmittedResponse != acceptedResponse); -auto firstRecipientAgreement = recipientHandler.processTransmittedAddbaResp(transmittedResponsePacket, transmittedResponse, &recipientCallback); -delete extractedResponses; -ASSERT(firstRecipientAgreement != nullptr); -ASSERT(firstRecipientAgreement->getStartingSequenceNumber() == SequenceNumberCyclic(0)); -ASSERT(recipientHandler.processTransmittedAddbaResp(transmittedResponsePacket, transmittedResponse, &recipientCallback) == nullptr); -responseDataService.release(transmittedResponsePacket); - -recipientRequest->setDialogToken(79); -recipientRequest->setStartingSequenceNumber(SequenceNumberCyclic(9)); -recipientHandler.processReceivedAddbaRequest(recipientRequest, &recipientPolicy, &recipientCallback); -auto replacementResponsePacket = recipientCallback.managementPacket; -auto replacementResponse = dynamicPtrCast(recipientCallback.managementHeader); -ASSERT(replacementResponse->getDialogToken() == 79); -ASSERT(recipientHandler.getAgreement(6, peer1) == firstRecipientAgreement); -ASSERT(recipientHandler.getAgreement(6, peer1)->getStartingSequenceNumber() == SequenceNumberCyclic(0)); -auto replacementAgreement = recipientHandler.processTransmittedAddbaResp(replacementResponsePacket, replacementResponse, &recipientCallback); -ASSERT(replacementAgreement != nullptr); -ASSERT(replacementAgreement != firstRecipientAgreement); -ASSERT(recipientHandler.getAgreement(6, peer1) == replacementAgreement); -ASSERT(replacementAgreement->getStartingSequenceNumber() == SequenceNumberCyclic(9)); - -recipientPolicy.accepted = false; -recipientRequest->setDialogToken(80); -recipientRequest->setStartingSequenceNumber(SequenceNumberCyclic(12)); -recipientHandler.processReceivedAddbaRequest(recipientRequest, &recipientPolicy, &recipientCallback); -auto refusedResponsePacket = recipientCallback.managementPacket; -auto refusedResponse = dynamicPtrCast(recipientCallback.managementHeader); -ASSERT(refusedResponse->getDialogToken() == 80); -ASSERT(refusedResponse->getTid() == 6); -ASSERT(refusedResponse->getStatusCode() == 1); -ASSERT(recipientHandler.getAgreement(6, peer1) == replacementAgreement); -ASSERT(recipientHandler.processTransmittedAddbaResp(refusedResponsePacket, refusedResponse, &recipientCallback) == nullptr); -ASSERT(recipientHandler.getAgreement(6, peer1) == replacementAgreement); - -recipientPolicy.accepted = true; -recipientRequest->setTransmitterAddress(peer3); -recipientRequest->setTid(2); -recipientRequest->setDialogToken(0); -recipientHandler.processReceivedAddbaRequest(recipientRequest, &recipientPolicy, &recipientCallback); -auto zeroTokenResponse = dynamicPtrCast(recipientCallback.managementHeader); -ASSERT(zeroTokenResponse->getDialogToken() == 0); -ASSERT(zeroTokenResponse->getStatusCode() == 1); -ASSERT(recipientHandler.getAgreement(2, peer3) == nullptr); - -// Overlapping accepted responses retain their own negotiated parameters. The -// exact response object, rather than peer/TID/token alone, owns staged state. -{ - RecipientBlockAckAgreementHandler overlapHandler; - TestRecipientPolicy overlapPolicy; - TestCallback overlapCallback; - auto requestA = makeRequest(peer1, 1, 90, SequenceNumberCyclic(100), 16); - auto requestB = makeRequest(peer1, 1, 91, SequenceNumberCyclic(200), 32); - overlapHandler.processReceivedAddbaRequest(requestA, &overlapPolicy, &overlapCallback); - auto responsePacketA = overlapCallback.managementPacket; - auto responseA = dynamicPtrCast(overlapCallback.managementHeader); - overlapHandler.processReceivedAddbaRequest(requestB, &overlapPolicy, &overlapCallback); - auto responsePacketB = overlapCallback.managementPacket; - auto responseB = dynamicPtrCast(overlapCallback.managementHeader); - ASSERT(overlapHandler.getAgreement(1, peer1) == nullptr); - auto agreementA = overlapHandler.processTransmittedAddbaResp(responsePacketA, responseA, &overlapCallback); - ASSERT(agreementA->getStartingSequenceNumber() == SequenceNumberCyclic(100)); - ASSERT(agreementA->getBufferSize() == 16); - auto agreementB = overlapHandler.processTransmittedAddbaResp(responsePacketB, responseB, &overlapCallback); - ASSERT(agreementB->getStartingSequenceNumber() == SequenceNumberCyclic(200)); - ASSERT(agreementB->getBufferSize() == 32); - ASSERT(overlapHandler.getAgreement(1, peer1) == agreementB); - ASSERT(overlapHandler.processTransmittedAddbaResp(responsePacketA, responseA, &overlapCallback) == nullptr); -} +// Renegotiation replaces the recipient agreement and the HCF reset boundary +// must discard the old reorder window before frames use the new SSN. +RecipientBlockAckAgreement *replacementImmediateAgreement = nullptr; { - RecipientBlockAckAgreementHandler overlapHandler; - TestRecipientPolicy overlapPolicy; - TestCallback overlapCallback; - // Reusing a token cannot make one queued response activate the other's SSN. - auto requestA = makeRequest(peer1, 1, 92, SequenceNumberCyclic(300), 24); - auto requestB = makeRequest(peer1, 1, 92, SequenceNumberCyclic(400), 40); - overlapHandler.processReceivedAddbaRequest(requestA, &overlapPolicy, &overlapCallback); - auto responsePacketA = overlapCallback.managementPacket; - auto responseA = dynamicPtrCast(overlapCallback.managementHeader); - overlapHandler.processReceivedAddbaRequest(requestB, &overlapPolicy, &overlapCallback); - auto responsePacketB = overlapCallback.managementPacket; - auto responseB = dynamicPtrCast(overlapCallback.managementHeader); - auto agreementB = overlapHandler.processTransmittedAddbaResp(responsePacketB, responseB, &overlapCallback); - ASSERT(agreementB->getStartingSequenceNumber() == SequenceNumberCyclic(400)); - ASSERT(agreementB->getBufferSize() == 40); - auto agreementA = overlapHandler.processTransmittedAddbaResp(responsePacketA, responseA, &overlapCallback); - ASSERT(agreementA->getStartingSequenceNumber() == SequenceNumberCyclic(300)); - ASSERT(agreementA->getBufferSize() == 24); - ASSERT(overlapHandler.getAgreement(1, peer1) == agreementA); - ASSERT(overlapHandler.processTransmittedAddbaResp(responsePacketB, responseB, &overlapCallback) == nullptr); + TestBlockAckReordering reordering; + auto oldWindowHeader = makeQosHeader(peer1, 6, SequenceNumberCyclic(1)); + auto oldWindowPacket = new Packet("oldWindowPacket", oldWindowHeader); + ASSERT(reordering.processReceivedQoSFrame(firstImmediateAgreement, oldWindowPacket, oldWindowHeader).empty()); + ASSERT(reordering.getNumReceiveBuffers() == 1); + ASSERT(reordering.getReceiveBuffer(6, peer1)->getLength() == 1); + + recipientRequest->setDialogToken(79); + recipientRequest->setStartingSequenceNumber(SequenceNumberCyclic(9)); + replacementImmediateAgreement = immediateRecipientHandler.processReceivedAddbaRequest(recipientRequest, &recipientPolicy, &recipientCallback, &recipientCallback); + ASSERT(replacementImmediateAgreement != nullptr); + ASSERT(replacementImmediateAgreement != firstImmediateAgreement); + ASSERT(immediateRecipientHandler.getAgreement(6, peer1) == replacementImmediateAgreement); + ASSERT(replacementImmediateAgreement->getStartingSequenceNumber() == SequenceNumberCyclic(9)); + reordering.resetReceiveBuffer(6, peer1); + ASSERT(reordering.getNumReceiveBuffers() == 0); + auto newWindowHeader = makeQosHeader(peer1, 6, SequenceNumberCyclic(10)); + auto newWindowPacket = new Packet("newWindowPacket", newWindowHeader); + ASSERT(reordering.processReceivedQoSFrame(replacementImmediateAgreement, newWindowPacket, newWindowHeader).empty()); + ASSERT(reordering.getReceiveBuffer(6, peer1)->getNextExpectedSequenceNumber() == SequenceNumberCyclic(9)); + ASSERT(reordering.getReceiveBuffer(6, peer1)->getLength() == 1); } -// A rejected overlapping request creates no staged agreement and does not -// discard either the active agreement or an earlier accepted transaction. -{ - RecipientBlockAckAgreementHandler overlapHandler; - TestRecipientPolicy overlapPolicy; - TestCallback overlapCallback; - auto initialRequest = makeRequest(peer2, 2, 93, SequenceNumberCyclic(10), 8); - overlapHandler.processReceivedAddbaRequest(initialRequest, &overlapPolicy, &overlapCallback); - auto initialResponsePacket = overlapCallback.managementPacket; - auto initialResponse = dynamicPtrCast(overlapCallback.managementHeader); - auto initialAgreement = overlapHandler.processTransmittedAddbaResp(initialResponsePacket, initialResponse, &overlapCallback); - auto requestA = makeRequest(peer2, 2, 94, SequenceNumberCyclic(500), 48); - overlapHandler.processReceivedAddbaRequest(requestA, &overlapPolicy, &overlapCallback); - auto responsePacketA = overlapCallback.managementPacket; - auto responseA = dynamicPtrCast(overlapCallback.managementHeader); - overlapPolicy.accepted = false; - auto requestB = makeRequest(peer2, 2, 95, SequenceNumberCyclic(600), 56); - overlapHandler.processReceivedAddbaRequest(requestB, &overlapPolicy, &overlapCallback); - auto responsePacketB = overlapCallback.managementPacket; - auto responseB = dynamicPtrCast(overlapCallback.managementHeader); - ASSERT(overlapHandler.getAgreement(2, peer2) == initialAgreement); - ASSERT(overlapHandler.processTransmittedAddbaResp(responsePacketB, responseB, &overlapCallback) == nullptr); - ASSERT(overlapHandler.getAgreement(2, peer2) == initialAgreement); - auto agreementA = overlapHandler.processTransmittedAddbaResp(responsePacketA, responseA, &overlapCallback); - ASSERT(agreementA->getStartingSequenceNumber() == SequenceNumberCyclic(500)); - ASSERT(agreementA->getBufferSize() == 48); - ASSERT(overlapHandler.getAgreement(2, peer2) == agreementA); -} - -{ - RecipientBlockAckAgreementHandler overlapHandler; - TestRecipientPolicy overlapPolicy; - TestCallback overlapCallback; - auto requestA = makeRequest(peer2, 2, 96, SequenceNumberCyclic(700), 20); - overlapHandler.processReceivedAddbaRequest(requestA, &overlapPolicy, &overlapCallback); - auto responsePacketA = overlapCallback.managementPacket; - auto responseA = dynamicPtrCast(overlapCallback.managementHeader); - overlapPolicy.accepted = false; - auto requestB = makeRequest(peer2, 2, 97, SequenceNumberCyclic(800), 28); - overlapHandler.processReceivedAddbaRequest(requestB, &overlapPolicy, &overlapCallback); - auto responsePacketB = overlapCallback.managementPacket; - auto responseB = dynamicPtrCast(overlapCallback.managementHeader); - auto agreementA = overlapHandler.processTransmittedAddbaResp(responsePacketA, responseA, &overlapCallback); - ASSERT(agreementA->getStartingSequenceNumber() == SequenceNumberCyclic(700)); - ASSERT(agreementA->getBufferSize() == 20); - ASSERT(overlapHandler.processTransmittedAddbaResp(responsePacketB, responseB, &overlapCallback) == nullptr); - ASSERT(overlapHandler.getAgreement(2, peer2) == agreementA); -} +recipientPolicy.accepted = false; +recipientRequest->setDialogToken(80); +recipientRequest->setStartingSequenceNumber(SequenceNumberCyclic(12)); +ASSERT(immediateRecipientHandler.processReceivedAddbaRequest(recipientRequest, &recipientPolicy, &recipientCallback, &recipientCallback) == nullptr); +ASSERT(immediateRecipientHandler.getAgreement(6, peer1) == replacementImmediateAgreement); OriginatorQosMacDataService dataService; dataService.setFrameEligibilityFunction([&handler](const Packet *packet) { @@ -462,6 +635,147 @@ inProgressFrames.addFrame(prefetchedOtherTidFrame); ASSERT(inProgressFrames.hasEligibleFrame()); ASSERT(inProgressFrames.getFrameToTransmit() == prefetchedOtherTidFrame); +// Availability checks must not extract, aggregate, or assign a sequence number. +TestPacketQueue deferredQueue; +auto deferredFrame = new Packet("deferredFrame", makeQosHeader(peer2, 4, SequenceNumberCyclic(123))); +deferredQueue.packets = { deferredFrame }; +TestInProgressFrames deferredInProgressFrames; +deferredInProgressFrames.configure(&dataService, &ackHandler, &deferredQueue); +ASSERT(!deferredInProgressFrames.hasInProgressFrames()); +ASSERT(deferredQueue.getNumPackets() == 1); +ASSERT(deferredFrame->peekAtFront()->getSequenceNumber() == SequenceNumberCyclic(123)); +delete deferredFrame; + +// A real leaf queue records selected-dequeue residence once, while the +// compound boundary emits its own semantic pull without duplicating it. +Packet *compoundPacket = nullptr; +{ + TestPacketPulledListener pullListener; + TestRealPacketQueue leafQueue; + TestCompoundPacketQueue compoundQueue; + compoundQueue.configure(&leafQueue, &leafQueue); + pullListener.leaf = &leafQueue; + pullListener.compound = &compoundQueue; + leafQueue.subscribe(packetPulledSignal, &pullListener); + compoundQueue.subscribe(packetPulledSignal, &pullListener); + compoundPacket = new Packet("compoundPacket", makeQosHeader(peer1, 1, SequenceNumberCyclic(1))); + auto queueingTimeTag = compoundPacket->addRegionTag(); + queueingTimeTag->setBitTotalTimesArraySize(1); + queueingTimeTag->setBitTotalTimes(0, 0); + queueingTimeTag->setPacketTotalTimesArraySize(1); + queueingTimeTag->setPacketTotalTimes(0, 0); + compoundPacket->addRegionTag(); + leafQueue.insertPacket(compoundPacket, simTime() - 1); + ASSERT(compoundQueue.dequeuePacket(compoundPacket) == compoundPacket); + int numQueuedEvents = 0; + compoundPacket->mapAllRegionTags(b(0), compoundPacket->getTotalLength(), [&](b, b, const Ptr& tag) { + for (size_t i = 0; i < tag->getPacketEventsArraySize(); i++) + if (tag->getPacketEvents(i)->getKind() == PEK_QUEUED) + numQueuedEvents++; + }); + simtime_t totalQueueingTime = -1; + compoundPacket->mapAllRegionTags(b(0), compoundPacket->getTotalLength(), [&](b, b, const Ptr& tag) { + ASSERT(tag->getPacketTotalTimesArraySize() == 1); + totalQueueingTime = tag->getPacketTotalTimes(0); + }); + ASSERT(numQueuedEvents == 1); + ASSERT(totalQueueingTime == 1); + ASSERT(leafQueue.getNumPackets() == 0); + ASSERT(pullListener.numLeafPulls == 1); + ASSERT(pullListener.numCompoundPulls == 1); + leafQueue.unsubscribe(packetPulledSignal, &pullListener); + compoundQueue.unsubscribe(packetPulledSignal, &pullListener); + compoundQueue.release(compoundPacket); +} +delete compoundPacket; + +// Internal overflow invokes the typed queue callback once for the destructive +// discard and never for the packet later removed normally. +{ + TestRealPacketQueue overflowQueue; + TestPacketDropCallback dropCallback; + overflowQueue.configureTailDrop(1); + overflowQueue.addPacketDropCallback(&dropCallback); + auto retainedPacket = new Packet("retainedPacket", makeShared(B(1))); + auto overflowPacket = new Packet("overflowPacket", makeShared(B(1))); + auto overflowPacketId = overflowPacket->getId(); + overflowQueue.pushPacket(retainedPacket, nullptr); + overflowQueue.pushPacket(overflowPacket, nullptr); + ASSERT(dropCallback.numDroppedPackets == 1); + ASSERT(dropCallback.lastDroppedPacketId == overflowPacketId); + overflowQueue.removePacketDropCallback(&dropCallback); + overflowQueue.removePacket(retainedPacket); + overflowQueue.release(retainedPacket); + ASSERT(dropCallback.numDroppedPackets == 1); + delete retainedPacket; +} + +// At the TXOP continuation boundary, an eligible pending frame is +// materialized by the real repeating-sequence lifecycle. +{ + TestOriginatorQosMacDataService txopDataService; + TestPacketQueue txopPendingQueue; + auto initialTxopFrame = new Packet("initialTxopFrame", makeQosHeader(peer1, 1, SequenceNumberCyclic(2))); + auto continuedTxopFrame = new Packet("continuedTxopFrame", makeQosHeader(peer1, 1, SequenceNumberCyclic(3))); + txopPendingQueue.packets = { continuedTxopFrame }; + TestInProgressFrames txopInProgressFrames; + txopInProgressFrames.configure(&txopDataService, &ackHandler, &txopPendingQueue); + txopInProgressFrames.addFrame(initialTxopFrame); + TestTxopProcedure txopProcedure; + FrameSequenceContext txopContext(MacAddress::UNSPECIFIED_ADDRESS, nullptr, &txopInProgressFrames, nullptr, nullptr, nullptr, new QoSContext(nullptr, nullptr, nullptr, &txopProcedure)); + HcfFs hcfFs; + RepeatingFs repeatingFs(new TestEmptyFrameSequence(), [&hcfFs](RepeatingFs *frameSequence, FrameSequenceContext *context) { return hcfFs.hasMoreTxOps(frameSequence, context); }); + repeatingFs.startSequence(&txopContext, 0); + ASSERT(repeatingFs.getCount() == 1); + ASSERT(txopPendingQueue.getNumPackets() == 1); + txopInProgressFrames.dropFrame(initialTxopFrame); + repeatingFs.repeatSequence(&txopContext); + ASSERT(repeatingFs.getCount() == 2); + ASSERT(txopPendingQueue.getNumPackets() == 0); + ASSERT(txopPendingQueue.numSelectedDequeues == 1); + ASSERT(txopInProgressFrames.getFrameToTransmit() == continuedTxopFrame); + auto txopFrames = txopInProgressFrames.releaseFrames(); + ASSERT(txopFrames.size() == 2); + for (auto frame : txopFrames) + delete frame; +} + +// Legacy DCF materializes its first queued frame before starting DcfFs, and a +// stale empty grant is released without dereferencing a null frame. +{ + TestOriginatorQosMacDataService dcfDataService; + TestPacketQueue dcfPendingQueue; + TestInProgressFrames dcfInProgressFrames; + dcfInProgressFrames.configure(&dcfDataService, &ackHandler, &dcfPendingQueue); + TestDcaf dcaf; + dcaf.configure(&dcfInProgressFrames); + TestDcf dcf; + auto dcfHandler = dcf.configure(&dcaf); + dcf.grant(&dcaf); + ASSERT(dcaf.numReleasedChannels == 1); + ASSERT(dcfHandler->numStartedSequences == 0); +} +{ + TestOriginatorQosMacDataService dcfDataService; + TestPacketQueue dcfPendingQueue; + auto firstDcfFrame = new Packet("firstDcfFrame", makeQosHeader(peer1, 0, SequenceNumberCyclic(0))); + dcfPendingQueue.packets = { firstDcfFrame }; + TestInProgressFrames dcfInProgressFrames; + dcfInProgressFrames.configure(&dcfDataService, &ackHandler, &dcfPendingQueue); + TestDcaf dcaf; + dcaf.configure(&dcfInProgressFrames); + TestDcf dcf; + auto dcfHandler = dcf.configure(&dcaf); + dcf.grant(&dcaf); + ASSERT(dcaf.numReleasedChannels == 0); + ASSERT(dcfHandler->numStartedSequences == 1); + ASSERT(dcfPendingQueue.getNumPackets() == 0); + ASSERT(dcfInProgressFrames.getFrameToTransmit() == firstDcfFrame); + auto dcfFrames = dcfInProgressFrames.releaseFrames(); + ASSERT(dcfFrames.size() == 1); + delete dcfFrames.at(0); +} + EV << "ADDBA transaction and negotiated SSN integrity checks passed.\n"; %contains: stdout From f61ad58b13c65353c6d7977db283d42a9d26563f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Mon, 17 Aug 2026 03:01:57 +0200 Subject: [PATCH 03/14] Fix ADDBA transaction lifecycle and frame eligibility Defer ADDBA initiation until the triggering QoS MPDU is acknowledged and its final fragment completes. This preserves the negotiated starting sequence number and prevents an outstanding retry or remaining fragment from falling below the recipient reorder window. Resume eligible channel access when a dropped setup frame terminates a pending transaction, and cancel tagged ADDBA requests when DELBA tears down pending state. Restore DCF pending-frame materialization and defensively handle internal collisions with no selectable frame. Use the configured EDCAF count for HCF queue traversal, track packet-drop callback registration, and unregister callbacks during teardown. Extend the focused unit test with ACK timing, fragmented MSDU, DELBA cancellation, queue wakeup, and DCF materialization coverage. Validation: release build and focused Ieee80211AddbaTransaction_1 unit test pass; git diff --check passes. The full unit and fingerprint suites were not run. --- .../OriginatorBlockAckAgreementHandler.cc | 29 +++- .../OriginatorBlockAckAgreementHandler.h | 6 +- .../ieee80211/mac/channelaccess/Edca.h | 2 +- .../IOriginatorBlockAckAgreementHandler.h | 6 +- .../ieee80211/mac/coordinationfunction/Hcf.cc | 28 +++- .../ieee80211/mac/coordinationfunction/Hcf.h | 1 + .../ieee80211/mac/queue/InProgressFrames.h | 2 +- tests/unit/Ieee80211AddbaTransaction_1.test | 144 ++++++++++++++++-- 8 files changed, 184 insertions(+), 34 deletions(-) diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc index d21d8398167..460155ef4ae 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc @@ -192,8 +192,13 @@ void OriginatorBlockAckAgreementHandler::terminateAgreement(MacAddress originato } } -void OriginatorBlockAckAgreementHandler::processTransmittedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) +void OriginatorBlockAckAgreementHandler::processAcknowledgedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback) { + // IEEE Std 802.11-2024, Table 9-466: the Starting Sequence Number identifies + // the first or next MSDU/A-MSDU sent under the agreement. Wait until the + // final fragment is acknowledged so no remaining fragment precedes the SSN. + if (dataHeader->getMoreFragments()) + return; auto receiverAddr = dataHeader->getReceiverAddress(); auto tid = dataHeader->getTid(); auto agreementId = std::make_pair(receiverAddr, tid); @@ -203,6 +208,9 @@ void OriginatorBlockAckAgreementHandler::processTransmittedDataFrame(Packet *pac if (blockAckAgreementPolicy->isAddbaReqNeeded(packet, dataHeader) && agreement == nullptr && retryAllowed) { if (retryIt != addbaRetryDeadlines.end()) addbaRetryDeadlines.erase(retryIt); + // IEEE Std 802.11-2024, 10.25.2 and 11.5.2.2: Normal Ack data is + // permitted before an agreement exists, and the requested SSN starts + // after the acknowledged trigger MPDU. auto addbaReq = buildAddbaRequest(receiverAddr, tid, dataHeader->getSequenceNumber() + 1, blockAckAgreementPolicy); auto transactionId = nextTransactionId++; createAgreement(addbaReq, transactionId); @@ -277,15 +285,28 @@ void OriginatorBlockAckAgreementHandler::processDroppedAddbaReq(Packet *packet, } } -void OriginatorBlockAckAgreementHandler::processTransmittedDelba(const Ptr& delba) +void OriginatorBlockAckAgreementHandler::processTransmittedDelba(const Ptr& delba, IBlockAckAgreementHandlerCallback *callback) { + auto agreement = getAgreement(delba->getReceiverAddress(), delba->getTid()); + bool cancelPendingTransaction = agreement != nullptr && agreement->isPending(); + auto transactionId = cancelPendingTransaction ? agreement->getTransactionId() : 0; terminateAgreement(delba->getReceiverAddress(), delba->getTid()); + scheduleAddbaResponseTimer(callback); + if (cancelPendingTransaction) + callback->cancelAddbaTransaction(transactionId, nullptr); } -void OriginatorBlockAckAgreementHandler::processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy) +void OriginatorBlockAckAgreementHandler::processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) { - if (blockAckAgreementPolicy->isDelbaAccepted(delba)) + if (blockAckAgreementPolicy->isDelbaAccepted(delba)) { + auto agreement = getAgreement(delba->getTransmitterAddress(), delba->getTid()); + bool cancelPendingTransaction = agreement != nullptr && agreement->isPending(); + auto transactionId = cancelPendingTransaction ? agreement->getTransactionId() : 0; terminateAgreement(delba->getTransmitterAddress(), delba->getTid()); + scheduleAddbaResponseTimer(callback); + if (cancelPendingTransaction) + callback->cancelAddbaTransaction(transactionId, nullptr); + } } OriginatorBlockAckAgreementHandler::~OriginatorBlockAckAgreementHandler() diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h index 4d212cae9cf..24454e53548 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h @@ -43,11 +43,11 @@ class INET_API OriginatorBlockAckAgreementHandler : public IOriginatorBlockAckAg virtual ~OriginatorBlockAckAgreementHandler(); virtual void processTransmittedAddbaReq(Packet *packet, const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; virtual void processDroppedAddbaReq(Packet *packet, const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; - virtual void processTransmittedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) override; + virtual void processAcknowledgedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback) override; virtual void processReceivedBlockAck(const Ptr& blockAck, IBlockAckAgreementHandlerCallback *callback) override; virtual OriginatorBlockAckAgreement *processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; - virtual void processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy) override; - virtual void processTransmittedDelba(const Ptr& delba) override; + virtual void processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; + virtual void processTransmittedDelba(const Ptr& delba, IBlockAckAgreementHandlerCallback *callback) override; virtual void blockAckAgreementExpired(IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) override; virtual void addbaResponseTimeoutExpired(IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Edca.h b/src/inet/linklayer/ieee80211/mac/channelaccess/Edca.h index c481e9a82c8..69436da824d 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Edca.h +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Edca.h @@ -38,6 +38,7 @@ class INET_API Edca : public SimpleModule virtual ~Edca(); virtual AccessCategory classifyFrame(const Ptr& header); + virtual int getNumEdcafs() const { return numEdcafs; } virtual Edcaf *getEdcaf(AccessCategory ac) const { return edcafs[ac]; } virtual Edcaf *getChannelOwner(); virtual std::vector getInternallyCollidedEdcafs(); @@ -51,4 +52,3 @@ class INET_API Edca : public SimpleModule } /* namespace inet */ #endif - diff --git a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h index 2b3231d03b2..169371bad21 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h @@ -27,10 +27,10 @@ class INET_API IOriginatorBlockAckAgreementHandler virtual void processReceivedBlockAck(const Ptr& blockAck, IBlockAckAgreementHandlerCallback *callback) = 0; virtual void processTransmittedAddbaReq(Packet *packet, const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; virtual void processDroppedAddbaReq(Packet *packet, const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; - virtual void processTransmittedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) = 0; + virtual void processAcknowledgedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback) = 0; virtual OriginatorBlockAckAgreement *processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; - virtual void processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy) = 0; - virtual void processTransmittedDelba(const Ptr& delba) = 0; + virtual void processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; + virtual void processTransmittedDelba(const Ptr& delba, IBlockAckAgreementHandlerCallback *callback) = 0; virtual void blockAckAgreementExpired(IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) = 0; virtual void addbaResponseTimeoutExpired(IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc index 4cfe74f766b..2374468a919 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc @@ -71,8 +71,10 @@ void Hcf::initialize(int stage) } } else if (stage == INITSTAGE_LAST) { - for (int ac = 0; ac < AC_NUMCATEGORIES; ac++) + for (int ac = 0; ac < edca->getNumEdcafs(); ac++) { edca->getEdcaf(AccessCategory(ac))->getPendingQueue()->addPacketDropCallback(this); + numPacketDropCallbacksRegistered++; + } } } @@ -89,6 +91,7 @@ void Hcf::handlePacketDropped(Packet *packet) { Enter_Method("handlePacketDropped"); processDroppedBlockAckSetupFrame(packet); + resumeEligibleChannelAccess(); } std::string Hcf::getFrameSequenceInfo() const @@ -210,7 +213,7 @@ void Hcf::cancelAddbaTransaction(uint64_t transactionId, Packet *excludedPacket) auto transactionTag = packet->findTag(); return packet != excludedPacket && transactionTag != nullptr && transactionTag->getTransactionId() == transactionId; }; - for (int ac = 0; ac < AC_NUMCATEGORIES; ac++) { + for (int ac = 0; ac < edca->getNumEdcafs(); ac++) { auto edcaf = edca->getEdcaf(AccessCategory(ac)); auto pendingQueue = edcaf->getPendingQueue(); for (int i = pendingQueue->getNumPackets() - 1; i >= 0; i--) { @@ -320,6 +323,10 @@ void Hcf::handleInternalCollision(std::vector internallyCollidedEdcafs) AccessCategory ac = edcaf->getAccessCategory(); auto dataRecoveryProcedure = edcaf->getRecoveryProcedure(); Packet *internallyCollidedFrame = edcaf->getInProgressFrames()->getFrameToTransmit(); + if (internallyCollidedFrame == nullptr) { + EV_DETAIL << "Ignoring internal collision because no eligible frame is available for " << printAccessCategory(ac) << ".\n"; + continue; + } auto internallyCollidedHeader = internallyCollidedFrame->peekAtFront(); EV_INFO << printAccessCategory(ac) << " (" << internallyCollidedFrame->getName() << ")" << endl; bool retryLimitReached = false; @@ -453,7 +460,8 @@ void Hcf::recipientProcessReceivedManagementFrame(const PtrgetAgreement(delba->getReceiverAddress(), delba->getTid()); if (agreement != nullptr) emit(blockAckAgreementDeletedSignal, agreement); - originatorBlockAckAgreementHandler->processReceivedDelba(delba, originatorBlockAckAgreementPolicy); + originatorBlockAckAgreementHandler->processReceivedDelba(delba, originatorBlockAckAgreementPolicy, this); + resumeEligibleChannelAccess(); } } else @@ -546,8 +554,6 @@ void Hcf::originatorProcessTransmittedDataFrame(Packet *packet, const PtrgetEdcaf(ac); edcaf->getAckHandler()->processTransmittedDataOrMgmtFrame(dataHeader); - if (originatorBlockAckAgreementHandler) - originatorBlockAckAgreementHandler->processTransmittedDataFrame(packet, dataHeader, originatorBlockAckAgreementPolicy, this, this); if (dataHeader->getAckPolicy() == NO_ACK) edcaf->getInProgressFrames()->dropFrame(packet); } @@ -565,7 +571,7 @@ void Hcf::originatorProcessTransmittedManagementFrame(Packet *packet, const Ptr< ; // Recipient agreement was established when the successful response was formed. else if (auto delba = dynamicPtrCast(mgmtHeader)) { if (delba->getInitiator()) - originatorBlockAckAgreementHandler->processTransmittedDelba(delba); + originatorBlockAckAgreementHandler->processTransmittedDelba(delba, this); else recipientBlockAckAgreementHandler->processTransmittedDelba(delba); } @@ -697,6 +703,10 @@ void Hcf::originatorProcessReceivedControlFrame(Packet *packet, const PtrgetAckHandler()->processReceivedAck(ackFrame, lastTransmittedDataOrMgmtHeader); edcaf->getInProgressFrames()->dropFrame(lastTransmittedPacket); edcaf->getAckHandler()->dropFrame(lastTransmittedDataOrMgmtHeader); + if (auto dataHeader = dynamicPtrCast(lastTransmittedHeader)) { + if (originatorBlockAckAgreementHandler) + originatorBlockAckAgreementHandler->processAcknowledgedDataFrame(lastTransmittedPacket, dataHeader, originatorBlockAckAgreementPolicy, this); + } } else if (auto blockAck = dynamicPtrCast(header)) { EV_INFO << "BasicBlockAck has arrived" << std::endl; @@ -747,7 +757,7 @@ bool Hcf::hasFrameToTransmit() void Hcf::requestEligibleChannelAccess() { - for (int ac = 0; ac < AC_NUMCATEGORIES; ac++) { + for (int ac = 0; ac < edca->getNumEdcafs(); ac++) { auto accessCategory = AccessCategory(ac); if (hasFrameToTransmit(accessCategory)) edca->requestChannelAccess(accessCategory, this); @@ -886,6 +896,10 @@ void Hcf::corruptedFrameReceived() Hcf::~Hcf() { + if (edca != nullptr) { + for (int ac = 0; ac < numPacketDropCallbacksRegistered; ac++) + edca->getEdcaf(AccessCategory(ac))->getPendingQueue()->removePacketDropCallback(this); + } cancelAndDelete(startRxTimer); cancelAndDelete(inactivityTimer); cancelAndDelete(addbaResponseTimer); diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h index 6f0eae4747d..146c969b31c 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h @@ -96,6 +96,7 @@ class INET_API Hcf : public ICoordinationFunction, public IFrameSequenceHandler: // Queues InProgressFrames *hccaInProgressFrame = nullptr; + int numPacketDropCallbacksRegistered = 0; // Frame sequence handler IFrameSequenceHandler *frameSequenceHandler = nullptr; diff --git a/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.h b/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.h index 0efea3f23e8..31b6d637a43 100644 --- a/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.h +++ b/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.h @@ -49,7 +49,7 @@ class INET_API InProgressFrames : public SimpleModule virtual void dropFrame(Packet *packet); virtual void dropFrames(std::set>> seqAndFragNums); - virtual bool hasInProgressFrames() { return hasEligibleFrameToTransmit(); } + virtual bool hasInProgressFrames() { ensureHasFrameToTransmit(); return hasEligibleFrameToTransmit(); } virtual std::vector getOutstandingFrames(); virtual void clearDroppedFrames(); diff --git a/tests/unit/Ieee80211AddbaTransaction_1.test b/tests/unit/Ieee80211AddbaTransaction_1.test index 36564546b7f..14778ec7eb0 100644 --- a/tests/unit/Ieee80211AddbaTransaction_1.test +++ b/tests/unit/Ieee80211AddbaTransaction_1.test @@ -242,9 +242,11 @@ class TestEdcaf : public Edcaf public: queueing::IPacketQueue *pendingQueue = nullptr; InProgressFrames *inProgressFrames = nullptr; + QosAckHandler *qosAckHandler = nullptr; virtual queueing::IPacketQueue *getPendingQueue() const override { return pendingQueue; } virtual InProgressFrames *getInProgressFrames() const override { return inProgressFrames; } + virtual QosAckHandler *getAckHandler() const override { return qosAckHandler; } }; class TestEdca : public Edca @@ -252,15 +254,28 @@ class TestEdca : public Edca public: Edcaf *edcaf = nullptr; + virtual int getNumEdcafs() const override { return 1; } virtual Edcaf *getEdcaf(AccessCategory) const override { return edcaf; } }; class TestHcf : public Hcf { public: + int numProcessedDroppedSetupFrames = 0; + int numResumedEligibleChannelAccess = 0; + void configure(Edca *edca) { this->edca = edca; } + void configureTransmittedData(Edca *edca, IOriginatorBlockAckAgreementHandler *handler, IOriginatorBlockAckAgreementPolicy *policy) { + this->edca = edca; + originatorBlockAckAgreementHandler = handler; + originatorBlockAckAgreementPolicy = policy; + } + void processTransmittedData(Packet *packet, const Ptr& dataHeader, AccessCategory ac) { originatorProcessTransmittedDataFrame(packet, dataHeader, ac); } + void dropPacket(Packet *packet) { handlePacketDropped(packet); } protected: + virtual void processDroppedBlockAckSetupFrame(Packet *) override { numProcessedDroppedSetupFrames++; } + virtual void resumeEligibleChannelAccess() override { numResumedEligibleChannelAccess++; } virtual void scheduleAddbaResponseTimer(simtime_t) override {} }; @@ -404,7 +419,49 @@ MacAddress peer3("00:00:00:00:00:03"); TestOriginatorHandler handler; Packet triggerPacket("trigger"); auto triggerHeader = makeQosHeader(peer1, 3, SequenceNumberCyclic(4095)); -handler.processTransmittedDataFrame(&triggerPacket, triggerHeader, &originatorPolicy, &callback, &callback); + +// Transmitting the trigger MPDU does not create an agreement; initiation is +// deferred until the HCF successful-ACK path calls processAcknowledgedDataFrame(). +{ + auto preAckHandler = new TestOriginatorHandler(); + QosAckHandler preAckAckHandler; + TestEdcaf preAckEdcaf; + preAckEdcaf.qosAckHandler = &preAckAckHandler; + TestEdca preAckEdca; + preAckEdca.edcaf = &preAckEdcaf; + TestHcf preAckHcf; + preAckHcf.configureTransmittedData(&preAckEdca, preAckHandler, &originatorPolicy); + Packet preAckPacket("preAckPacket"); + auto preAckHeader = makeQosHeader(peer1, 3, SequenceNumberCyclic(4095)); + preAckHeader->setAckPolicy(NORMAL_ACK); + preAckHcf.processTransmittedData(&preAckPacket, preAckHeader, AC_BE); + if (preAckHandler->getAgreement(peer1, 3) != nullptr) + throw cRuntimeError("HCF initiated ADDBA before the trigger MPDU was acknowledged"); + ASSERT(preAckHandler->getAgreement(peer1, 3) == nullptr); +} + +// A fragmented MSDU cannot trigger ADDBA until its final fragment is ACKed, +// otherwise the remaining fragments would precede the advertised SSN. +{ + TestOriginatorHandler fragmentedDataHandler; + TestCallback fragmentedDataCallback; + Packet fragmentedDataPacket("fragmentedDataPacket"); + auto firstFragmentHeader = makeQosHeader(peer1, 6, SequenceNumberCyclic(100)); + firstFragmentHeader->setFragmentNumber(0); + firstFragmentHeader->setMoreFragments(true); + fragmentedDataHandler.processAcknowledgedDataFrame(&fragmentedDataPacket, firstFragmentHeader, &originatorPolicy, &fragmentedDataCallback); + if (fragmentedDataHandler.getAgreement(peer1, 6) != nullptr || fragmentedDataCallback.managementPacket != nullptr) + throw cRuntimeError("ADDBA was initiated before the final data fragment was acknowledged"); + auto finalFragmentHeader = makeQosHeader(peer1, 6, SequenceNumberCyclic(100)); + finalFragmentHeader->setFragmentNumber(1); + finalFragmentHeader->setMoreFragments(false); + fragmentedDataHandler.processAcknowledgedDataFrame(&fragmentedDataPacket, finalFragmentHeader, &originatorPolicy, &fragmentedDataCallback); + auto fragmentedDataRequest = dynamicPtrCast(fragmentedDataCallback.managementHeader); + ASSERT(fragmentedDataRequest != nullptr); + ASSERT(fragmentedDataRequest->getStartingSequenceNumber() == SequenceNumberCyclic(101)); +} + +handler.processAcknowledgedDataFrame(&triggerPacket, triggerHeader, &originatorPolicy, &callback); auto request = dynamicPtrCast(callback.managementHeader); ASSERT(request != nullptr); ASSERT(request->getDialogToken() != 0); @@ -432,21 +489,21 @@ ASSERT(handler.processReceivedAddbaResp(success, &originatorPolicy, &callback) = Packet rejectedTrigger("rejectedTrigger"); auto rejectedHeader = makeQosHeader(peer2, 4, SequenceNumberCyclic(10)); -handler.processTransmittedDataFrame(&rejectedTrigger, rejectedHeader, &originatorPolicy, &callback, &callback); +handler.processAcknowledgedDataFrame(&rejectedTrigger, rejectedHeader, &originatorPolicy, &callback); auto rejectedRequest = dynamicPtrCast(callback.managementHeader); handler.processTransmittedAddbaReq(callback.managementPacket, rejectedRequest, &originatorPolicy, &callback); auto rejection = makeResponse(peer2, 4, rejectedRequest->getDialogToken(), 1); ASSERT(handler.processReceivedAddbaResp(rejection, &originatorPolicy, &callback) == nullptr); ASSERT(handler.getAgreement(peer2, 4) == nullptr); -handler.processTransmittedDataFrame(&rejectedTrigger, rejectedHeader, &originatorPolicy, &callback, &callback); +handler.processAcknowledgedDataFrame(&rejectedTrigger, rejectedHeader, &originatorPolicy, &callback); ASSERT(handler.getAgreement(peer2, 4) == nullptr); handler.allowRetryNow(peer2, 4); -handler.processTransmittedDataFrame(&rejectedTrigger, rejectedHeader, &originatorPolicy, &callback, &callback); +handler.processAcknowledgedDataFrame(&rejectedTrigger, rejectedHeader, &originatorPolicy, &callback); ASSERT(handler.isAddbaResponsePending(peer2, 4)); Packet timeoutTrigger("timeoutTrigger"); auto timeoutHeader = makeQosHeader(peer3, 5, SequenceNumberCyclic(20)); -handler.processTransmittedDataFrame(&timeoutTrigger, timeoutHeader, &originatorPolicy, &callback, &callback); +handler.processAcknowledgedDataFrame(&timeoutTrigger, timeoutHeader, &originatorPolicy, &callback); auto timedRequest = dynamicPtrCast(callback.managementHeader); auto timedRequestPacket = callback.managementPacket; auto timedTransactionId = timedRequestPacket->getTag()->getTransactionId(); @@ -460,16 +517,16 @@ handler.processTransmittedAddbaReq(timedRequestPacket, timedRequest, &originator ASSERT(handler.getAgreement(peer3, 5) == nullptr); auto staleResponse = makeResponse(peer3, 5, timedRequest->getDialogToken(), 0); ASSERT(handler.processReceivedAddbaResp(staleResponse, &originatorPolicy, &callback) == nullptr); -handler.processTransmittedDataFrame(&timeoutTrigger, timeoutHeader, &originatorPolicy, &callback, &callback); +handler.processAcknowledgedDataFrame(&timeoutTrigger, timeoutHeader, &originatorPolicy, &callback); ASSERT(handler.getAgreement(peer3, 5) == nullptr); handler.allowRetryNow(peer3, 5); -handler.processTransmittedDataFrame(&timeoutTrigger, timeoutHeader, &originatorPolicy, &callback, &callback); +handler.processAcknowledgedDataFrame(&timeoutTrigger, timeoutHeader, &originatorPolicy, &callback); ASSERT(handler.isAddbaResponsePending(peer3, 5)); // An unsent transaction has no response deadline and is retired by a typed drop callback. Packet unsentTrigger("unsentTrigger"); auto unsentHeader = makeQosHeader(peer2, 5, SequenceNumberCyclic(40)); -handler.processTransmittedDataFrame(&unsentTrigger, unsentHeader, &originatorPolicy, &callback, &callback); +handler.processAcknowledgedDataFrame(&unsentTrigger, unsentHeader, &originatorPolicy, &callback); ASSERT(handler.isAddbaResponsePending(peer2, 5)); ASSERT(callback.addbaDeadline == SIMTIME_MAX); auto unsentRequest = dynamicPtrCast(callback.managementHeader); @@ -480,6 +537,45 @@ ASSERT(handler.getAgreement(peer2, 5) == nullptr); ASSERT(callback.cancelledTransactionIds.back() == unsentTransactionId); ASSERT(callback.excludedPackets.back() == unsentRequestPacket); +// A DELBA that terminates a pending transaction also cancels its tagged ADDBA +// Request and recomputes the response timer. +{ + TestOriginatorHandler receivedDelbaHandler; + TestCallback receivedDelbaCallback; + Packet receivedDelbaTrigger("receivedDelbaTrigger"); + auto receivedDelbaTriggerHeader = makeQosHeader(peer1, 2, SequenceNumberCyclic(60)); + receivedDelbaHandler.processAcknowledgedDataFrame(&receivedDelbaTrigger, receivedDelbaTriggerHeader, &originatorPolicy, &receivedDelbaCallback); + auto receivedDelbaTransactionId = receivedDelbaCallback.managementPacket->getTag()->getTransactionId(); + auto receivedDelba = makeShared(); + receivedDelba->setTransmitterAddress(peer1); + receivedDelba->setTid(2); + receivedDelbaHandler.processReceivedDelba(receivedDelba, &originatorPolicy, &receivedDelbaCallback); + if (receivedDelbaHandler.getAgreement(peer1, 2) != nullptr || receivedDelbaCallback.cancelledTransactionIds.size() != 1 || receivedDelbaCallback.cancelledTransactionIds.back() != receivedDelbaTransactionId) + throw cRuntimeError("Received DELBA did not cancel the pending ADDBA transaction"); + ASSERT(receivedDelbaHandler.getAgreement(peer1, 2) == nullptr); + ASSERT(receivedDelbaCallback.cancelledTransactionIds.back() == receivedDelbaTransactionId); + ASSERT(receivedDelbaCallback.excludedPackets.back() == nullptr); + ASSERT(receivedDelbaCallback.addbaDeadline == SIMTIME_MAX); +} +{ + TestOriginatorHandler transmittedDelbaHandler; + TestCallback transmittedDelbaCallback; + Packet transmittedDelbaTrigger("transmittedDelbaTrigger"); + auto transmittedDelbaTriggerHeader = makeQosHeader(peer2, 2, SequenceNumberCyclic(70)); + transmittedDelbaHandler.processAcknowledgedDataFrame(&transmittedDelbaTrigger, transmittedDelbaTriggerHeader, &originatorPolicy, &transmittedDelbaCallback); + auto transmittedDelbaTransactionId = transmittedDelbaCallback.managementPacket->getTag()->getTransactionId(); + auto transmittedDelba = makeShared(); + transmittedDelba->setReceiverAddress(peer2); + transmittedDelba->setTid(2); + transmittedDelbaHandler.processTransmittedDelba(transmittedDelba, &transmittedDelbaCallback); + if (transmittedDelbaHandler.getAgreement(peer2, 2) != nullptr || transmittedDelbaCallback.cancelledTransactionIds.size() != 1 || transmittedDelbaCallback.cancelledTransactionIds.back() != transmittedDelbaTransactionId) + throw cRuntimeError("Transmitted DELBA did not cancel the pending ADDBA transaction"); + ASSERT(transmittedDelbaHandler.getAgreement(peer2, 2) == nullptr); + ASSERT(transmittedDelbaCallback.cancelledTransactionIds.back() == transmittedDelbaTransactionId); + ASSERT(transmittedDelbaCallback.excludedPackets.back() == nullptr); + ASSERT(transmittedDelbaCallback.addbaDeadline == SIMTIME_MAX); +} + // Fragmentation preserves the local transaction identity on every MPDU, and // typed cancellation removes all exact siblings while retaining the dropped // fragment and an unrelated transaction. @@ -488,7 +584,7 @@ ASSERT(callback.excludedPackets.back() == unsentRequestPacket); TestCallback fragmentedCallback; Packet fragmentedTrigger("fragmentedTrigger"); auto fragmentedTriggerHeader = makeQosHeader(peer1, 7, SequenceNumberCyclic(50)); - fragmentedHandler.processTransmittedDataFrame(&fragmentedTrigger, fragmentedTriggerHeader, &originatorPolicy, &fragmentedCallback, &fragmentedCallback); + fragmentedHandler.processAcknowledgedDataFrame(&fragmentedTrigger, fragmentedTriggerHeader, &originatorPolicy, &fragmentedCallback); auto unfragmentedRequestPacket = fragmentedCallback.managementPacket; auto fragmentedTransactionId = unfragmentedRequestPacket->getTag()->getTransactionId(); fragmentedCallback.forgetManagementPacket(unfragmentedRequestPacket); @@ -539,7 +635,7 @@ ASSERT(callback.excludedPackets.back() == unsentRequestPacket); auto invalidTimeoutHeader = makeQosHeader(peer2, 1, SequenceNumberCyclic(30)); bool threw = false; try { - invalidTimeoutHandler.processTransmittedDataFrame(&invalidTimeoutTrigger, invalidTimeoutHeader, &invalidTimeoutPolicy, &invalidTimeoutCallback, &invalidTimeoutCallback); + invalidTimeoutHandler.processAcknowledgedDataFrame(&invalidTimeoutTrigger, invalidTimeoutHeader, &invalidTimeoutPolicy, &invalidTimeoutCallback); auto invalidRequest = dynamicPtrCast(invalidTimeoutCallback.managementHeader); invalidTimeoutHandler.processTransmittedAddbaReq(invalidTimeoutCallback.managementPacket, invalidRequest, &invalidTimeoutPolicy, &invalidTimeoutCallback); } @@ -635,16 +731,22 @@ inProgressFrames.addFrame(prefetchedOtherTidFrame); ASSERT(inProgressFrames.hasEligibleFrame()); ASSERT(inProgressFrames.getFrameToTransmit() == prefetchedOtherTidFrame); -// Availability checks must not extract, aggregate, or assign a sequence number. +// DCF sequence predicates materialize the next eligible pending frame. TestPacketQueue deferredQueue; auto deferredFrame = new Packet("deferredFrame", makeQosHeader(peer2, 4, SequenceNumberCyclic(123))); deferredQueue.packets = { deferredFrame }; +TestOriginatorQosMacDataService deferredDataService; TestInProgressFrames deferredInProgressFrames; -deferredInProgressFrames.configure(&dataService, &ackHandler, &deferredQueue); -ASSERT(!deferredInProgressFrames.hasInProgressFrames()); -ASSERT(deferredQueue.getNumPackets() == 1); +deferredInProgressFrames.configure(&deferredDataService, &ackHandler, &deferredQueue); +bool hasDeferredInProgressFrame = deferredInProgressFrames.hasInProgressFrames(); +if (!hasDeferredInProgressFrame || deferredQueue.getNumPackets() != 0) + throw cRuntimeError("In-progress frame predicate did not materialize the eligible pending frame"); +ASSERT(hasDeferredInProgressFrame); +ASSERT(deferredQueue.getNumPackets() == 0); ASSERT(deferredFrame->peekAtFront()->getSequenceNumber() == SequenceNumberCyclic(123)); -delete deferredFrame; +auto deferredFrames = deferredInProgressFrames.releaseFrames(); +ASSERT(deferredFrames.size() == 1); +delete deferredFrames.at(0); // A real leaf queue records selected-dequeue residence once, while the // compound boundary emits its own semantic pull without duplicating it. @@ -689,6 +791,18 @@ Packet *compoundPacket = nullptr; } delete compoundPacket; +// Queue-drop cleanup immediately rechecks eligible channel access, so frames +// released by a discarded setup transaction do not wait for unrelated traffic. +{ + TestHcf droppedSetupHcf; + Packet droppedSetupPacket("droppedSetupPacket", makeShared(B(1))); + droppedSetupHcf.dropPacket(&droppedSetupPacket); + if (droppedSetupHcf.numProcessedDroppedSetupFrames != 1 || droppedSetupHcf.numResumedEligibleChannelAccess != 1) + throw cRuntimeError("Queue-drop cleanup did not resume eligible channel access"); + ASSERT(droppedSetupHcf.numProcessedDroppedSetupFrames == 1); + ASSERT(droppedSetupHcf.numResumedEligibleChannelAccess == 1); +} + // Internal overflow invokes the typed queue callback once for the destructive // discard and never for the packet later removed normally. { From df72eadb73f759896e76b26c55ac9380e78a3be8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Mon, 17 Aug 2026 19:38:18 +0200 Subject: [PATCH 04/14] Fix transactional Block Ack queue eligibility Keep ADDBA setup requests and same-peer/TID data synchronized with the pending originator agreement. Harden cancellation, retry cleanup, DELBA teardown, reordering ownership, and in-progress frame disposal so stale setup traffic cannot be double-freed or leave protocol state behind. Add an HCF-owned per-access-category eligibility index. Track exact queue departures through typed lifecycle callbacks and rebuild eligibility only when Block Ack state changes, making routine channel-access availability checks independent of pending queue depth. Preserve configurable queue-provider policy during selective extraction and A-MSDU formation. Add predicate-aware Priority, WRR, Label, flow, leaf, and compound extraction; batch overflow notifications to avoid reentrant victim selection; and conservatively skip pre-extraction aggregation through transforming packet flows. Document the incompatible queue contracts and DELBA standards trace. Expand focused coverage for ADDBA lifecycle transitions, direct and RTS failure cleanup, custom and compound queues, scheduler accounting, shared buffers, A-MSDU processing, and the no-scan eligibility invariant. Validated with release/debug builds, focused unit tests in both modes, runtime-clean affected fingerprint simulations, and an independent INET/WLAN architecture review. --- WHATSNEW | 28 + .../OriginatorBlockAckAgreementHandler.cc | 18 +- .../OriginatorBlockAckAgreementHandler.h | 3 +- .../RecipientBlockAckAgreementHandler.cc | 17 +- .../RecipientBlockAckAgreementHandler.h | 3 +- .../IOriginatorBlockAckAgreementHandler.h | 4 +- .../IRecipientBlockAckAgreementHandler.h | 4 +- .../ieee80211/mac/coordinationfunction/Hcf.cc | 187 ++- .../ieee80211/mac/coordinationfunction/Hcf.h | 19 +- .../mac/originator/NonQosRecoveryProcedure.cc | 29 +- .../mac/originator/NonQosRecoveryProcedure.h | 4 +- .../originator/OriginatorMacDataService.cc | 17 +- .../originator/OriginatorQosMacDataService.cc | 42 +- .../ieee80211/mac/queue/InProgressFrames.cc | 5 +- .../ieee80211/mac/queue/InProgressFrames.h | 1 + .../recipient/RecipientQosMacDataService.cc | 2 - src/inet/queueing/base/PacketFlowBase.cc | 6 +- src/inet/queueing/base/PacketFlowBase.h | 7 +- src/inet/queueing/base/PacketQueueBase.cc | 20 +- src/inet/queueing/base/PacketQueueBase.h | 8 +- src/inet/queueing/buffer/PacketBuffer.cc | 18 +- src/inet/queueing/contract/IPacketBuffer.h | 8 +- src/inet/queueing/contract/IPacketExtractor.h | 21 +- src/inet/queueing/contract/IPacketQueue.h | 26 +- .../queueing/queue/CompoundPacketQueueBase.cc | 39 +- .../queueing/queue/CompoundPacketQueueBase.h | 6 +- src/inet/queueing/queue/PacketQueue.cc | 32 +- src/inet/queueing/queue/PacketQueue.h | 5 +- src/inet/queueing/scheduler/LabelScheduler.cc | 48 +- src/inet/queueing/scheduler/LabelScheduler.h | 4 +- .../queueing/scheduler/PriorityScheduler.cc | 49 +- .../queueing/scheduler/PriorityScheduler.h | 4 +- src/inet/queueing/scheduler/WrrScheduler.cc | 60 +- src/inet/queueing/scheduler/WrrScheduler.h | 5 +- tests/unit/Ieee80211AddbaTransaction_1.test | 1071 ++++++++++++++++- 35 files changed, 1558 insertions(+), 262 deletions(-) diff --git a/WHATSNEW b/WHATSNEW index e79d52ecdfd..1b895d3f54b 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -119,6 +119,34 @@ Notable backward incompatible changes are the following: These changes are backward incompatible for C++ code that directly references the old combined ICMP error indication or tag types. +7. Packet queue extraction and lifecycle callbacks + + IPacketQueue now supports queue-lifecycle callbacks and predicate-based extraction. + Direct C++ implementations of IPacketQueue must implement findPacket(), the + predicate overload of dequeuePacket(), addPacketCallback(), and + removePacketCallback(), and must notify callbacks exactly once whenever a + packet is dequeued, explicitly removed, or destructively dropped. Providers + connected to CompoundPacketQueueBase must also implement IPacketExtractor so + that a compound queue preserves the provider's scheduling policy when it + extracts a matching packet. Direct C++ + implementations of IPacketExtractor must replace selected-pointer extraction + with findPacket(predicate) and dequeuePacket(predicate); predicates may be + evaluated repeatedly and must be stable and side-effect free throughout one + logical selection. Extractors whose IPacketCollection enumeration order is also + their scheduling order may override isPacketOrderPreserved(); the conservative + default is false. + IPacketBuffer::ICallback also provides an optional handlePacketDropped() + notification. Shared buffers invoke it only after all victims selected by one + overload operation have been detached from their owning queues. + +8. Block Ack DELBA agreement ownership + + IOriginatorBlockAckAgreementHandler::processReceivedDelba() and + IRecipientBlockAckAgreementHandler::processReceivedDelba() now return the + terminated agreement as a unique_ptr. Custom handler implementations and + callers must adopt the new return type; a null result means the DELBA was + rejected or did not match an agreement. + Notable backward compatible changes are the following: 1. IPv6 network configurator diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc index 460155ef4ae..332c991bdee 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc @@ -181,15 +181,21 @@ const Ptr OriginatorBlockAckAgreementHandler::buildDelba(MacAddr return delba; } -void OriginatorBlockAckAgreementHandler::terminateAgreement(MacAddress originatorAddr, Tid tid) +OriginatorBlockAckAgreement *OriginatorBlockAckAgreementHandler::removeAgreement(MacAddress originatorAddr, Tid tid) { auto agreementId = std::make_pair(originatorAddr, tid); auto it = blockAckAgreements.find(agreementId); if (it != blockAckAgreements.end()) { - OriginatorBlockAckAgreement *agreement = it->second; + auto agreement = it->second; blockAckAgreements.erase(it); - delete agreement; + return agreement; } + return nullptr; +} + +void OriginatorBlockAckAgreementHandler::terminateAgreement(MacAddress originatorAddr, Tid tid) +{ + delete removeAgreement(originatorAddr, tid); } void OriginatorBlockAckAgreementHandler::processAcknowledgedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback) @@ -296,17 +302,19 @@ void OriginatorBlockAckAgreementHandler::processTransmittedDelba(const PtrcancelAddbaTransaction(transactionId, nullptr); } -void OriginatorBlockAckAgreementHandler::processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) +std::unique_ptr OriginatorBlockAckAgreementHandler::processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) { if (blockAckAgreementPolicy->isDelbaAccepted(delba)) { auto agreement = getAgreement(delba->getTransmitterAddress(), delba->getTid()); bool cancelPendingTransaction = agreement != nullptr && agreement->isPending(); auto transactionId = cancelPendingTransaction ? agreement->getTransactionId() : 0; - terminateAgreement(delba->getTransmitterAddress(), delba->getTid()); + std::unique_ptr terminatedAgreement(removeAgreement(delba->getTransmitterAddress(), delba->getTid())); scheduleAddbaResponseTimer(callback); if (cancelPendingTransaction) callback->cancelAddbaTransaction(transactionId, nullptr); + return terminatedAgreement; } + return nullptr; } OriginatorBlockAckAgreementHandler::~OriginatorBlockAckAgreementHandler() diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h index 24454e53548..102e2e3ef8b 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h @@ -30,6 +30,7 @@ class INET_API OriginatorBlockAckAgreementHandler : public IOriginatorBlockAckAg virtual uint8_t allocateDialogToken(); virtual void createAgreement(const Ptr& addbaRequest, uint64_t transactionId); virtual void updateAgreement(OriginatorBlockAckAgreement *agreement, const Ptr& addbaResp); + virtual OriginatorBlockAckAgreement *removeAgreement(MacAddress originatorAddr, Tid tid); virtual void terminateAgreement(MacAddress originatorAddr, Tid tid); virtual const Ptr buildDelba(MacAddress receiverAddr, Tid tid, int reasonCode); virtual simtime_t computeEarliestExpirationTime(); @@ -46,7 +47,7 @@ class INET_API OriginatorBlockAckAgreementHandler : public IOriginatorBlockAckAg virtual void processAcknowledgedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback) override; virtual void processReceivedBlockAck(const Ptr& blockAck, IBlockAckAgreementHandlerCallback *callback) override; virtual OriginatorBlockAckAgreement *processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; - virtual void processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; + virtual std::unique_ptr processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; virtual void processTransmittedDelba(const Ptr& delba, IBlockAckAgreementHandlerCallback *callback) override; virtual void blockAckAgreementExpired(IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) override; virtual void addbaResponseTimeoutExpired(IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc index 2f87f5e8e8c..794ba6c6c61 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc @@ -97,15 +97,21 @@ const Ptr RecipientBlockAckAgreementHandler::buildAddbaR return addbaResponse; } -void RecipientBlockAckAgreementHandler::terminateAgreement(MacAddress originatorAddr, Tid tid) +RecipientBlockAckAgreement *RecipientBlockAckAgreementHandler::removeAgreement(MacAddress originatorAddr, Tid tid) { auto agreementId = std::make_pair(originatorAddr, tid); auto it = blockAckAgreements.find(agreementId); if (it != blockAckAgreements.end()) { - RecipientBlockAckAgreement *agreement = it->second; + auto agreement = it->second; blockAckAgreements.erase(it); - delete agreement; + return agreement; } + return nullptr; +} + +void RecipientBlockAckAgreementHandler::terminateAgreement(MacAddress originatorAddr, Tid tid) +{ + delete removeAgreement(originatorAddr, tid); } RecipientBlockAckAgreement *RecipientBlockAckAgreementHandler::getAgreement(Tid tid, MacAddress originatorAddr) @@ -147,10 +153,11 @@ void RecipientBlockAckAgreementHandler::processTransmittedDelba(const PtrgetReceiverAddress(), delba->getTid()); } -void RecipientBlockAckAgreementHandler::processReceivedDelba(const Ptr& delba, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy) +std::unique_ptr RecipientBlockAckAgreementHandler::processReceivedDelba(const Ptr& delba, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy) { if (blockAckAgreementPolicy->isDelbaAccepted(delba)) - terminateAgreement(delba->getReceiverAddress(), delba->getTid()); + return std::unique_ptr(removeAgreement(delba->getTransmitterAddress(), delba->getTid())); + return nullptr; } RecipientBlockAckAgreementHandler::~RecipientBlockAckAgreementHandler() diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h index 717f046dddd..65fcfbfd3bc 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h @@ -28,6 +28,7 @@ class INET_API RecipientBlockAckAgreementHandler : public IRecipientBlockAckAgre std::map, RecipientBlockAckAgreement *> blockAckAgreements; protected: + virtual RecipientBlockAckAgreement *removeAgreement(MacAddress originatorAddr, Tid tid); virtual void terminateAgreement(MacAddress originatorAddr, Tid tid); virtual const Ptr buildAddbaResponse(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, bool accepted); virtual const Ptr buildDelba(MacAddress receiverAddr, Tid tid, int reasonCode); @@ -37,7 +38,7 @@ class INET_API RecipientBlockAckAgreementHandler : public IRecipientBlockAckAgre public: virtual ~RecipientBlockAckAgreementHandler(); virtual RecipientBlockAckAgreement *processReceivedAddbaRequest(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) override; - virtual void processReceivedDelba(const Ptr& delba, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy) override; + virtual std::unique_ptr processReceivedDelba(const Ptr& delba, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy) override; virtual void qosFrameReceived(const Ptr& qosHeader, IBlockAckAgreementHandlerCallback *callback) override; virtual void processTransmittedDelba(const Ptr& delba) override; virtual void blockAckAgreementExpired(IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) override; diff --git a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h index 169371bad21..8369055d8af 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h @@ -8,6 +8,8 @@ #ifndef __INET_IORIGINATORBLOCKACKAGREEMENTHANDLER_H #define __INET_IORIGINATORBLOCKACKAGREEMENTHANDLER_H +#include + #include "inet/common/packet/Packet.h" #include "inet/linklayer/common/MacAddress.h" #include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" @@ -29,7 +31,7 @@ class INET_API IOriginatorBlockAckAgreementHandler virtual void processDroppedAddbaReq(Packet *packet, const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; virtual void processAcknowledgedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback) = 0; virtual OriginatorBlockAckAgreement *processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; - virtual void processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; + virtual std::unique_ptr processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; virtual void processTransmittedDelba(const Ptr& delba, IBlockAckAgreementHandlerCallback *callback) = 0; virtual void blockAckAgreementExpired(IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) = 0; virtual void addbaResponseTimeoutExpired(IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; diff --git a/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h index 21b96cf3aee..dac7d884050 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h @@ -8,6 +8,8 @@ #ifndef __INET_IRECIPIENTBLOCKACKAGREEMENTHANDLER_H #define __INET_IRECIPIENTBLOCKACKAGREEMENTHANDLER_H +#include + #include "inet/linklayer/common/MacAddress.h" #include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" #include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h" @@ -25,7 +27,7 @@ class INET_API IRecipientBlockAckAgreementHandler virtual ~IRecipientBlockAckAgreementHandler() {} virtual RecipientBlockAckAgreement *processReceivedAddbaRequest(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) = 0; - virtual void processReceivedDelba(const Ptr& delba, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy) = 0; + virtual std::unique_ptr processReceivedDelba(const Ptr& delba, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy) = 0; virtual void processTransmittedDelba(const Ptr& delba) = 0; virtual void qosFrameReceived(const Ptr& qosHeader, IBlockAckAgreementHandlerCallback *callback) = 0; virtual void blockAckAgreementExpired(IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) = 0; diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc index 2374468a919..ac985294000 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc @@ -13,6 +13,7 @@ #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h" #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckProcedure.h" #include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h" +#include "inet/linklayer/ieee80211/mac/framesequence/FrameSequenceStep.h" #include "inet/linklayer/ieee80211/mac/framesequence/HcfFs.h" #include "inet/linklayer/ieee80211/mac/recipient/RecipientAckProcedure.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" @@ -72,26 +73,69 @@ void Hcf::initialize(int stage) } else if (stage == INITSTAGE_LAST) { for (int ac = 0; ac < edca->getNumEdcafs(); ac++) { - edca->getEdcaf(AccessCategory(ac))->getPendingQueue()->addPacketDropCallback(this); - numPacketDropCallbacksRegistered++; + auto pendingQueue = edca->getEdcaf(AccessCategory(ac))->getPendingQueue(); + pendingQueue->addPacketCallback(this); } + rebuildPendingFrameEligibility(); } } -void Hcf::processDroppedBlockAckSetupFrame(Packet *packet) +void Hcf::trackPendingFrame(Packet *packet, AccessCategory accessCategory) +{ + untrackPendingFrame(packet); + bool eligible = originatorDataService->isFrameEligible(packet); + pendingFrameEligibility.emplace(packet, PendingFrameEligibility { accessCategory, eligible }); + if (eligible) + numEligiblePendingFrames[accessCategory]++; +} + +void Hcf::untrackPendingFrame(const Packet *packet) +{ + auto it = pendingFrameEligibility.find(packet); + if (it != pendingFrameEligibility.end()) { + if (it->second.eligible) { + ASSERT(numEligiblePendingFrames[it->second.accessCategory] > 0); + numEligiblePendingFrames[it->second.accessCategory]--; + } + pendingFrameEligibility.erase(it); + } +} + +void Hcf::rebuildPendingFrameEligibility() +{ + pendingFrameEligibility.clear(); + numEligiblePendingFrames.fill(0); + int numPendingFrames = 0; + for (int ac = 0; ac < edca->getNumEdcafs(); ac++) { + auto accessCategory = AccessCategory(ac); + auto pendingQueue = edca->getEdcaf(accessCategory)->getPendingQueue(); + for (int i = 0; i < pendingQueue->getNumPackets(); i++) { + trackPendingFrame(pendingQueue->getPacket(i), accessCategory); + numPendingFrames++; + } + } + ASSERT((int)pendingFrameEligibility.size() == numPendingFrames); +} + +bool Hcf::processDroppedBlockAckSetupFrame(Packet *packet) { if (originatorBlockAckAgreementHandler) { auto addbaReq = dynamicPtrCast(packet->peekAtFront()); - if (addbaReq != nullptr) + if (addbaReq != nullptr && originatorBlockAckAgreementHandler->isAddbaRequestPending(packet, addbaReq)) { originatorBlockAckAgreementHandler->processDroppedAddbaReq(packet, addbaReq, originatorBlockAckAgreementPolicy, this); + rebuildPendingFrameEligibility(); + return true; + } } + return false; } -void Hcf::handlePacketDropped(Packet *packet) +void Hcf::handlePacketRemoved(Packet *packet, queueing::IPacketQueue::PacketRemovalReason reason) { - Enter_Method("handlePacketDropped"); - processDroppedBlockAckSetupFrame(packet); - resumeEligibleChannelAccess(); + Enter_Method("handlePacketRemoved"); + untrackPendingFrame(packet); + if (reason == queueing::IPacketQueue::PacketRemovalReason::DROPPED && processDroppedBlockAckSetupFrame(packet)) + resumeEligibleChannelAccess(); } std::string Hcf::getFrameSequenceInfo() const @@ -131,6 +175,7 @@ void Hcf::handleMessage(cMessage *msg) else if (msg == addbaResponseTimer) { if (originatorBlockAckAgreementHandler) { originatorBlockAckAgreementHandler->addbaResponseTimeoutExpired(originatorBlockAckAgreementPolicy, this); + rebuildPendingFrameEligibility(); resumeEligibleChannelAccess(); } else @@ -173,6 +218,7 @@ void Hcf::processUpperFrame(Packet *packet, const PtrgetEdcaf(ac)->getPendingQueue(); + trackPendingFrame(packet, ac); pendingQueue->enqueuePacket(packet); if (hasFrameToTransmit(ac)) { auto edcaf = edca->getChannelOwner(); @@ -183,6 +229,24 @@ void Hcf::processUpperFrame(Packet *packet, const PtrisSequenceRunning()) + return false; + auto context = frameSequenceHandler->getContext(); + if (context == nullptr) + return false; + for (int i = 0; i < context->getNumSteps(); i++) { + auto transmitStep = dynamic_cast(context->getStep(i)); + if (transmitStep != nullptr && transmitStep->getFrameToTransmit() == packet) + return true; + auto rtsTransmitStep = dynamic_cast(transmitStep); + if (rtsTransmitStep != nullptr && rtsTransmitStep->getProtectedFrame() == packet) + return true; + } + return false; +} + void Hcf::scheduleStartRxTimer(simtime_t timeout) { Enter_Method("scheduleStartRxTimer"); @@ -209,9 +273,9 @@ void Hcf::scheduleAddbaResponseTimer(simtime_t deadline) void Hcf::cancelAddbaTransaction(uint64_t transactionId, Packet *excludedPacket) { Enter_Method("cancelAddbaTransaction"); - auto belongsToTransaction = [transactionId, excludedPacket](Packet *packet) { + auto belongsToTransaction = [this, transactionId, excludedPacket](Packet *packet) { auto transactionTag = packet->findTag(); - return packet != excludedPacket && transactionTag != nullptr && transactionTag->getTransactionId() == transactionId; + return packet != excludedPacket && !isPacketReferencedByCurrentFrameSequence(packet) && transactionTag != nullptr && transactionTag->getTransactionId() == transactionId; }; for (int ac = 0; ac < edca->getNumEdcafs(); ac++) { auto edcaf = edca->getEdcaf(AccessCategory(ac)); @@ -379,8 +443,7 @@ void Hcf::frameSequenceFinished() edcaf->releaseChannel(this); mac->sendDownPendingRadioConfigMsg(); // TODO review edcaf->getTxopProcedure()->endTxop(); - // FrameSequenceHandler clears its running state after this callback, - // so request all eligible ACs here without the idle-state guard. + // Agreement transitions may have made frames in any AC eligible. requestEligibleChannelAccess(); } else if (hcca->isOwning()) { @@ -444,23 +507,31 @@ void Hcf::recipientProcessReceivedManagementFrame(const Ptr(header)) { + bool wasPending = originatorBlockAckAgreementHandler->isAddbaResponsePending(addbaResp->getTransmitterAddress(), addbaResp->getTid()); auto establishedAgreement = originatorBlockAckAgreementHandler->processReceivedAddbaResp(addbaResp, originatorBlockAckAgreementPolicy, this); + if (wasPending && !originatorBlockAckAgreementHandler->isAddbaResponsePending(addbaResp->getTransmitterAddress(), addbaResp->getTid())) + rebuildPendingFrameEligibility(); if (establishedAgreement != nullptr) emit(blockAckAgreementAddedSignal, establishedAgreement); resumeEligibleChannelAccess(); } else if (auto delba = dynamicPtrCast(header)) { + // IEEE Std 802.11-2024, 9.4.1.16, 10.25.4, and 11.5.3.3: + // Initiator selects the agreement direction; the transmitter is the peer. if (delba->getInitiator()) { - auto agreement = recipientBlockAckAgreementHandler->getAgreement(delba->getTid(), delba->getReceiverAddress()); - if (agreement != nullptr) - emit(blockAckAgreementDeletedSignal, agreement); - recipientBlockAckAgreementHandler->processReceivedDelba(delba, recipientBlockAckAgreementPolicy); + auto agreement = recipientBlockAckAgreementHandler->processReceivedDelba(delba, recipientBlockAckAgreementPolicy); + if (agreement != nullptr) { + recipientDataService->resetBlockAckReordering(delba->getTid(), delba->getTransmitterAddress()); + emit(blockAckAgreementDeletedSignal, agreement.get()); + } } else { - auto agreement = originatorBlockAckAgreementHandler->getAgreement(delba->getReceiverAddress(), delba->getTid()); + bool wasPending = originatorBlockAckAgreementHandler->isAddbaResponsePending(delba->getTransmitterAddress(), delba->getTid()); + auto agreement = originatorBlockAckAgreementHandler->processReceivedDelba(delba, originatorBlockAckAgreementPolicy, this); + if (wasPending && !originatorBlockAckAgreementHandler->isAddbaResponsePending(delba->getTransmitterAddress(), delba->getTid())) + rebuildPendingFrameEligibility(); if (agreement != nullptr) - emit(blockAckAgreementDeletedSignal, agreement); - originatorBlockAckAgreementHandler->processReceivedDelba(delba, originatorBlockAckAgreementPolicy, this); + emit(blockAckAgreementDeletedSignal, agreement.get()); resumeEligibleChannelAccess(); } } @@ -498,25 +569,33 @@ void Hcf::originatorProcessRtsProtectionFailed(Packet *packet) } else if (auto mgmtHeader = dynamicPtrCast(protectedHeader)) { edca->getMgmtAndNonQoSRecoveryProcedure()->rtsFrameTransmissionFailed(mgmtHeader, edcaf->getStationRetryCounters()); - retryLimitReached = edca->getMgmtAndNonQoSRecoveryProcedure()->isRtsFrameRetryLimitReached(packet, dataHeader); + retryLimitReached = edca->getMgmtAndNonQoSRecoveryProcedure()->isRtsFrameRetryLimitReached(packet, mgmtHeader); } else throw cRuntimeError("Unknown frame"); // TODO QoSDataFrame, NonQoSDataFrame - if (retryLimitReached) { - if (auto dataHeader = dynamicPtrCast(protectedHeader)) - edcaf->getRecoveryProcedure()->retryLimitReached(packet, dataHeader); - else if (auto mgmtHeader = dynamicPtrCast(protectedHeader)) - edca->getMgmtAndNonQoSRecoveryProcedure()->retryLimitReached(packet, mgmtHeader); - else ; // TODO nonqos data + auto addbaRequest = dynamicPtrCast(protectedHeader); + bool staleAddbaRequest = addbaRequest != nullptr && originatorBlockAckAgreementHandler != nullptr && !originatorBlockAckAgreementHandler->isAddbaRequestPending(packet, addbaRequest); + if (retryLimitReached || staleAddbaRequest) { + if (retryLimitReached) { + if (auto dataHeader = dynamicPtrCast(protectedHeader)) + edcaf->getRecoveryProcedure()->retryLimitReached(packet, dataHeader); + else if (auto mgmtHeader = dynamicPtrCast(protectedHeader)) + edca->getMgmtAndNonQoSRecoveryProcedure()->rtsFrameRetryLimitReached(packet, mgmtHeader); + else ; // TODO nonqos data + } + else + edca->getMgmtAndNonQoSRecoveryProcedure()->discardRtsFrame(addbaRequest); edcaf->getInProgressFrames()->dropFrame(packet); processDroppedBlockAckSetupFrame(packet); edcaf->getAckHandler()->dropFrame(protectedHeader); - EV_INFO << "Dropping RTS/CTS protected frame " << packet->getName() << ", because retry limit is reached.\n"; + EV_INFO << "Dropping RTS/CTS protected frame " << packet->getName() << (retryLimitReached ? ", because retry limit is reached.\n" : ", because its ADDBA transaction is no longer pending.\n"); PacketDropDetails details; - details.setReason(RETRY_LIMIT_REACHED); - details.setLimit(-1); // TODO + details.setReason(retryLimitReached ? RETRY_LIMIT_REACHED : OTHER_PACKET_DROP); + if (retryLimitReached) + details.setLimit(-1); // TODO emit(packetDroppedSignal, packet, &details); - emit(linkBrokenSignal, packet); + if (retryLimitReached) + emit(linkBrokenSignal, packet); } } else @@ -570,8 +649,12 @@ void Hcf::originatorProcessTransmittedManagementFrame(Packet *packet, const Ptr< else if (dynamicPtrCast(mgmtHeader)) ; // Recipient agreement was established when the successful response was formed. else if (auto delba = dynamicPtrCast(mgmtHeader)) { - if (delba->getInitiator()) + if (delba->getInitiator()) { + bool wasPending = originatorBlockAckAgreementHandler->isAddbaResponsePending(delba->getReceiverAddress(), delba->getTid()); originatorBlockAckAgreementHandler->processTransmittedDelba(delba, this); + if (wasPending) + rebuildPendingFrameEligibility(); + } else recipientBlockAckAgreementHandler->processTransmittedDelba(delba); } @@ -623,20 +706,28 @@ void Hcf::originatorProcessFailedFrame(Packet *failedPacket) } else throw cRuntimeError("Unknown frame"); // TODO qos, nonqos - if (retryLimitReached) { - if (auto dataHeader = dynamicPtrCast(failedHeader)) - edcaf->getRecoveryProcedure()->retryLimitReached(failedPacket, dataHeader); - else if (auto mgmtHeader = dynamicPtrCast(failedHeader)) - edca->getMgmtAndNonQoSRecoveryProcedure()->retryLimitReached(failedPacket, mgmtHeader); + auto addbaRequest = dynamicPtrCast(failedHeader); + bool staleAddbaRequest = addbaRequest != nullptr && originatorBlockAckAgreementHandler != nullptr && !originatorBlockAckAgreementHandler->isAddbaRequestPending(failedPacket, addbaRequest); + if (retryLimitReached || staleAddbaRequest) { + if (retryLimitReached) { + if (auto dataHeader = dynamicPtrCast(failedHeader)) + edcaf->getRecoveryProcedure()->retryLimitReached(failedPacket, dataHeader); + else if (auto mgmtHeader = dynamicPtrCast(failedHeader)) + edca->getMgmtAndNonQoSRecoveryProcedure()->retryLimitReached(failedPacket, mgmtHeader); + } + else + edca->getMgmtAndNonQoSRecoveryProcedure()->discardFrame(failedPacket, addbaRequest); edcaf->getInProgressFrames()->dropFrame(failedPacket); processDroppedBlockAckSetupFrame(failedPacket); edcaf->getAckHandler()->dropFrame(dynamicPtrCast(failedHeader)); - EV_INFO << "Dropping frame " << failedPacket->getName() << ", because retry limit is reached.\n"; + EV_INFO << "Dropping frame " << failedPacket->getName() << (retryLimitReached ? ", because retry limit is reached.\n" : ", because its ADDBA transaction is no longer pending.\n"); PacketDropDetails details; - details.setReason(RETRY_LIMIT_REACHED); - details.setLimit(-1); // TODO + details.setReason(retryLimitReached ? RETRY_LIMIT_REACHED : OTHER_PACKET_DROP); + if (retryLimitReached) + details.setLimit(-1); // TODO emit(packetDroppedSignal, failedPacket, &details); - emit(linkBrokenSignal, failedPacket); + if (retryLimitReached) + emit(linkBrokenSignal, failedPacket); } else { EV_INFO << "Retrying frame " << failedPacket->getName() << ".\n"; @@ -704,8 +795,12 @@ void Hcf::originatorProcessReceivedControlFrame(Packet *packet, const PtrgetInProgressFrames()->dropFrame(lastTransmittedPacket); edcaf->getAckHandler()->dropFrame(lastTransmittedDataOrMgmtHeader); if (auto dataHeader = dynamicPtrCast(lastTransmittedHeader)) { - if (originatorBlockAckAgreementHandler) + if (originatorBlockAckAgreementHandler) { + bool wasPending = originatorBlockAckAgreementHandler->isAddbaResponsePending(dataHeader->getReceiverAddress(), dataHeader->getTid()); originatorBlockAckAgreementHandler->processAcknowledgedDataFrame(lastTransmittedPacket, dataHeader, originatorBlockAckAgreementPolicy, this); + if (!wasPending && originatorBlockAckAgreementHandler->isAddbaResponsePending(dataHeader->getReceiverAddress(), dataHeader->getTid())) + rebuildPendingFrameEligibility(); + } } } else if (auto blockAck = dynamicPtrCast(header)) { @@ -741,7 +836,7 @@ bool Hcf::hasFrameToTransmit(AccessCategory ac) { auto edcaf = edca->getEdcaf(ac); if (edcaf) - return originatorDataService->hasEligibleFrame(edcaf->getPendingQueue()) || edcaf->getInProgressFrames()->hasInProgressFrames(); + return numEligiblePendingFrames[ac] != 0 || edcaf->getInProgressFrames()->hasEligibleInProgressFrames(); else throw cRuntimeError("Hcca is unimplemented"); } @@ -750,7 +845,7 @@ bool Hcf::hasFrameToTransmit() { auto edcaf = edca->getChannelOwner(); if (edcaf) - return originatorDataService->hasEligibleFrame(edcaf->getPendingQueue()) || edcaf->getInProgressFrames()->hasInProgressFrames(); + return numEligiblePendingFrames[edcaf->getAccessCategory()] != 0 || edcaf->getInProgressFrames()->hasEligibleInProgressFrames(); else throw cRuntimeError("Hcca is unimplemented"); } @@ -896,10 +991,8 @@ void Hcf::corruptedFrameReceived() Hcf::~Hcf() { - if (edca != nullptr) { - for (int ac = 0; ac < numPacketDropCallbacksRegistered; ac++) - edca->getEdcaf(AccessCategory(ac))->getPendingQueue()->removePacketDropCallback(this); - } + // Callback pointers are stored by child queues, which are destroyed with + // this compound module. Traversing edca here may reach deleted children. cancelAndDelete(startRxTimer); cancelAndDelete(inactivityTimer); cancelAndDelete(addbaResponseTimer); diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h index 146c969b31c..21effa97af5 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h @@ -8,6 +8,9 @@ #ifndef __INET_HCF_H #define __INET_HCF_H +#include +#include + #include "inet/linklayer/ieee80211/mac/channelaccess/Edca.h" #include "inet/linklayer/ieee80211/mac/channelaccess/Hcca.h" #include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" @@ -96,7 +99,13 @@ class INET_API Hcf : public ICoordinationFunction, public IFrameSequenceHandler: // Queues InProgressFrames *hccaInProgressFrame = nullptr; - int numPacketDropCallbacksRegistered = 0; + + struct PendingFrameEligibility { + AccessCategory accessCategory; + bool eligible; + }; + std::map pendingFrameEligibility; + std::array numEligiblePendingFrames = {}; // Frame sequence handler IFrameSequenceHandler *frameSequenceHandler = nullptr; @@ -120,8 +129,12 @@ class INET_API Hcf : public ICoordinationFunction, public IFrameSequenceHandler: virtual bool hasFrameToTransmit(AccessCategory ac); virtual void requestEligibleChannelAccess(); virtual void resumeEligibleChannelAccess(); - virtual void processDroppedBlockAckSetupFrame(Packet *packet); - virtual void handlePacketDropped(Packet *packet) override; + virtual bool processDroppedBlockAckSetupFrame(Packet *packet); + virtual bool isPacketReferencedByCurrentFrameSequence(const Packet *packet) const; + virtual void handlePacketRemoved(Packet *packet, queueing::IPacketQueue::PacketRemovalReason reason) override; + virtual void trackPendingFrame(Packet *packet, AccessCategory accessCategory); + virtual void untrackPendingFrame(const Packet *packet); + virtual void rebuildPendingFrameEligibility(); virtual bool isReceptionInProgress(); // Recipient diff --git a/src/inet/linklayer/ieee80211/mac/originator/NonQosRecoveryProcedure.cc b/src/inet/linklayer/ieee80211/mac/originator/NonQosRecoveryProcedure.cc index 4074ff0004c..9e516146123 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/NonQosRecoveryProcedure.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/NonQosRecoveryProcedure.cc @@ -127,12 +127,8 @@ void NonQosRecoveryProcedure::ackFrameReceived(Packet *packet, const Ptr& header) +void NonQosRecoveryProcedure::discardFrame(Packet *packet, const Ptr& header) { - EV_WARN << "Retry limit reached for " << *packet << ".\n"; auto id = SequenceControlField(header->getSequenceNumber().get(), header->getFragmentNumber()); if (packet->getByteLength() >= rtsThreshold) { auto it = longRetryCounter.find(id); @@ -144,6 +140,28 @@ void NonQosRecoveryProcedure::retryLimitReached(Packet *packet, const Ptr& protectedHeader) +{ + auto id = SequenceControlField(protectedHeader->getSequenceNumber().get(), protectedHeader->getFragmentNumber()); + shortRetryCounter.erase(id); +} + +// After dropping a frame because it reached its retry limit we need to clear the +// retry counters +// +void NonQosRecoveryProcedure::retryLimitReached(Packet *packet, const Ptr& header) +{ + EV_WARN << "Retry limit reached for " << *packet << ".\n"; + discardFrame(packet, header); + emit(retryLimitReachedSignal, packet); +} + +void NonQosRecoveryProcedure::rtsFrameRetryLimitReached(Packet *packet, const Ptr& protectedHeader) +{ + EV_WARN << "RTS retry limit reached for " << *packet << ".\n"; + discardRtsFrame(protectedHeader); emit(retryLimitReachedSignal, packet); } @@ -247,4 +265,3 @@ bool NonQosRecoveryProcedure::isMulticastFrame(const Ptr& failedHeader); virtual bool isRtsFrameRetryLimitReached(Packet *packet, const Ptr& protectedHeader); + virtual void discardFrame(Packet *packet, const Ptr& header); + virtual void discardRtsFrame(const Ptr& protectedHeader); virtual void retryLimitReached(Packet *packet, const Ptr& header); + virtual void rtsFrameRetryLimitReached(Packet *packet, const Ptr& protectedHeader); virtual int getLongRetryLimit() { return longRetryLimit; } virtual int getShortRetryLimit() { return shortRetryLimit; } @@ -74,4 +77,3 @@ class INET_API NonQosRecoveryProcedure : public SimpleModule, public IRecoveryPr } /* namespace inet */ #endif - diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.cc b/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.cc index 3a984705c4e..4db58ed7e98 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.cc @@ -52,29 +52,18 @@ bool OriginatorMacDataService::isFrameEligible(const Packet *packet) const bool OriginatorMacDataService::hasEligibleFrame(queueing::IPacketQueue *pendingQueue) const { - for (int i = 0; i < pendingQueue->getNumPackets(); i++) - if (isFrameEligible(pendingQueue->getPacket(i))) - return true; - return false; + return pendingQueue->findPacket([this](const Packet *packet) { return isFrameEligible(packet); }) != nullptr; } std::vector *OriginatorMacDataService::extractFramesToTransmit(queueing::IPacketQueue *pendingQueue) { Enter_Method("extractFramesToTransmit"); - if (!hasEligibleFrame(pendingQueue)) + auto packet = pendingQueue->dequeuePacket([this](const Packet *packet) { return isFrameEligible(packet); }); + if (packet == nullptr) return nullptr; else { // if (msduRateLimiting) // txRateLimitingIfNeeded(); - Packet *packet = nullptr; - for (int i = 0; i < pendingQueue->getNumPackets(); i++) { - auto candidate = pendingQueue->getPacket(i); - if (isFrameEligible(candidate)) { - packet = pendingQueue->dequeuePacket(candidate); - break; - } - } - ASSERT(packet != nullptr); take(packet); if (sequenceNumberAssignment) { auto frame = packet->removeAtFront(); diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc index a58823f3d59..f09e0c12ee2 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc @@ -37,9 +37,15 @@ Packet *OriginatorQosMacDataService::aMsduAggregateIfNeeded(queueing::IPacketQue { auto subframes = aMsduAggregationPolicy->computeAggregateFrames(pendingQueue); if (subframes) { + if (!std::all_of(subframes->begin(), subframes->end(), [this](const Packet *packet) { return isFrameEligible(packet); })) { + delete subframes; + return nullptr; + } for (auto subframe : *subframes) { - pendingQueue->removePacket(subframe); - take(subframe); + auto dequeuedSubframe = pendingQueue->dequeuePacket([subframe](const Packet *packet) { return packet == subframe; }); + if (dequeuedSubframe != subframe) + throw cRuntimeError("A-MSDU policy-selected subframe is no longer available in scheduling order"); + take(dequeuedSubframe); } auto aggregatedFrame = aMsduAggregation->aggregateFrames(subframes); emit(packetAggregatedSignal, aggregatedFrame); @@ -92,35 +98,29 @@ bool OriginatorQosMacDataService::isFrameEligible(const Packet *packet) const bool OriginatorQosMacDataService::hasEligibleFrame(queueing::IPacketQueue *pendingQueue) const { - for (int i = 0; i < pendingQueue->getNumPackets(); i++) - if (isFrameEligible(pendingQueue->getPacket(i))) - return true; - return false; + return pendingQueue->findPacket([this](const Packet *packet) { return isFrameEligible(packet); }) != nullptr; } std::vector *OriginatorQosMacDataService::extractFramesToTransmit(queueing::IPacketQueue *pendingQueue) { Enter_Method("extractFramesToTransmit"); - if (!hasEligibleFrame(pendingQueue)) + auto predicate = [this](const Packet *packet) { return isFrameEligible(packet); }; + auto candidate = pendingQueue->findPacket(predicate); + if (candidate == nullptr) return nullptr; else { // if (msduRateLimiting) // txRateLimitingIfNeeded(); Packet *packet = nullptr; - for (int i = 0; i < pendingQueue->getNumPackets(); i++) { - auto candidate = pendingQueue->getPacket(i); - if (!isFrameEligible(candidate)) - continue; - // The current A-MSDU policy only aggregates the queue head with - // frames for the same receiver and TID, so the head eligibility - // decision also applies to every selected subframe. - if (aMsduAggregationPolicy && i == 0) - packet = aMsduAggregateIfNeeded(pendingQueue); - if (!packet) { - packet = pendingQueue->dequeuePacket(candidate); - take(packet); - } - break; + // The current A-MSDU policy enumerates the queue when selecting all + // aggregate members. Use it only if enumeration is guaranteed to be + // the provider's scheduling order for the entire aggregate. + if (aMsduAggregationPolicy && pendingQueue->isPacketOrderPreserved() && pendingQueue->getNumPackets() != 0 && pendingQueue->getPacket(0) == candidate) + packet = aMsduAggregateIfNeeded(pendingQueue); + if (!packet) { + packet = pendingQueue->dequeuePacket(predicate); + ASSERT(packet == candidate); + take(packet); } ASSERT(packet != nullptr); // PS Defer Queueing diff --git a/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.cc b/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.cc index 6a1b37b9af7..0e6dc1635e6 100644 --- a/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.cc +++ b/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.cc @@ -120,8 +120,11 @@ Packet *InProgressFrames::getPendingFrameFor(Packet *frame) void InProgressFrames::dropFrame(Packet *packet) { + auto it = std::find(inProgressFrames.begin(), inProgressFrames.end(), packet); + if (it == inProgressFrames.end()) + return; EV_DEBUG << "Dropping frame " << packet->getName() << ".\n"; - inProgressFrames.erase(std::remove(inProgressFrames.begin(), inProgressFrames.end(), packet), inProgressFrames.end()); + inProgressFrames.erase(it); droppedFrames.push_back(packet); emit(packetDequeuedSignal, packet); } diff --git a/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.h b/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.h index 31b6d637a43..27f7591bb4a 100644 --- a/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.h +++ b/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.h @@ -49,6 +49,7 @@ class INET_API InProgressFrames : public SimpleModule virtual void dropFrame(Packet *packet); virtual void dropFrames(std::set>> seqAndFragNums); + virtual bool hasEligibleInProgressFrames() { return hasEligibleFrameToTransmit(); } virtual bool hasInProgressFrames() { ensureHasFrameToTransmit(); return hasEligibleFrameToTransmit(); } virtual std::vector getOutstandingFrames(); diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc index 8e8b052d0f2..7d0e74e697e 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc @@ -128,8 +128,6 @@ std::vector RecipientQosMacDataService::managementFrameReceived(Packet if (basicReassembly) { // FIXME defragmentation mgmtPacket = defragment(mgmtPacket); } - if (auto delba = dynamicPtrCast(mgmtHeader)) - blockAckReordering->processReceivedDelba(delba); // TODO Defrag, MSDU Integrity, Replay Detection, RX MSDU Rate Limiting if (dynamicPtrCast(mgmtHeader)) { delete mgmtPacket; diff --git a/src/inet/queueing/base/PacketFlowBase.cc b/src/inet/queueing/base/PacketFlowBase.cc index 66b0e60e638..a9500a99a8d 100644 --- a/src/inet/queueing/base/PacketFlowBase.cc +++ b/src/inet/queueing/base/PacketFlowBase.cc @@ -169,11 +169,13 @@ Packet *PacketFlowBase::pullPacket(const cGate *gate) return packet; } -Packet *PacketFlowBase::dequeuePacket(Packet *packet) +Packet *PacketFlowBase::dequeuePacket(const PacketPredicate& predicate) { Enter_Method("dequeuePacket"); checkPacketStreaming(nullptr); - packet = packetExtractor->dequeuePacket(packet); + auto packet = packetExtractor->dequeuePacket(predicate); + if (packet == nullptr) + return nullptr; take(packet); emit(packetPulledInSignal, packet); processPacket(packet); diff --git a/src/inet/queueing/base/PacketFlowBase.h b/src/inet/queueing/base/PacketFlowBase.h index 7a1c9a8c777..27f86e03e5c 100644 --- a/src/inet/queueing/base/PacketFlowBase.h +++ b/src/inet/queueing/base/PacketFlowBase.h @@ -84,7 +84,12 @@ class INET_API PacketFlowBase : public PacketProcessorBase, public virtual IPack virtual Packet *getPacket(int index) const override { return collection->getPacket(index); } virtual bool isEmpty() const override { return collection->isEmpty(); } virtual void removePacket(Packet *packet) override { collection->removePacket(packet); } - virtual Packet *dequeuePacket(Packet *packet) override; + // Pull-side processing runs after a packet is selected and may change its + // contents, so collection order before processing does not guarantee stable + // packet properties after extraction. + virtual bool isPacketOrderPreserved() const override { return false; } + virtual Packet *findPacket(const PacketPredicate& predicate) const override { return packetExtractor->findPacket(predicate); } + virtual Packet *dequeuePacket(const PacketPredicate& predicate) override; virtual void removeAllPackets() override { collection->removeAllPackets(); } }; diff --git a/src/inet/queueing/base/PacketQueueBase.cc b/src/inet/queueing/base/PacketQueueBase.cc index ecf7a39e573..9a8dd6bc74c 100644 --- a/src/inet/queueing/base/PacketQueueBase.cc +++ b/src/inet/queueing/base/PacketQueueBase.cc @@ -57,23 +57,23 @@ Packet *PacketQueueBase::dequeuePacket() return packet; } -void PacketQueueBase::addPacketDropCallback(IPacketQueue::ICallback *callback) +void PacketQueueBase::addPacketCallback(IPacketQueue::ICallback *callback) { - Enter_Method("addPacketDropCallback"); - if (std::find(packetDropCallbacks.begin(), packetDropCallbacks.end(), callback) == packetDropCallbacks.end()) - packetDropCallbacks.push_back(callback); + Enter_Method("addPacketCallback"); + if (std::find(packetCallbacks.begin(), packetCallbacks.end(), callback) == packetCallbacks.end()) + packetCallbacks.push_back(callback); } -void PacketQueueBase::removePacketDropCallback(IPacketQueue::ICallback *callback) +void PacketQueueBase::removePacketCallback(IPacketQueue::ICallback *callback) { - Enter_Method("removePacketDropCallback"); - packetDropCallbacks.erase(std::remove(packetDropCallbacks.begin(), packetDropCallbacks.end(), callback), packetDropCallbacks.end()); + Enter_Method("removePacketCallback"); + packetCallbacks.erase(std::remove(packetCallbacks.begin(), packetCallbacks.end(), callback), packetCallbacks.end()); } -void PacketQueueBase::notifyPacketDropped(Packet *packet) +void PacketQueueBase::notifyPacketRemoved(Packet *packet, IPacketQueue::PacketRemovalReason reason) { - for (auto callback : packetDropCallbacks) - callback->handlePacketDropped(packet); + for (auto callback : packetCallbacks) + callback->handlePacketRemoved(packet, reason); } void PacketQueueBase::recordPacketDequeued(Packet *packet) diff --git a/src/inet/queueing/base/PacketQueueBase.h b/src/inet/queueing/base/PacketQueueBase.h index c7415c3d751..bc03fef282d 100644 --- a/src/inet/queueing/base/PacketQueueBase.h +++ b/src/inet/queueing/base/PacketQueueBase.h @@ -25,7 +25,7 @@ class INET_API PacketQueueBase : public PacketProcessorBase, public virtual IPac cGate *inputGate = nullptr; cGate *outputGate = nullptr; - std::vector packetDropCallbacks; + std::vector packetCallbacks; protected: virtual void initialize(int stage) override; @@ -33,7 +33,7 @@ class INET_API PacketQueueBase : public PacketProcessorBase, public virtual IPac virtual void emit(simsignal_t signal, cObject *object, cObject *details = nullptr) override; virtual void recordPacketDequeued(Packet *packet); - virtual void notifyPacketDropped(Packet *packet); + virtual void notifyPacketRemoved(Packet *packet, IPacketQueue::PacketRemovalReason reason); virtual std::string resolveDirective(char directive) const override; @@ -43,8 +43,8 @@ class INET_API PacketQueueBase : public PacketProcessorBase, public virtual IPac virtual void enqueuePacket(Packet *packet) override; virtual Packet *dequeuePacket() override; - virtual void addPacketDropCallback(IPacketQueue::ICallback *callback) override; - virtual void removePacketDropCallback(IPacketQueue::ICallback *callback) override; + virtual void addPacketCallback(IPacketQueue::ICallback *callback) override; + virtual void removePacketCallback(IPacketQueue::ICallback *callback) override; virtual void pushPacketStart(Packet *packet, const cGate *gate, bps datarate) override { throw cRuntimeError("Invalid operation"); } virtual void pushPacketEnd(Packet *packet, const cGate *gate) override { throw cRuntimeError("Invalid operation"); } diff --git a/src/inet/queueing/buffer/PacketBuffer.cc b/src/inet/queueing/buffer/PacketBuffer.cc index a67da4be8ca..08b4544e8c2 100644 --- a/src/inet/queueing/buffer/PacketBuffer.cc +++ b/src/inet/queueing/buffer/PacketBuffer.cc @@ -58,16 +58,23 @@ void PacketBuffer::addPacket(Packet *packet) packets.push_back(packet); if (isOverloaded()) { if (packetDropperFunction != nullptr) { + std::vector> droppedPackets; while (!isEmpty() && isOverloaded()) { auto packet = packetDropperFunction->selectPacket(this); EV_INFO << "Dropping packet" << EV_FIELD(packet) << EV_ENDL; packets.erase(find(packets, packet)); + ICallback *callback = nullptr; auto queue = dynamic_cast(packet->getOwner()); - if (queue != nullptr) { - ICallback *callback = dynamic_cast(queue->getOwner()); - if (callback != nullptr) - callback->handlePacketRemoved(packet); - } + if (queue != nullptr) + callback = dynamic_cast(queue->getOwner()); + droppedPackets.emplace_back(packet, callback); + } + for (auto& [packet, callback] : droppedPackets) + if (callback != nullptr) + callback->handlePacketRemoved(packet); + for (auto& [packet, callback] : droppedPackets) { + if (callback != nullptr) + callback->handlePacketDropped(packet); // TODO maybe the buffer should take ownership and queues should be aware of it take(packet); dropPacket(packet, QUEUE_OVERFLOW); @@ -118,4 +125,3 @@ Packet *PacketBuffer::getPacket(int index) const } // namespace queueing } // namespace inet - diff --git a/src/inet/queueing/contract/IPacketBuffer.h b/src/inet/queueing/contract/IPacketBuffer.h index ced7f9556f0..f89f04823a4 100644 --- a/src/inet/queueing/contract/IPacketBuffer.h +++ b/src/inet/queueing/contract/IPacketBuffer.h @@ -27,6 +27,13 @@ class INET_API IPacketBuffer : public virtual IPacketCollection * The packet is never nullptr. */ virtual void handlePacketRemoved(Packet *packet) = 0; + + /** + * Notifies the packet owner after all packets selected by one overload + * operation have been removed from their owners. The default implementation + * preserves compatibility for owners which do not distinguish buffer drops. + */ + virtual void handlePacketDropped(Packet *packet) {} }; public: @@ -47,4 +54,3 @@ class INET_API IPacketBuffer : public virtual IPacketCollection } // namespace inet #endif - diff --git a/src/inet/queueing/contract/IPacketExtractor.h b/src/inet/queueing/contract/IPacketExtractor.h index f53b822717c..48f43f292a4 100644 --- a/src/inet/queueing/contract/IPacketExtractor.h +++ b/src/inet/queueing/contract/IPacketExtractor.h @@ -7,6 +7,8 @@ #ifndef __INET_IPACKETEXTRACTOR_H #define __INET_IPACKETEXTRACTOR_H +#include + #include "inet/common/packet/Packet.h" namespace inet { @@ -15,9 +17,26 @@ namespace queueing { /** Provides semantic pull accounting while extracting a selected packet. */ class INET_API IPacketExtractor { + public: + /** + * Predicates must be stable and side-effect free from an initial + * findPacket() through the corresponding dequeuePacket() selection. + * Composite extractors may evaluate the predicate multiple times and on + * multiple candidates while preserving their scheduling policy. + */ + using PacketPredicate = std::function; + public: virtual ~IPacketExtractor() {} - virtual Packet *dequeuePacket(Packet *packet) = 0; + /** + * Returns true when IPacketCollection::getPacket() enumeration order on + * the same object is also the order in which this extractor selects + * packets. The conservative default prevents clients from bypassing a + * provider-specific scheduling policy. + */ + virtual bool isPacketOrderPreserved() const { return false; } + virtual Packet *findPacket(const PacketPredicate& predicate) const = 0; + virtual Packet *dequeuePacket(const PacketPredicate& predicate) = 0; }; } // namespace queueing diff --git a/src/inet/queueing/contract/IPacketQueue.h b/src/inet/queueing/contract/IPacketQueue.h index cf35bc6f00d..15c62bcb13d 100644 --- a/src/inet/queueing/contract/IPacketQueue.h +++ b/src/inet/queueing/contract/IPacketQueue.h @@ -22,10 +22,22 @@ namespace queueing { class INET_API IPacketQueue : public virtual IPacketCollection, public virtual IPacketExtractor, public virtual IPassivePacketSink, public virtual IPassivePacketSource { public: + enum class PacketRemovalReason { + DEQUEUED, + REMOVED, + DROPPED, + }; + class INET_API ICallback { public: virtual ~ICallback() {} - virtual void handlePacketDropped(Packet *packet) = 0; + + /** + * Called exactly once when the packet ceases to be a member of this + * logical queue, before ownership is transferred or the packet is + * deleted. DROPPED identifies a destructive queue-side removal. + */ + virtual void handlePacketRemoved(Packet *packet, PacketRemovalReason reason) = 0; }; /** @@ -44,14 +56,16 @@ class INET_API IPacketQueue : public virtual IPacketCollection, public virtual I */ virtual Packet *dequeuePacket() = 0; + virtual Packet *findPacket(const PacketPredicate& predicate) const = 0; + /** - * Dequeues the specified packet with the same accounting as dequeuePacket(). - * The queue must contain the packet. Ownership is transferred to the caller. + * Dequeues the first matching packet according to the queue provider's + * scheduling policy. Ownership is transferred to the caller. */ - virtual Packet *dequeuePacket(Packet *packet) = 0; + virtual Packet *dequeuePacket(const PacketPredicate& predicate) = 0; - virtual void addPacketDropCallback(ICallback *callback) = 0; - virtual void removePacketDropCallback(ICallback *callback) = 0; + virtual void addPacketCallback(ICallback *callback) = 0; + virtual void removePacketCallback(ICallback *callback) = 0; }; } // namespace queueing diff --git a/src/inet/queueing/queue/CompoundPacketQueueBase.cc b/src/inet/queueing/queue/CompoundPacketQueueBase.cc index 4f3332cd230..f2cdcbe30b8 100644 --- a/src/inet/queueing/queue/CompoundPacketQueueBase.cc +++ b/src/inet/queueing/queue/CompoundPacketQueueBase.cc @@ -28,7 +28,7 @@ void CompoundPacketQueueBase::initialize(int stage) auto childQueue = dynamic_cast(*it); if (childQueue != nullptr) { childQueues.push_back(childQueue); - childQueue->addPacketDropCallback(this); + childQueue->addPacketCallback(this); } } packetDropperFunction = createDropperFunction(par("dropperClass")); @@ -66,11 +66,16 @@ void CompoundPacketQueueBase::pushPacket(Packet *packet, const cGate *gate) EV_INFO << "Pushing packet" << EV_FIELD(packet) << EV_ENDL; consumer.pushPacket(packet); if (packetDropperFunction != nullptr) { + std::vector droppedPackets; while (isOverloaded()) { auto packet = packetDropperFunction->selectPacket(this); EV_INFO << "Dropping packet" << EV_FIELD(packet) << EV_ENDL; - removePacket(packet); - notifyPacketDropped(packet); + collection->removePacket(packet); + emit(packetRemovedSignal, packet); + droppedPackets.push_back(packet); + } + for (auto packet : droppedPackets) { + notifyPacketRemoved(packet, IPacketQueue::PacketRemovalReason::DROPPED); dropPacket(packet, QUEUE_OVERFLOW); } } @@ -84,6 +89,7 @@ Packet *CompoundPacketQueueBase::pullPacket(const cGate *gate) Enter_Method("pullPacket"); auto packet = provider.pullPacket(); take(packet); + notifyPacketRemoved(packet, IPacketQueue::PacketRemovalReason::DEQUEUED); emit(packetPulledSignal, packet); return packet; } @@ -92,14 +98,23 @@ void CompoundPacketQueueBase::removePacket(Packet *packet) { Enter_Method("removePacket"); collection->removePacket(packet); + notifyPacketRemoved(packet, IPacketQueue::PacketRemovalReason::REMOVED); emit(packetRemovedSignal, packet); } -Packet *CompoundPacketQueueBase::dequeuePacket(Packet *packet) +Packet *CompoundPacketQueueBase::findPacket(const PacketPredicate& predicate) const +{ + return packetExtractor->findPacket(predicate); +} + +Packet *CompoundPacketQueueBase::dequeuePacket(const PacketPredicate& predicate) { Enter_Method("dequeuePacket"); - packet = packetExtractor->dequeuePacket(packet); + auto packet = packetExtractor->dequeuePacket(predicate); + if (packet == nullptr) + return nullptr; take(packet); + notifyPacketRemoved(packet, IPacketQueue::PacketRemovalReason::DEQUEUED); // The owning leaf/provider has already recorded queue residence. The // compound boundary mirrors pullPacket() and emits only its pull event. emit(packetPulledSignal, packet); @@ -110,7 +125,12 @@ Packet *CompoundPacketQueueBase::dequeuePacket(Packet *packet) void CompoundPacketQueueBase::removeAllPackets() { Enter_Method("removeAllPacket"); - collection->removeAllPackets(); + while (getNumPackets() != 0) { + auto packet = getPacket(0); + removePacket(packet); + take(packet); + delete packet; + } } bool CompoundPacketQueueBase::canPushSomePacket(const cGate *gate) const @@ -146,10 +166,11 @@ void CompoundPacketQueueBase::receiveSignal(cComponent *source, simsignal_t sign throw cRuntimeError("Unknown signal"); } -void CompoundPacketQueueBase::handlePacketDropped(Packet *packet) +void CompoundPacketQueueBase::handlePacketRemoved(Packet *packet, IPacketQueue::PacketRemovalReason reason) { - Enter_Method("handlePacketDropped"); - notifyPacketDropped(packet); + Enter_Method("handlePacketRemoved"); + if (reason == IPacketQueue::PacketRemovalReason::DROPPED) + notifyPacketRemoved(packet, reason); } } // namespace queueing diff --git a/src/inet/queueing/queue/CompoundPacketQueueBase.h b/src/inet/queueing/queue/CompoundPacketQueueBase.h index bc00b5b4ad1..cd2632379b4 100644 --- a/src/inet/queueing/queue/CompoundPacketQueueBase.h +++ b/src/inet/queueing/queue/CompoundPacketQueueBase.h @@ -52,7 +52,9 @@ class INET_API CompoundPacketQueueBase : public PacketQueueBase, public cListene virtual bool isEmpty() const override { return collection->isEmpty(); } virtual Packet *getPacket(int index) const override { return collection->getPacket(index); } virtual void removePacket(Packet *packet) override; - virtual Packet *dequeuePacket(Packet *packet) override; + virtual bool isPacketOrderPreserved() const override { return packetExtractor->isPacketOrderPreserved(); } + virtual Packet *findPacket(const PacketPredicate& predicate) const override; + virtual Packet *dequeuePacket(const PacketPredicate& predicate) override; virtual void removeAllPackets() override; virtual bool supportsPacketPushing(const cGate *gate) const override { return inputGate == gate; } @@ -66,7 +68,7 @@ class INET_API CompoundPacketQueueBase : public PacketQueueBase, public cListene virtual Packet *pullPacket(const cGate *gate) override; virtual void receiveSignal(cComponent *source, simsignal_t signal, cObject *object, cObject *details) override; - virtual void handlePacketDropped(Packet *packet) override; + virtual void handlePacketRemoved(Packet *packet, IPacketQueue::PacketRemovalReason reason) override; }; } // namespace queueing diff --git a/src/inet/queueing/queue/PacketQueue.cc b/src/inet/queueing/queue/PacketQueue.cc index 5eb7d35dd98..8d42765e40b 100644 --- a/src/inet/queueing/queue/PacketQueue.cc +++ b/src/inet/queueing/queue/PacketQueue.cc @@ -98,11 +98,15 @@ void PacketQueue::pushPacket(Packet *packet, const cGate *gate) throw cRuntimeError("Queue is overloaded while using a packet buffer"); } else if (packetDropperFunction != nullptr) { + std::vector droppedPackets; while (isOverloaded()) { auto packet = packetDropperFunction->selectPacket(this); EV_INFO << "Dropping packet" << EV_FIELD(packet) << EV_ENDL; queue.remove(packet); - notifyPacketDropped(packet); + droppedPackets.push_back(packet); + } + for (auto packet : droppedPackets) { + notifyPacketRemoved(packet, IPacketQueue::PacketRemovalReason::DROPPED); dropPacket(packet, QUEUE_OVERFLOW); } } @@ -125,19 +129,34 @@ Packet *PacketQueue::pullPacket(const cGate *gate) } else queue.pop(); + notifyPacketRemoved(packet, IPacketQueue::PacketRemovalReason::DEQUEUED); recordPacketDequeued(packet); if (collector != nullptr) animatePullPacket(packet, outputGate, collector.getReferencedGate()); return packet; } -Packet *PacketQueue::dequeuePacket(Packet *packet) +Packet *PacketQueue::findPacket(const PacketPredicate& predicate) const +{ + for (int i = 0; i < queue.getLength(); i++) { + auto packet = check_and_cast(queue.get(i)); + if (predicate(packet)) + return packet; + } + return nullptr; +} + +Packet *PacketQueue::dequeuePacket(const PacketPredicate& predicate) { Enter_Method("dequeuePacket"); + auto packet = findPacket(predicate); + if (packet == nullptr) + return nullptr; EV_INFO << "Dequeuing packet" << EV_FIELD(packet) << EV_ENDL; queue.remove(packet); if (buffer != nullptr) buffer->removePacket(packet); + notifyPacketRemoved(packet, IPacketQueue::PacketRemovalReason::DEQUEUED); recordPacketDequeued(packet); drop(packet); return packet; @@ -150,6 +169,7 @@ void PacketQueue::removePacket(Packet *packet) queue.remove(packet); if (buffer != nullptr) buffer->removePacket(packet); + notifyPacketRemoved(packet, IPacketQueue::PacketRemovalReason::REMOVED); emit(packetRemovedSignal, packet); } @@ -163,6 +183,7 @@ void PacketQueue::removeAllPackets() if (buffer != nullptr) buffer->removeAllPackets(); for (auto packet : packets) { + notifyPacketRemoved(packet, IPacketQueue::PacketRemovalReason::REMOVED); emit(packetRemovedSignal, packet); delete packet; } @@ -196,10 +217,15 @@ void PacketQueue::handlePacketRemoved(Packet *packet) if (queue.contains(packet)) { EV_INFO << "Removing packet" << EV_FIELD(packet) << EV_ENDL; queue.remove(packet); - notifyPacketDropped(packet); emit(packetRemovedSignal, packet); } } +void PacketQueue::handlePacketDropped(Packet *packet) +{ + Enter_Method("handlePacketDropped"); + notifyPacketRemoved(packet, IPacketQueue::PacketRemovalReason::DROPPED); +} + } // namespace queueing } // namespace inet diff --git a/src/inet/queueing/queue/PacketQueue.h b/src/inet/queueing/queue/PacketQueue.h index a92a358e4b9..bff2c16e15a 100644 --- a/src/inet/queueing/queue/PacketQueue.h +++ b/src/inet/queueing/queue/PacketQueue.h @@ -60,7 +60,9 @@ class INET_API PacketQueue : public PacketQueueBase, public IPacketBuffer::ICall virtual bool isEmpty() const override { return getNumPackets() == 0; } virtual Packet *getPacket(int index) const override; virtual void removePacket(Packet *packet) override; - virtual Packet *dequeuePacket(Packet *packet) override; + virtual bool isPacketOrderPreserved() const override { return true; } + virtual Packet *findPacket(const PacketPredicate& predicate) const override; + virtual Packet *dequeuePacket(const PacketPredicate& predicate) override; virtual void removeAllPackets() override; virtual bool supportsPacketPushing(const cGate *gate) const override { return inputGate == gate; } @@ -74,6 +76,7 @@ class INET_API PacketQueue : public PacketQueueBase, public IPacketBuffer::ICall virtual Packet *pullPacket(const cGate *gate) override; virtual void handlePacketRemoved(Packet *packet) override; + virtual void handlePacketDropped(Packet *packet) override; }; } // namespace queueing diff --git a/src/inet/queueing/scheduler/LabelScheduler.cc b/src/inet/queueing/scheduler/LabelScheduler.cc index da7c97cc45d..5f8df144194 100644 --- a/src/inet/queueing/scheduler/LabelScheduler.cc +++ b/src/inet/queueing/scheduler/LabelScheduler.cc @@ -66,22 +66,46 @@ void LabelScheduler::removePacket(Packet *packet) throw cRuntimeError("Cannot find packet"); } -Packet *LabelScheduler::dequeuePacket(Packet *packet) +int LabelScheduler::findInput(const PacketPredicate& predicate) const { - Enter_Method("dequeuePacket"); - for (auto collection : collections) { - for (int i = 0; i < collection->getNumPackets(); i++) { - if (collection->getPacket(i) == packet) { - packet = check_and_cast(collection)->dequeuePacket(packet); - take(packet); - handlePacketProcessed(packet); - emit(packetPulledSignal, packet); - drop(packet); - return packet; + std::vector candidates; + for (auto collection : collections) + candidates.push_back(check_and_cast(collection)->findPacket(predicate)); + for (auto label : labels) { + for (size_t i = 0; i < candidates.size(); i++) { + auto packet = candidates[i]; + if (packet == nullptr) + continue; + const auto& labelsTag = packet->findTag(); + if (labelsTag != nullptr) { + for (size_t j = 0; j < labelsTag->getLabelsArraySize(); j++) + if (label == labelsTag->getLabels(j)) + return i; } } } - throw cRuntimeError("Cannot find packet"); + return defaultGateIndex >= 0 && defaultGateIndex < (int)candidates.size() && candidates[defaultGateIndex] != nullptr ? defaultGateIndex : -1; +} + +Packet *LabelScheduler::findPacket(const PacketPredicate& predicate) const +{ + auto index = findInput(predicate); + return index == -1 ? nullptr : check_and_cast(collections[index])->findPacket(predicate); +} + +Packet *LabelScheduler::dequeuePacket(const PacketPredicate& predicate) +{ + Enter_Method("dequeuePacket"); + auto index = findInput(predicate); + if (index == -1) + return nullptr; + auto packet = check_and_cast(collections[index])->dequeuePacket(predicate); + ASSERT(packet != nullptr); + take(packet); + handlePacketProcessed(packet); + emit(packetPulledSignal, packet); + drop(packet); + return packet; } void LabelScheduler::removeAllPackets() diff --git a/src/inet/queueing/scheduler/LabelScheduler.h b/src/inet/queueing/scheduler/LabelScheduler.h index e94dd4461cc..4bdf8722a35 100644 --- a/src/inet/queueing/scheduler/LabelScheduler.h +++ b/src/inet/queueing/scheduler/LabelScheduler.h @@ -25,6 +25,7 @@ class INET_API LabelScheduler : public PacketSchedulerBase, public virtual IPack protected: virtual void initialize(int stage) override; virtual int schedulePacket() override; + virtual int findInput(const PacketPredicate& predicate) const; public: virtual int getMaxNumPackets() const override { return -1; } @@ -36,7 +37,8 @@ class INET_API LabelScheduler : public PacketSchedulerBase, public virtual IPack virtual bool isEmpty() const override { return getNumPackets() == 0; } virtual Packet *getPacket(int index) const override; virtual void removePacket(Packet *packet) override; - virtual Packet *dequeuePacket(Packet *packet) override; + virtual Packet *findPacket(const PacketPredicate& predicate) const override; + virtual Packet *dequeuePacket(const PacketPredicate& predicate) override; virtual void removeAllPackets() override; }; diff --git a/src/inet/queueing/scheduler/PriorityScheduler.cc b/src/inet/queueing/scheduler/PriorityScheduler.cc index 22d7625b778..b146cbc5ecf 100644 --- a/src/inet/queueing/scheduler/PriorityScheduler.cc +++ b/src/inet/queueing/scheduler/PriorityScheduler.cc @@ -71,23 +71,44 @@ void PriorityScheduler::removePacket(Packet *packet) throw cRuntimeError("Cannot find packet"); } -Packet *PriorityScheduler::dequeuePacket(Packet *packet) +bool PriorityScheduler::isPacketOrderPreserved() const { - Enter_Method("dequeuePacket"); + if (reverseOrder) + return false; for (auto collection : collections) { - for (int i = 0; i < collection->getNumPackets(); i++) { - if (collection->getPacket(i) == packet) { - auto extractor = check_and_cast(collection); - packet = extractor->dequeuePacket(packet); - take(packet); - handlePacketProcessed(packet); - emit(packetPulledSignal, packet); - drop(packet); - return packet; - } - } + auto extractor = dynamic_cast(collection); + if (extractor == nullptr || !extractor->isPacketOrderPreserved()) + return false; } - throw cRuntimeError("Cannot find packet"); + return true; +} + +Packet *PriorityScheduler::findPacket(const PacketPredicate& predicate) const +{ + for (size_t i = 0; i < collections.size(); i++) { + auto index = reverseOrder ? collections.size() - i - 1 : i; + auto packet = check_and_cast(collections[index])->findPacket(predicate); + if (packet != nullptr) + return packet; + } + return nullptr; +} + +Packet *PriorityScheduler::dequeuePacket(const PacketPredicate& predicate) +{ + Enter_Method("dequeuePacket"); + for (size_t i = 0; i < collections.size(); i++) { + auto index = reverseOrder ? collections.size() - i - 1 : i; + auto packet = check_and_cast(collections[index])->dequeuePacket(predicate); + if (packet == nullptr) + continue; + take(packet); + handlePacketProcessed(packet); + emit(packetPulledSignal, packet); + drop(packet); + return packet; + } + return nullptr; } void PriorityScheduler::removeAllPackets() diff --git a/src/inet/queueing/scheduler/PriorityScheduler.h b/src/inet/queueing/scheduler/PriorityScheduler.h index 81123e0cef4..1d7a70254c1 100644 --- a/src/inet/queueing/scheduler/PriorityScheduler.h +++ b/src/inet/queueing/scheduler/PriorityScheduler.h @@ -34,7 +34,9 @@ class INET_API PriorityScheduler : public PacketSchedulerBase, public virtual IP virtual bool isEmpty() const override { return getNumPackets() == 0; } virtual Packet *getPacket(int index) const override; virtual void removePacket(Packet *packet) override; - virtual Packet *dequeuePacket(Packet *packet) override; + virtual bool isPacketOrderPreserved() const override; + virtual Packet *findPacket(const PacketPredicate& predicate) const override; + virtual Packet *dequeuePacket(const PacketPredicate& predicate) override; virtual void removeAllPackets() override; virtual void handleCanPullPacketChanged(const cGate *gate) override; diff --git a/src/inet/queueing/scheduler/WrrScheduler.cc b/src/inet/queueing/scheduler/WrrScheduler.cc index 152ba3de94e..75fb27f6deb 100644 --- a/src/inet/queueing/scheduler/WrrScheduler.cc +++ b/src/inet/queueing/scheduler/WrrScheduler.cc @@ -83,22 +83,56 @@ void WrrScheduler::removePacket(Packet *packet) throw cRuntimeError("Cannot find packet"); } -Packet *WrrScheduler::dequeuePacket(Packet *packet) +int WrrScheduler::findInput(const PacketPredicate& predicate) const { - Enter_Method("dequeuePacket"); - for (auto collection : collections) { - for (int i = 0; i < collection->getNumPackets(); i++) { - if (collection->getPacket(i) == packet) { - packet = check_and_cast(collection)->dequeuePacket(packet); - take(packet); - handlePacketProcessed(packet); - emit(packetPulledSignal, packet); - drop(packet); - return packet; - } + int firstWeighted = -1; + int firstNonWeighted = -1; + for (size_t i = 0; i < collections.size(); ++i) { + auto extractor = check_and_cast(collections[i]); + if (extractor->findPacket(predicate) != nullptr) { + if (buckets[i] > 0) + return i; + else if (firstWeighted == -1 && weights[i] > 0) + firstWeighted = i; + else if (firstNonWeighted == -1 && weights[i] == 0) + firstNonWeighted = i; } } - throw cRuntimeError("Cannot find packet"); + return firstWeighted != -1 ? firstWeighted : firstNonWeighted; +} + +void WrrScheduler::consumeBucket(int index) +{ + if (weights[index] == 0) + return; + if (buckets[index] == 0) { + for (size_t i = 0; i < collections.size(); ++i) + buckets[i] = weights[i]; + } + ASSERT(buckets[index] > 0); + buckets[index]--; +} + +Packet *WrrScheduler::findPacket(const PacketPredicate& predicate) const +{ + auto index = findInput(predicate); + return index == -1 ? nullptr : check_and_cast(collections[index])->findPacket(predicate); +} + +Packet *WrrScheduler::dequeuePacket(const PacketPredicate& predicate) +{ + Enter_Method("dequeuePacket"); + auto index = findInput(predicate); + if (index == -1) + return nullptr; + auto packet = check_and_cast(collections[index])->dequeuePacket(predicate); + ASSERT(packet != nullptr); + consumeBucket(index); + take(packet); + handlePacketProcessed(packet); + emit(packetPulledSignal, packet); + drop(packet); + return packet; } void WrrScheduler::removeAllPackets() diff --git a/src/inet/queueing/scheduler/WrrScheduler.h b/src/inet/queueing/scheduler/WrrScheduler.h index a763b66899d..1ccfeef525f 100644 --- a/src/inet/queueing/scheduler/WrrScheduler.h +++ b/src/inet/queueing/scheduler/WrrScheduler.h @@ -29,6 +29,8 @@ class INET_API WrrScheduler : public PacketSchedulerBase, public virtual IPacket protected: virtual void initialize(int stage) override; virtual int schedulePacket() override; + virtual int findInput(const PacketPredicate& predicate) const; + virtual void consumeBucket(int index); public: virtual ~WrrScheduler(); @@ -42,7 +44,8 @@ class INET_API WrrScheduler : public PacketSchedulerBase, public virtual IPacket virtual bool isEmpty() const override { return getNumPackets() == 0; } virtual Packet *getPacket(int index) const override; virtual void removePacket(Packet *packet) override; - virtual Packet *dequeuePacket(Packet *packet) override; + virtual Packet *findPacket(const PacketPredicate& predicate) const override; + virtual Packet *dequeuePacket(const PacketPredicate& predicate) override; virtual void removeAllPackets() override; }; diff --git a/tests/unit/Ieee80211AddbaTransaction_1.test b/tests/unit/Ieee80211AddbaTransaction_1.test index 14778ec7eb0..5a8edf6c99c 100644 --- a/tests/unit/Ieee80211AddbaTransaction_1.test +++ b/tests/unit/Ieee80211AddbaTransaction_1.test @@ -3,11 +3,14 @@ Test IEEE 802.11 ADDBA transactions, reordering reset, fragmentation cleanup, DC %includes: #include +#include #include "inet/common/PacketEventTag.h" #include "inet/common/Simsignals.h" #include "inet/common/TimeTag_m.h" +#include "inet/common/LabelsTag_m.h" #include "inet/common/packet/chunk/ByteCountChunk.h" +#include "inet/linklayer/ieee80211/mac/aggregation/MsduAggregation.h" #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h" #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h" #include "inet/linklayer/ieee80211/mac/blockack/Ieee80211AddbaTransactionTag_m.h" @@ -19,12 +22,17 @@ Test IEEE 802.11 ADDBA transactions, reordering reset, fragmentation cleanup, DC #include "inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.h" #include "inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h" #include "inet/linklayer/ieee80211/mac/fragmentation/Fragmentation.h" +#include "inet/linklayer/ieee80211/mac/framesequence/FrameSequenceStep.h" #include "inet/linklayer/ieee80211/mac/framesequence/HcfFs.h" #include "inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.h" +#include "inet/linklayer/ieee80211/mac/originator/NonQosRecoveryProcedure.h" #include "inet/linklayer/ieee80211/mac/queue/InProgressFrames.h" #include "inet/linklayer/ieee80211/mac/sequencenumberassignment/QoSSequenceNumberAssignment.h" #include "inet/queueing/queue/CompoundPacketQueueBase.h" #include "inet/queueing/queue/PacketQueue.h" +#include "inet/queueing/scheduler/LabelScheduler.h" +#include "inet/queueing/scheduler/PriorityScheduler.h" +#include "inet/queueing/scheduler/WrrScheduler.h" %global: using namespace inet; @@ -35,10 +43,11 @@ class TestOriginatorPolicy : public IOriginatorBlockAckAgreementPolicy public: simtime_t addbaResponseTimeout = 1; simtime_t addbaRetryBackoff = 2; + bool delbaAccepted = true; virtual bool isAddbaReqNeeded(Packet *, const Ptr&) override { return true; } virtual bool isAddbaReqAccepted(const Ptr&, OriginatorBlockAckAgreement *) override { return true; } - virtual bool isDelbaAccepted(const Ptr&) override { return true; } + virtual bool isDelbaAccepted(const Ptr&) override { return delbaAccepted; } virtual bool isMsduSupported() const override { return true; } virtual simtime_t getAddbaResponseTimeout() const override { return addbaResponseTimeout; } virtual simtime_t computeAddbaRetryBackoff() const override { return addbaRetryBackoff; } @@ -51,9 +60,10 @@ class TestRecipientPolicy : public IRecipientBlockAckAgreementPolicy { public: bool accepted = true; + bool delbaAccepted = true; virtual bool isAddbaReqAccepted(const Ptr&) override { return accepted; } - virtual bool isDelbaAccepted(const Ptr&) override { return true; } + virtual bool isDelbaAccepted(const Ptr&) override { return delbaAccepted; } virtual simtime_t getBlockAckTimeoutValue() const override { return 0; } virtual bool aMsduSupported() const override { return true; } virtual bool delayedBlockAckPolicySupported() const override { return false; } @@ -90,6 +100,11 @@ class TestOriginatorHandler : public OriginatorBlockAckAgreementHandler { public: using OriginatorBlockAckAgreementHandler::buildAddbaRequest; + void addEstablishedAgreement(MacAddress receiverAddress, Tid tid) { + auto agreement = new OriginatorBlockAckAgreement(receiverAddress, tid, SequenceNumberCyclic(0), 64, true, false, 1, 1); + agreement->setIsAddbaResponseReceived(true); + blockAckAgreements[std::make_pair(receiverAddress, tid)] = agreement; + } void setNextDialogToken(uint8_t token) { nextDialogToken = token; } void expireNow(MacAddress receiverAddress, Tid tid) { getAgreement(receiverAddress, tid)->setAddbaResponseDeadline(simTime()); } void allowRetryNow(MacAddress receiverAddress, Tid tid) { addbaRetryDeadlines[std::make_pair(receiverAddress, tid)] = simTime(); } @@ -109,21 +124,61 @@ class TestPacketQueue : public queueing::IPacketQueue { public: std::vector packets; + std::vector callbacks; int numSelectedDequeues = 0; + mutable int numGetPacketCalls = 0; + + void notifyPacketRemoved(Packet *packet, PacketRemovalReason reason) { + for (auto callback : callbacks) + callback->handlePacketRemoved(packet, reason); + } virtual int getMaxNumPackets() const override { return -1; } virtual int getNumPackets() const override { return packets.size(); } virtual b getMaxTotalLength() const override { return b(-1); } virtual b getTotalLength() const override { return b(0); } - virtual Packet *getPacket(int index) const override { return packets.at(index); } + virtual Packet *getPacket(int index) const override { numGetPacketCalls++; return packets.at(index); } virtual bool isEmpty() const override { return packets.empty(); } - virtual void removePacket(Packet *packet) override { packets.erase(std::find(packets.begin(), packets.end(), packet)); } - virtual void removeAllPackets() override { packets.clear(); } + virtual void removePacket(Packet *packet) override { + packets.erase(std::find(packets.begin(), packets.end(), packet)); + notifyPacketRemoved(packet, PacketRemovalReason::REMOVED); + } + virtual bool isPacketOrderPreserved() const override { return true; } + virtual void removeAllPackets() override { + auto removedPackets = packets; + packets.clear(); + for (auto packet : removedPackets) + notifyPacketRemoved(packet, PacketRemovalReason::REMOVED); + } virtual void enqueuePacket(Packet *packet) override { packets.push_back(packet); } - virtual Packet *dequeuePacket() override { auto packet = packets.front(); packets.erase(packets.begin()); return packet; } - virtual Packet *dequeuePacket(Packet *packet) override { numSelectedDequeues++; removePacket(packet); return packet; } - virtual void addPacketDropCallback(ICallback *) override {} - virtual void removePacketDropCallback(ICallback *) override {} + virtual Packet *dequeuePacket() override { + auto packet = packets.front(); + packets.erase(packets.begin()); + notifyPacketRemoved(packet, PacketRemovalReason::DEQUEUED); + return packet; + } + virtual Packet *findPacket(const PacketPredicate& predicate) const override { + auto it = std::find_if(packets.begin(), packets.end(), predicate); + return it == packets.end() ? nullptr : *it; + } + virtual Packet *dequeuePacket(const PacketPredicate& predicate) override { + auto packet = findPacket(predicate); + if (packet != nullptr) { + numSelectedDequeues++; + packets.erase(std::find(packets.begin(), packets.end(), packet)); + notifyPacketRemoved(packet, PacketRemovalReason::DEQUEUED); + } + return packet; + } + void dropPacketFromQueue(Packet *packet) { + packets.erase(std::find(packets.begin(), packets.end(), packet)); + notifyPacketRemoved(packet, PacketRemovalReason::DROPPED); + } + virtual void addPacketCallback(ICallback *callback) override { + if (std::find(callbacks.begin(), callbacks.end(), callback) == callbacks.end()) + callbacks.push_back(callback); + } + virtual void removePacketCallback(ICallback *callback) override { callbacks.erase(std::remove(callbacks.begin(), callbacks.end(), callback), callbacks.end()); } virtual bool canPullSomePacket(const cGate *) const override { return !packets.empty(); } virtual Packet *canPullPacket(const cGate *) const override { return packets.empty() ? nullptr : packets.front(); } virtual Packet *pullPacket(const cGate *) override { return dequeuePacket(); } @@ -151,43 +206,70 @@ class TestCompoundPacketQueue : public queueing::CompoundPacketQueueBase } }; -class TestTailDropper : public queueing::IPacketDropperFunction +class TestPriorityScheduler : public queueing::PriorityScheduler { public: - virtual Packet *selectPacket(queueing::IPacketCollection *collection) const override { return collection->getPacket(collection->getNumPackets() - 1); } + void configure(const std::vector& collections, bool reverseOrder = false) { + this->collections = collections; + this->reverseOrder = reverseOrder; + numProcessedPackets = 0; + processedTotalLength = b(0); + } + int getNumProcessedPackets() const { return numProcessedPackets; } + void release(Packet *packet) { take(packet); drop(packet); } }; -class TestRealPacketQueue : public queueing::PacketQueue +class TestWrrScheduler : public queueing::WrrScheduler { public: - void insertPacket(Packet *packet, simtime_t arrivalTime) { - // Stack-constructed test modules don't get the normal OMNeT++ member - // ownership setup, so keep packets owned by the module itself. - queue.setTakeOwnership(false); - take(packet); - packet->setArrivalTime(arrivalTime); - queue.insert(packet); - } - void configureTailDrop(int capacity) { - queue.setTakeOwnership(false); - packetCapacity = capacity; - packetDropperFunction = new TestTailDropper(); + void configure(const std::vector& collections, const std::vector& weights, const std::vector& buckets) { + ASSERT(collections.size() == weights.size()); + ASSERT(collections.size() == buckets.size()); + this->collections = collections; + this->weights = new unsigned int[weights.size()]; + this->buckets = new unsigned int[buckets.size()]; + for (size_t i = 0; i < collections.size(); i++) { + this->weights[i] = weights[i]; + this->buckets[i] = buckets[i]; + } } - void release(Packet *packet) { - take(packet); - drop(packet); + unsigned int getBucket(int index) const { return buckets[index]; } + void release(Packet *packet) { take(packet); drop(packet); } +}; + +class TestLabelScheduler : public queueing::LabelScheduler +{ + public: + void configure(const std::vector& collections, const std::vector& labels, int defaultGateIndex) { + this->collections = collections; + this->labels = labels; + this->defaultGateIndex = defaultGateIndex; } + void release(Packet *packet) { take(packet); drop(packet); } }; class TestPacketDropCallback : public queueing::IPacketQueue::ICallback { public: + int numDequeuedPackets = 0; + int numRemovedPackets = 0; int numDroppedPackets = 0; int64_t lastDroppedPacketId = -1; - - virtual void handlePacketDropped(Packet *packet) override { - numDroppedPackets++; - lastDroppedPacketId = packet->getId(); + std::vector> expectedPacketCountsAtFirstDrop; + bool allExpectedPacketsDetachedAtFirstDrop = true; + + virtual void handlePacketRemoved(Packet *packet, queueing::IPacketQueue::PacketRemovalReason reason) override { + if (reason == queueing::IPacketQueue::PacketRemovalReason::DEQUEUED) + numDequeuedPackets++; + else if (reason == queueing::IPacketQueue::PacketRemovalReason::REMOVED) + numRemovedPackets++; + else if (reason == queueing::IPacketQueue::PacketRemovalReason::DROPPED) { + if (numDroppedPackets == 0) + for (auto& [queue, expectedPacketCount] : expectedPacketCountsAtFirstDrop) + allExpectedPacketsDetachedAtFirstDrop &= queue->getNumPackets() == expectedPacketCount; + numDroppedPackets++; + lastDroppedPacketId = packet->getId(); + } } }; @@ -207,6 +289,14 @@ class TestPacketPulledListener : public cListener } }; +class TestSignalListener : public cListener +{ + public: + int numSignals = 0; + + virtual void receiveSignal(cComponent *, simsignal_t, cObject *, cObject *) override { numSignals++; } +}; + class TestAckHandler : public IAckHandler { public: @@ -243,19 +333,45 @@ class TestEdcaf : public Edcaf queueing::IPacketQueue *pendingQueue = nullptr; InProgressFrames *inProgressFrames = nullptr; QosAckHandler *qosAckHandler = nullptr; + QosRecoveryProcedure *qosRecoveryProcedure = nullptr; + StationRetryCounters stationRetryCounters; virtual queueing::IPacketQueue *getPendingQueue() const override { return pendingQueue; } virtual InProgressFrames *getInProgressFrames() const override { return inProgressFrames; } virtual QosAckHandler *getAckHandler() const override { return qosAckHandler; } + virtual QosRecoveryProcedure *getRecoveryProcedure() const override { return qosRecoveryProcedure; } + virtual StationRetryCounters *getStationRetryCounters() const override { return const_cast(&stationRetryCounters); } }; class TestEdca : public Edca { public: Edcaf *edcaf = nullptr; + std::array edcafs = {}; + int numEdcafs = 1; + NonQosRecoveryProcedure *nonQosRecoveryProcedure = nullptr; + + virtual int getNumEdcafs() const override { return numEdcafs; } + virtual Edcaf *getEdcaf(AccessCategory accessCategory) const override { return numEdcafs == 1 ? edcaf : edcafs[accessCategory]; } + virtual Edcaf *getChannelOwner() override { return edcaf; } + virtual NonQosRecoveryProcedure *getMgmtAndNonQoSRecoveryProcedure() const override { return nonQosRecoveryProcedure; } +}; - virtual int getNumEdcafs() const override { return 1; } - virtual Edcaf *getEdcaf(AccessCategory) const override { return edcaf; } +class TestRecipientDataService : public IRecipientQosMacDataService +{ + public: + int numReorderingResets = 0; + Tid lastTid = -1; + MacAddress lastOriginatorAddress; + + virtual std::vector dataFrameReceived(Packet *, const Ptr&, IRecipientBlockAckAgreementHandler *) override { return {}; } + virtual std::vector controlFrameReceived(Packet *, const Ptr&, IRecipientBlockAckAgreementHandler *) override { return {}; } + virtual std::vector managementFrameReceived(Packet *, const Ptr&) override { return {}; } + virtual void resetBlockAckReordering(Tid tid, MacAddress originatorAddress) override { + numReorderingResets++; + lastTid = tid; + lastOriginatorAddress = originatorAddress; + } }; class TestHcf : public Hcf @@ -263,18 +379,43 @@ class TestHcf : public Hcf public: int numProcessedDroppedSetupFrames = 0; int numResumedEligibleChannelAccess = 0; + bool droppedSetupCancelled = false; void configure(Edca *edca) { this->edca = edca; } + void configureEligibilityIndex(Edca *edca, IOriginatorMacDataService *originatorDataService) { + this->edca = edca; + this->originatorDataService = originatorDataService; + } + void rebuildEligibilityIndex() { rebuildPendingFrameEligibility(); } + void trackFrame(Packet *packet, AccessCategory accessCategory) { trackPendingFrame(packet, accessCategory); } + void untrackFrame(Packet *packet) { untrackPendingFrame(packet); } + bool hasFrame(AccessCategory accessCategory) { return hasFrameToTransmit(accessCategory); } + int getNumEligiblePendingFrames(AccessCategory accessCategory) const { return numEligiblePendingFrames[accessCategory]; } + int getNumTrackedPendingFrames() const { return pendingFrameEligibility.size(); } + void observePendingQueue(queueing::IPacketQueue *pendingQueue) { + pendingQueue->addPacketCallback(this); + } + void configureFrameSequenceHandler(IFrameSequenceHandler *frameSequenceHandler) { this->frameSequenceHandler = frameSequenceHandler; } + void configureRecipientDataService(IRecipientQosMacDataService *recipientDataService) { this->recipientDataService = recipientDataService; } void configureTransmittedData(Edca *edca, IOriginatorBlockAckAgreementHandler *handler, IOriginatorBlockAckAgreementPolicy *policy) { this->edca = edca; originatorBlockAckAgreementHandler = handler; originatorBlockAckAgreementPolicy = policy; } + void configureBlockAckHandlers(IOriginatorBlockAckAgreementHandler *originatorHandler, IOriginatorBlockAckAgreementPolicy *originatorPolicy, IRecipientBlockAckAgreementHandler *recipientHandler, IRecipientBlockAckAgreementPolicy *recipientPolicy) { + originatorBlockAckAgreementHandler = originatorHandler; + originatorBlockAckAgreementPolicy = originatorPolicy; + recipientBlockAckAgreementHandler = recipientHandler; + recipientBlockAckAgreementPolicy = recipientPolicy; + } void processTransmittedData(Packet *packet, const Ptr& dataHeader, AccessCategory ac) { originatorProcessTransmittedDataFrame(packet, dataHeader, ac); } - void dropPacket(Packet *packet) { handlePacketDropped(packet); } + void dropPacket(Packet *packet) { handlePacketRemoved(packet, queueing::IPacketQueue::PacketRemovalReason::DROPPED); } + void cancelTransaction(uint64_t transactionId) { cancelAddbaTransaction(transactionId, nullptr); } + void processReceivedManagementFrame(const Ptr& header) { recipientProcessReceivedManagementFrame(header); } + void processRtsProtectionFailure(Packet *packet) { originatorProcessRtsProtectionFailed(packet); } protected: - virtual void processDroppedBlockAckSetupFrame(Packet *) override { numProcessedDroppedSetupFrames++; } + virtual bool processDroppedBlockAckSetupFrame(Packet *) override { numProcessedDroppedSetupFrames++; return droppedSetupCancelled; } virtual void resumeEligibleChannelAccess() override { numResumedEligibleChannelAccess++; } virtual void scheduleAddbaResponseTimer(simtime_t) override {} }; @@ -300,9 +441,57 @@ class TestFragmentationPolicy : public IFragmentationPolicy virtual std::vector computeFragmentSizes(Packet *) override { return { 2, 2 }; } }; +class TestMsduAggregationPolicy : public IMsduAggregationPolicy +{ + public: + int numCalls = 0; + + virtual std::vector *computeAggregateFrames(queueing::IPacketQueue *) override { numCalls++; return nullptr; } +}; + +class TestSelectedMsduAggregationPolicy : public IMsduAggregationPolicy +{ + public: + std::vector selectedPackets; + + virtual std::vector *computeAggregateFrames(queueing::IPacketQueue *) override { return new std::vector(selectedPackets); } +}; + +class TestNonQosRecoveryProcedure : public NonQosRecoveryProcedure +{ + public: + void addRetry(const Ptr& header) { + rtsThreshold = INT_MAX; + shortRetryCounter[SequenceControlField(header->getSequenceNumber().get(), header->getFragmentNumber())] = 1; + } + bool hasRetry(const Ptr& header) const { + return shortRetryCounter.find(SequenceControlField(header->getSequenceNumber().get(), header->getFragmentNumber())) != shortRetryCounter.end(); + } + void configureRts(int threshold, int retryLimit, ICwCalculator *cwCalculator) { + rtsThreshold = threshold; + shortRetryLimit = retryLimit; + longRetryLimit = retryLimit; + this->cwCalculator = cwCalculator; + } +}; + +class TestCwCalculator : public IRecoveryProcedure::ICwCalculator +{ + public: + int cw = 1; + virtual void incrementCw() override { cw++; } + virtual void resetCw() override { cw = 1; } + virtual int getCw() override { return cw; } +}; + class TestOriginatorQosMacDataService : public OriginatorQosMacDataService { public: + void enableMsduAggregationPolicy(IMsduAggregationPolicy *policy) { + aMsduAggregationPolicy = policy; + if (aMsduAggregation == nullptr) + aMsduAggregation = new MsduAggregation(); + } void enableSequenceNumberAssignment() { sequenceNumberAssignment = new QoSSequenceNumberAssignment(); } void enableFragmentation(IFragmentationPolicy *policy) { fragmentationPolicy = policy; @@ -406,6 +595,13 @@ TestCallback callback; MacAddress peer1("00:00:00:00:00:01"); MacAddress peer2("00:00:00:00:00:02"); MacAddress peer3("00:00:00:00:00:03"); +auto createPacketQueue = [this](const char *name, int packetCapacity = -1, const char *dropperClass = "") { + auto module = cModuleType::get("inet.queueing.queue.PacketQueue")->create(name, this); + module->par("packetCapacity").setIntValue(packetCapacity); + module->par("dropperClass").setStringValue(dropperClass); + module->callInitialize(); + return check_and_cast(module); +}; { TestOriginatorHandler tokenHandler; @@ -523,6 +719,41 @@ handler.allowRetryNow(peer3, 5); handler.processAcknowledgedDataFrame(&timeoutTrigger, timeoutHeader, &originatorPolicy, &callback); ASSERT(handler.isAddbaResponsePending(peer3, 5)); +// Transaction cancellation leaves packets borrowed by the active frame +// sequence under that sequence's ownership, including RTS-protected packets. +{ + const uint64_t transactionId = 900; + TestPacketQueue pendingQueue; + TestInProgressFrames activeFrames; + auto directRequest = makeShared(); + auto directPacket = new Packet("activeAddbaRequest", directRequest); + directPacket->addTag()->setTransactionId(transactionId); + auto protectedRequest = makeShared(); + auto protectedPacket = new Packet("rtsProtectedAddbaRequest", protectedRequest); + protectedPacket->addTag()->setTransactionId(transactionId); + activeFrames.addFrame(directPacket); + activeFrames.addFrame(protectedPacket); + TestEdcaf edcaf; + edcaf.pendingQueue = &pendingQueue; + edcaf.inProgressFrames = &activeFrames; + TestEdca edca; + edca.edcaf = &edcaf; + auto frameSequenceHandler = new TestFrameSequenceHandler(); + frameSequenceHandler->running = true; + frameSequenceHandler->context = new FrameSequenceContext(MacAddress::UNSPECIFIED_ADDRESS, nullptr, &activeFrames, nullptr, nullptr, nullptr, nullptr); + frameSequenceHandler->context->addStep(new TransmitStep(directPacket, 0)); + frameSequenceHandler->context->addStep(new RtsTransmitStep(protectedPacket, new Packet("rts", makeShared()), 0)); + TestHcf hcf; + hcf.configure(&edca); + hcf.configureFrameSequenceHandler(frameSequenceHandler); + hcf.cancelTransaction(transactionId); + ASSERT(activeFrames.getLength() == 2); + auto releasedFrames = activeFrames.releaseFrames(); + ASSERT(releasedFrames.size() == 2); + for (auto frame : releasedFrames) + delete frame; +} + // An unsent transaction has no response deadline and is retired by a typed drop callback. Packet unsentTrigger("unsentTrigger"); auto unsentHeader = makeQosHeader(peer2, 5, SequenceNumberCyclic(40)); @@ -549,7 +780,8 @@ ASSERT(callback.excludedPackets.back() == unsentRequestPacket); auto receivedDelba = makeShared(); receivedDelba->setTransmitterAddress(peer1); receivedDelba->setTid(2); - receivedDelbaHandler.processReceivedDelba(receivedDelba, &originatorPolicy, &receivedDelbaCallback); + auto terminatedAgreement = receivedDelbaHandler.processReceivedDelba(receivedDelba, &originatorPolicy, &receivedDelbaCallback); + ASSERT(terminatedAgreement != nullptr); if (receivedDelbaHandler.getAgreement(peer1, 2) != nullptr || receivedDelbaCallback.cancelledTransactionIds.size() != 1 || receivedDelbaCallback.cancelledTransactionIds.back() != receivedDelbaTransactionId) throw cRuntimeError("Received DELBA did not cancel the pending ADDBA transaction"); ASSERT(receivedDelbaHandler.getAgreement(peer1, 2) == nullptr); @@ -698,6 +930,113 @@ recipientRequest->setStartingSequenceNumber(SequenceNumberCyclic(12)); ASSERT(immediateRecipientHandler.processReceivedAddbaRequest(recipientRequest, &recipientPolicy, &recipientCallback, &recipientCallback) == nullptr); ASSERT(immediateRecipientHandler.getAgreement(6, peer1) == replacementImmediateAgreement); +// A received DELBA is keyed by its transmitter (the peer), not by its receiver +// address (the local station). +{ + RecipientBlockAckAgreementHandler delbaRecipientHandler; + TestRecipientPolicy delbaRecipientPolicy; + TestCallback delbaRecipientCallback; + auto request = makeRequest(peer2, 4, 81, SequenceNumberCyclic(12), 32); + auto establishedAgreement = delbaRecipientHandler.processReceivedAddbaRequest(request, &delbaRecipientPolicy, &delbaRecipientCallback, &delbaRecipientCallback); + ASSERT(establishedAgreement != nullptr); + auto delba = makeShared(); + delba->setTransmitterAddress(peer2); + delba->setReceiverAddress(peer1); + delba->setTid(4); + delba->setInitiator(true); + auto terminatedAgreement = delbaRecipientHandler.processReceivedDelba(delba, &delbaRecipientPolicy); + ASSERT(terminatedAgreement != nullptr); + ASSERT(delbaRecipientHandler.getAgreement(4, peer2) == nullptr); +} + +// HCF emits one deletion event for the peer-keyed recipient agreement and can +// then be torn down without walking an EDCA child hierarchy. +{ + auto originatorHandler = new TestOriginatorHandler(); + auto recipientHandler = new RecipientBlockAckAgreementHandler(); + TestCallback delbaCallback; + auto request = makeRequest(peer2, 2, 82, SequenceNumberCyclic(14), 32); + recipientPolicy.accepted = true; + auto establishedAgreement = recipientHandler->processReceivedAddbaRequest(request, &recipientPolicy, &delbaCallback, &delbaCallback); + ASSERT(establishedAgreement != nullptr); + TestHcf hcf; + hcf.configureBlockAckHandlers(originatorHandler, &originatorPolicy, recipientHandler, &recipientPolicy); + TestRecipientDataService recipientDataService; + hcf.configureRecipientDataService(&recipientDataService); + TestSignalListener deletionListener; + hcf.subscribe(Hcf::blockAckAgreementDeletedSignal, &deletionListener); + auto delba = makeShared(); + delba->setTransmitterAddress(peer2); + delba->setReceiverAddress(peer1); + delba->setTid(2); + delba->setInitiator(true); + hcf.processReceivedManagementFrame(delba); + ASSERT(deletionListener.numSignals == 1); + ASSERT(recipientDataService.numReorderingResets == 1); + ASSERT(recipientDataService.lastTid == 2); + ASSERT(recipientDataService.lastOriginatorAddress == peer2); + ASSERT(recipientHandler->getAgreement(2, peer2) == nullptr); + hcf.unsubscribe(Hcf::blockAckAgreementDeletedSignal, &deletionListener); +} + +// A rejected DELBA leaves the recipient agreement, reordering state, and +// agreement statistics unchanged. +{ + auto originatorHandler = new TestOriginatorHandler(); + auto recipientHandler = new RecipientBlockAckAgreementHandler(); + TestRecipientPolicy rejectingPolicy; + TestCallback delbaCallback; + auto request = makeRequest(peer2, 2, 83, SequenceNumberCyclic(15), 32); + auto establishedAgreement = recipientHandler->processReceivedAddbaRequest(request, &rejectingPolicy, &delbaCallback, &delbaCallback); + ASSERT(establishedAgreement != nullptr); + rejectingPolicy.delbaAccepted = false; + TestHcf hcf; + hcf.configureBlockAckHandlers(originatorHandler, &originatorPolicy, recipientHandler, &rejectingPolicy); + TestRecipientDataService recipientDataService; + hcf.configureRecipientDataService(&recipientDataService); + TestSignalListener deletionListener; + hcf.subscribe(Hcf::blockAckAgreementDeletedSignal, &deletionListener); + auto delba = makeShared(); + delba->setTransmitterAddress(peer2); + delba->setReceiverAddress(peer1); + delba->setTid(2); + delba->setInitiator(true); + hcf.processReceivedManagementFrame(delba); + ASSERT(deletionListener.numSignals == 0); + ASSERT(recipientDataService.numReorderingResets == 0); + ASSERT(recipientHandler->getAgreement(2, peer2) != nullptr); + hcf.unsubscribe(Hcf::blockAckAgreementDeletedSignal, &deletionListener); +} + +// The reverse DELBA direction terminates the originator agreement and emits +// exactly one deletion event; policy rejection emits nothing and preserves it. +{ + auto originatorHandler = new TestOriginatorHandler(); + originatorHandler->addEstablishedAgreement(peer3, 5); + auto recipientHandler = new RecipientBlockAckAgreementHandler(); + TestOriginatorPolicy delbaPolicy; + TestRecipientPolicy delbaRecipientPolicy; + TestHcf hcf; + hcf.configureBlockAckHandlers(originatorHandler, &delbaPolicy, recipientHandler, &delbaRecipientPolicy); + TestSignalListener deletionListener; + hcf.subscribe(Hcf::blockAckAgreementDeletedSignal, &deletionListener); + auto acceptedDelba = makeShared(); + acceptedDelba->setTransmitterAddress(peer3); + acceptedDelba->setReceiverAddress(peer1); + acceptedDelba->setTid(5); + acceptedDelba->setInitiator(false); + hcf.processReceivedManagementFrame(acceptedDelba); + ASSERT(deletionListener.numSignals == 1); + ASSERT(originatorHandler->getAgreement(peer3, 5) == nullptr); + originatorHandler->addEstablishedAgreement(peer3, 5); + delbaPolicy.delbaAccepted = false; + auto rejectedDelba = makeShared(*acceptedDelba); + hcf.processReceivedManagementFrame(rejectedDelba); + ASSERT(deletionListener.numSignals == 1); + ASSERT(originatorHandler->getAgreement(peer3, 5) != nullptr); + hcf.unsubscribe(Hcf::blockAckAgreementDeletedSignal, &deletionListener); +} + OriginatorQosMacDataService dataService; dataService.setFrameEligibilityFunction([&handler](const Packet *packet) { auto header = dynamicPtrCast(packet->peekAtFront()); @@ -719,6 +1058,523 @@ ASSERT(dataService.hasEligibleFrame(&mixedQueue)); mixedQueue.packets = { &heldFrame }; ASSERT(!dataService.hasEligibleFrame(&mixedQueue)); +// HCF availability is a count lookup: rebuilding evaluates each pending frame +// once, while repeated availability checks neither enumerate queues nor +// reevaluate the Block Ack eligibility predicate. +{ + TestOriginatorQosMacDataService indexedDataService; + bool addbaResponsePending = true; + int numEligibilityChecks = 0; + Packet *setupFrame = nullptr; + indexedDataService.setFrameEligibilityFunction([&](const Packet *packet) { + numEligibilityChecks++; + return !addbaResponsePending || packet == setupFrame; + }); + TestAckHandler indexedAckHandler; + TestPacketQueue pendingQueues[AC_NUMCATEGORIES]; + TestInProgressFrames inProgressFrames[AC_NUMCATEGORIES]; + TestEdcaf edcafs[AC_NUMCATEGORIES]; + TestEdca indexedEdca; + indexedEdca.numEdcafs = AC_NUMCATEGORIES; + for (int ac = 0; ac < AC_NUMCATEGORIES; ac++) { + inProgressFrames[ac].configure(&indexedDataService, &indexedAckHandler); + edcafs[ac].pendingQueue = &pendingQueues[ac]; + edcafs[ac].inProgressFrames = &inProgressFrames[ac]; + indexedEdca.edcafs[ac] = &edcafs[ac]; + } + std::vector indexedPackets; + for (int i = 0; i < 128; i++) { + auto packet = new Packet("heldIndexedFrame", makeShared(B(1))); + indexedPackets.push_back(packet); + pendingQueues[AC_BK].enqueuePacket(packet); + } + auto voiceFrame = new Packet("eligibleVoiceFrame", makeShared(B(1))); + indexedPackets.push_back(voiceFrame); + pendingQueues[AC_VO].enqueuePacket(voiceFrame); + TestHcf indexedHcf; + indexedHcf.configureEligibilityIndex(&indexedEdca, &indexedDataService); + indexedHcf.rebuildEligibilityIndex(); + auto requireIndex = [](bool condition, const char *message) { + if (!condition) + throw cRuntimeError("Eligibility index test failed: %s", message); + }; + requireIndex(indexedHcf.getNumTrackedPendingFrames() == 129, "initial cardinality"); + requireIndex(indexedHcf.getNumEligiblePendingFrames(AC_BK) == 0, "initial background count"); + requireIndex(indexedHcf.getNumEligiblePendingFrames(AC_VO) == 0, "initial voice count"); + int eligibilityChecksAfterRebuild = numEligibilityChecks; + int queueReadsAfterRebuild = 0; + for (int ac = 0; ac < AC_NUMCATEGORIES; ac++) + queueReadsAfterRebuild += pendingQueues[ac].numGetPacketCalls; + bool allHeldQueriesUnavailable = true; + for (int i = 0; i < 1000; i++) { + bool hasBackgroundFrame = indexedHcf.hasFrame(AC_BK); + bool hasVoiceFrame = indexedHcf.hasFrame(AC_VO); + allHeldQueriesUnavailable &= !hasBackgroundFrame && !hasVoiceFrame; + } + requireIndex(allHeldQueriesUnavailable, "all-held availability"); + ASSERT(allHeldQueriesUnavailable); + bool predicateWasNotReevaluated = numEligibilityChecks == eligibilityChecksAfterRebuild; + requireIndex(predicateWasNotReevaluated, "availability reevaluated the predicate"); + ASSERT(predicateWasNotReevaluated); + int queueReadsAfterQueries = 0; + for (int ac = 0; ac < AC_NUMCATEGORIES; ac++) + queueReadsAfterQueries += pendingQueues[ac].numGetPacketCalls; + bool queueWasNotEnumerated = queueReadsAfterQueries == queueReadsAfterRebuild; + requireIndex(queueWasNotEnumerated, "availability enumerated a pending queue"); + ASSERT(queueWasNotEnumerated); + + auto originalSetupFrame = new Packet("eligibleSetupFrame", makeShared(B(1))); + setupFrame = originalSetupFrame; + indexedPackets.push_back(originalSetupFrame); + indexedHcf.trackFrame(originalSetupFrame, AC_BK); + pendingQueues[AC_BK].enqueuePacket(originalSetupFrame); + bool hasSetupFrame = indexedHcf.hasFrame(AC_BK); + requireIndex(hasSetupFrame, "tracked setup frame unavailable"); + auto replacementFrame = new Packet("eligibleReplacementFrame", makeShared(B(1))); + setupFrame = replacementFrame; + indexedPackets.push_back(replacementFrame); + indexedHcf.trackFrame(replacementFrame, AC_BK); + pendingQueues[AC_BK].enqueuePacket(replacementFrame); + indexedHcf.untrackFrame(indexedPackets.front()); + pendingQueues[AC_BK].removePacket(indexedPackets.front()); + requireIndex(indexedHcf.getNumEligiblePendingFrames(AC_BK) == 2, "existing-victim count"); + auto tailDroppedArrival = new Packet("tailDroppedArrival", makeShared(B(1))); + indexedHcf.trackFrame(tailDroppedArrival, AC_BK); + indexedHcf.untrackFrame(tailDroppedArrival); + indexedHcf.untrackFrame(tailDroppedArrival); + delete tailDroppedArrival; + requireIndex(indexedHcf.getNumEligiblePendingFrames(AC_BK) == 2, "tail-drop count"); + indexedHcf.untrackFrame(replacementFrame); + pendingQueues[AC_BK].removePacket(replacementFrame); + indexedHcf.untrackFrame(originalSetupFrame); + pendingQueues[AC_BK].removePacket(originalSetupFrame); + bool hasFrameAfterSelectedRemovals = indexedHcf.hasFrame(AC_BK); + requireIndex(!hasFrameAfterSelectedRemovals, "selected removals left availability"); + + addbaResponsePending = false; + indexedHcf.rebuildEligibilityIndex(); + requireIndex(indexedHcf.getNumEligiblePendingFrames(AC_BK) == pendingQueues[AC_BK].getNumPackets(), "terminal-transition background count"); + requireIndex(indexedHcf.getNumEligiblePendingFrames(AC_VO) == 1, "terminal-transition voice count"); + addbaResponsePending = true; + setupFrame = nullptr; + indexedHcf.rebuildEligibilityIndex(); + bool hasBackgroundFrameAfterCreation = indexedHcf.hasFrame(AC_BK); + bool hasVoiceFrameAfterCreation = indexedHcf.hasFrame(AC_VO); + requireIndex(!hasBackgroundFrameAfterCreation && !hasVoiceFrameAfterCreation, "creation-transition availability"); + auto inProgressFrame = new Packet("inProgressOnlyFrame", makeQosHeader(peer1, 3, SequenceNumberCyclic(1))); + setupFrame = inProgressFrame; + inProgressFrames[AC_VI].addFrame(inProgressFrame); + bool hasInProgressFrame = indexedHcf.hasFrame(AC_VI); + requireIndex(hasInProgressFrame, "eligible in-progress frame unavailable"); + requireIndex(indexedHcf.getNumTrackedPendingFrames() == 128, "in-progress frame entered pending index"); + auto releasedFrames = inProgressFrames[AC_VI].releaseFrames(); + requireIndex(releasedFrames.size() == 1, "in-progress release count"); + delete releasedFrames.front(); + setupFrame = nullptr; + bool hasFrameAfterInProgressRelease = indexedHcf.hasFrame(AC_VI); + requireIndex(!hasFrameAfterInProgressRelease, "released in-progress frame remained available"); + + for (int ac = 0; ac < AC_NUMCATEGORIES; ac++) + pendingQueues[ac].removeAllPackets(); + for (auto packet : indexedPackets) + delete packet; +} + +// A direct IPacketQueue implementation without OMNeT++ signals cannot leave +// the index stale because ownership departures use the typed queue callback. +{ + TestOriginatorQosMacDataService customQueueDataService; + TestPacketQueue customQueue; + TestAckHandler customQueueAckHandler; + TestInProgressFrames customQueueInProgressFrames; + customQueueInProgressFrames.configure(&customQueueDataService, &customQueueAckHandler, &customQueue); + TestEdcaf customQueueEdcaf; + customQueueEdcaf.pendingQueue = &customQueue; + customQueueEdcaf.inProgressFrames = &customQueueInProgressFrames; + TestEdca customQueueEdca; + customQueueEdca.edcaf = &customQueueEdcaf; + TestHcf customQueueHcf; + customQueueHcf.configureEligibilityIndex(&customQueueEdca, &customQueueDataService); + customQueueHcf.observePendingQueue(&customQueue); + customQueueHcf.rebuildEligibilityIndex(); + auto customPacket = new Packet("customQueuePacket", makeShared(B(1))); + customQueueHcf.trackFrame(customPacket, AC_BK); + customQueue.enqueuePacket(customPacket); + if (customQueueHcf.getNumEligiblePendingFrames(AC_BK) != 1) + throw cRuntimeError("Custom queue enqueue was not indexed"); + auto customDequeuedPacket = customQueue.dequeuePacket(); + if (customDequeuedPacket != customPacket || customQueueHcf.getNumTrackedPendingFrames() != 0) + throw cRuntimeError("Custom queue departure callback left the index stale"); + delete customDequeuedPacket; +} + +// Top-level queue lifecycle callbacks maintain the same index incrementally; +// destructive drops are distinguished from ordinary departures. +{ + TestOriginatorQosMacDataService indexedDataService; + indexedDataService.setFrameEligibilityFunction([](const Packet *) { return true; }); + QosAckHandler indexedAckHandler; + TestInProgressFrames indexedInProgressFrames; + indexedInProgressFrames.configure(&indexedDataService, &indexedAckHandler); + TestEdcaf indexedEdcaf; + TestEdca indexedEdca; + indexedEdca.edcaf = &indexedEdcaf; + indexedEdcaf.inProgressFrames = &indexedInProgressFrames; + auto overflowQueue = createPacketQueue("indexedOverflowQueue", 1, "inet::queueing::PacketAtCollectionEndDropper"); + indexedEdcaf.pendingQueue = overflowQueue; + TestHcf indexedHcf; + indexedHcf.configureEligibilityIndex(&indexedEdca, &indexedDataService); + indexedHcf.observePendingQueue(overflowQueue); + indexedHcf.rebuildEligibilityIndex(); + auto retainedPacket = new Packet("indexedRetainedPacket", makeShared(B(1))); + indexedHcf.trackFrame(retainedPacket, AC_BK); + overflowQueue->pushPacket(retainedPacket, nullptr); + auto droppedArrival = new Packet("indexedDroppedArrival", makeShared(B(1))); + indexedHcf.trackFrame(droppedArrival, AC_BK); + overflowQueue->pushPacket(droppedArrival, nullptr); + if (indexedHcf.getNumTrackedPendingFrames() != 1 || indexedHcf.getNumEligiblePendingFrames(AC_BK) != 1) + throw cRuntimeError("Eligibility index did not account for overflow"); + auto selectedPacket = overflowQueue->dequeuePacket([](const Packet *) { return true; }); + if (selectedPacket != retainedPacket || indexedHcf.getNumTrackedPendingFrames() != 0) + throw cRuntimeError("Eligibility index did not account for selected dequeue"); + bool hasFrameAfterSelectedDequeue = indexedHcf.hasFrame(AC_BK); + if (hasFrameAfterSelectedDequeue) + throw cRuntimeError("Eligibility index reports a frame after selected dequeue"); + take(selectedPacket); + delete selectedPacket; + overflowQueue->callFinish(); + overflowQueue->deleteModule(); +} + +// Predicate extraction follows each provider's scheduling policy instead of +// the compound collection's gate-order enumeration. +{ + TestPacketQueue firstQueue; + TestPacketQueue secondQueue; + auto firstPacket = new Packet("priorityFirstPacket", makeShared(B(1))); + auto secondPacket = new Packet("prioritySecondPacket", makeShared(B(1))); + firstQueue.packets = { firstPacket }; + secondQueue.packets = { secondPacket }; + TestPriorityScheduler scheduler; + scheduler.configure({ &firstQueue, &secondQueue }, true); + auto predicate = [](const Packet *) { return true; }; + ASSERT(scheduler.findPacket(predicate) == secondPacket); + auto dequeuedPacket = scheduler.dequeuePacket(predicate); + ASSERT(dequeuedPacket == secondPacket); + scheduler.release(secondPacket); + delete secondPacket; + firstQueue.removePacket(firstPacket); + delete firstPacket; +} +{ + TestPacketQueue firstQueue; + TestPacketQueue secondQueue; + auto firstPacket = new Packet("wrrFirstPacket", makeShared(B(1))); + auto secondPacket = new Packet("wrrSecondPacket", makeShared(B(1))); + firstQueue.packets = { firstPacket }; + secondQueue.packets = { secondPacket }; + TestWrrScheduler scheduler; + scheduler.configure({ &firstQueue, &secondQueue }, { 1, 2 }, { 0, 2 }); + auto predicate = [](const Packet *) { return true; }; + ASSERT(scheduler.findPacket(predicate) == secondPacket); + ASSERT(scheduler.findPacket(predicate) == secondPacket); + ASSERT(scheduler.getBucket(1) == 2); + auto dequeuedPacket = scheduler.dequeuePacket(predicate); + ASSERT(dequeuedPacket == secondPacket); + ASSERT(scheduler.getBucket(1) == 1); + scheduler.release(secondPacket); + delete secondPacket; + firstQueue.removePacket(firstPacket); + delete firstPacket; +} +{ + TestPacketQueue firstQueue; + TestPacketQueue secondQueue; + auto blockedPacket = new Packet("labelBlockedPacket", makeShared(B(1))); + auto lowPacket = new Packet("labelLowPacket", makeShared(B(1))); + auto highPacket = new Packet("labelHighPacket", makeShared(B(1))); + auto lowLabels = lowPacket->addTag(); + lowLabels->setLabelsArraySize(1); + lowLabels->setLabels(0, "low"); + auto highLabels = highPacket->addTag(); + highLabels->setLabelsArraySize(1); + highLabels->setLabels(0, "high"); + firstQueue.packets = { blockedPacket, lowPacket }; + secondQueue.packets = { highPacket }; + TestLabelScheduler scheduler; + scheduler.configure({ &firstQueue, &secondQueue }, { "high", "low" }, 0); + auto predicate = [blockedPacket](const Packet *packet) { return packet != blockedPacket; }; + ASSERT(scheduler.findPacket(predicate) == highPacket); + auto dequeuedPacket = scheduler.dequeuePacket(predicate); + ASSERT(dequeuedPacket == highPacket); + scheduler.release(highPacket); + delete highPacket; + firstQueue.removePacket(blockedPacket); + firstQueue.removePacket(lowPacket); + delete blockedPacket; + delete lowPacket; +} + +// Every A-MSDU member is extracted through the scheduler, so provider +// accounting runs for all policy-selected subframes. +{ + auto makeAggregatePacket = [&](const char *name, SequenceNumberCyclic sequenceNumber) { + auto header = makeQosHeader(peer1, 1, sequenceNumber); + header->setTransmitterAddress(peer2); + auto packet = new Packet(name, header); + packet->insertAtBack(makeShared(B(8))); + packet->insertAtBack(makeShared()); + return packet; + }; + TestPacketQueue firstQueue; + TestPacketQueue secondQueue; + auto firstSubframe = makeAggregatePacket("schedulerAggregateFirst", SequenceNumberCyclic(10)); + auto secondSubframe = makeAggregatePacket("schedulerAggregateSecond", SequenceNumberCyclic(11)); + firstQueue.enqueuePacket(firstSubframe); + secondQueue.enqueuePacket(secondSubframe); + TestPriorityScheduler scheduler; + scheduler.configure({ &firstQueue, &secondQueue }); + TestCompoundPacketQueue compoundQueue; + compoundQueue.configure(&scheduler, &scheduler); + TestSelectedMsduAggregationPolicy aggregationPolicy; + aggregationPolicy.selectedPackets = { firstSubframe, secondSubframe }; + TestOriginatorQosMacDataService service; + service.enableMsduAggregationPolicy(&aggregationPolicy); + auto frames = service.extractFramesToTransmit(&compoundQueue); + if (frames == nullptr || frames->size() != 1 || scheduler.getNumProcessedPackets() != 2 || !firstQueue.isEmpty() || !secondQueue.isEmpty()) + throw cRuntimeError("A-MSDU extraction bypassed scheduler processing"); + service.release(frames->front()); + delete frames->front(); + delete frames; +} + +// PacketFlowBase cannot advertise A-MSDU-safe order because processing happens +// after selection and may transform aggregation-relevant packet contents. +// Ordinary predicate extraction still runs the flow processing exactly once. +{ + auto leafQueue = createPacketQueue("aggregationFlowLeaf"); + auto flowModule = cModuleType::get("inet.queueing.common.BackPressureBarrier")->create("aggregationFlow", this); + leafQueue->gate("out")->connectTo(flowModule->gate("in")); + flowModule->callInitialize(); + TestSignalListener flowListener; + flowModule->subscribe(packetPulledOutSignal, &flowListener); + auto makeAggregatePacket = [&](const char *name, SequenceNumberCyclic sequenceNumber) { + auto header = makeQosHeader(peer1, 1, sequenceNumber); + header->setTransmitterAddress(peer2); + auto packet = new Packet(name, header); + packet->insertAtBack(makeShared(B(8))); + packet->insertAtBack(makeShared()); + return packet; + }; + auto firstSubframe = makeAggregatePacket("flowAggregateFirst", SequenceNumberCyclic(12)); + auto secondSubframe = makeAggregatePacket("flowAggregateSecond", SequenceNumberCyclic(13)); + leafQueue->pushPacket(firstSubframe, nullptr); + leafQueue->pushPacket(secondSubframe, nullptr); + auto flowCollection = check_and_cast(flowModule); + auto flowExtractor = check_and_cast(flowModule); + TestCompoundPacketQueue compoundQueue; + compoundQueue.configure(flowCollection, flowExtractor); + TestMsduAggregationPolicy aggregationPolicy; + TestOriginatorQosMacDataService service; + service.enableMsduAggregationPolicy(&aggregationPolicy); + auto frames = service.extractFramesToTransmit(&compoundQueue); + if (frames == nullptr || frames->size() != 1 || frames->front() != firstSubframe || aggregationPolicy.numCalls != 0 || flowListener.numSignals != 1 || leafQueue->getNumPackets() != 1) + throw cRuntimeError("PacketFlowBase incorrectly advertised A-MSDU-safe ordering"); + service.release(frames->front()); + delete frames->front(); + delete frames; + auto remainingPacket = flowExtractor->dequeuePacket([secondSubframe](const Packet *packet) { return packet == secondSubframe; }); + if (remainingPacket != secondSubframe || flowListener.numSignals != 2 || leafQueue->getNumPackets() != 0) + throw cRuntimeError("Predicate dequeue bypassed PacketFlowBase processing"); + take(remainingPacket); + delete remainingPacket; + flowModule->unsubscribe(packetPulledOutSignal, &flowListener); + flowModule->callFinish(); + flowModule->deleteModule(); + leafQueue->callFinish(); + leafQueue->deleteModule(); +} + +// End-to-end compound extraction uses the provider's reverse-priority choice. +// A-MSDU aggregation is conservatively skipped when queue enumeration has a +// different head, so it cannot remove a held frame behind the scheduler. +{ + TestPacketQueue firstQueue; + TestPacketQueue secondQueue; + auto heldPacket = new Packet("compoundHeldPacket", makeQosHeader(peer1, 1, SequenceNumberCyclic(1))); + auto selectedPacket = new Packet("compoundSelectedPacket", makeQosHeader(peer1, 1, SequenceNumberCyclic(2))); + firstQueue.packets = { heldPacket }; + secondQueue.packets = { selectedPacket }; + TestPriorityScheduler scheduler; + scheduler.configure({ &firstQueue, &secondQueue }, true); + TestCompoundPacketQueue compoundQueue; + compoundQueue.configure(&scheduler, &scheduler); + TestMsduAggregationPolicy aggregationPolicy; + TestOriginatorQosMacDataService service; + service.enableMsduAggregationPolicy(&aggregationPolicy); + service.setFrameEligibilityFunction([heldPacket](const Packet *packet) { return packet != heldPacket; }); + auto frames = service.extractFramesToTransmit(&compoundQueue); + ASSERT(frames != nullptr); + ASSERT(frames->size() == 1); + ASSERT(frames->front() == selectedPacket); + ASSERT(aggregationPolicy.numCalls == 0); + ASSERT(firstQueue.getNumPackets() == 1); + service.release(selectedPacket); + delete selectedPacket; + delete frames; + firstQueue.removePacket(heldPacket); + delete heldPacket; +} + +// Even when a WRR provider's current choice is the collection head, A-MSDU +// aggregation must not enumerate subsequent members outside WRR policy. +{ + TestPacketQueue firstQueue; + TestPacketQueue secondQueue; + auto selectedPacket = new Packet("wrrAggregateSelectedPacket", makeQosHeader(peer1, 1, SequenceNumberCyclic(3))); + auto sameInputPacket = new Packet("wrrAggregateSameInputPacket", makeQosHeader(peer1, 1, SequenceNumberCyclic(4))); + auto otherInputPacket = new Packet("wrrAggregateOtherInputPacket", makeQosHeader(peer1, 1, SequenceNumberCyclic(5))); + firstQueue.packets = { selectedPacket, sameInputPacket }; + secondQueue.packets = { otherInputPacket }; + TestWrrScheduler scheduler; + scheduler.configure({ &firstQueue, &secondQueue }, { 1, 1 }, { 1, 1 }); + TestCompoundPacketQueue compoundQueue; + compoundQueue.configure(&scheduler, &scheduler); + ASSERT(compoundQueue.getPacket(0) == selectedPacket); + ASSERT(!compoundQueue.isPacketOrderPreserved()); + TestMsduAggregationPolicy aggregationPolicy; + TestOriginatorQosMacDataService service; + service.enableMsduAggregationPolicy(&aggregationPolicy); + auto frames = service.extractFramesToTransmit(&compoundQueue); + ASSERT(frames != nullptr); + ASSERT(frames->size() == 1); + ASSERT(frames->front() == selectedPacket); + ASSERT(aggregationPolicy.numCalls == 0); + ASSERT(firstQueue.getNumPackets() == 1); + ASSERT(secondQueue.getNumPackets() == 1); + service.release(selectedPacket); + delete selectedPacket; + delete frames; + firstQueue.removePacket(sameInputPacket); + secondQueue.removePacket(otherInputPacket); + delete sameInputPacket; + delete otherInputPacket; +} + +// Discarding a stale setup frame clears its per-frame retry state without +// emitting the retry-limit semantic event. +{ + TestNonQosRecoveryProcedure recoveryProcedure; + TestSignalListener retryLimitListener; + recoveryProcedure.subscribe(IRecoveryProcedure::retryLimitReachedSignal, &retryLimitListener); + auto header = makeShared(); + header->setSequenceNumber(SequenceNumberCyclic(42)); + Packet packet("staleRecoveryPacket", header); + recoveryProcedure.addRetry(header); + ASSERT(recoveryProcedure.hasRetry(header)); + recoveryProcedure.discardFrame(&packet, header); + ASSERT(!recoveryProcedure.hasRetry(header)); + ASSERT(retryLimitListener.numSignals == 0); + recoveryProcedure.unsubscribe(IRecoveryProcedure::retryLimitReachedSignal, &retryLimitListener); +} + +// A stale RTS-protected ADDBA request uses the management header for its retry +// check and clears the RTS short-retry entry even when the protected frame is +// long. It is a transaction discard, not a retry-limit or link-break event. +{ + TestCwCalculator cwCalculator; + TestNonQosRecoveryProcedure recoveryProcedure; + recoveryProcedure.configureRts(10, 4, &cwCalculator); + QosAckHandler ackHandler; + TestOriginatorQosMacDataService originatorDataService; + TestInProgressFrames activeFrames; + activeFrames.configure(&originatorDataService, &ackHandler); + TestEdcaf edcaf; + edcaf.inProgressFrames = &activeFrames; + edcaf.qosAckHandler = &ackHandler; + TestEdca edca; + edca.edcaf = &edcaf; + edca.nonQosRecoveryProcedure = &recoveryProcedure; + auto staleHandler = new TestOriginatorHandler(); + TestHcf hcf; + hcf.configureTransmittedData(&edca, staleHandler, &originatorPolicy); + TestSignalListener packetDropListener; + TestSignalListener linkBrokenListener; + hcf.subscribe(packetDroppedSignal, &packetDropListener); + hcf.subscribe(linkBrokenSignal, &linkBrokenListener); + auto request = makeShared(); + request->setReceiverAddress(peer1); + request->setTid(3); + request->setDialogToken(91); + request->setSequenceNumber(SequenceNumberCyclic(77)); + auto packet = new Packet("staleRtsProtectedAddba", request); + packet->insertAtBack(makeShared(B(100))); + ASSERT(packet->getByteLength() >= 10); + activeFrames.addFrame(packet); + ackHandler.processTransmittedDataOrMgmtFrame(request); + ASSERT(ackHandler.getMgmtOrNonQoSAckStatus(request) == QosAckHandler::Status::WAITING_FOR_NORMAL_ACK); + hcf.processRtsProtectionFailure(packet); + ASSERT(recoveryProcedure.getShortRetryCount(packet, request) == 0); + ASSERT(recoveryProcedure.getLongRetryCount(packet, request) == 0); + ASSERT(ackHandler.getMgmtOrNonQoSAckStatus(request) == QosAckHandler::Status::FRAME_NOT_YET_TRANSMITTED); + ASSERT(packetDropListener.numSignals == 1); + ASSERT(linkBrokenListener.numSignals == 0); + hcf.unsubscribe(packetDroppedSignal, &packetDropListener); + hcf.unsubscribe(linkBrokenSignal, &linkBrokenListener); + auto releasedFrames = activeFrames.releaseFrames(); + ASSERT(releasedFrames.size() == 1); + delete releasedFrames.front(); +} + +// At the RTS retry limit, the same long protected management frame still +// clears its short-retry entry and emits each terminal event exactly once. +{ + TestCwCalculator cwCalculator; + TestNonQosRecoveryProcedure recoveryProcedure; + recoveryProcedure.configureRts(10, 1, &cwCalculator); + QosAckHandler ackHandler; + TestOriginatorQosMacDataService originatorDataService; + TestInProgressFrames activeFrames; + activeFrames.configure(&originatorDataService, &ackHandler); + TestEdcaf edcaf; + edcaf.inProgressFrames = &activeFrames; + edcaf.qosAckHandler = &ackHandler; + TestEdca edca; + edca.edcaf = &edcaf; + edca.nonQosRecoveryProcedure = &recoveryProcedure; + TestHcf hcf; + hcf.configureTransmittedData(&edca, new TestOriginatorHandler(), &originatorPolicy); + TestSignalListener retryLimitListener; + TestSignalListener packetDropListener; + TestSignalListener linkBrokenListener; + recoveryProcedure.subscribe(IRecoveryProcedure::retryLimitReachedSignal, &retryLimitListener); + hcf.subscribe(packetDroppedSignal, &packetDropListener); + hcf.subscribe(linkBrokenSignal, &linkBrokenListener); + auto request = makeShared(); + request->setReceiverAddress(peer1); + request->setTid(3); + request->setDialogToken(92); + request->setSequenceNumber(SequenceNumberCyclic(78)); + auto packet = new Packet("retryLimitedRtsProtectedAddba", request); + packet->insertAtBack(makeShared(B(100))); + ASSERT(packet->getByteLength() >= 10); + activeFrames.addFrame(packet); + ackHandler.processTransmittedDataOrMgmtFrame(request); + hcf.processRtsProtectionFailure(packet); + ASSERT(recoveryProcedure.getShortRetryCount(packet, request) == 0); + ASSERT(recoveryProcedure.getLongRetryCount(packet, request) == 0); + ASSERT(ackHandler.getMgmtOrNonQoSAckStatus(request) == QosAckHandler::Status::FRAME_NOT_YET_TRANSMITTED); + ASSERT(retryLimitListener.numSignals == 1); + ASSERT(packetDropListener.numSignals == 1); + ASSERT(linkBrokenListener.numSignals == 1); + recoveryProcedure.unsubscribe(IRecoveryProcedure::retryLimitReachedSignal, &retryLimitListener); + hcf.unsubscribe(packetDroppedSignal, &packetDropListener); + hcf.unsubscribe(linkBrokenSignal, &linkBrokenListener); + auto releasedFrames = activeFrames.releaseFrames(); + ASSERT(releasedFrames.size() == 1); + delete releasedFrames.front(); +} + // A frame prefetched for duration calculation already has an SSN, but it must // remain in progress without being selected while the same transaction is pending. TestAckHandler ackHandler; @@ -731,6 +1587,19 @@ inProgressFrames.addFrame(prefetchedOtherTidFrame); ASSERT(inProgressFrames.hasEligibleFrame()); ASSERT(inProgressFrames.getFrameToTransmit() == prefetchedOtherTidFrame); +// Repeated cleanup requests move an in-progress frame to the discard list once. +{ + TestInProgressFrames idempotentDropFrames; + auto frame = new Packet("idempotentDropFrame", makeQosHeader(peer1, 1, SequenceNumberCyclic(22))); + idempotentDropFrames.addFrame(frame); + idempotentDropFrames.dropFrame(frame); + idempotentDropFrames.dropFrame(frame); + auto releasedFrames = idempotentDropFrames.releaseFrames(); + ASSERT(releasedFrames.size() == 1); + ASSERT(releasedFrames.front() == frame); + delete frame; +} + // DCF sequence predicates materialize the next eligible pending frame. TestPacketQueue deferredQueue; auto deferredFrame = new Packet("deferredFrame", makeQosHeader(peer2, 4, SequenceNumberCyclic(123))); @@ -748,18 +1617,14 @@ auto deferredFrames = deferredInProgressFrames.releaseFrames(); ASSERT(deferredFrames.size() == 1); delete deferredFrames.at(0); -// A real leaf queue records selected-dequeue residence once, while the -// compound boundary emits its own semantic pull without duplicating it. +// A simulation-registered real queue records selected-dequeue residence and +// emits its semantic pull exactly once. Packet *compoundPacket = nullptr; { TestPacketPulledListener pullListener; - TestRealPacketQueue leafQueue; - TestCompoundPacketQueue compoundQueue; - compoundQueue.configure(&leafQueue, &leafQueue); - pullListener.leaf = &leafQueue; - pullListener.compound = &compoundQueue; - leafQueue.subscribe(packetPulledSignal, &pullListener); - compoundQueue.subscribe(packetPulledSignal, &pullListener); + auto leafQueue = createPacketQueue("selectedDequeueQueue"); + pullListener.leaf = leafQueue; + leafQueue->subscribe(packetPulledSignal, &pullListener); compoundPacket = new Packet("compoundPacket", makeQosHeader(peer1, 1, SequenceNumberCyclic(1))); auto queueingTimeTag = compoundPacket->addRegionTag(); queueingTimeTag->setBitTotalTimesArraySize(1); @@ -767,8 +1632,10 @@ Packet *compoundPacket = nullptr; queueingTimeTag->setPacketTotalTimesArraySize(1); queueingTimeTag->setPacketTotalTimes(0, 0); compoundPacket->addRegionTag(); - leafQueue.insertPacket(compoundPacket, simTime() - 1); - ASSERT(compoundQueue.dequeuePacket(compoundPacket) == compoundPacket); + compoundPacket->setArrivalTime(simTime() - 1); + leafQueue->pushPacket(compoundPacket, nullptr); + auto dequeuedPacket = leafQueue->dequeuePacket([compoundPacket](const Packet *packet) { return packet == compoundPacket; }); + ASSERT(dequeuedPacket == compoundPacket); int numQueuedEvents = 0; compoundPacket->mapAllRegionTags(b(0), compoundPacket->getTotalLength(), [&](b, b, const Ptr& tag) { for (size_t i = 0; i < tag->getPacketEventsArraySize(); i++) @@ -782,46 +1649,120 @@ Packet *compoundPacket = nullptr; }); ASSERT(numQueuedEvents == 1); ASSERT(totalQueueingTime == 1); - ASSERT(leafQueue.getNumPackets() == 0); + ASSERT(leafQueue->getNumPackets() == 0); ASSERT(pullListener.numLeafPulls == 1); - ASSERT(pullListener.numCompoundPulls == 1); - leafQueue.unsubscribe(packetPulledSignal, &pullListener); - compoundQueue.unsubscribe(packetPulledSignal, &pullListener); - compoundQueue.release(compoundPacket); + leafQueue->unsubscribe(packetPulledSignal, &pullListener); + leafQueue->callFinish(); + leafQueue->deleteModule(); + compoundPacket = nullptr; } delete compoundPacket; -// Queue-drop cleanup immediately rechecks eligible channel access, so frames -// released by a discarded setup transaction do not wait for unrelated traffic. +// Ordinary queue overflow must not trigger channel-access re-evaluation, while +// cancelling a dropped setup transaction must immediately release held traffic. { TestHcf droppedSetupHcf; Packet droppedSetupPacket("droppedSetupPacket", makeShared(B(1))); droppedSetupHcf.dropPacket(&droppedSetupPacket); - if (droppedSetupHcf.numProcessedDroppedSetupFrames != 1 || droppedSetupHcf.numResumedEligibleChannelAccess != 1) - throw cRuntimeError("Queue-drop cleanup did not resume eligible channel access"); ASSERT(droppedSetupHcf.numProcessedDroppedSetupFrames == 1); + ASSERT(droppedSetupHcf.numResumedEligibleChannelAccess == 0); + droppedSetupHcf.droppedSetupCancelled = true; + droppedSetupHcf.dropPacket(&droppedSetupPacket); + ASSERT(droppedSetupHcf.numProcessedDroppedSetupFrames == 2); ASSERT(droppedSetupHcf.numResumedEligibleChannelAccess == 1); } +// The typed queue callback covers every non-destructive ownership departure, +// including bulk removal, exactly once at the queue boundary. +{ + auto lifecycleQueue = createPacketQueue("lifecycleQueue"); + TestPacketDropCallback lifecycleCallback; + lifecycleQueue->addPacketCallback(&lifecycleCallback); + auto pulledPacket = new Packet("lifecyclePulled", makeShared(B(1))); + auto selectedPacket = new Packet("lifecycleSelected", makeShared(B(1))); + auto removedPacket = new Packet("lifecycleRemoved", makeShared(B(1))); + auto bulkRemovedPacket = new Packet("lifecycleBulkRemoved", makeShared(B(1))); + lifecycleQueue->pushPacket(pulledPacket, nullptr); + lifecycleQueue->pushPacket(selectedPacket, nullptr); + lifecycleQueue->pushPacket(removedPacket, nullptr); + lifecycleQueue->pushPacket(bulkRemovedPacket, nullptr); + auto pulledResult = lifecycleQueue->dequeuePacket(); + auto selectedResult = lifecycleQueue->dequeuePacket([selectedPacket](const Packet *packet) { return packet == selectedPacket; }); + lifecycleQueue->removePacket(removedPacket); + take(pulledResult); + take(selectedResult); + take(removedPacket); + delete pulledResult; + delete selectedResult; + delete removedPacket; + lifecycleQueue->removeAllPackets(); + if (lifecycleCallback.numDequeuedPackets != 2 || lifecycleCallback.numRemovedPackets != 2 || lifecycleCallback.numDroppedPackets != 0) + throw cRuntimeError("Typed queue lifecycle callback did not cover every departure exactly once"); + lifecycleQueue->removePacketCallback(&lifecycleCallback); + lifecycleQueue->callFinish(); + lifecycleQueue->deleteModule(); +} + // Internal overflow invokes the typed queue callback once for the destructive // discard and never for the packet later removed normally. { - TestRealPacketQueue overflowQueue; + auto overflowQueue = createPacketQueue("overflowQueue", 1, "inet::queueing::PacketAtCollectionEndDropper"); TestPacketDropCallback dropCallback; - overflowQueue.configureTailDrop(1); - overflowQueue.addPacketDropCallback(&dropCallback); + overflowQueue->addPacketCallback(&dropCallback); auto retainedPacket = new Packet("retainedPacket", makeShared(B(1))); auto overflowPacket = new Packet("overflowPacket", makeShared(B(1))); auto overflowPacketId = overflowPacket->getId(); - overflowQueue.pushPacket(retainedPacket, nullptr); - overflowQueue.pushPacket(overflowPacket, nullptr); + overflowQueue->pushPacket(retainedPacket, nullptr); + overflowQueue->pushPacket(overflowPacket, nullptr); ASSERT(dropCallback.numDroppedPackets == 1); ASSERT(dropCallback.lastDroppedPacketId == overflowPacketId); - overflowQueue.removePacketDropCallback(&dropCallback); - overflowQueue.removePacket(retainedPacket); - overflowQueue.release(retainedPacket); + overflowQueue->removePacketCallback(&dropCallback); + auto dequeuedPacket = overflowQueue->dequeuePacket([retainedPacket](const Packet *packet) { return packet == retainedPacket; }); + ASSERT(dequeuedPacket == retainedPacket); ASSERT(dropCallback.numDroppedPackets == 1); - delete retainedPacket; + overflowQueue->callFinish(); + overflowQueue->deleteModule(); +} + +// A shared buffer detaches every victim from its owning queue before the first +// typed drop callback. This keeps protocol cleanup from reentering while a +// later victim is still selectable from another queue. +{ + auto bufferModule = cModuleType::get("inet.queueing.buffer.PacketBuffer")->create("sharedOverflowBuffer", this); + bufferModule->par("dataCapacity").setIntValue(24); + bufferModule->par("dropperClass").setStringValue("inet::queueing::PacketAtCollectionBeginDropper"); + bufferModule->callInitialize(); + auto createBufferedQueue = [&](const char *name) { + auto module = cModuleType::get("inet.queueing.queue.PacketQueue")->create(name, this); + module->par("bufferModule").setStringValue("^.sharedOverflowBuffer"); + module->callInitialize(); + return check_and_cast(module); + }; + auto firstQueue = createBufferedQueue("sharedOverflowFirstQueue"); + auto secondQueue = createBufferedQueue("sharedOverflowSecondQueue"); + TestPacketDropCallback dropCallback; + dropCallback.expectedPacketCountsAtFirstDrop = { { firstQueue, 1 }, { secondQueue, 0 } }; + firstQueue->addPacketCallback(&dropCallback); + secondQueue->addPacketCallback(&dropCallback); + firstQueue->pushPacket(new Packet("sharedFirstVictim", makeShared(B(1))), nullptr); + secondQueue->pushPacket(new Packet("sharedSecondVictim", makeShared(B(1))), nullptr); + auto retainedPacket = new Packet("sharedRetainedPacket", makeShared(B(3))); + firstQueue->pushPacket(retainedPacket, nullptr); + if (dropCallback.numDroppedPackets != 2 || !dropCallback.allExpectedPacketsDetachedAtFirstDrop || firstQueue->getNumPackets() != 1 || secondQueue->getNumPackets() != 0) + throw cRuntimeError("Shared buffer notified a drop before detaching the full victim batch"); + auto dequeuedPacket = firstQueue->dequeuePacket([retainedPacket](const Packet *packet) { return packet == retainedPacket; }); + if (dequeuedPacket != retainedPacket) + throw cRuntimeError("Shared buffer retained the wrong packet after overload"); + take(dequeuedPacket); + delete dequeuedPacket; + firstQueue->removePacketCallback(&dropCallback); + secondQueue->removePacketCallback(&dropCallback); + firstQueue->callFinish(); + secondQueue->callFinish(); + bufferModule->callFinish(); + firstQueue->deleteModule(); + secondQueue->deleteModule(); + bufferModule->deleteModule(); } // At the TXOP continuation boundary, an eligible pending frame is From a8801b3a1a481d1074619f1e2eec31ec179e27e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Mon, 17 Aug 2026 23:53:47 +0200 Subject: [PATCH 05/14] Fix packet extraction and ADDBA cleanup edge cases Prevent predicate-based extraction from bypassing closed gates or implicit guard bands by validating the exact upstream candidate before dequeueing it. Drain PacketQueue instances completely during bulk removal and detach only the queue's own packets from shared buffers so removal callbacks remain complete and isolated. Transfer buffered Block Ack frames to the recipient data service during reset, emit one packet-drop signal per discarded MPDU, and reclaim cancelled out-of-sequence ADDBA frames immediately. Clear both short and long retry state on terminal frame discard, and make scheduler extractor requirements explicit while preserving PriorityScheduler's nullable collection accounting. Add focused regression coverage for closed and guarded gates, multi-packet and shared-buffer removal, reorder-reset drop accounting, idle ADDBA cancellation, retry-state cleanup, and extractor-only priority inputs. Validated with the focused Ieee80211AddbaTransaction_1 unit test in release and debug modes. --- .../blockackreordering/BlockAckReordering.cc | 10 +- .../blockackreordering/BlockAckReordering.h | 3 +- .../mac/blockackreordering/ReceiveBuffer.cc | 11 +- .../mac/blockackreordering/ReceiveBuffer.h | 2 +- .../ieee80211/mac/coordinationfunction/Hcf.cc | 5 +- .../mac/originator/NonQosRecoveryProcedure.cc | 13 +- .../ieee80211/mac/queue/InProgressFrames.cc | 13 + .../ieee80211/mac/queue/InProgressFrames.h | 1 + .../recipient/RecipientQosMacDataService.cc | 13 +- src/inet/queueing/base/PacketGateBase.cc | 15 +- src/inet/queueing/base/PacketGateBase.h | 3 +- src/inet/queueing/queue/PacketQueue.cc | 5 +- src/inet/queueing/scheduler/LabelScheduler.cc | 19 +- src/inet/queueing/scheduler/LabelScheduler.h | 1 + .../queueing/scheduler/PriorityScheduler.cc | 19 +- .../queueing/scheduler/PriorityScheduler.h | 1 + src/inet/queueing/scheduler/WrrScheduler.cc | 18 +- src/inet/queueing/scheduler/WrrScheduler.h | 1 + tests/unit/Ieee80211AddbaTransaction_1.test | 301 +++++++++++++++++- 19 files changed, 401 insertions(+), 53 deletions(-) diff --git a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc index 0ac0cb679dc..1dbff9f3f17 100644 --- a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc +++ b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc @@ -185,19 +185,17 @@ ReceiveBuffer *BlockAckReordering::createReceiveBufferIfNecessary(RecipientBlock return it->second; } -void BlockAckReordering::processReceivedDelba(const Ptr& delba) -{ - resetReceiveBuffer(delba->getTid(), delba->getTransmitterAddress()); -} - -void BlockAckReordering::resetReceiveBuffer(Tid tid, MacAddress originatorAddr) +std::vector BlockAckReordering::resetReceiveBuffer(Tid tid, MacAddress originatorAddr) { + std::vector frames; auto id = std::make_pair(tid, originatorAddr); auto it = receiveBuffers.find(id); if (it != receiveBuffers.end()) { + frames = it->second->extractFrames(); delete it->second; receiveBuffers.erase(it); } + return frames; } void BlockAckReordering::passedUp(RecipientBlockAckAgreement *agreement, ReceiveBuffer *receiveBuffer, SequenceNumberCyclic sequenceNumber) diff --git a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h index 91578f1b9ac..014f2c0a318 100644 --- a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h +++ b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h @@ -43,8 +43,7 @@ class INET_API BlockAckReordering public: virtual ~BlockAckReordering(); - void processReceivedDelba(const Ptr& delba); - void resetReceiveBuffer(Tid tid, MacAddress originatorAddr); + std::vector resetReceiveBuffer(Tid tid, MacAddress originatorAddr); ReorderBuffer processReceivedQoSFrame(RecipientBlockAckAgreement *agreement, Packet *dataPacket, const Ptr& dataHeader); ReorderBuffer processReceivedBlockAckReq(RecipientBlockAckAgreement *agreement, const Ptr& blockAckReq); }; diff --git a/src/inet/linklayer/ieee80211/mac/blockackreordering/ReceiveBuffer.cc b/src/inet/linklayer/ieee80211/mac/blockackreordering/ReceiveBuffer.cc index 1dd9c959c8f..3bf4115a4b1 100644 --- a/src/inet/linklayer/ieee80211/mac/blockackreordering/ReceiveBuffer.cc +++ b/src/inet/linklayer/ieee80211/mac/blockackreordering/ReceiveBuffer.cc @@ -77,6 +77,16 @@ void ReceiveBuffer::removeFrame(SequenceNumberCyclic sequenceNumber) throw cRuntimeError("Unknown sequence number: %d", sequenceNumber.get()); } +ReceiveBuffer::Fragments ReceiveBuffer::extractFrames() +{ + Fragments frames; + for (auto& [sequenceNumber, fragments] : buffer) + frames.insert(frames.end(), fragments.begin(), fragments.end()); + buffer.clear(); + length = 0; + return frames; +} + ReceiveBuffer::~ReceiveBuffer() { for (auto fragments : buffer) { @@ -87,4 +97,3 @@ ReceiveBuffer::~ReceiveBuffer() } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/src/inet/linklayer/ieee80211/mac/blockackreordering/ReceiveBuffer.h b/src/inet/linklayer/ieee80211/mac/blockackreordering/ReceiveBuffer.h index a499465d198..84e8fd5b6c5 100644 --- a/src/inet/linklayer/ieee80211/mac/blockackreordering/ReceiveBuffer.h +++ b/src/inet/linklayer/ieee80211/mac/blockackreordering/ReceiveBuffer.h @@ -37,6 +37,7 @@ class INET_API ReceiveBuffer bool insertFrame(Packet *dataPacket, const Ptr& dataHeader); void dropFramesUntil(SequenceNumberCyclic sequenceNumber); void removeFrame(SequenceNumberCyclic sequenceNumber); + Fragments extractFrames(); const ReorderBuffer& getBuffer() { return buffer; } int getLength() { return length; } @@ -50,4 +51,3 @@ class INET_API ReceiveBuffer } /* namespace inet */ #endif - diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc index ac985294000..0bf6938b1ff 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc @@ -296,11 +296,14 @@ void Hcf::cancelAddbaTransaction(uint64_t transactionId, Packet *excludedPacket) auto packet = inProgressFrames->getFrames(i); if (belongsToTransaction(packet)) { auto header = packet->peekAtFront(); - inProgressFrames->dropFrame(packet); + auto extractedPacket = inProgressFrames->extractFrame(packet); + ASSERT(extractedPacket == packet); + take(packet); edcaf->getAckHandler()->dropFrame(header); PacketDropDetails details; details.setReason(OTHER_PACKET_DROP); emit(packetDroppedSignal, packet, &details); + delete packet; } } } diff --git a/src/inet/linklayer/ieee80211/mac/originator/NonQosRecoveryProcedure.cc b/src/inet/linklayer/ieee80211/mac/originator/NonQosRecoveryProcedure.cc index 9e516146123..f5d01a0b70a 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/NonQosRecoveryProcedure.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/NonQosRecoveryProcedure.cc @@ -130,22 +130,15 @@ void NonQosRecoveryProcedure::ackFrameReceived(Packet *packet, const Ptr& header) { auto id = SequenceControlField(header->getSequenceNumber().get(), header->getFragmentNumber()); - if (packet->getByteLength() >= rtsThreshold) { - auto it = longRetryCounter.find(id); - if (it != longRetryCounter.end()) - longRetryCounter.erase(it); - } - else { - auto it = shortRetryCounter.find(id); - if (it != shortRetryCounter.end()) - shortRetryCounter.erase(it); - } + shortRetryCounter.erase(id); + longRetryCounter.erase(id); } void NonQosRecoveryProcedure::discardRtsFrame(const Ptr& protectedHeader) { auto id = SequenceControlField(protectedHeader->getSequenceNumber().get(), protectedHeader->getFragmentNumber()); shortRetryCounter.erase(id); + longRetryCounter.erase(id); } // After dropping a frame because it reached its retry limit we need to clear the diff --git a/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.cc b/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.cc index 0e6dc1635e6..0d842aebdea 100644 --- a/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.cc +++ b/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.cc @@ -129,6 +129,19 @@ void InProgressFrames::dropFrame(Packet *packet) emit(packetDequeuedSignal, packet); } +Packet *InProgressFrames::extractFrame(Packet *packet) +{ + Enter_Method("extractFrame"); + auto it = std::find(inProgressFrames.begin(), inProgressFrames.end(), packet); + if (it == inProgressFrames.end()) + return nullptr; + EV_DEBUG << "Extracting frame " << packet->getName() << ".\n"; + inProgressFrames.erase(it); + emit(packetDequeuedSignal, packet); + drop(packet); + return packet; +} + void InProgressFrames::dropFrames(std::set>> seqAndFragNums) { for (auto it = inProgressFrames.begin(); it != inProgressFrames.end();) { diff --git a/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.h b/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.h index 27f7591bb4a..98063fd07ce 100644 --- a/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.h +++ b/src/inet/linklayer/ieee80211/mac/queue/InProgressFrames.h @@ -47,6 +47,7 @@ class INET_API InProgressFrames : public SimpleModule virtual Packet *getFrameToTransmit(); virtual Packet *getPendingFrameFor(Packet *frame); virtual void dropFrame(Packet *packet); + virtual Packet *extractFrame(Packet *packet); virtual void dropFrames(std::set>> seqAndFragNums); virtual bool hasEligibleInProgressFrames() { return hasEligibleFrameToTransmit(); } diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc index 7d0e74e697e..6f1f87cc3a3 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc @@ -32,8 +32,17 @@ void RecipientQosMacDataService::initialize() void RecipientQosMacDataService::resetBlockAckReordering(Tid tid, MacAddress originatorAddr) { - if (blockAckReordering) - blockAckReordering->resetReceiveBuffer(tid, originatorAddr); + Enter_Method("resetBlockAckReordering"); + if (blockAckReordering) { + auto droppedFrames = blockAckReordering->resetReceiveBuffer(tid, originatorAddr); + for (auto packet : droppedFrames) { + take(packet); + PacketDropDetails details; + details.setReason(OTHER_PACKET_DROP); + emit(packetDroppedSignal, packet, &details); + delete packet; + } + } } Packet *RecipientQosMacDataService::defragment(std::vector completeFragments) diff --git a/src/inet/queueing/base/PacketGateBase.cc b/src/inet/queueing/base/PacketGateBase.cc index 69d237af107..126e2340345 100644 --- a/src/inet/queueing/base/PacketGateBase.cc +++ b/src/inet/queueing/base/PacketGateBase.cc @@ -98,6 +98,20 @@ void PacketGateBase::removePacket(Packet *packet) PacketFlowBase::removePacket(packet); } +Packet *PacketGateBase::findPacket(const PacketPredicate& predicate) const +{ + if (!isOpen()) + return nullptr; + auto packet = PacketFlowBase::findPacket(predicate); + return packet != nullptr && canPacketFlowThrough(packet) ? packet : nullptr; +} + +Packet *PacketGateBase::dequeuePacket(const PacketPredicate& predicate) +{ + auto packet = findPacket(predicate); + return packet == nullptr ? nullptr : PacketFlowBase::dequeuePacket([packet](const Packet *candidate) { return candidate == packet; }); +} + void PacketGateBase::removeAllPackets() { if (isOpen()) @@ -162,4 +176,3 @@ void PacketGateBase::refreshDisplay() const } // namespace queueing } // namespace inet - diff --git a/src/inet/queueing/base/PacketGateBase.h b/src/inet/queueing/base/PacketGateBase.h index 9ccb2701836..c94cc662777 100644 --- a/src/inet/queueing/base/PacketGateBase.h +++ b/src/inet/queueing/base/PacketGateBase.h @@ -53,6 +53,8 @@ class INET_API PacketGateBase : public PacketFlowBase, public TransparentProtoco virtual Packet* getPacket(int index) const override; virtual bool isEmpty() const override; virtual void removePacket(Packet *packet) override; + virtual Packet *findPacket(const PacketPredicate& predicate) const override; + virtual Packet *dequeuePacket(const PacketPredicate& predicate) override; virtual void removeAllPackets() override; virtual IPassivePacketSink *getConsumer(const cGate *gate) override { return this; } @@ -75,4 +77,3 @@ class INET_API PacketGateBase : public PacketFlowBase, public TransparentProtoco } // namespace inet #endif - diff --git a/src/inet/queueing/queue/PacketQueue.cc b/src/inet/queueing/queue/PacketQueue.cc index 8d42765e40b..eff98bf3a35 100644 --- a/src/inet/queueing/queue/PacketQueue.cc +++ b/src/inet/queueing/queue/PacketQueue.cc @@ -178,10 +178,11 @@ void PacketQueue::removeAllPackets() Enter_Method("removeAllPackets"); EV_INFO << "Removing all packets" << EV_ENDL; std::vector packets; - for (int i = 0; i < getNumPackets(); i++) + while (!queue.isEmpty()) packets.push_back(check_and_cast(queue.pop())); if (buffer != nullptr) - buffer->removeAllPackets(); + for (auto packet : packets) + buffer->removePacket(packet); for (auto packet : packets) { notifyPacketRemoved(packet, IPacketQueue::PacketRemovalReason::REMOVED); emit(packetRemovedSignal, packet); diff --git a/src/inet/queueing/scheduler/LabelScheduler.cc b/src/inet/queueing/scheduler/LabelScheduler.cc index 5f8df144194..41a983eb0c2 100644 --- a/src/inet/queueing/scheduler/LabelScheduler.cc +++ b/src/inet/queueing/scheduler/LabelScheduler.cc @@ -20,8 +20,15 @@ void LabelScheduler::initialize(int stage) if (stage == INITSTAGE_LOCAL) { defaultGateIndex = par("defaultGateIndex"); labels = cStringTokenizer(par("labels")).asVector(); - for (auto provider : providers) - collections.push_back(dynamic_cast(provider.get())); + for (size_t i = 0; i < providers.size(); i++) { + auto provider = providers[i].get(); + auto collection = dynamic_cast(provider); + auto packetExtractor = dynamic_cast(provider); + if (collection == nullptr || packetExtractor == nullptr) + throw cRuntimeError("Input provider at gate index %d must implement both IPacketCollection and IPacketExtractor", (int)i); + collections.push_back(collection); + packetExtractors.push_back(packetExtractor); + } } } @@ -69,8 +76,8 @@ void LabelScheduler::removePacket(Packet *packet) int LabelScheduler::findInput(const PacketPredicate& predicate) const { std::vector candidates; - for (auto collection : collections) - candidates.push_back(check_and_cast(collection)->findPacket(predicate)); + for (auto packetExtractor : packetExtractors) + candidates.push_back(packetExtractor->findPacket(predicate)); for (auto label : labels) { for (size_t i = 0; i < candidates.size(); i++) { auto packet = candidates[i]; @@ -90,7 +97,7 @@ int LabelScheduler::findInput(const PacketPredicate& predicate) const Packet *LabelScheduler::findPacket(const PacketPredicate& predicate) const { auto index = findInput(predicate); - return index == -1 ? nullptr : check_and_cast(collections[index])->findPacket(predicate); + return index == -1 ? nullptr : packetExtractors[index]->findPacket(predicate); } Packet *LabelScheduler::dequeuePacket(const PacketPredicate& predicate) @@ -99,7 +106,7 @@ Packet *LabelScheduler::dequeuePacket(const PacketPredicate& predicate) auto index = findInput(predicate); if (index == -1) return nullptr; - auto packet = check_and_cast(collections[index])->dequeuePacket(predicate); + auto packet = packetExtractors[index]->dequeuePacket(predicate); ASSERT(packet != nullptr); take(packet); handlePacketProcessed(packet); diff --git a/src/inet/queueing/scheduler/LabelScheduler.h b/src/inet/queueing/scheduler/LabelScheduler.h index 4bdf8722a35..aca5ce7b84d 100644 --- a/src/inet/queueing/scheduler/LabelScheduler.h +++ b/src/inet/queueing/scheduler/LabelScheduler.h @@ -21,6 +21,7 @@ class INET_API LabelScheduler : public PacketSchedulerBase, public virtual IPack int defaultGateIndex = -1; std::vector labels; std::vector collections; + std::vector packetExtractors; protected: virtual void initialize(int stage) override; diff --git a/src/inet/queueing/scheduler/PriorityScheduler.cc b/src/inet/queueing/scheduler/PriorityScheduler.cc index b146cbc5ecf..be5925cf13a 100644 --- a/src/inet/queueing/scheduler/PriorityScheduler.cc +++ b/src/inet/queueing/scheduler/PriorityScheduler.cc @@ -16,8 +16,14 @@ void PriorityScheduler::initialize(int stage) { PacketSchedulerBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { - for (auto provider : providers) - collections.push_back(dynamic_cast(provider.get())); + for (size_t i = 0; i < providers.size(); i++) { + auto provider = providers[i].get(); + collections.push_back(dynamic_cast(provider)); + auto packetExtractor = dynamic_cast(provider); + if (packetExtractor == nullptr) + throw cRuntimeError("Input provider at gate index %d must implement IPacketExtractor", (int)i); + packetExtractors.push_back(packetExtractor); + } } } @@ -75,9 +81,8 @@ bool PriorityScheduler::isPacketOrderPreserved() const { if (reverseOrder) return false; - for (auto collection : collections) { - auto extractor = dynamic_cast(collection); - if (extractor == nullptr || !extractor->isPacketOrderPreserved()) + for (auto packetExtractor : packetExtractors) { + if (!packetExtractor->isPacketOrderPreserved()) return false; } return true; @@ -87,7 +92,7 @@ Packet *PriorityScheduler::findPacket(const PacketPredicate& predicate) const { for (size_t i = 0; i < collections.size(); i++) { auto index = reverseOrder ? collections.size() - i - 1 : i; - auto packet = check_and_cast(collections[index])->findPacket(predicate); + auto packet = packetExtractors[index]->findPacket(predicate); if (packet != nullptr) return packet; } @@ -99,7 +104,7 @@ Packet *PriorityScheduler::dequeuePacket(const PacketPredicate& predicate) Enter_Method("dequeuePacket"); for (size_t i = 0; i < collections.size(); i++) { auto index = reverseOrder ? collections.size() - i - 1 : i; - auto packet = check_and_cast(collections[index])->dequeuePacket(predicate); + auto packet = packetExtractors[index]->dequeuePacket(predicate); if (packet == nullptr) continue; take(packet); diff --git a/src/inet/queueing/scheduler/PriorityScheduler.h b/src/inet/queueing/scheduler/PriorityScheduler.h index 1d7a70254c1..c9ecf2032a5 100644 --- a/src/inet/queueing/scheduler/PriorityScheduler.h +++ b/src/inet/queueing/scheduler/PriorityScheduler.h @@ -19,6 +19,7 @@ class INET_API PriorityScheduler : public PacketSchedulerBase, public virtual IP { protected: std::vector collections; + std::vector packetExtractors; protected: virtual void initialize(int stage) override; diff --git a/src/inet/queueing/scheduler/WrrScheduler.cc b/src/inet/queueing/scheduler/WrrScheduler.cc index 75fb27f6deb..1484befe521 100644 --- a/src/inet/queueing/scheduler/WrrScheduler.cc +++ b/src/inet/queueing/scheduler/WrrScheduler.cc @@ -37,8 +37,15 @@ void WrrScheduler::initialize(int stage) if (tokenizer.hasMoreTokens()) throw cRuntimeError("Too many values given in the weights parameter."); - for (auto provider : providers) - collections.push_back(dynamic_cast(provider.get())); + for (size_t i = 0; i < providers.size(); i++) { + auto provider = providers[i].get(); + auto collection = dynamic_cast(provider); + auto packetExtractor = dynamic_cast(provider); + if (collection == nullptr || packetExtractor == nullptr) + throw cRuntimeError("Input provider at gate index %d must implement both IPacketCollection and IPacketExtractor", (int)i); + collections.push_back(collection); + packetExtractors.push_back(packetExtractor); + } } } @@ -88,8 +95,7 @@ int WrrScheduler::findInput(const PacketPredicate& predicate) const int firstWeighted = -1; int firstNonWeighted = -1; for (size_t i = 0; i < collections.size(); ++i) { - auto extractor = check_and_cast(collections[i]); - if (extractor->findPacket(predicate) != nullptr) { + if (packetExtractors[i]->findPacket(predicate) != nullptr) { if (buckets[i] > 0) return i; else if (firstWeighted == -1 && weights[i] > 0) @@ -116,7 +122,7 @@ void WrrScheduler::consumeBucket(int index) Packet *WrrScheduler::findPacket(const PacketPredicate& predicate) const { auto index = findInput(predicate); - return index == -1 ? nullptr : check_and_cast(collections[index])->findPacket(predicate); + return index == -1 ? nullptr : packetExtractors[index]->findPacket(predicate); } Packet *WrrScheduler::dequeuePacket(const PacketPredicate& predicate) @@ -125,7 +131,7 @@ Packet *WrrScheduler::dequeuePacket(const PacketPredicate& predicate) auto index = findInput(predicate); if (index == -1) return nullptr; - auto packet = check_and_cast(collections[index])->dequeuePacket(predicate); + auto packet = packetExtractors[index]->dequeuePacket(predicate); ASSERT(packet != nullptr); consumeBucket(index); take(packet); diff --git a/src/inet/queueing/scheduler/WrrScheduler.h b/src/inet/queueing/scheduler/WrrScheduler.h index 1ccfeef525f..dc5907ac794 100644 --- a/src/inet/queueing/scheduler/WrrScheduler.h +++ b/src/inet/queueing/scheduler/WrrScheduler.h @@ -25,6 +25,7 @@ class INET_API WrrScheduler : public PacketSchedulerBase, public virtual IPacket unsigned int *buckets = nullptr; // array of tokens in buckets (has numInputs elements) std::vector collections; + std::vector packetExtractors; protected: virtual void initialize(int stage) override; diff --git a/tests/unit/Ieee80211AddbaTransaction_1.test b/tests/unit/Ieee80211AddbaTransaction_1.test index 5a8edf6c99c..a354fb25152 100644 --- a/tests/unit/Ieee80211AddbaTransaction_1.test +++ b/tests/unit/Ieee80211AddbaTransaction_1.test @@ -27,7 +27,10 @@ Test IEEE 802.11 ADDBA transactions, reordering reset, fragmentation cleanup, DC #include "inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.h" #include "inet/linklayer/ieee80211/mac/originator/NonQosRecoveryProcedure.h" #include "inet/linklayer/ieee80211/mac/queue/InProgressFrames.h" +#include "inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.h" #include "inet/linklayer/ieee80211/mac/sequencenumberassignment/QoSSequenceNumberAssignment.h" +#include "inet/queueing/gate/PacketGate.h" +#include "inet/queueing/gate/PeriodicGate.h" #include "inet/queueing/queue/CompoundPacketQueueBase.h" #include "inet/queueing/queue/PacketQueue.h" #include "inet/queueing/scheduler/LabelScheduler.h" @@ -210,7 +213,15 @@ class TestPriorityScheduler : public queueing::PriorityScheduler { public: void configure(const std::vector& collections, bool reverseOrder = false) { + std::vector packetExtractors; + for (auto collection : collections) + packetExtractors.push_back(check_and_cast(collection)); + configure(collections, packetExtractors, reverseOrder); + } + void configure(const std::vector& collections, const std::vector& packetExtractors, bool reverseOrder = false) { + ASSERT(collections.size() == packetExtractors.size()); this->collections = collections; + this->packetExtractors = packetExtractors; this->reverseOrder = reverseOrder; numProcessedPackets = 0; processedTotalLength = b(0); @@ -226,6 +237,8 @@ class TestWrrScheduler : public queueing::WrrScheduler ASSERT(collections.size() == weights.size()); ASSERT(collections.size() == buckets.size()); this->collections = collections; + for (auto collection : collections) + packetExtractors.push_back(check_and_cast(collection)); this->weights = new unsigned int[weights.size()]; this->buckets = new unsigned int[buckets.size()]; for (size_t i = 0; i < collections.size(); i++) { @@ -242,6 +255,8 @@ class TestLabelScheduler : public queueing::LabelScheduler public: void configure(const std::vector& collections, const std::vector& labels, int defaultGateIndex) { this->collections = collections; + for (auto collection : collections) + packetExtractors.push_back(check_and_cast(collection)); this->labels = labels; this->defaultGateIndex = defaultGateIndex; } @@ -297,6 +312,20 @@ class TestSignalListener : public cListener virtual void receiveSignal(cComponent *, simsignal_t, cObject *, cObject *) override { numSignals++; } }; +class TestPacketDropSignalListener : public cListener +{ + public: + int numSignals = 0; + int numOtherPacketDrops = 0; + + virtual void receiveSignal(cComponent *, simsignal_t, cObject *, cObject *details) override { + numSignals++; + auto packetDropDetails = check_and_cast(details); + if (packetDropDetails->getReason() == OTHER_PACKET_DROP) + numOtherPacketDrops++; + } +}; + class TestAckHandler : public IAckHandler { public: @@ -314,7 +343,9 @@ class TestInProgressFrames : public InProgressFrames this->pendingQueue = pendingQueue; } void addFrame(Packet *packet) { inProgressFrames.push_back(packet); } + void addOwnedFrame(Packet *packet) { take(packet); inProgressFrames.push_back(packet); } bool hasEligibleFrame() { return hasEligibleFrameToTransmit(); } + int getNumDroppedFrames() const { return droppedFrames.size(); } std::vector releaseFrames() { std::vector frames = inProgressFrames; frames.insert(frames.end(), droppedFrames.begin(), droppedFrames.end()); @@ -464,6 +495,12 @@ class TestNonQosRecoveryProcedure : public NonQosRecoveryProcedure rtsThreshold = INT_MAX; shortRetryCounter[SequenceControlField(header->getSequenceNumber().get(), header->getFragmentNumber())] = 1; } + void addShortRetry(const Ptr& header, int count = 1) { + shortRetryCounter[SequenceControlField(header->getSequenceNumber().get(), header->getFragmentNumber())] = count; + } + void addLongRetry(const Ptr& header, int count = 1) { + longRetryCounter[SequenceControlField(header->getSequenceNumber().get(), header->getFragmentNumber())] = count; + } bool hasRetry(const Ptr& header) const { return shortRetryCounter.find(SequenceControlField(header->getSequenceNumber().get(), header->getFragmentNumber())) != shortRetryCounter.end(); } @@ -475,6 +512,24 @@ class TestNonQosRecoveryProcedure : public NonQosRecoveryProcedure } }; +class TestOnlyPacketExtractor : public queueing::IPacketExtractor +{ + public: + std::vector packets; + + virtual bool isPacketOrderPreserved() const override { return true; } + virtual Packet *findPacket(const PacketPredicate& predicate) const override { + auto it = std::find_if(packets.begin(), packets.end(), predicate); + return it == packets.end() ? nullptr : *it; + } + virtual Packet *dequeuePacket(const PacketPredicate& predicate) override { + auto packet = findPacket(predicate); + if (packet != nullptr) + packets.erase(std::find(packets.begin(), packets.end(), packet)); + return packet; + } +}; + class TestCwCalculator : public IRecoveryProcedure::ICwCalculator { public: @@ -754,6 +809,40 @@ ASSERT(handler.isAddbaResponsePending(peer3, 5)); delete frame; } + +// A transaction frame that is not referenced by an active sequence is removed +// from in-progress ownership and reclaimed immediately, without entering the +// deferred dropped-frame list. +{ + const uint64_t transactionId = 901; + TestPacketQueue pendingQueue; + QosAckHandler ackHandler; + TestInProgressFrames inProgressFrames; + auto request = makeShared(); + request->setReceiverAddress(peer1); + request->setSequenceNumber(SequenceNumberCyclic(31)); + auto packet = new Packet("idleAddbaRequest", request); + packet->addTag()->setTransactionId(transactionId); + inProgressFrames.addOwnedFrame(packet); + ackHandler.frameGotInProgress(request); + TestEdcaf edcaf; + edcaf.pendingQueue = &pendingQueue; + edcaf.inProgressFrames = &inProgressFrames; + edcaf.qosAckHandler = &ackHandler; + TestEdca edca; + edca.edcaf = &edcaf; + TestHcf hcf; + hcf.configure(&edca); + TestPacketDropSignalListener dropListener; + hcf.subscribe(packetDroppedSignal, &dropListener); + hcf.cancelTransaction(transactionId); + ASSERT(inProgressFrames.getLength() == 0); + ASSERT(inProgressFrames.getNumDroppedFrames() == 0); + ASSERT(dropListener.numSignals == 1); + ASSERT(dropListener.numOtherPacketDrops == 1); + hcf.unsubscribe(packetDroppedSignal, &dropListener); +} + // An unsent transaction has no response deadline and is retired by a typed drop callback. Packet unsentTrigger("unsentTrigger"); auto unsentHeader = makeQosHeader(peer2, 5, SequenceNumberCyclic(40)); @@ -904,10 +993,19 @@ RecipientBlockAckAgreement *replacementImmediateAgreement = nullptr; TestBlockAckReordering reordering; auto oldWindowHeader = makeQosHeader(peer1, 6, SequenceNumberCyclic(1)); auto oldWindowPacket = new Packet("oldWindowPacket", oldWindowHeader); - ASSERT(reordering.processReceivedQoSFrame(firstImmediateAgreement, oldWindowPacket, oldWindowHeader).empty()); + auto oldWindowFrames = reordering.processReceivedQoSFrame(firstImmediateAgreement, oldWindowPacket, oldWindowHeader); + ASSERT(oldWindowFrames.empty()); ASSERT(reordering.getNumReceiveBuffers() == 1); ASSERT(reordering.getReceiveBuffer(6, peer1)->getLength() == 1); + auto discardedOldWindowFrames = reordering.resetReceiveBuffer(6, peer1); + ASSERT(discardedOldWindowFrames.size() == 1); + ASSERT(discardedOldWindowFrames.front() == oldWindowPacket); + take(discardedOldWindowFrames.front()); + drop(discardedOldWindowFrames.front()); + delete discardedOldWindowFrames.front(); + ASSERT(reordering.getNumReceiveBuffers() == 0); + recipientRequest->setDialogToken(79); recipientRequest->setStartingSequenceNumber(SequenceNumberCyclic(9)); replacementImmediateAgreement = immediateRecipientHandler.processReceivedAddbaRequest(recipientRequest, &recipientPolicy, &recipientCallback, &recipientCallback); @@ -915,13 +1013,41 @@ RecipientBlockAckAgreement *replacementImmediateAgreement = nullptr; ASSERT(replacementImmediateAgreement != firstImmediateAgreement); ASSERT(immediateRecipientHandler.getAgreement(6, peer1) == replacementImmediateAgreement); ASSERT(replacementImmediateAgreement->getStartingSequenceNumber() == SequenceNumberCyclic(9)); - reordering.resetReceiveBuffer(6, peer1); - ASSERT(reordering.getNumReceiveBuffers() == 0); auto newWindowHeader = makeQosHeader(peer1, 6, SequenceNumberCyclic(10)); auto newWindowPacket = new Packet("newWindowPacket", newWindowHeader); - ASSERT(reordering.processReceivedQoSFrame(replacementImmediateAgreement, newWindowPacket, newWindowHeader).empty()); + auto newWindowFrames = reordering.processReceivedQoSFrame(replacementImmediateAgreement, newWindowPacket, newWindowHeader); + ASSERT(newWindowFrames.empty()); ASSERT(reordering.getReceiveBuffer(6, peer1)->getNextExpectedSequenceNumber() == SequenceNumberCyclic(9)); ASSERT(reordering.getReceiveBuffer(6, peer1)->getLength() == 1); + auto discardedNewWindowFrames = reordering.resetReceiveBuffer(6, peer1); + ASSERT(discardedNewWindowFrames.size() == 1); + ASSERT(discardedNewWindowFrames.front() == newWindowPacket); + take(discardedNewWindowFrames.front()); + drop(discardedNewWindowFrames.front()); + delete discardedNewWindowFrames.front(); +} + +// The recipient data service owns reorder-buffer reset observability: every +// buffered MPDU is reported exactly once before it is deleted. +{ + auto recipientDataServiceModule = cModuleType::get("inet.linklayer.ieee80211.mac.recipient.RecipientQosMacDataService")->create("reorderingDropDataService", this); + recipientDataServiceModule->callInitialize(); + auto recipientDataService = check_and_cast(recipientDataServiceModule); + TestPacketDropSignalListener dropListener; + recipientDataService->subscribe(packetDroppedSignal, &dropListener); + auto bufferedHeader = makeQosHeader(peer1, 6, SequenceNumberCyclic(11)); + bufferedHeader->setTransmitterAddress(peer1); + auto bufferedPacket = new Packet("signaledOldWindowPacket", bufferedHeader); + auto signaledOldWindowFrames = recipientDataService->dataFrameReceived(bufferedPacket, bufferedHeader, &immediateRecipientHandler); + ASSERT(signaledOldWindowFrames.empty()); + recipientDataService->resetBlockAckReordering(6, peer1); + ASSERT(dropListener.numSignals == 1); + ASSERT(dropListener.numOtherPacketDrops == 1); + recipientDataService->resetBlockAckReordering(6, peer1); + ASSERT(dropListener.numSignals == 1); + recipientDataService->unsubscribe(packetDroppedSignal, &dropListener); + recipientDataServiceModule->callFinish(); + recipientDataServiceModule->deleteModule(); } recipientPolicy.accepted = false; @@ -1266,6 +1392,23 @@ ASSERT(!dataService.hasEligibleFrame(&mixedQueue)); firstQueue.removePacket(firstPacket); delete firstPacket; } + +// Priority extraction is independent of collection enumeration, so an input +// may support predicate extraction even when it cannot report aggregate size. +{ + TestOnlyPacketExtractor packetExtractor; + auto packet = new Packet("extractorOnlyPacket", makeShared(B(1))); + packetExtractor.packets = { packet }; + TestPriorityScheduler scheduler; + scheduler.configure(std::vector({ nullptr }), std::vector({ &packetExtractor })); + ASSERT(scheduler.getNumPackets() == -1); + ASSERT(scheduler.findPacket([](const Packet *) { return true; }) == packet); + auto dequeuedPacket = scheduler.dequeuePacket([](const Packet *) { return true; }); + ASSERT(dequeuedPacket == packet); + ASSERT(packetExtractor.packets.empty()); + scheduler.release(packet); + delete packet; +} { TestPacketQueue firstQueue; TestPacketQueue secondQueue; @@ -1315,6 +1458,76 @@ ASSERT(!dataService.hasEligibleFrame(&mixedQueue)); delete lowPacket; } + +// Closed gates expose no predicate-selected packet and remove nothing. Once +// opened, the same exact candidate crosses through PacketFlowBase accounting. +{ + auto leafQueue = createPacketQueue("closedGateLeaf"); + auto gateModule = cModuleType::get("inet.queueing.gate.PacketGate")->create("closedPredicateGate", this); + gateModule->par("initiallyOpen").setBoolValue(false); + gateModule->par("openTime").setDoubleValue(10); + gateModule->par("closeTime").setDoubleValue(20); + leafQueue->gate("out")->connectTo(gateModule->gate("in")); + gateModule->callInitialize(); + auto packet = new Packet("closedGatePacket", makeShared(B(1))); + leafQueue->pushPacket(packet, nullptr); + auto packetExtractor = check_and_cast(gateModule); + auto closedFoundPacket = packetExtractor->findPacket([](const Packet *) { return true; }); + auto closedDequeuedPacket = packetExtractor->dequeuePacket([](const Packet *) { return true; }); + ASSERT(closedFoundPacket == nullptr); + ASSERT(closedDequeuedPacket == nullptr); + ASSERT(leafQueue->getNumPackets() == 1); + check_and_cast(gateModule)->open(); + ASSERT(packetExtractor->findPacket([](const Packet *) { return true; }) == packet); + auto dequeuedPacket = packetExtractor->dequeuePacket([](const Packet *) { return true; }); + ASSERT(dequeuedPacket == packet); + ASSERT(leafQueue->getNumPackets() == 0); + take(packet); + delete packet; + gateModule->callFinish(); + leafQueue->callFinish(); + gateModule->deleteModule(); + leafQueue->deleteModule(); +} + +// An implicit guard band rejects the provider's exact first match instead of +// skipping it and extracting a later packet that happens to fit. +{ + auto leafQueue = createPacketQueue("guardBandLeaf"); + auto gateModule = cModuleType::get("inet.queueing.gate.PeriodicGate")->create("predicateGuardBandGate", this); + auto durations = new cValueArray(); + durations->add(cValue(1, "s")); + durations->add(cValue(1, "s")); + gateModule->par("durations").setObjectValue(durations); + gateModule->par("initiallyOpen").setBoolValue(true); + gateModule->par("bitrate").setDoubleValue(8); + leafQueue->gate("out")->connectTo(gateModule->gate("in")); + gateModule->callInitialize(); + auto blockedPacket = new Packet("guardBandBlockedPacket", makeShared(B(2))); + auto fittingPacket = new Packet("guardBandFittingPacket", makeShared(B(1))); + leafQueue->pushPacket(blockedPacket, nullptr); + leafQueue->pushPacket(fittingPacket, nullptr); + auto packetExtractor = check_and_cast(gateModule); + auto blockedFoundPacket = packetExtractor->findPacket([](const Packet *) { return true; }); + auto blockedDequeuedPacket = packetExtractor->dequeuePacket([](const Packet *) { return true; }); + ASSERT(blockedFoundPacket == nullptr); + ASSERT(blockedDequeuedPacket == nullptr); + ASSERT(leafQueue->getNumPackets() == 2); + auto dequeuedPacket = packetExtractor->dequeuePacket([blockedPacket](const Packet *packet) { return packet != blockedPacket; }); + ASSERT(dequeuedPacket == fittingPacket); + ASSERT(leafQueue->getNumPackets() == 1); + take(fittingPacket); + delete fittingPacket; + auto retainedPacket = leafQueue->dequeuePacket([blockedPacket](const Packet *packet) { return packet == blockedPacket; }); + ASSERT(retainedPacket == blockedPacket); + take(blockedPacket); + delete blockedPacket; + gateModule->callFinish(); + leafQueue->callFinish(); + gateModule->deleteModule(); + leafQueue->deleteModule(); +} + // Every A-MSDU member is extracted through the scheduler, so provider // accounting runs for all policy-selected subframes. { @@ -1478,6 +1691,33 @@ ASSERT(!dataService.hasEligibleFrame(&mixedQueue)); recoveryProcedure.unsubscribe(IRecoveryProcedure::retryLimitReachedSignal, &retryLimitListener); } + +// Either terminal discard path clears both per-frame retry maps. Reusing the +// same sequence/fragment identity therefore starts from zero after either +// data-first or RTS-first failure history. +{ + TestNonQosRecoveryProcedure recoveryProcedure; + auto header = makeShared(); + header->setSequenceNumber(SequenceNumberCyclic(43)); + Packet shortPacket("shortTerminalDiscard", header); + recoveryProcedure.addShortRetry(header, 2); + recoveryProcedure.addLongRetry(header, 3); + ASSERT(recoveryProcedure.getShortRetryCount(&shortPacket, header) == 2); + ASSERT(recoveryProcedure.getLongRetryCount(&shortPacket, header) == 3); + recoveryProcedure.discardFrame(&shortPacket, header); + ASSERT(recoveryProcedure.getShortRetryCount(&shortPacket, header) == 0); + ASSERT(recoveryProcedure.getLongRetryCount(&shortPacket, header) == 0); + + Packet reusedPacket("reusedTerminalDiscard", header); + recoveryProcedure.addLongRetry(header, 4); + recoveryProcedure.addShortRetry(header, 5); + ASSERT(recoveryProcedure.getShortRetryCount(&reusedPacket, header) == 5); + ASSERT(recoveryProcedure.getLongRetryCount(&reusedPacket, header) == 4); + recoveryProcedure.discardRtsFrame(header); + ASSERT(recoveryProcedure.getShortRetryCount(&reusedPacket, header) == 0); + ASSERT(recoveryProcedure.getLongRetryCount(&reusedPacket, header) == 0); +} + // A stale RTS-protected ADDBA request uses the management header for its retry // check and clears the RTS short-retry entry even when the protected frame is // long. It is a transaction discard, not a retry-limit or link-break event. @@ -1681,11 +1921,15 @@ delete compoundPacket; auto pulledPacket = new Packet("lifecyclePulled", makeShared(B(1))); auto selectedPacket = new Packet("lifecycleSelected", makeShared(B(1))); auto removedPacket = new Packet("lifecycleRemoved", makeShared(B(1))); - auto bulkRemovedPacket = new Packet("lifecycleBulkRemoved", makeShared(B(1))); + auto firstBulkRemovedPacket = new Packet("lifecycleBulkRemovedFirst", makeShared(B(1))); + auto secondBulkRemovedPacket = new Packet("lifecycleBulkRemovedSecond", makeShared(B(1))); + auto thirdBulkRemovedPacket = new Packet("lifecycleBulkRemovedThird", makeShared(B(1))); lifecycleQueue->pushPacket(pulledPacket, nullptr); lifecycleQueue->pushPacket(selectedPacket, nullptr); lifecycleQueue->pushPacket(removedPacket, nullptr); - lifecycleQueue->pushPacket(bulkRemovedPacket, nullptr); + lifecycleQueue->pushPacket(firstBulkRemovedPacket, nullptr); + lifecycleQueue->pushPacket(secondBulkRemovedPacket, nullptr); + lifecycleQueue->pushPacket(thirdBulkRemovedPacket, nullptr); auto pulledResult = lifecycleQueue->dequeuePacket(); auto selectedResult = lifecycleQueue->dequeuePacket([selectedPacket](const Packet *packet) { return packet == selectedPacket; }); lifecycleQueue->removePacket(removedPacket); @@ -1696,13 +1940,56 @@ delete compoundPacket; delete selectedResult; delete removedPacket; lifecycleQueue->removeAllPackets(); - if (lifecycleCallback.numDequeuedPackets != 2 || lifecycleCallback.numRemovedPackets != 2 || lifecycleCallback.numDroppedPackets != 0) + if (lifecycleCallback.numDequeuedPackets != 2 || lifecycleCallback.numRemovedPackets != 4 || lifecycleCallback.numDroppedPackets != 0 || !lifecycleQueue->isEmpty()) throw cRuntimeError("Typed queue lifecycle callback did not cover every departure exactly once"); lifecycleQueue->removePacketCallback(&lifecycleCallback); lifecycleQueue->callFinish(); lifecycleQueue->deleteModule(); } + +// Bulk removal detaches only this queue's packets from a shared buffer; packets +// belonging to another queue remain buffered and receive no removal callback. +{ + auto bufferModule = cModuleType::get("inet.queueing.buffer.PacketBuffer")->create("sharedRemovalBuffer", this); + bufferModule->callInitialize(); + auto createBufferedQueue = [&](const char *name) { + auto module = cModuleType::get("inet.queueing.queue.PacketQueue")->create(name, this); + module->par("bufferModule").setStringValue("^.sharedRemovalBuffer"); + module->callInitialize(); + return check_and_cast(module); + }; + auto firstQueue = createBufferedQueue("sharedRemovalFirstQueue"); + auto secondQueue = createBufferedQueue("sharedRemovalSecondQueue"); + TestPacketDropCallback firstCallback; + TestPacketDropCallback secondCallback; + firstQueue->addPacketCallback(&firstCallback); + secondQueue->addPacketCallback(&secondCallback); + firstQueue->pushPacket(new Packet("sharedRemovalFirstA", makeShared(B(1))), nullptr); + firstQueue->pushPacket(new Packet("sharedRemovalFirstB", makeShared(B(1))), nullptr); + auto retainedPacket = new Packet("sharedRemovalRetained", makeShared(B(1))); + secondQueue->pushPacket(retainedPacket, nullptr); + firstQueue->removeAllPackets(); + auto buffer = check_and_cast(bufferModule); + ASSERT(firstQueue->isEmpty()); + ASSERT(secondQueue->getNumPackets() == 1); + ASSERT(buffer->getNumPackets() == 1); + ASSERT(firstCallback.numRemovedPackets == 2); + ASSERT(secondCallback.numRemovedPackets == 0); + auto dequeuedPacket = secondQueue->dequeuePacket([retainedPacket](const Packet *packet) { return packet == retainedPacket; }); + ASSERT(dequeuedPacket == retainedPacket); + take(retainedPacket); + delete retainedPacket; + firstQueue->removePacketCallback(&firstCallback); + secondQueue->removePacketCallback(&secondCallback); + firstQueue->callFinish(); + secondQueue->callFinish(); + bufferModule->callFinish(); + firstQueue->deleteModule(); + secondQueue->deleteModule(); + bufferModule->deleteModule(); +} + // Internal overflow invokes the typed queue callback once for the destructive // discard and never for the packet later removed normally. { From 2a389ecc75d3618f3b9e2880a4914c2ea704aaf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Tue, 18 Aug 2026 14:03:11 +0200 Subject: [PATCH 06/14] Fix Block Ack teardown and queue removal callbacks Reset recipient-side Block Ack reordering when a locally transmitted recipient DELBA removes an agreement. Keep the removed agreement alive through HCF notification so teardown observers receive valid state, and remove the unused ADDBA-response-sent flag. Return removed recipient agreements from the transmitted-DELBA handler and cover teardown plus same-peer/TID reestablishment with a reorder-buffer regression. The test proves stale sequence state and buffered fragments do not leak into the replacement session. Report shared PacketBuffer removals to PacketQueue observers exactly once. A dedicated pre-drop detach callback preserves batch removal semantics: ordinary removals report REMOVED, overload victims report DROPPED, and all selected victims are detached before drop callbacks can re-enter queue selection. Add focused coverage for direct shared-buffer removal, overload callback reasons, recipient DELBA cleanup, and fresh reorder state after renegotiation. Validation: - Release and debug builds pass. - Focused ADDBA unit tests pass in release and debug. - The full release unit suite passes 78/90; the 12 failures are unrelated pre-existing clock and TCP cases. - Focused Block Ack fingerprint mismatches are unchanged by an old-vs-new HCF channel-resumption A/B, so fingerprint baselines remain untouched. - Architectural review reports no new violations. --- .../mac/blockack/RecipientBlockAckAgreement.h | 3 - .../RecipientBlockAckAgreementHandler.cc | 9 +- .../RecipientBlockAckAgreementHandler.h | 3 +- .../IRecipientBlockAckAgreementHandler.h | 2 +- .../ieee80211/mac/coordinationfunction/Hcf.cc | 12 ++- src/inet/queueing/buffer/PacketBuffer.cc | 2 +- src/inet/queueing/contract/IPacketBuffer.h | 7 ++ src/inet/queueing/queue/PacketQueue.cc | 11 +++ src/inet/queueing/queue/PacketQueue.h | 1 + tests/unit/Ieee80211AddbaTransaction_1.test | 83 +++++++++++++++++-- 10 files changed, 111 insertions(+), 22 deletions(-) diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h index 40a4186fead..98f9ba589c4 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h @@ -21,7 +21,6 @@ class INET_API RecipientBlockAckAgreement : public cObject SequenceNumberCyclic startingSequenceNumber; int bufferSize = -1; simtime_t blockAckTimeoutValue = 0; - bool isAddbaResponseSent = false; simtime_t expirationTime = -1; public: @@ -35,7 +34,6 @@ class INET_API RecipientBlockAckAgreement : public cObject virtual int getBufferSize() const { return bufferSize; } virtual SequenceNumberCyclic getStartingSequenceNumber() const { return startingSequenceNumber; } - virtual void addbaResposneSent() { isAddbaResponseSent = true; } virtual void calculateExpirationTime() { expirationTime = blockAckTimeoutValue == 0 ? SIMTIME_MAX : simTime() + blockAckTimeoutValue; } virtual simtime_t getExpirationTime() { return expirationTime; } friend std::ostream& operator<<(std::ostream& os, const RecipientBlockAckAgreement& agreement); @@ -45,4 +43,3 @@ class INET_API RecipientBlockAckAgreement : public cObject } /* namespace inet */ #endif - diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc index 794ba6c6c61..044da6d2014 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc @@ -109,11 +109,6 @@ RecipientBlockAckAgreement *RecipientBlockAckAgreementHandler::removeAgreement(M return nullptr; } -void RecipientBlockAckAgreementHandler::terminateAgreement(MacAddress originatorAddr, Tid tid) -{ - delete removeAgreement(originatorAddr, tid); -} - RecipientBlockAckAgreement *RecipientBlockAckAgreementHandler::getAgreement(Tid tid, MacAddress originatorAddr) { auto agreementId = std::make_pair(originatorAddr, tid); @@ -148,9 +143,9 @@ RecipientBlockAckAgreement *RecipientBlockAckAgreementHandler::processReceivedAd return agreement; } -void RecipientBlockAckAgreementHandler::processTransmittedDelba(const Ptr& delba) +std::unique_ptr RecipientBlockAckAgreementHandler::processTransmittedDelba(const Ptr& delba) { - terminateAgreement(delba->getReceiverAddress(), delba->getTid()); + return std::unique_ptr(removeAgreement(delba->getReceiverAddress(), delba->getTid())); } std::unique_ptr RecipientBlockAckAgreementHandler::processReceivedDelba(const Ptr& delba, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy) diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h index 65fcfbfd3bc..7d95b02a6a7 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h @@ -29,7 +29,6 @@ class INET_API RecipientBlockAckAgreementHandler : public IRecipientBlockAckAgre protected: virtual RecipientBlockAckAgreement *removeAgreement(MacAddress originatorAddr, Tid tid); - virtual void terminateAgreement(MacAddress originatorAddr, Tid tid); virtual const Ptr buildAddbaResponse(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, bool accepted); virtual const Ptr buildDelba(MacAddress receiverAddr, Tid tid, int reasonCode); virtual simtime_t computeEarliestExpirationTime(); @@ -40,7 +39,7 @@ class INET_API RecipientBlockAckAgreementHandler : public IRecipientBlockAckAgre virtual RecipientBlockAckAgreement *processReceivedAddbaRequest(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) override; virtual std::unique_ptr processReceivedDelba(const Ptr& delba, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy) override; virtual void qosFrameReceived(const Ptr& qosHeader, IBlockAckAgreementHandlerCallback *callback) override; - virtual void processTransmittedDelba(const Ptr& delba) override; + virtual std::unique_ptr processTransmittedDelba(const Ptr& delba) override; virtual void blockAckAgreementExpired(IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) override; virtual RecipientBlockAckAgreement *getAgreement(Tid tid, MacAddress originatorAddr) override; diff --git a/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h index dac7d884050..25761d25d0d 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h @@ -28,7 +28,7 @@ class INET_API IRecipientBlockAckAgreementHandler virtual RecipientBlockAckAgreement *processReceivedAddbaRequest(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) = 0; virtual std::unique_ptr processReceivedDelba(const Ptr& delba, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy) = 0; - virtual void processTransmittedDelba(const Ptr& delba) = 0; + virtual std::unique_ptr processTransmittedDelba(const Ptr& delba) = 0; virtual void qosFrameReceived(const Ptr& qosHeader, IBlockAckAgreementHandlerCallback *callback) = 0; virtual void blockAckAgreementExpired(IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) = 0; diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc index 0bf6938b1ff..77c3508dac0 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc @@ -658,8 +658,16 @@ void Hcf::originatorProcessTransmittedManagementFrame(Packet *packet, const Ptr< if (wasPending) rebuildPendingFrameEligibility(); } - else - recipientBlockAckAgreementHandler->processTransmittedDelba(delba); + else { + auto agreement = recipientBlockAckAgreementHandler->processTransmittedDelba(delba); + if (agreement != nullptr) { + // IEEE Std 802.11-2024, 10.25.4 and 11.5.3.5: recipient + // resources are released whether the recipient transmitted or + // received DELBA. The reorder window is such a resource. + recipientDataService->resetBlockAckReordering(delba->getTid(), delba->getReceiverAddress()); + emit(blockAckAgreementDeletedSignal, agreement.get()); + } + } } else ; // TODO other mgmt frames if needed } diff --git a/src/inet/queueing/buffer/PacketBuffer.cc b/src/inet/queueing/buffer/PacketBuffer.cc index 08b4544e8c2..2509ced7fa3 100644 --- a/src/inet/queueing/buffer/PacketBuffer.cc +++ b/src/inet/queueing/buffer/PacketBuffer.cc @@ -71,7 +71,7 @@ void PacketBuffer::addPacket(Packet *packet) } for (auto& [packet, callback] : droppedPackets) if (callback != nullptr) - callback->handlePacketRemoved(packet); + callback->handlePacketDropping(packet); for (auto& [packet, callback] : droppedPackets) { if (callback != nullptr) callback->handlePacketDropped(packet); diff --git a/src/inet/queueing/contract/IPacketBuffer.h b/src/inet/queueing/contract/IPacketBuffer.h index f89f04823a4..4e0bf6302c8 100644 --- a/src/inet/queueing/contract/IPacketBuffer.h +++ b/src/inet/queueing/contract/IPacketBuffer.h @@ -28,6 +28,13 @@ class INET_API IPacketBuffer : public virtual IPacketCollection */ virtual void handlePacketRemoved(Packet *packet) = 0; + /** + * Detaches a packet selected for an overload drop from its owner before + * drop observers are notified. The default preserves compatibility for + * owners which do not distinguish the two removal phases. + */ + virtual void handlePacketDropping(Packet *packet) { handlePacketRemoved(packet); } + /** * Notifies the packet owner after all packets selected by one overload * operation have been removed from their owners. The default implementation diff --git a/src/inet/queueing/queue/PacketQueue.cc b/src/inet/queueing/queue/PacketQueue.cc index eff98bf3a35..c0374894510 100644 --- a/src/inet/queueing/queue/PacketQueue.cc +++ b/src/inet/queueing/queue/PacketQueue.cc @@ -215,6 +215,17 @@ bool PacketQueue::canPushPacket(Packet *packet, const cGate *gate) const void PacketQueue::handlePacketRemoved(Packet *packet) { Enter_Method("handlePacketRemoved"); + if (queue.contains(packet)) { + EV_INFO << "Removing packet" << EV_FIELD(packet) << EV_ENDL; + queue.remove(packet); + emit(packetRemovedSignal, packet); + notifyPacketRemoved(packet, IPacketQueue::PacketRemovalReason::REMOVED); + } +} + +void PacketQueue::handlePacketDropping(Packet *packet) +{ + Enter_Method("handlePacketDropping"); if (queue.contains(packet)) { EV_INFO << "Removing packet" << EV_FIELD(packet) << EV_ENDL; queue.remove(packet); diff --git a/src/inet/queueing/queue/PacketQueue.h b/src/inet/queueing/queue/PacketQueue.h index bff2c16e15a..5c32a9156e5 100644 --- a/src/inet/queueing/queue/PacketQueue.h +++ b/src/inet/queueing/queue/PacketQueue.h @@ -76,6 +76,7 @@ class INET_API PacketQueue : public PacketQueueBase, public IPacketBuffer::ICall virtual Packet *pullPacket(const cGate *gate) override; virtual void handlePacketRemoved(Packet *packet) override; + virtual void handlePacketDropping(Packet *packet) override; virtual void handlePacketDropped(Packet *packet) override; }; diff --git a/tests/unit/Ieee80211AddbaTransaction_1.test b/tests/unit/Ieee80211AddbaTransaction_1.test index a354fb25152..dbae9fe2de7 100644 --- a/tests/unit/Ieee80211AddbaTransaction_1.test +++ b/tests/unit/Ieee80211AddbaTransaction_1.test @@ -24,6 +24,7 @@ Test IEEE 802.11 ADDBA transactions, reordering reset, fragmentation cleanup, DC #include "inet/linklayer/ieee80211/mac/fragmentation/Fragmentation.h" #include "inet/linklayer/ieee80211/mac/framesequence/FrameSequenceStep.h" #include "inet/linklayer/ieee80211/mac/framesequence/HcfFs.h" +#include "inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.h" #include "inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.h" #include "inet/linklayer/ieee80211/mac/originator/NonQosRecoveryProcedure.h" #include "inet/linklayer/ieee80211/mac/queue/InProgressFrames.h" @@ -433,6 +434,10 @@ class TestHcf : public Hcf originatorBlockAckAgreementHandler = handler; originatorBlockAckAgreementPolicy = policy; } + void configureTransmittedManagement(Edca *edca, IOriginatorQoSAckPolicy *originatorAckPolicy) { + this->edca = edca; + this->originatorAckPolicy = originatorAckPolicy; + } void configureBlockAckHandlers(IOriginatorBlockAckAgreementHandler *originatorHandler, IOriginatorBlockAckAgreementPolicy *originatorPolicy, IRecipientBlockAckAgreementHandler *recipientHandler, IRecipientBlockAckAgreementPolicy *recipientPolicy) { originatorBlockAckAgreementHandler = originatorHandler; originatorBlockAckAgreementPolicy = originatorPolicy; @@ -440,6 +445,7 @@ class TestHcf : public Hcf recipientBlockAckAgreementPolicy = recipientPolicy; } void processTransmittedData(Packet *packet, const Ptr& dataHeader, AccessCategory ac) { originatorProcessTransmittedDataFrame(packet, dataHeader, ac); } + void processTransmittedManagement(Packet *packet, const Ptr& managementHeader, AccessCategory ac) { originatorProcessTransmittedManagementFrame(packet, managementHeader, ac); } void dropPacket(Packet *packet) { handlePacketRemoved(packet, queueing::IPacketQueue::PacketRemovalReason::DROPPED); } void cancelTransaction(uint64_t transactionId) { cancelAddbaTransaction(transactionId, nullptr); } void processReceivedManagementFrame(const Ptr& header) { recipientProcessReceivedManagementFrame(header); } @@ -1134,6 +1140,68 @@ ASSERT(immediateRecipientHandler.getAgreement(6, peer1) == replacementImmediateA hcf.unsubscribe(Hcf::blockAckAgreementDeletedSignal, &deletionListener); } +// IEEE Std 802.11-2024, 10.25.4 and 11.5.3.5: locally transmitting a +// recipient DELBA tears down the agreement and its reorder resources before a +// replacement agreement for the same peer and TID starts a fresh window. +{ + auto recipientDataServiceModule = cModuleType::get("inet.linklayer.ieee80211.mac.recipient.RecipientQosMacDataService")->create("transmittedDelbaDataService", this); + recipientDataServiceModule->callInitialize(); + auto recipientDataService = check_and_cast(recipientDataServiceModule); + TestPacketDropSignalListener reorderDropListener; + recipientDataService->subscribe(packetDroppedSignal, &reorderDropListener); + + auto originatorHandler = new TestOriginatorHandler(); + auto recipientHandler = new RecipientBlockAckAgreementHandler(); + TestRecipientPolicy transmittedDelbaPolicy; + TestCallback transmittedDelbaCallback; + auto firstRequest = makeRequest(peer2, 3, 84, SequenceNumberCyclic(100), 32); + auto firstAgreement = recipientHandler->processReceivedAddbaRequest(firstRequest, &transmittedDelbaPolicy, &transmittedDelbaCallback, &transmittedDelbaCallback); + ASSERT(firstAgreement != nullptr); + auto oldWindowHeader = makeQosHeader(peer1, 3, SequenceNumberCyclic(101)); + oldWindowHeader->setTransmitterAddress(peer2); + auto oldWindowPacket = new Packet("transmittedDelbaOldWindow", oldWindowHeader); + ASSERT(recipientDataService->dataFrameReceived(oldWindowPacket, oldWindowHeader, recipientHandler).empty()); + + QosAckHandler transmittedDelbaAckHandler; + TestEdcaf transmittedDelbaEdcaf; + transmittedDelbaEdcaf.qosAckHandler = &transmittedDelbaAckHandler; + TestEdca transmittedDelbaEdca; + transmittedDelbaEdca.edcaf = &transmittedDelbaEdcaf; + OriginatorQosAckPolicy transmittedDelbaAckPolicy; + TestHcf hcf; + hcf.configureBlockAckHandlers(originatorHandler, &originatorPolicy, recipientHandler, &transmittedDelbaPolicy); + hcf.configureRecipientDataService(recipientDataService); + hcf.configureTransmittedManagement(&transmittedDelbaEdca, &transmittedDelbaAckPolicy); + TestSignalListener deletionListener; + hcf.subscribe(Hcf::blockAckAgreementDeletedSignal, &deletionListener); + + auto delba = makeShared(); + delba->setReceiverAddress(peer2); + delba->setTid(3); + delba->setInitiator(false); + delba->setSequenceNumber(SequenceNumberCyclic(200)); + Packet delbaPacket("transmittedRecipientDelba", delba); + hcf.processTransmittedManagement(&delbaPacket, delba, AC_BE); + ASSERT(recipientHandler->getAgreement(3, peer2) == nullptr); + ASSERT(deletionListener.numSignals == 1); + ASSERT(reorderDropListener.numOtherPacketDrops == 1); + + auto replacementRequest = makeRequest(peer2, 3, 85, SequenceNumberCyclic(10), 32); + auto replacementAgreement = recipientHandler->processReceivedAddbaRequest(replacementRequest, &transmittedDelbaPolicy, &transmittedDelbaCallback, &transmittedDelbaCallback); + ASSERT(replacementAgreement != nullptr); + auto newWindowHeader = makeQosHeader(peer1, 3, SequenceNumberCyclic(11)); + newWindowHeader->setTransmitterAddress(peer2); + auto newWindowPacket = new Packet("transmittedDelbaNewWindow", newWindowHeader); + ASSERT(recipientDataService->dataFrameReceived(newWindowPacket, newWindowHeader, recipientHandler).empty()); + recipientDataService->resetBlockAckReordering(3, peer2); + ASSERT(reorderDropListener.numOtherPacketDrops == 2); + + hcf.unsubscribe(Hcf::blockAckAgreementDeletedSignal, &deletionListener); + recipientDataService->unsubscribe(packetDroppedSignal, &reorderDropListener); + recipientDataServiceModule->callFinish(); + recipientDataServiceModule->deleteModule(); +} + // The reverse DELBA direction terminates the originator agreement and emits // exactly one deletion event; policy rejection emits nothing and preserves it. { @@ -1948,8 +2016,8 @@ delete compoundPacket; } -// Bulk removal detaches only this queue's packets from a shared buffer; packets -// belonging to another queue remain buffered and receive no removal callback. +// Bulk removal detaches only this queue's packets from a shared buffer. A +// direct buffer removal then notifies the owning queue exactly once. { auto bufferModule = cModuleType::get("inet.queueing.buffer.PacketBuffer")->create("sharedRemovalBuffer", this); bufferModule->callInitialize(); @@ -1970,14 +2038,17 @@ delete compoundPacket; auto retainedPacket = new Packet("sharedRemovalRetained", makeShared(B(1))); secondQueue->pushPacket(retainedPacket, nullptr); firstQueue->removeAllPackets(); - auto buffer = check_and_cast(bufferModule); + auto buffer = check_and_cast(bufferModule); ASSERT(firstQueue->isEmpty()); ASSERT(secondQueue->getNumPackets() == 1); ASSERT(buffer->getNumPackets() == 1); ASSERT(firstCallback.numRemovedPackets == 2); ASSERT(secondCallback.numRemovedPackets == 0); - auto dequeuedPacket = secondQueue->dequeuePacket([retainedPacket](const Packet *packet) { return packet == retainedPacket; }); - ASSERT(dequeuedPacket == retainedPacket); + buffer->removePacket(retainedPacket); + ASSERT(secondQueue->isEmpty()); + ASSERT(buffer->getNumPackets() == 0); + ASSERT(secondCallback.numRemovedPackets == 1); + ASSERT(secondCallback.numDroppedPackets == 0); take(retainedPacket); delete retainedPacket; firstQueue->removePacketCallback(&firstCallback); @@ -2035,7 +2106,7 @@ delete compoundPacket; secondQueue->pushPacket(new Packet("sharedSecondVictim", makeShared(B(1))), nullptr); auto retainedPacket = new Packet("sharedRetainedPacket", makeShared(B(3))); firstQueue->pushPacket(retainedPacket, nullptr); - if (dropCallback.numDroppedPackets != 2 || !dropCallback.allExpectedPacketsDetachedAtFirstDrop || firstQueue->getNumPackets() != 1 || secondQueue->getNumPackets() != 0) + if (dropCallback.numDroppedPackets != 2 || dropCallback.numRemovedPackets != 0 || !dropCallback.allExpectedPacketsDetachedAtFirstDrop || firstQueue->getNumPackets() != 1 || secondQueue->getNumPackets() != 0) throw cRuntimeError("Shared buffer notified a drop before detaching the full victim batch"); auto dequeuedPacket = firstQueue->dequeuePacket([retainedPacket](const Packet *packet) { return packet == retainedPacket; }); if (dequeuedPacket != retainedPacket) From f084266dbe0e46719d9e17c02844d8b6164285b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Wed, 19 Aug 2026 06:50:34 +0200 Subject: [PATCH 07/14] Fix IEEE 802.11 Block Ack teardown handling Return removed originator agreements from transmitted DELBA processing so HCF can emit balanced agreement lifecycle signals only for agreements that were actually established. Treat successful ADDBA responses as established before applying a local policy veto, then queue an initiator DELBA with END_BA. Handle transmitted and pre-transmission-aborted teardown frames idempotently, preserve retry suppression, and restore frame eligibility without leaving a blocked TID. Make DROPPED and REMOVED unsent ADDBA requests terminal while keeping DEQUEUED as an ownership transfer. Count only actionable EDCA internal collisions and retain release/end-TXOP/all-AC resumption ordering without restarting active contention. Rename addbaFailureTimeout to addbaResponseTimeout, document the API and configuration migration, and add focused debug/release coverage for lifecycle signals, queue removal, local veto teardown, retry backoff, collision filtering, and channel-access resumption. Validation: debug and release builds pass; Ieee80211AddbaTransaction_1 passes in both modes; git diff --check passes. Fingerprint baselines are intentionally unchanged pending explicit acceptance of the attributed EDCAF timing shift. --- WHATSNEW | 15 +- examples/wireless/qos/omnetpp.ini | 2 +- .../blockack/OriginatorBlockAckAgreement.h | 3 + .../OriginatorBlockAckAgreementHandler.cc | 49 +- .../OriginatorBlockAckAgreementHandler.h | 5 +- .../OriginatorBlockAckAgreementPolicy.cc | 6 +- .../OriginatorBlockAckAgreementPolicy.h | 4 +- .../OriginatorBlockAckAgreementPolicy.ned | 2 +- .../IOriginatorBlockAckAgreementHandler.h | 11 +- .../ieee80211/mac/coordinationfunction/Hcf.cc | 59 ++- .../ieee80211/mac/coordinationfunction/Hcf.h | 3 +- .../ieee80211/mgmt/Ieee80211MgmtFrame.msg | 1 + tests/unit/Ieee80211AddbaTransaction_1.test | 476 +++++++++++++++++- 13 files changed, 591 insertions(+), 45 deletions(-) diff --git a/WHATSNEW b/WHATSNEW index 1b895d3f54b..d130a1f3e31 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -141,11 +141,18 @@ Notable backward incompatible changes are the following: 8. Block Ack DELBA agreement ownership - IOriginatorBlockAckAgreementHandler::processReceivedDelba() and + IOriginatorBlockAckAgreementHandler::processReceivedDelba(), + IOriginatorBlockAckAgreementHandler::processTransmittedDelba(), and IRecipientBlockAckAgreementHandler::processReceivedDelba() now return the - terminated agreement as a unique_ptr. Custom handler implementations and - callers must adopt the new return type; a null result means the DELBA was - rejected or did not match an agreement. + terminated agreement as a unique_ptr. Originator ADDBA response processing + now returns a typed outcome containing the established agreement and, when + local policy vetoes a successful response, a deferred initiator DELBA for + Hcf to enqueue after emitting the Added signal. Custom handler + implementations and callers must adopt the new return types; a null result + means the DELBA did not remove an agreement. + + The originator policy parameter `addbaFailureTimeout` was renamed to + `addbaResponseTimeout`; update custom configurations accordingly. Notable backward compatible changes are the following: diff --git a/examples/wireless/qos/omnetpp.ini b/examples/wireless/qos/omnetpp.ini index 1810255a502..ae47835b014 100644 --- a/examples/wireless/qos/omnetpp.ini +++ b/examples/wireless/qos/omnetpp.ini @@ -104,7 +104,7 @@ sim-time-limit = 3s *.ap.wlan[0].mac.hcf.isBlockAckSupported = true *.srvHost.wlan[0].mac.hcf.isBlockAckSupported = false **.mac.hcf.originatorAckPolicy.blockAckReqThreshold = 2 -**.mac.hcf.originatorBlockAckAgreementPolicy.addbaFailureTimeout = 250ms +**.mac.hcf.originatorBlockAckAgreementPolicy.addbaResponseTimeout = 250ms # radio medium *.radioMedium.sameTransmissionStartTimeCheck = "ignore" diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h index 8099d7c1bdb..89ad4021dc8 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h @@ -38,6 +38,7 @@ class INET_API OriginatorBlockAckAgreement : public cObject simtime_t addbaResponseDeadline = -1; simtime_t blockAckTimeoutValue = -1; simtime_t expirationTime = -1; + bool teardownPending = false; public: OriginatorBlockAckAgreement(MacAddress receiverAddr, Tid tid, SequenceNumberCyclic startingSequenceNumber, int bufferSize, bool isAMsduSupported, bool isDelayedBlockAckPolicySupported, uint8_t dialogToken, uint64_t transactionId) : @@ -77,10 +78,12 @@ class INET_API OriginatorBlockAckAgreement : public cObject virtual void setIsDelayedBlockAckPolicySupported(bool isDelayedBlockAckPolicySupported) { this->isDelayedBlockAckPolicySupported = isDelayedBlockAckPolicySupported; } virtual void setBlockAckTimeoutValue(const simtime_t blockAckTimeoutValue) { this->blockAckTimeoutValue = blockAckTimeoutValue; } virtual void setAddbaResponseDeadline(simtime_t addbaResponseDeadline) { this->addbaResponseDeadline = addbaResponseDeadline; } + virtual void setTeardownPending(bool teardownPending) { this->teardownPending = teardownPending; } virtual void baPolicyFrameSent() { numSentBaPolicyFrames++; } virtual void calculateExpirationTime() { expirationTime = blockAckTimeoutValue == 0 ? SIMTIME_MAX : simTime() + blockAckTimeoutValue; } virtual simtime_t getExpirationTime() { return expirationTime; } + virtual bool isTeardownPending() const { return teardownPending; } }; } /* namespace ieee80211 */ diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc index 332c991bdee..ff0d20d49be 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc @@ -9,6 +9,7 @@ #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h" #include "inet/linklayer/ieee80211/mac/blockack/Ieee80211AddbaTransactionTag_m.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame_m.h" namespace inet { namespace ieee80211 { @@ -99,7 +100,7 @@ void OriginatorBlockAckAgreementHandler::blockAckAgreementExpired(IProcedureCall simtime_t now = simTime(); for (auto id : blockAckAgreements) { auto agreement = id.second; - if (agreement->getExpirationTime() == now) { + if (agreement->getExpirationTime() == now && !agreement->isTeardownPending()) { MacAddress receiverAddr = id.first.first; Tid tid = id.first.second; const auto& delba = buildDelba(receiverAddr, tid, 39); @@ -226,21 +227,35 @@ void OriginatorBlockAckAgreementHandler::processAcknowledgedDataFrame(Packet *pa } } -OriginatorBlockAckAgreement *OriginatorBlockAckAgreementHandler::processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) +OriginatorBlockAckAgreementResponse OriginatorBlockAckAgreementHandler::processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) { + OriginatorBlockAckAgreementResponse response; auto agreement = getAgreement(addbaResp->getTransmitterAddress(), addbaResp->getTid()); // IEEE Std 802.11-2024, 11.5.2.2: only a successful response matching the // outstanding peer, TID, and Dialog Token establishes the agreement. if (agreement == nullptr || !agreement->isPending() || !agreement->getIsAddbaRequestSent() || agreement->getDialogToken() != addbaResp->getDialogToken()) - return nullptr; - if (addbaResp->getStatusCode() == 0 && blockAckAgreementPolicy->isAddbaReqAccepted(addbaResp, agreement)) { + return response; + bool acceptedByLocalPolicy = addbaResp->getStatusCode() == 0 && blockAckAgreementPolicy->isAddbaReqAccepted(addbaResp, agreement); + if (addbaResp->getStatusCode() == 0) { auto transactionId = agreement->getTransactionId(); updateAgreement(agreement, addbaResp); - addbaRetryDeadlines.erase(std::make_pair(addbaResp->getTransmitterAddress(), addbaResp->getTid())); + if (acceptedByLocalPolicy) + addbaRetryDeadlines.erase(std::make_pair(addbaResp->getTransmitterAddress(), addbaResp->getTid())); + else + recordAddbaFailure(addbaResp->getTransmitterAddress(), addbaResp->getTid(), blockAckAgreementPolicy); scheduleInactivityTimer(callback); scheduleAddbaResponseTimer(callback); callback->cancelAddbaTransaction(transactionId, nullptr); - return agreement; + if (!acceptedByLocalPolicy) { + // IEEE Std 802.11-2024, 10.25.2 and Figure 11-32: SUCCESS + // establishes the peer agreement. If local policy rejects the + // accepted parameters, tear it down with an initiator DELBA; + // retain the established state until that DELBA is transmitted. + agreement->setTeardownPending(true); + response.teardownDelba = buildDelba(addbaResp->getTransmitterAddress(), addbaResp->getTid(), RC_END_BA); + } + response.agreement = agreement; + return response; } else { auto transactionId = agreement->getTransactionId(); @@ -248,7 +263,7 @@ OriginatorBlockAckAgreement *OriginatorBlockAckAgreementHandler::processReceived terminateAgreement(addbaResp->getTransmitterAddress(), addbaResp->getTid()); scheduleAddbaResponseTimer(callback); callback->cancelAddbaTransaction(transactionId, nullptr); - return nullptr; + return response; } } @@ -291,15 +306,31 @@ void OriginatorBlockAckAgreementHandler::processDroppedAddbaReq(Packet *packet, } } -void OriginatorBlockAckAgreementHandler::processTransmittedDelba(const Ptr& delba, IBlockAckAgreementHandlerCallback *callback) +std::unique_ptr OriginatorBlockAckAgreementHandler::processTransmittedDelba(const Ptr& delba, IBlockAckAgreementHandlerCallback *callback) { + // IEEE Std 802.11-2024, 11.5.3.2: the originator tears down its local + // agreement when its initiator DELBA is transmitted. auto agreement = getAgreement(delba->getReceiverAddress(), delba->getTid()); bool cancelPendingTransaction = agreement != nullptr && agreement->isPending(); auto transactionId = cancelPendingTransaction ? agreement->getTransactionId() : 0; - terminateAgreement(delba->getReceiverAddress(), delba->getTid()); + std::unique_ptr terminatedAgreement(removeAgreement(delba->getReceiverAddress(), delba->getTid())); scheduleAddbaResponseTimer(callback); if (cancelPendingTransaction) callback->cancelAddbaTransaction(transactionId, nullptr); + return terminatedAgreement; +} + +std::unique_ptr OriginatorBlockAckAgreementHandler::processAbortedDelba(Packet *packet, IBlockAckAgreementHandlerCallback *callback) +{ + auto delba = packet->peekAtFront(); + if (!delba->getInitiator()) + return nullptr; + auto agreement = getAgreement(delba->getReceiverAddress(), delba->getTid()); + if (agreement == nullptr || !agreement->isTeardownPending()) + return nullptr; + std::unique_ptr terminatedAgreement(removeAgreement(delba->getReceiverAddress(), delba->getTid())); + scheduleAddbaResponseTimer(callback); + return terminatedAgreement; } std::unique_ptr OriginatorBlockAckAgreementHandler::processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h index 102e2e3ef8b..3d174e4564f 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h @@ -46,9 +46,10 @@ class INET_API OriginatorBlockAckAgreementHandler : public IOriginatorBlockAckAg virtual void processDroppedAddbaReq(Packet *packet, const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; virtual void processAcknowledgedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback) override; virtual void processReceivedBlockAck(const Ptr& blockAck, IBlockAckAgreementHandlerCallback *callback) override; - virtual OriginatorBlockAckAgreement *processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; + virtual OriginatorBlockAckAgreementResponse processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; virtual std::unique_ptr processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; - virtual void processTransmittedDelba(const Ptr& delba, IBlockAckAgreementHandlerCallback *callback) override; + virtual std::unique_ptr processTransmittedDelba(const Ptr& delba, IBlockAckAgreementHandlerCallback *callback) override; + virtual std::unique_ptr processAbortedDelba(Packet *packet, IBlockAckAgreementHandlerCallback *callback) override; virtual void blockAckAgreementExpired(IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) override; virtual void addbaResponseTimeoutExpired(IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc index 0bd101f3949..0382f53e378 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc @@ -24,10 +24,10 @@ void OriginatorBlockAckAgreementPolicy::initialize(int stage) aMsduSupported = par("aMsduSupported"); maximumAllowedBufferSize = par("maximumAllowedBufferSize"); blockAckTimeoutValue = par("blockAckTimeoutValue"); - addbaFailureTimeout = par("addbaFailureTimeout"); + addbaResponseTimeout = par("addbaResponseTimeout"); addbaRetryBackoff = par("addbaRetryBackoff"); - if (addbaFailureTimeout <= 0) - throw cRuntimeError("addbaFailureTimeout must be greater than zero"); + if (addbaResponseTimeout <= 0) + throw cRuntimeError("addbaResponseTimeout must be greater than zero"); if (addbaRetryBackoff < 0) throw cRuntimeError("addbaRetryBackoff must not be negative"); WATCH(blockAckReqThreshold); diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.h index 9cacbc9ec78..a65fd75c037 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.h @@ -25,7 +25,7 @@ class INET_API OriginatorBlockAckAgreementPolicy : public ModeSetListener, publi bool aMsduSupported = false; int maximumAllowedBufferSize = -1; simtime_t blockAckTimeoutValue = -1; - simtime_t addbaFailureTimeout = -1; + simtime_t addbaResponseTimeout = -1; simtime_t addbaRetryBackoff = -1; protected: @@ -37,7 +37,7 @@ class INET_API OriginatorBlockAckAgreementPolicy : public ModeSetListener, publi virtual bool isAddbaReqAccepted(const Ptr& addbaResp, OriginatorBlockAckAgreement *agreement) override; virtual bool isDelbaAccepted(const Ptr& delba) override; - virtual simtime_t getAddbaResponseTimeout() const override { return addbaFailureTimeout; } + virtual simtime_t getAddbaResponseTimeout() const override { return addbaResponseTimeout; } virtual simtime_t computeAddbaRetryBackoff() const override { return addbaRetryBackoff; } virtual bool isMsduSupported() const override { return aMsduSupported; } diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.ned b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.ned index 6163b66238a..89e42723736 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.ned +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.ned @@ -23,7 +23,7 @@ simple OriginatorBlockAckAgreementPolicy extends SimpleModule like IOriginatorBl bool aMsduSupported = default(true); int maximumAllowedBufferSize = default(64); double blockAckTimeoutValue @unit(s) = default(0s); // 0 means that it depends on the originator - double addbaFailureTimeout @unit(s) = default(1s); // Model response-wait timeout, retained under its legacy parameter name for configuration compatibility + double addbaResponseTimeout @unit(s) = default(1s); // Model response-wait timeout after the ADDBA Request is transmitted double addbaRetryBackoff @unit(s) = default(1s); // Model retry suppression after a failed or discarded ADDBA transaction; not an IEEE 802.11 timer @display("i=block/control"); } diff --git a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h index 8369055d8af..4f59066f817 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h @@ -21,6 +21,12 @@ namespace inet { namespace ieee80211 { +struct INET_API OriginatorBlockAckAgreementResponse +{ + OriginatorBlockAckAgreement *agreement = nullptr; + Ptr teardownDelba; +}; + class INET_API IOriginatorBlockAckAgreementHandler { public: @@ -30,9 +36,10 @@ class INET_API IOriginatorBlockAckAgreementHandler virtual void processTransmittedAddbaReq(Packet *packet, const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; virtual void processDroppedAddbaReq(Packet *packet, const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; virtual void processAcknowledgedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback) = 0; - virtual OriginatorBlockAckAgreement *processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; + virtual OriginatorBlockAckAgreementResponse processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; virtual std::unique_ptr processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; - virtual void processTransmittedDelba(const Ptr& delba, IBlockAckAgreementHandlerCallback *callback) = 0; + virtual std::unique_ptr processTransmittedDelba(const Ptr& delba, IBlockAckAgreementHandlerCallback *callback) = 0; + virtual std::unique_ptr processAbortedDelba(Packet *packet, IBlockAckAgreementHandlerCallback *callback) = 0; virtual void blockAckAgreementExpired(IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) = 0; virtual void addbaResponseTimeoutExpired(IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc index 77c3508dac0..ef70dc00147 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc @@ -67,7 +67,8 @@ void Hcf::initialize(int stage) if (auto addbaReq = dynamicPtrCast(packet->peekAtFront())) return originatorBlockAckAgreementHandler->isAddbaRequestPending(packet, addbaReq); auto dataHeader = dynamicPtrCast(packet->peekAtFront()); - return dataHeader == nullptr || dataHeader->getType() != ST_DATA_WITH_QOS || !originatorBlockAckAgreementHandler->isAddbaResponsePending(dataHeader->getReceiverAddress(), dataHeader->getTid()); + auto agreement = dataHeader == nullptr ? nullptr : originatorBlockAckAgreementHandler->getAgreement(dataHeader->getReceiverAddress(), dataHeader->getTid()); + return dataHeader == nullptr || dataHeader->getType() != ST_DATA_WITH_QOS || ((agreement == nullptr || !agreement->isTeardownPending()) && !originatorBlockAckAgreementHandler->isAddbaResponsePending(dataHeader->getReceiverAddress(), dataHeader->getTid())); }); } } @@ -130,11 +131,32 @@ bool Hcf::processDroppedBlockAckSetupFrame(Packet *packet) return false; } +bool Hcf::processDroppedBlockAckTeardownFrame(Packet *packet) +{ + if (originatorBlockAckAgreementHandler) { + auto delba = dynamicPtrCast(packet->peekAtFront()); + if (delba != nullptr) { + auto agreement = originatorBlockAckAgreementHandler->processAbortedDelba(packet, this); + if (agreement != nullptr) { + emit(blockAckAgreementDeletedSignal, agreement.get()); + rebuildPendingFrameEligibility(); + return true; + } + } + } + return false; +} + void Hcf::handlePacketRemoved(Packet *packet, queueing::IPacketQueue::PacketRemovalReason reason) { Enter_Method("handlePacketRemoved"); untrackPendingFrame(packet); - if (reason == queueing::IPacketQueue::PacketRemovalReason::DROPPED && processDroppedBlockAckSetupFrame(packet)) + bool shouldResume = false; + if (reason == queueing::IPacketQueue::PacketRemovalReason::DROPPED || reason == queueing::IPacketQueue::PacketRemovalReason::REMOVED) { + shouldResume |= processDroppedBlockAckSetupFrame(packet); + shouldResume |= processDroppedBlockAckTeardownFrame(packet); + } + if (shouldResume) resumeEligibleChannelAccess(); } @@ -356,8 +378,11 @@ void Hcf::channelGranted(IChannelAccess *channelAccess) auto internallyCollidedEdcafs = edca->getInternallyCollidedEdcafs(); if (internallyCollidedEdcafs.size() > 0) { EV_INFO << "Internal collision happened with the following queues:" << std::endl; - handleInternalCollision(internallyCollidedEdcafs); - emit(edcaCollisionDetectedSignal, (unsigned long)internallyCollidedEdcafs.size()); + // IEEE Std 802.11-2024, 10.23.2.4: an EDCAF with no eligible + // frame has no collision recovery action to perform. + auto handledCollisions = handleInternalCollision(internallyCollidedEdcafs); + if (handledCollisions > 0) + emit(edcaCollisionDetectedSignal, (unsigned long)handledCollisions); } if (!hasFrameToTransmit(ac)) { EV_DETAIL << "Releasing channel because no eligible frame is available.\n"; @@ -384,8 +409,9 @@ void Hcf::startFrameSequence(AccessCategory ac) emit(IFrameSequenceHandler::frameSequenceStartedSignal, frameSequenceHandler->getContext()); } -void Hcf::handleInternalCollision(std::vector internallyCollidedEdcafs) +int Hcf::handleInternalCollision(std::vector internallyCollidedEdcafs) { + int handledCollisions = 0; for (auto edcaf : internallyCollidedEdcafs) { AccessCategory ac = edcaf->getAccessCategory(); auto dataRecoveryProcedure = edcaf->getRecoveryProcedure(); @@ -394,6 +420,7 @@ void Hcf::handleInternalCollision(std::vector internallyCollidedEdcafs) EV_DETAIL << "Ignoring internal collision because no eligible frame is available for " << printAccessCategory(ac) << ".\n"; continue; } + handledCollisions++; auto internallyCollidedHeader = internallyCollidedFrame->peekAtFront(); EV_INFO << printAccessCategory(ac) << " (" << internallyCollidedFrame->getName() << ")" << endl; bool retryLimitReached = false; @@ -417,6 +444,7 @@ void Hcf::handleInternalCollision(std::vector internallyCollidedEdcafs) else ; // TODO + NonQoSDataFrame edcaf->getInProgressFrames()->dropFrame(internallyCollidedFrame); processDroppedBlockAckSetupFrame(internallyCollidedFrame); + processDroppedBlockAckTeardownFrame(internallyCollidedFrame); edcaf->getAckHandler()->dropFrame(internallyCollidedHeader); PacketDropDetails details; details.setReason(RETRY_LIMIT_REACHED); @@ -429,6 +457,7 @@ void Hcf::handleInternalCollision(std::vector internallyCollidedEdcafs) else edcaf->requestChannel(this); } + return handledCollisions; } /* @@ -511,11 +540,15 @@ void Hcf::recipientProcessReceivedManagementFrame(const Ptr(header)) { bool wasPending = originatorBlockAckAgreementHandler->isAddbaResponsePending(addbaResp->getTransmitterAddress(), addbaResp->getTid()); - auto establishedAgreement = originatorBlockAckAgreementHandler->processReceivedAddbaResp(addbaResp, originatorBlockAckAgreementPolicy, this); + auto response = originatorBlockAckAgreementHandler->processReceivedAddbaResp(addbaResp, originatorBlockAckAgreementPolicy, this); if (wasPending && !originatorBlockAckAgreementHandler->isAddbaResponsePending(addbaResp->getTransmitterAddress(), addbaResp->getTid())) rebuildPendingFrameEligibility(); - if (establishedAgreement != nullptr) - emit(blockAckAgreementAddedSignal, establishedAgreement); + if (response.agreement != nullptr) + emit(blockAckAgreementAddedSignal, response.agreement); + if (response.teardownDelba != nullptr) { + auto delbaPacket = new Packet("Delba", response.teardownDelba); + processMgmtFrame(delbaPacket, response.teardownDelba); + } resumeEligibleChannelAccess(); } else if (auto delba = dynamicPtrCast(header)) { @@ -533,7 +566,7 @@ void Hcf::recipientProcessReceivedManagementFrame(const PtrprocessReceivedDelba(delba, originatorBlockAckAgreementPolicy, this); if (wasPending && !originatorBlockAckAgreementHandler->isAddbaResponsePending(delba->getTransmitterAddress(), delba->getTid())) rebuildPendingFrameEligibility(); - if (agreement != nullptr) + if (agreement != nullptr && agreement->getIsAddbaResponseReceived()) emit(blockAckAgreementDeletedSignal, agreement.get()); resumeEligibleChannelAccess(); } @@ -590,6 +623,7 @@ void Hcf::originatorProcessRtsProtectionFailed(Packet *packet) edca->getMgmtAndNonQoSRecoveryProcedure()->discardRtsFrame(addbaRequest); edcaf->getInProgressFrames()->dropFrame(packet); processDroppedBlockAckSetupFrame(packet); + processDroppedBlockAckTeardownFrame(packet); edcaf->getAckHandler()->dropFrame(protectedHeader); EV_INFO << "Dropping RTS/CTS protected frame " << packet->getName() << (retryLimitReached ? ", because retry limit is reached.\n" : ", because its ADDBA transaction is no longer pending.\n"); PacketDropDetails details; @@ -654,9 +688,11 @@ void Hcf::originatorProcessTransmittedManagementFrame(Packet *packet, const Ptr< else if (auto delba = dynamicPtrCast(mgmtHeader)) { if (delba->getInitiator()) { bool wasPending = originatorBlockAckAgreementHandler->isAddbaResponsePending(delba->getReceiverAddress(), delba->getTid()); - originatorBlockAckAgreementHandler->processTransmittedDelba(delba, this); + auto agreement = originatorBlockAckAgreementHandler->processTransmittedDelba(delba, this); if (wasPending) rebuildPendingFrameEligibility(); + if (agreement != nullptr && agreement->getIsAddbaResponseReceived()) + emit(blockAckAgreementDeletedSignal, agreement.get()); } else { auto agreement = recipientBlockAckAgreementHandler->processTransmittedDelba(delba); @@ -730,6 +766,7 @@ void Hcf::originatorProcessFailedFrame(Packet *failedPacket) edca->getMgmtAndNonQoSRecoveryProcedure()->discardFrame(failedPacket, addbaRequest); edcaf->getInProgressFrames()->dropFrame(failedPacket); processDroppedBlockAckSetupFrame(failedPacket); + processDroppedBlockAckTeardownFrame(failedPacket); edcaf->getAckHandler()->dropFrame(dynamicPtrCast(failedHeader)); EV_INFO << "Dropping frame " << failedPacket->getName() << (retryLimitReached ? ", because retry limit is reached.\n" : ", because its ADDBA transaction is no longer pending.\n"); PacketDropDetails details; @@ -893,6 +930,8 @@ void Hcf::transmitFrame(Packet *packet, simtime_t ifs) OriginatorBlockAckAgreement *agreement = nullptr; if (originatorBlockAckAgreementHandler) agreement = originatorBlockAckAgreementHandler->getAgreement(dataFrame->getReceiverAddress(), dataFrame->getTid()); + if (agreement != nullptr && agreement->isTeardownPending()) + agreement = nullptr; auto ackPolicy = originatorAckPolicy->computeAckPolicy(packet, dataFrame, agreement); auto dataHeader = packet->removeAtFront(); dataHeader->setAckPolicy(ackPolicy); diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h index 21effa97af5..9585b31906b 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h @@ -121,7 +121,7 @@ class INET_API Hcf : public ICoordinationFunction, public IFrameSequenceHandler: virtual void refreshDisplay() const override; void startFrameSequence(AccessCategory ac); - void handleInternalCollision(std::vector internallyCollidedEdcafs); + int handleInternalCollision(std::vector internallyCollidedEdcafs); void sendUp(const std::vector& completeFrames); FrameSequenceContext *buildContext(AccessCategory ac); @@ -130,6 +130,7 @@ class INET_API Hcf : public ICoordinationFunction, public IFrameSequenceHandler: virtual void requestEligibleChannelAccess(); virtual void resumeEligibleChannelAccess(); virtual bool processDroppedBlockAckSetupFrame(Packet *packet); + virtual bool processDroppedBlockAckTeardownFrame(Packet *packet); virtual bool isPacketReferencedByCurrentFrameSequence(const Packet *packet) const; virtual void handlePacketRemoved(Packet *packet, queueing::IPacketQueue::PacketRemovalReason reason) override; virtual void trackPendingFrame(Packet *packet, AccessCategory accessCategory); diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame.msg b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame.msg index a469125efef..89e6ddb6754 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame.msg +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame.msg @@ -61,6 +61,7 @@ enum Ieee80211ReasonCode RC_NONASS_CLASS3 = 7; RC_DIASS_MS_LEAVING = 8; RC_NONAUTH_ASS_REQUEST = 9; + RC_END_BA = 37; RC_MESH_PEERING_CANCELLED = 52; RC_MESH_MAX_PEERS = 53; RC_MESH_CONFIGURATION_POLICY_VIOLATION = 54; diff --git a/tests/unit/Ieee80211AddbaTransaction_1.test b/tests/unit/Ieee80211AddbaTransaction_1.test index dbae9fe2de7..331d99fae17 100644 --- a/tests/unit/Ieee80211AddbaTransaction_1.test +++ b/tests/unit/Ieee80211AddbaTransaction_1.test @@ -21,6 +21,7 @@ Test IEEE 802.11 ADDBA transactions, reordering reset, fragmentation cleanup, DC #include "inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementPolicy.h" #include "inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.h" #include "inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Mac.h" #include "inet/linklayer/ieee80211/mac/fragmentation/Fragmentation.h" #include "inet/linklayer/ieee80211/mac/framesequence/FrameSequenceStep.h" #include "inet/linklayer/ieee80211/mac/framesequence/HcfFs.h" @@ -30,6 +31,7 @@ Test IEEE 802.11 ADDBA transactions, reordering reset, fragmentation cleanup, DC #include "inet/linklayer/ieee80211/mac/queue/InProgressFrames.h" #include "inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.h" #include "inet/linklayer/ieee80211/mac/sequencenumberassignment/QoSSequenceNumberAssignment.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame_m.h" #include "inet/queueing/gate/PacketGate.h" #include "inet/queueing/gate/PeriodicGate.h" #include "inet/queueing/queue/CompoundPacketQueueBase.h" @@ -47,10 +49,11 @@ class TestOriginatorPolicy : public IOriginatorBlockAckAgreementPolicy public: simtime_t addbaResponseTimeout = 1; simtime_t addbaRetryBackoff = 2; + bool addbaAccepted = true; bool delbaAccepted = true; virtual bool isAddbaReqNeeded(Packet *, const Ptr&) override { return true; } - virtual bool isAddbaReqAccepted(const Ptr&, OriginatorBlockAckAgreement *) override { return true; } + virtual bool isAddbaReqAccepted(const Ptr&, OriginatorBlockAckAgreement *) override { return addbaAccepted; } virtual bool isDelbaAccepted(const Ptr&) override { return delbaAccepted; } virtual bool isMsduSupported() const override { return true; } virtual simtime_t getAddbaResponseTimeout() const override { return addbaResponseTimeout; } @@ -112,6 +115,7 @@ class TestOriginatorHandler : public OriginatorBlockAckAgreementHandler void setNextDialogToken(uint8_t token) { nextDialogToken = token; } void expireNow(MacAddress receiverAddress, Tid tid) { getAgreement(receiverAddress, tid)->setAddbaResponseDeadline(simTime()); } void allowRetryNow(MacAddress receiverAddress, Tid tid) { addbaRetryDeadlines[std::make_pair(receiverAddress, tid)] = simTime(); } + simtime_t getRetryDeadline(MacAddress receiverAddress, Tid tid) const { auto it = addbaRetryDeadlines.find(std::make_pair(receiverAddress, tid)); return it == addbaRetryDeadlines.end() ? SIMTIME_MAX : it->second; } }; class TestBlockAckReordering : public BlockAckReordering @@ -197,6 +201,17 @@ class TestPacketQueue : public queueing::IPacketQueue virtual void pushPacketProgress(Packet *, const cGate *, bps, b, b) override { throw cRuntimeError("Unsupported"); } }; +class TestImmediateRemovalQueue : public TestPacketQueue +{ + public: + PacketRemovalReason removalReason = PacketRemovalReason::DROPPED; + + virtual void enqueuePacket(Packet *packet) override { + notifyPacketRemoved(packet, removalReason); + delete packet; + } +}; + class TestCompoundPacketQueue : public queueing::CompoundPacketQueueBase { public: @@ -367,12 +382,21 @@ class TestEdcaf : public Edcaf QosAckHandler *qosAckHandler = nullptr; QosRecoveryProcedure *qosRecoveryProcedure = nullptr; StationRetryCounters stationRetryCounters; + int numReleaseChannelCalls = 0; + std::vector *eventOrder = nullptr; + bool contentionActive = false; + int numRequestChannelCalls = 0; + int numContentionStarts = 0; virtual queueing::IPacketQueue *getPendingQueue() const override { return pendingQueue; } virtual InProgressFrames *getInProgressFrames() const override { return inProgressFrames; } virtual QosAckHandler *getAckHandler() const override { return qosAckHandler; } virtual QosRecoveryProcedure *getRecoveryProcedure() const override { return qosRecoveryProcedure; } virtual StationRetryCounters *getStationRetryCounters() const override { return const_cast(&stationRetryCounters); } + virtual void releaseChannel(IChannelAccess::ICallback *) override { numReleaseChannelCalls++; if (eventOrder != nullptr) eventOrder->push_back(1); } + virtual void requestChannel(IChannelAccess::ICallback *) override { numRequestChannelCalls++; if (!contentionActive) numContentionStarts++; } + void setAccessCategory(AccessCategory accessCategory) { ac = accessCategory; } + void setTxopProcedure(TxopProcedure *txopProcedure) { this->txopProcedure = txopProcedure; } }; class TestEdca : public Edca @@ -382,10 +406,15 @@ class TestEdca : public Edca std::array edcafs = {}; int numEdcafs = 1; NonQosRecoveryProcedure *nonQosRecoveryProcedure = nullptr; + std::vector requestedAccessCategories; + std::vector internallyCollidedEdcafs; + std::vector *eventOrder = nullptr; virtual int getNumEdcafs() const override { return numEdcafs; } virtual Edcaf *getEdcaf(AccessCategory accessCategory) const override { return numEdcafs == 1 ? edcaf : edcafs[accessCategory]; } virtual Edcaf *getChannelOwner() override { return edcaf; } + virtual std::vector getInternallyCollidedEdcafs() override { return internallyCollidedEdcafs; } + virtual void requestChannelAccess(AccessCategory accessCategory, IChannelAccess::ICallback *callback) override { requestedAccessCategories.push_back(accessCategory); if (eventOrder != nullptr) eventOrder->push_back(3); getEdcaf(accessCategory)->requestChannel(callback); } virtual NonQosRecoveryProcedure *getMgmtAndNonQoSRecoveryProcedure() const override { return nonQosRecoveryProcedure; } }; @@ -411,9 +440,13 @@ class TestHcf : public Hcf public: int numProcessedDroppedSetupFrames = 0; int numResumedEligibleChannelAccess = 0; + int numCancelledAddbaTransactions = 0; + int numRebuildEligibilityCalls = 0; bool droppedSetupCancelled = false; + bool delegateDroppedSetupHandling = false; void configure(Edca *edca) { this->edca = edca; } + void configureMac(Ieee80211Mac *mac) { this->mac = mac; } void configureEligibilityIndex(Edca *edca, IOriginatorMacDataService *originatorDataService) { this->edca = edca; this->originatorDataService = originatorDataService; @@ -422,6 +455,10 @@ class TestHcf : public Hcf void trackFrame(Packet *packet, AccessCategory accessCategory) { trackPendingFrame(packet, accessCategory); } void untrackFrame(Packet *packet) { untrackPendingFrame(packet); } bool hasFrame(AccessCategory accessCategory) { return hasFrameToTransmit(accessCategory); } + void requestEligible() { requestEligibleChannelAccess(); } + int processInternalCollision(const std::vector& edcafs) { return handleInternalCollision(edcafs); } + void grantChannel(IChannelAccess *channelAccess) { channelGranted(channelAccess); } + void finishSequence() { frameSequenceFinished(); } int getNumEligiblePendingFrames(AccessCategory accessCategory) const { return numEligiblePendingFrames[accessCategory]; } int getNumTrackedPendingFrames() const { return pendingFrameEligibility.size(); } void observePendingQueue(queueing::IPacketQueue *pendingQueue) { @@ -447,12 +484,16 @@ class TestHcf : public Hcf void processTransmittedData(Packet *packet, const Ptr& dataHeader, AccessCategory ac) { originatorProcessTransmittedDataFrame(packet, dataHeader, ac); } void processTransmittedManagement(Packet *packet, const Ptr& managementHeader, AccessCategory ac) { originatorProcessTransmittedManagementFrame(packet, managementHeader, ac); } void dropPacket(Packet *packet) { handlePacketRemoved(packet, queueing::IPacketQueue::PacketRemovalReason::DROPPED); } + void removePacket(Packet *packet) { handlePacketRemoved(packet, queueing::IPacketQueue::PacketRemovalReason::REMOVED); } + void dequeuePacket(Packet *packet) { handlePacketRemoved(packet, queueing::IPacketQueue::PacketRemovalReason::DEQUEUED); } void cancelTransaction(uint64_t transactionId) { cancelAddbaTransaction(transactionId, nullptr); } void processReceivedManagementFrame(const Ptr& header) { recipientProcessReceivedManagementFrame(header); } void processRtsProtectionFailure(Packet *packet) { originatorProcessRtsProtectionFailed(packet); } protected: - virtual bool processDroppedBlockAckSetupFrame(Packet *) override { numProcessedDroppedSetupFrames++; return droppedSetupCancelled; } + virtual bool processDroppedBlockAckSetupFrame(Packet *packet) override { numProcessedDroppedSetupFrames++; return delegateDroppedSetupHandling ? Hcf::processDroppedBlockAckSetupFrame(packet) : droppedSetupCancelled; } + virtual void cancelAddbaTransaction(uint64_t transactionId, Packet *excludedPacket) override { numCancelledAddbaTransactions++; Hcf::cancelAddbaTransaction(transactionId, excludedPacket); } + virtual void rebuildPendingFrameEligibility() override { numRebuildEligibilityCalls++; Hcf::rebuildPendingFrameEligibility(); } virtual void resumeEligibleChannelAccess() override { numResumedEligibleChannelAccess++; } virtual void scheduleAddbaResponseTimer(simtime_t) override {} }; @@ -460,7 +501,16 @@ class TestHcf : public Hcf class TestTxopProcedure : public TxopProcedure { public: + std::vector *eventOrder = nullptr; virtual simtime_t getRemaining() const override { return 1; } + virtual void endTxop() override { if (eventOrder != nullptr) eventOrder->push_back(2); } +}; + +class TestMac : public Ieee80211Mac +{ + public: + int numPendingRadioConfigSends = 0; + virtual void sendDownPendingRadioConfigMsg() override { numPendingRadioConfigSends++; } }; class TestEmptyFrameSequence : public IFrameSequence @@ -728,21 +778,62 @@ ASSERT(callback.addbaDeadline == simTime() + 1); auto requestTransactionId = callback.managementPacket->getTag()->getTransactionId(); auto wrongToken = makeResponse(peer1, 3, request->getDialogToken() + 1, 0); -ASSERT(handler.processReceivedAddbaResp(wrongToken, &originatorPolicy, &callback) == nullptr); +ASSERT(handler.processReceivedAddbaResp(wrongToken, &originatorPolicy, &callback).agreement == nullptr); ASSERT(handler.isAddbaResponsePending(peer1, 3)); auto wrongTid = makeResponse(peer1, 4, request->getDialogToken(), 0); -ASSERT(handler.processReceivedAddbaResp(wrongTid, &originatorPolicy, &callback) == nullptr); +ASSERT(handler.processReceivedAddbaResp(wrongTid, &originatorPolicy, &callback).agreement == nullptr); auto wrongPeer = makeResponse(peer2, 3, request->getDialogToken(), 0); -ASSERT(handler.processReceivedAddbaResp(wrongPeer, &originatorPolicy, &callback) == nullptr); +ASSERT(handler.processReceivedAddbaResp(wrongPeer, &originatorPolicy, &callback).agreement == nullptr); auto success = makeResponse(peer1, 3, request->getDialogToken(), 0); -auto established = handler.processReceivedAddbaResp(success, &originatorPolicy, &callback); +auto establishedResponse = handler.processReceivedAddbaResp(success, &originatorPolicy, &callback); +auto established = establishedResponse.agreement; ASSERT(established != nullptr); ASSERT(callback.cancelledTransactionIds.back() == requestTransactionId); ASSERT(callback.excludedPackets.back() == nullptr); ASSERT(established->getStartingSequenceNumber() == SequenceNumberCyclic(0)); ASSERT(!handler.isAddbaResponsePending(peer1, 3)); -ASSERT(handler.processReceivedAddbaResp(success, &originatorPolicy, &callback) == nullptr); +ASSERT(handler.processReceivedAddbaResp(success, &originatorPolicy, &callback).agreement == nullptr); + +// IEEE Std 802.11-2024, 10.25.2 and Figure 11-32: a successful matching +// response establishes the agreement even when local policy rejects the +// negotiated parameters; the initiator then tears it down with END_BA. +{ + TestOriginatorHandler vetoHandler; + TestCallback vetoCallback; + TestOriginatorPolicy vetoPolicy; + vetoPolicy.addbaAccepted = false; + Packet vetoTrigger("vetoTrigger"); + auto vetoHeader = makeQosHeader(peer3, 6, SequenceNumberCyclic(30)); + vetoHandler.processAcknowledgedDataFrame(&vetoTrigger, vetoHeader, &vetoPolicy, &vetoCallback); + auto vetoRequest = dynamicPtrCast(vetoCallback.managementHeader); + vetoHandler.processTransmittedAddbaReq(vetoCallback.managementPacket, vetoRequest, &vetoPolicy, &vetoCallback); + auto vetoResponse = makeResponse(peer3, 6, vetoRequest->getDialogToken(), 0); + auto vetoResponseOutcome = vetoHandler.processReceivedAddbaResp(vetoResponse, &vetoPolicy, &vetoCallback); + auto vetoAgreement = vetoResponseOutcome.agreement; + ASSERT(vetoAgreement != nullptr); + ASSERT(vetoAgreement->getIsAddbaResponseReceived()); + ASSERT(vetoAgreement->isTeardownPending()); + auto vetoDelba = vetoResponseOutcome.teardownDelba; + ASSERT(vetoDelba != nullptr); + ASSERT(vetoDelba->getInitiator()); + ASSERT(vetoDelba->getReceiverAddress() == peer3); + ASSERT(vetoDelba->getTid() == 6); + ASSERT(vetoDelba->getReasonCode() == RC_END_BA); + auto vetoTerminated = vetoHandler.processTransmittedDelba(vetoDelba, &vetoCallback); + ASSERT(vetoTerminated != nullptr); + ASSERT(vetoTerminated->getIsAddbaResponseReceived()); + ASSERT(vetoHandler.getAgreement(peer3, 6) == nullptr); + ASSERT(vetoHandler.getRetryDeadline(peer3, 6) > simTime()); + TestCallback vetoRetryCallback; + Packet vetoRetryTrigger("vetoRetryTrigger"); + auto vetoRetryHeader = makeQosHeader(peer3, 6, SequenceNumberCyclic(31)); + vetoHandler.processAcknowledgedDataFrame(&vetoRetryTrigger, vetoRetryHeader, &vetoPolicy, &vetoRetryCallback); + ASSERT(vetoRetryCallback.managementPacket == nullptr); + vetoHandler.allowRetryNow(peer3, 6); + vetoHandler.processAcknowledgedDataFrame(&vetoRetryTrigger, vetoRetryHeader, &vetoPolicy, &vetoRetryCallback); + ASSERT(vetoRetryCallback.managementPacket != nullptr); +} Packet rejectedTrigger("rejectedTrigger"); auto rejectedHeader = makeQosHeader(peer2, 4, SequenceNumberCyclic(10)); @@ -750,7 +841,7 @@ handler.processAcknowledgedDataFrame(&rejectedTrigger, rejectedHeader, &originat auto rejectedRequest = dynamicPtrCast(callback.managementHeader); handler.processTransmittedAddbaReq(callback.managementPacket, rejectedRequest, &originatorPolicy, &callback); auto rejection = makeResponse(peer2, 4, rejectedRequest->getDialogToken(), 1); -ASSERT(handler.processReceivedAddbaResp(rejection, &originatorPolicy, &callback) == nullptr); +ASSERT(handler.processReceivedAddbaResp(rejection, &originatorPolicy, &callback).agreement == nullptr); ASSERT(handler.getAgreement(peer2, 4) == nullptr); handler.processAcknowledgedDataFrame(&rejectedTrigger, rejectedHeader, &originatorPolicy, &callback); ASSERT(handler.getAgreement(peer2, 4) == nullptr); @@ -773,7 +864,7 @@ ASSERT(callback.excludedPackets.back() == nullptr); handler.processTransmittedAddbaReq(timedRequestPacket, timedRequest, &originatorPolicy, &callback); ASSERT(handler.getAgreement(peer3, 5) == nullptr); auto staleResponse = makeResponse(peer3, 5, timedRequest->getDialogToken(), 0); -ASSERT(handler.processReceivedAddbaResp(staleResponse, &originatorPolicy, &callback) == nullptr); +ASSERT(handler.processReceivedAddbaResp(staleResponse, &originatorPolicy, &callback).agreement == nullptr); handler.processAcknowledgedDataFrame(&timeoutTrigger, timeoutHeader, &originatorPolicy, &callback); ASSERT(handler.getAgreement(peer3, 5) == nullptr); handler.allowRetryNow(peer3, 5); @@ -854,6 +945,75 @@ Packet unsentTrigger("unsentTrigger"); auto unsentHeader = makeQosHeader(peer2, 5, SequenceNumberCyclic(40)); handler.processAcknowledgedDataFrame(&unsentTrigger, unsentHeader, &originatorPolicy, &callback); ASSERT(handler.isAddbaResponsePending(peer2, 5)); + +// Real observed pending queues treat REMOVED as terminal for an unsent tagged +// ADDBA Request, while DEQUEUED is only ownership transfer. The held same-TID +// data frame becomes eligible exactly once after terminal cleanup. +auto verifyObservedPendingRemoval = [&](queueing::IPacketQueue::PacketRemovalReason removalReason, bool removeAll) { + auto observedHandler = new TestOriginatorHandler(); + TestOriginatorQosMacDataService observedDataService; + observedDataService.setFrameEligibilityFunction([observedHandler](const Packet *packet) { + auto header = packet->peekAtFront(); + if (auto addbaReq = dynamicPtrCast(header)) + return observedHandler->isAddbaRequestPending(packet, addbaReq); + auto dataHeader = dynamicPtrCast(header); + return dataHeader == nullptr || !observedHandler->isAddbaResponsePending(dataHeader->getReceiverAddress(), dataHeader->getTid()); + }); + TestPacketQueue observedQueue; + TestAckHandler observedAckHandler; + TestInProgressFrames observedInProgressFrames; + observedInProgressFrames.configure(&observedDataService, &observedAckHandler, &observedQueue); + TestEdcaf observedEdcaf; + observedEdcaf.pendingQueue = &observedQueue; + observedEdcaf.inProgressFrames = &observedInProgressFrames; + TestEdca observedEdca; + observedEdca.edcaf = &observedEdcaf; + TestHcf observedHcf; + observedHcf.configureEligibilityIndex(&observedEdca, &observedDataService); + auto observedRecipientHandler = new RecipientBlockAckAgreementHandler(); + TestRecipientPolicy observedRecipientPolicy; + observedHcf.configureBlockAckHandlers(observedHandler, &originatorPolicy, observedRecipientHandler, &observedRecipientPolicy); + observedHcf.delegateDroppedSetupHandling = true; + observedHcf.observePendingQueue(&observedQueue); + TestCallback observedCallback; + Packet observedTrigger("observedTrigger"); + auto observedHeader = makeQosHeader(peer2, 6, SequenceNumberCyclic(60)); + observedHandler->processAcknowledgedDataFrame(&observedTrigger, observedHeader, &originatorPolicy, &observedCallback); + auto observedRequestPacket = observedCallback.managementPacket; + auto observedRequest = dynamicPtrCast(observedCallback.managementHeader); + observedCallback.forgetManagementPacket(observedRequestPacket); + auto heldDataPacket = new Packet("heldSameTidData", makeQosHeader(peer2, 6, SequenceNumberCyclic(61))); + observedQueue.enqueuePacket(observedRequestPacket); + observedQueue.enqueuePacket(heldDataPacket); + observedHcf.trackFrame(observedRequestPacket, AC_BK); + observedHcf.trackFrame(heldDataPacket, AC_BK); + ASSERT(observedHcf.getNumEligiblePendingFrames(AC_BK) == 1); + if (removalReason == queueing::IPacketQueue::PacketRemovalReason::DEQUEUED) { + auto dequeuedPacket = observedQueue.dequeuePacket(); + ASSERT(dequeuedPacket == observedRequestPacket); + ASSERT(observedHandler->isAddbaResponsePending(peer2, 6)); + ASSERT(observedHcf.numResumedEligibleChannelAccess == 0); + observedHandler->processDroppedAddbaReq(dequeuedPacket, observedRequest, &originatorPolicy, &observedCallback); + delete dequeuedPacket; + } + else { + if (removeAll) + observedQueue.removeAllPackets(); + else + observedQueue.removePacket(observedRequestPacket); + ASSERT(observedHandler->getAgreement(peer2, 6) == nullptr); + ASSERT(observedHcf.getNumEligiblePendingFrames(AC_BK) == (removeAll ? 0 : 1)); + ASSERT(observedHcf.numResumedEligibleChannelAccess == 1); + if (!removeAll) + observedQueue.removePacket(heldDataPacket); + delete observedRequestPacket; + } + delete heldDataPacket; +}; +verifyObservedPendingRemoval(queueing::IPacketQueue::PacketRemovalReason::REMOVED, false); +verifyObservedPendingRemoval(queueing::IPacketQueue::PacketRemovalReason::REMOVED, true); +verifyObservedPendingRemoval(queueing::IPacketQueue::PacketRemovalReason::DEQUEUED, false); + ASSERT(callback.addbaDeadline == SIMTIME_MAX); auto unsentRequest = dynamicPtrCast(callback.managementHeader); auto unsentRequestPacket = callback.managementPacket; @@ -894,7 +1054,9 @@ ASSERT(callback.excludedPackets.back() == unsentRequestPacket); auto transmittedDelba = makeShared(); transmittedDelba->setReceiverAddress(peer2); transmittedDelba->setTid(2); - transmittedDelbaHandler.processTransmittedDelba(transmittedDelba, &transmittedDelbaCallback); + auto terminatedPendingAgreement = transmittedDelbaHandler.processTransmittedDelba(transmittedDelba, &transmittedDelbaCallback); + ASSERT(terminatedPendingAgreement != nullptr); + ASSERT(terminatedPendingAgreement->isPending()); if (transmittedDelbaHandler.getAgreement(peer2, 2) != nullptr || transmittedDelbaCallback.cancelledTransactionIds.size() != 1 || transmittedDelbaCallback.cancelledTransactionIds.back() != transmittedDelbaTransactionId) throw cRuntimeError("Transmitted DELBA did not cancel the pending ADDBA transaction"); ASSERT(transmittedDelbaHandler.getAgreement(peer2, 2) == nullptr); @@ -1228,9 +1390,170 @@ ASSERT(immediateRecipientHandler.getAgreement(6, peer1) == replacementImmediateA hcf.processReceivedManagementFrame(rejectedDelba); ASSERT(deletionListener.numSignals == 1); ASSERT(originatorHandler->getAgreement(peer3, 5) != nullptr); + + QosAckHandler transmittedOriginatorAckHandler; + TestEdcaf transmittedOriginatorEdcaf; + transmittedOriginatorEdcaf.qosAckHandler = &transmittedOriginatorAckHandler; + TestPacketQueue transmittedOriginatorPendingQueue; + TestInProgressFrames transmittedOriginatorInProgressFrames; + TestOriginatorQosMacDataService transmittedOriginatorDataService; + transmittedOriginatorInProgressFrames.configure(&transmittedOriginatorDataService, &transmittedOriginatorAckHandler, &transmittedOriginatorPendingQueue); + transmittedOriginatorEdcaf.pendingQueue = &transmittedOriginatorPendingQueue; + transmittedOriginatorEdcaf.inProgressFrames = &transmittedOriginatorInProgressFrames; + TestEdca transmittedOriginatorEdca; + transmittedOriginatorEdca.edcaf = &transmittedOriginatorEdcaf; + OriginatorQosAckPolicy transmittedOriginatorAckPolicy; + hcf.configureTransmittedManagement(&transmittedOriginatorEdca, &transmittedOriginatorAckPolicy); + originatorHandler->addEstablishedAgreement(peer3, 6); + auto transmittedEstablishedDelba = makeShared(); + transmittedEstablishedDelba->setReceiverAddress(peer3); + transmittedEstablishedDelba->setTid(6); + transmittedEstablishedDelba->setInitiator(true); + transmittedEstablishedDelba->setSequenceNumber(SequenceNumberCyclic(201)); + Packet transmittedEstablishedDelbaPacket("transmittedEstablishedOriginatorDelba", transmittedEstablishedDelba); + hcf.processTransmittedManagement(&transmittedEstablishedDelbaPacket, transmittedEstablishedDelba, AC_BE); + ASSERT(deletionListener.numSignals == 2); + ASSERT(originatorHandler->getAgreement(peer3, 6) == nullptr); + hcf.processTransmittedManagement(&transmittedEstablishedDelbaPacket, transmittedEstablishedDelba, AC_BE); + ASSERT(deletionListener.numSignals == 2); + + TestCallback pendingDelbaCallback; + Packet pendingDelbaTrigger("pendingDelbaTrigger"); + auto pendingDelbaTriggerHeader = makeQosHeader(peer3, 7, SequenceNumberCyclic(0)); + originatorHandler->processAcknowledgedDataFrame(&pendingDelbaTrigger, pendingDelbaTriggerHeader, &delbaPolicy, &pendingDelbaCallback); + ASSERT(originatorHandler->isAddbaResponsePending(peer3, 7)); + auto transmittedPendingDelba = makeShared(); + transmittedPendingDelba->setReceiverAddress(peer3); + transmittedPendingDelba->setTid(7); + transmittedPendingDelba->setInitiator(true); + transmittedPendingDelba->setSequenceNumber(SequenceNumberCyclic(202)); + Packet transmittedPendingDelbaPacket("transmittedPendingOriginatorDelba", transmittedPendingDelba); + hcf.processTransmittedManagement(&transmittedPendingDelbaPacket, transmittedPendingDelba, AC_BE); + ASSERT(deletionListener.numSignals == 2); + ASSERT(originatorHandler->getAgreement(peer3, 7) == nullptr); hcf.unsubscribe(Hcf::blockAckAgreementDeletedSignal, &deletionListener); } +// IEEE Std 802.11-2024, 11.5.2.2 and 11.5.3.2: a received DELBA can cancel +// an originator transaction before ADDBA Response success; that pending state +// was never observable as an active agreement and must not emit Deleted. +{ + auto originatorHandler = new TestOriginatorHandler(); + auto recipientHandler = new RecipientBlockAckAgreementHandler(); + TestOriginatorPolicy pendingPolicy; + TestRecipientPolicy pendingRecipientPolicy; + TestOriginatorQosMacDataService pendingDataService; + pendingDataService.setFrameEligibilityFunction([originatorHandler](const Packet *packet) { + auto header = packet->peekAtFront(); + if (auto addbaReq = dynamicPtrCast(header)) + return originatorHandler->isAddbaRequestPending(packet, addbaReq); + auto dataHeader = dynamicPtrCast(header); + return dataHeader == nullptr || !originatorHandler->isAddbaResponsePending(dataHeader->getReceiverAddress(), dataHeader->getTid()); + }); + TestPacketQueue pendingQueue; + QosAckHandler pendingAckHandler; + TestInProgressFrames pendingInProgressFrames; + pendingInProgressFrames.configure(&pendingDataService, &pendingAckHandler, &pendingQueue); + TestEdcaf pendingEdcaf; + pendingEdcaf.pendingQueue = &pendingQueue; + pendingEdcaf.inProgressFrames = &pendingInProgressFrames; + pendingEdcaf.qosAckHandler = &pendingAckHandler; + TestEdca pendingEdca; + pendingEdca.edcaf = &pendingEdcaf; + TestHcf pendingHcf; + pendingHcf.configureBlockAckHandlers(originatorHandler, &pendingPolicy, recipientHandler, &pendingRecipientPolicy); + pendingHcf.configureEligibilityIndex(&pendingEdca, &pendingDataService); + pendingHcf.observePendingQueue(&pendingQueue); + TestCallback pendingCallback; + Packet pendingTrigger("receivedPendingDelbaTrigger"); + auto pendingHeader = makeQosHeader(peer3, 8, SequenceNumberCyclic(203)); + originatorHandler->processAcknowledgedDataFrame(&pendingTrigger, pendingHeader, &pendingPolicy, &pendingCallback); + auto pendingRequestPacket = pendingCallback.managementPacket; + auto pendingRequest = dynamicPtrCast(pendingCallback.managementHeader); + pendingCallback.forgetManagementPacket(pendingRequestPacket); + auto heldPacket = new Packet("receivedPendingDelbaHeld", makeQosHeader(peer3, 8, SequenceNumberCyclic(204))); + pendingQueue.enqueuePacket(pendingRequestPacket); + pendingQueue.enqueuePacket(heldPacket); + pendingHcf.trackFrame(pendingRequestPacket, AC_BK); + pendingHcf.trackFrame(heldPacket, AC_BK); + ASSERT(originatorHandler->isAddbaResponsePending(peer3, 8)); + ASSERT(pendingHcf.getNumEligiblePendingFrames(AC_BK) == 1); + TestSignalListener pendingDeletionListener; + pendingHcf.subscribe(Hcf::blockAckAgreementDeletedSignal, &pendingDeletionListener); + auto receivedPendingDelba = makeShared(); + receivedPendingDelba->setTransmitterAddress(peer3); + receivedPendingDelba->setReceiverAddress(peer1); + receivedPendingDelba->setTid(8); + receivedPendingDelba->setInitiator(false); + pendingHcf.processReceivedManagementFrame(receivedPendingDelba); + ASSERT(pendingDeletionListener.numSignals == 0); + ASSERT(originatorHandler->getAgreement(peer3, 8) == nullptr); + ASSERT(pendingHcf.numCancelledAddbaTransactions == 1); + ASSERT(pendingHcf.numRebuildEligibilityCalls > 0); + ASSERT(pendingQueue.getNumPackets() == 1); + ASSERT(pendingQueue.getPacket(0) == heldPacket); + ASSERT(pendingDataService.isFrameEligible(heldPacket)); + ASSERT(pendingHcf.getNumEligiblePendingFrames(AC_BK) == 1); + ASSERT(pendingHcf.numResumedEligibleChannelAccess == 1); + pendingHcf.unsubscribe(Hcf::blockAckAgreementDeletedSignal, &pendingDeletionListener); + pendingQueue.removePacket(heldPacket); + delete heldPacket; +} + +// A locally vetoed successful response emits Added before its initiator DELBA +// enters the queue. IEEE 802.11 teardown is terminal if that frame is dropped +// or removed synchronously, and the transition is idempotent. +for (auto removalReason : { queueing::IPacketQueue::PacketRemovalReason::DROPPED, queueing::IPacketQueue::PacketRemovalReason::REMOVED }) { + TestImmediateRemovalQueue teardownQueue; + teardownQueue.removalReason = removalReason; + TestOriginatorQosMacDataService teardownDataService; + teardownDataService.setFrameEligibilityFunction([](const Packet *) { return true; }); + QosAckHandler teardownAckHandler; + TestInProgressFrames teardownInProgressFrames; + teardownInProgressFrames.configure(&teardownDataService, &teardownAckHandler, &teardownQueue); + TestEdcaf teardownEdcaf; + teardownEdcaf.pendingQueue = &teardownQueue; + teardownEdcaf.inProgressFrames = &teardownInProgressFrames; + teardownEdcaf.qosAckHandler = &teardownAckHandler; + TestEdca teardownEdca; + teardownEdca.edcaf = &teardownEdcaf; + auto teardownOriginatorHandler = new TestOriginatorHandler(); + auto teardownRecipientHandler = new RecipientBlockAckAgreementHandler(); + TestOriginatorPolicy teardownOriginatorPolicy; + teardownOriginatorPolicy.addbaAccepted = false; + TestRecipientPolicy teardownRecipientPolicy; + TestHcf teardownHcf; + teardownHcf.configureBlockAckHandlers(teardownOriginatorHandler, &teardownOriginatorPolicy, teardownRecipientHandler, &teardownRecipientPolicy); + teardownHcf.configureEligibilityIndex(&teardownEdca, &teardownDataService); + teardownHcf.observePendingQueue(&teardownQueue); + TestCallback teardownCallback; + Packet teardownTrigger("teardownTrigger"); + auto teardownTriggerHeader = makeQosHeader(peer1, removalReason == queueing::IPacketQueue::PacketRemovalReason::DROPPED ? 8 : 9, SequenceNumberCyclic(0)); + teardownOriginatorHandler->processAcknowledgedDataFrame(&teardownTrigger, teardownTriggerHeader, &teardownOriginatorPolicy, &teardownCallback); + auto teardownRequest = dynamicPtrCast(teardownCallback.managementHeader); + teardownOriginatorHandler->processTransmittedAddbaReq(teardownCallback.managementPacket, teardownRequest, &teardownOriginatorPolicy, &teardownCallback); + auto teardownResponse = makeResponse(peer1, teardownTriggerHeader->getTid(), teardownRequest->getDialogToken(), 0); + TestSignalListener addedListener; + TestSignalListener deletedListener; + teardownHcf.subscribe(Hcf::blockAckAgreementAddedSignal, &addedListener); + teardownHcf.subscribe(Hcf::blockAckAgreementDeletedSignal, &deletedListener); + teardownHcf.processReceivedManagementFrame(teardownResponse); + ASSERT(addedListener.numSignals == 1); + ASSERT(deletedListener.numSignals == 1); + ASSERT(teardownOriginatorHandler->getAgreement(peer1, teardownTriggerHeader->getTid()) == nullptr); + ASSERT(teardownHcf.getNumTrackedPendingFrames() == 0); + TestCallback teardownRetryCallback; + Packet teardownRetryTrigger("teardownRetryTrigger"); + auto teardownRetryHeader = makeQosHeader(peer1, teardownTriggerHeader->getTid(), SequenceNumberCyclic(1)); + teardownOriginatorHandler->processAcknowledgedDataFrame(&teardownRetryTrigger, teardownRetryHeader, &teardownOriginatorPolicy, &teardownRetryCallback); + ASSERT(teardownRetryCallback.managementPacket == nullptr); + teardownOriginatorHandler->allowRetryNow(peer1, teardownTriggerHeader->getTid()); + teardownOriginatorHandler->processAcknowledgedDataFrame(&teardownRetryTrigger, teardownRetryHeader, &teardownOriginatorPolicy, &teardownRetryCallback); + ASSERT(teardownRetryCallback.managementPacket != nullptr); + teardownHcf.unsubscribe(Hcf::blockAckAgreementAddedSignal, &addedListener); + teardownHcf.unsubscribe(Hcf::blockAckAgreementDeletedSignal, &deletedListener); +} + OriginatorQosMacDataService dataService; dataService.setFrameEligibilityFunction([&handler](const Packet *packet) { auto header = dynamicPtrCast(packet->peekAtFront()); @@ -1374,6 +1697,133 @@ ASSERT(!dataService.hasEligibleFrame(&mixedQueue)); delete packet; } +// IEEE Std 802.11-2024, 10.23.2.4: after the current owner releases the +// channel, Hcf re-arms every AC that has an eligible frame, and no idle AC. +{ + TestOriginatorQosMacDataService armingDataService; + armingDataService.setFrameEligibilityFunction([](const Packet *) { return true; }); + TestAckHandler armingAckHandler; + TestPacketQueue armingPendingQueues[AC_NUMCATEGORIES]; + TestInProgressFrames armingInProgressFrames[AC_NUMCATEGORIES]; + TestEdcaf armingEdcafs[AC_NUMCATEGORIES]; + TestEdca armingEdca; + armingEdca.numEdcafs = AC_NUMCATEGORIES; + armingEdca.edcaf = nullptr; + for (int ac = 0; ac < AC_NUMCATEGORIES; ac++) { + armingInProgressFrames[ac].configure(&armingDataService, &armingAckHandler, &armingPendingQueues[ac]); + armingEdcafs[ac].pendingQueue = &armingPendingQueues[ac]; + armingEdcafs[ac].inProgressFrames = &armingInProgressFrames[ac]; + armingEdca.edcafs[ac] = &armingEdcafs[ac]; + } + auto backgroundFrame = new Packet("armingBackgroundFrame", makeQosHeader(peer1, 1, SequenceNumberCyclic(1))); + auto voiceFrame = new Packet("armingVoiceFrame", makeQosHeader(peer1, 2, SequenceNumberCyclic(1))); + armingInProgressFrames[AC_BK].addOwnedFrame(backgroundFrame); + armingInProgressFrames[AC_VO].addOwnedFrame(voiceFrame); + TestHcf armingHcf; + armingHcf.configureEligibilityIndex(&armingEdca, &armingDataService); + armingHcf.requestEligible(); + ASSERT(armingEdca.requestedAccessCategories.size() == 2); + ASSERT(armingEdca.requestedAccessCategories[0] == AC_BK); + ASSERT(armingEdca.requestedAccessCategories[1] == AC_VO); + auto releasedBackground = armingInProgressFrames[AC_BK].releaseFrames(); + auto releasedVoice = armingInProgressFrames[AC_VO].releaseFrames(); + for (auto frame : releasedBackground) + delete frame; + for (auto frame : releasedVoice) + delete frame; +} + +// The frame-sequence boundary releases the owner and ends its TXOP before +// re-arming eligible ACs; an already-active AC receives the callback without +// restarting contention. +{ + TestOriginatorQosMacDataService sequenceDataService; + sequenceDataService.setFrameEligibilityFunction([](const Packet *) { return true; }); + TestAckHandler sequenceAckHandler; + TestPacketQueue sequencePendingQueues[AC_NUMCATEGORIES]; + TestInProgressFrames sequenceInProgressFrames[AC_NUMCATEGORIES]; + TestEdcaf sequenceEdcafs[AC_NUMCATEGORIES]; + TestEdca sequenceEdca; + sequenceEdca.numEdcafs = AC_NUMCATEGORIES; + for (int ac = 0; ac < AC_NUMCATEGORIES; ac++) { + sequenceInProgressFrames[ac].configure(&sequenceDataService, &sequenceAckHandler, &sequencePendingQueues[ac]); + sequenceEdcafs[ac].pendingQueue = &sequencePendingQueues[ac]; + sequenceEdcafs[ac].inProgressFrames = &sequenceInProgressFrames[ac]; + sequenceEdcafs[ac].setAccessCategory(AccessCategory(ac)); + sequenceEdca.edcafs[ac] = &sequenceEdcafs[ac]; + } + sequenceEdca.edcaf = &sequenceEdcafs[AC_BE]; + sequenceInProgressFrames[AC_BK].addFrame(new Packet("sequenceBackgroundFrame", makeQosHeader(peer1, 1, SequenceNumberCyclic(70)))); + sequenceInProgressFrames[AC_VO].addFrame(new Packet("sequenceVoiceFrame", makeQosHeader(peer1, 2, SequenceNumberCyclic(71)))); + sequenceEdcafs[AC_VO].contentionActive = true; + std::vector eventOrder; + sequenceEdcafs[AC_BE].eventOrder = &eventOrder; + TestTxopProcedure sequenceTxop; + sequenceTxop.eventOrder = &eventOrder; + sequenceEdcafs[AC_BE].setTxopProcedure(&sequenceTxop); + sequenceEdca.eventOrder = &eventOrder; + TestHcf sequenceHcf; + sequenceHcf.configureEligibilityIndex(&sequenceEdca, &sequenceDataService); + auto sequenceHandler = new TestFrameSequenceHandler(); + sequenceHcf.configureFrameSequenceHandler(sequenceHandler); + TestMac sequenceMac; + sequenceHcf.configureMac(&sequenceMac); + sequenceHcf.finishSequence(); + ASSERT(eventOrder == std::vector({ 1, 2, 3, 3 })); + ASSERT(sequenceEdca.requestedAccessCategories.size() == 2); + ASSERT(sequenceEdca.requestedAccessCategories[0] == AC_BK); + ASSERT(sequenceEdca.requestedAccessCategories[1] == AC_VO); + ASSERT(sequenceEdcafs[AC_VO].numRequestChannelCalls == 1); + ASSERT(sequenceEdcafs[AC_VO].numContentionStarts == 0); + auto releasedBackground = sequenceInProgressFrames[AC_BK].releaseFrames(); + auto releasedVoice = sequenceInProgressFrames[AC_VO].releaseFrames(); + for (auto frame : releasedBackground) + delete frame; + for (auto frame : releasedVoice) + delete frame; +} + +// IEEE Std 802.11-2024, 10.23.2.4: a collided AC with no eligible frame is +// not a handled collision and must not mutate recovery state or be counted. +{ + TestEdcaf emptyCollidedEdcaf; + emptyCollidedEdcaf.setAccessCategory(AC_BK); + TestInProgressFrames emptyInProgressFrames; + TestOriginatorQosMacDataService emptyDataService; + TestAckHandler emptyAckHandler; + TestPacketQueue emptyPendingQueue; + emptyInProgressFrames.configure(&emptyDataService, &emptyAckHandler, &emptyPendingQueue); + emptyCollidedEdcaf.inProgressFrames = &emptyInProgressFrames; + TestHcf collisionHcf; + ASSERT(collisionHcf.processInternalCollision({ &emptyCollidedEdcaf }) == 0); +} + +// A channel grant whose collision-controller vector contains only an AC with +// no eligible frame releases the channel without opening a TXOP or emitting a +// phantom collision statistic. +{ + TestPacketQueue grantedPendingQueue; + TestOriginatorQosMacDataService grantedDataService; + TestAckHandler grantedAckHandler; + TestInProgressFrames grantedInProgressFrames; + grantedInProgressFrames.configure(&grantedDataService, &grantedAckHandler, &grantedPendingQueue); + TestEdcaf grantedEdcaf; + grantedEdcaf.setAccessCategory(AC_BE); + grantedEdcaf.pendingQueue = &grantedPendingQueue; + grantedEdcaf.inProgressFrames = &grantedInProgressFrames; + TestEdca grantedEdca; + grantedEdca.edcaf = &grantedEdcaf; + grantedEdca.internallyCollidedEdcafs = { &grantedEdcaf }; + TestHcf grantedHcf; + grantedHcf.configure(&grantedEdca); + TestSignalListener collisionListener; + grantedHcf.subscribe(Hcf::edcaCollisionDetectedSignal, &collisionListener); + grantedHcf.grantChannel(&grantedEdcaf); + ASSERT(grantedEdcaf.numReleaseChannelCalls == 1); + ASSERT(collisionListener.numSignals == 0); + grantedHcf.unsubscribe(Hcf::edcaCollisionDetectedSignal, &collisionListener); +} + // A direct IPacketQueue implementation without OMNeT++ signals cannot leave // the index stale because ownership departures use the typed queue callback. { @@ -1978,6 +2428,12 @@ delete compoundPacket; droppedSetupHcf.dropPacket(&droppedSetupPacket); ASSERT(droppedSetupHcf.numProcessedDroppedSetupFrames == 2); ASSERT(droppedSetupHcf.numResumedEligibleChannelAccess == 1); + droppedSetupHcf.removePacket(&droppedSetupPacket); + ASSERT(droppedSetupHcf.numProcessedDroppedSetupFrames == 3); + ASSERT(droppedSetupHcf.numResumedEligibleChannelAccess == 2); + droppedSetupHcf.dequeuePacket(&droppedSetupPacket); + ASSERT(droppedSetupHcf.numProcessedDroppedSetupFrames == 3); + ASSERT(droppedSetupHcf.numResumedEligibleChannelAccess == 2); } // The typed queue callback covers every non-destructive ownership departure, From b73b55a8902cc072a64392e45fed87ff88a380d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Wed, 19 Aug 2026 12:19:15 +0200 Subject: [PATCH 08/14] Fix scheduler capabilities and QoS teardown handling Keep nullable packet collection and extractor capabilities in WrrScheduler, LabelScheduler, and PriorityScheduler. Validate aggregate and predicate operations lazily and consistently so ordinary passive sources remain valid scheduler inputs. Make A-MSDU selection candidate-aware across flow modules and non-order-preserving schedulers. Remove selected members through exact predicate dequeues, preserve provider accounting, and revalidate aggregation-critical frame fields before building the aggregate. Immediately roll back successful ADDBA agreements rejected by local policy while retaining Normal Ack data service. Track best-effort DELBAs by transaction, reject stale generations, handle fragmented transmit and abort lifecycles, and cancel obsolete queued or in-progress teardown packets before replacement setup. Document the updated scheduler and aggregation contracts, update WHATSNEW, and add focused regression coverage for scheduler capabilities, flow and reverse-priority aggregation, agreement signal ordering, stale teardown disposal, fragmentation, and queue cleanup. --- WHATSNEW | 28 +- doc/src/users-guide/ch-diffserv.rst | 9 +- .../aggregation/BasicMsduAggregationPolicy.cc | 50 ++- .../aggregation/BasicMsduAggregationPolicy.h | 2 +- .../blockack/OriginatorBlockAckAgreement.h | 3 - .../OriginatorBlockAckAgreementHandler.cc | 72 +++- .../OriginatorBlockAckAgreementHandler.h | 6 +- .../mac/contract/IMsduAggregationPolicy.h | 7 +- .../IOriginatorBlockAckAgreementHandler.h | 9 +- .../ieee80211/mac/coordinationfunction/Hcf.cc | 19 +- .../originator/OriginatorQosMacDataService.cc | 43 ++- .../originator/OriginatorQosMacDataService.h | 2 +- src/inet/queueing/base/PacketFlowBase.h | 4 - src/inet/queueing/contract/IPacketExtractor.h | 7 - .../queueing/queue/CompoundPacketQueueBase.h | 1 - src/inet/queueing/queue/PacketQueue.h | 1 - src/inet/queueing/scheduler/LabelScheduler.cc | 40 ++- .../queueing/scheduler/PriorityScheduler.cc | 61 ++-- .../queueing/scheduler/PriorityScheduler.h | 1 - src/inet/queueing/scheduler/WrrScheduler.cc | 38 +- tests/unit/Ieee80211AddbaTransaction_1.test | 336 +++++++++++++++--- 21 files changed, 560 insertions(+), 179 deletions(-) diff --git a/WHATSNEW b/WHATSNEW index d130a1f3e31..94556e59e6c 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -132,9 +132,14 @@ Notable backward incompatible changes are the following: implementations of IPacketExtractor must replace selected-pointer extraction with findPacket(predicate) and dequeuePacket(predicate); predicates may be evaluated repeatedly and must be stable and side-effect free throughout one - logical selection. Extractors whose IPacketCollection enumeration order is also - their scheduling order may override isPacketOrderPreserved(); the conservative - default is false. + logical selection. WrrScheduler, LabelScheduler, and PriorityScheduler accept + ordinary IPassivePacketSource inputs; they require IPacketCollection only when + collection/aggregate access is used and IPacketExtractor only when predicate + extraction is used, reporting the unsupported operation lazily. + A-MSDU policies now receive the provider-selected anchor and frame-eligibility + predicate. Additional policy-selected members are removed through exact + predicate dequeues, preserving scheduler/flow accounting without requiring + collection enumeration order to match scheduling order. IPacketBuffer::ICallback also provides an optional handlePacketDropped() notification. Shared buffers invoke it only after all victims selected by one overload operation have been detached from their owning queues. @@ -146,10 +151,19 @@ Notable backward incompatible changes are the following: IRecipientBlockAckAgreementHandler::processReceivedDelba() now return the terminated agreement as a unique_ptr. Originator ADDBA response processing now returns a typed outcome containing the established agreement and, when - local policy vetoes a successful response, a deferred initiator DELBA for - Hcf to enqueue after emitting the Added signal. Custom handler - implementations and callers must adopt the new return types; a null result - means the DELBA did not remove an agreement. + local policy vetoes a successful response, its immediately terminated local + agreement and a best-effort initiator DELBA for Hcf to enqueue after emitting + the Added and Deleted signals. Such DELBAs carry the ADDBA transaction identity + and ordinary data continues with Normal Ack. They remain eligible through the + final fragment; aborting one fragment cancels its siblings. After retry backoff, + a replacement ADDBA setup invalidates an older queued DELBA, so an unreported + disposal cannot suppress setup indefinitely and a delayed frame cannot + terminate a newer peer/TID agreement. The replacement setup reports that + obsolete identity so Hcf also removes all of its queued or in-progress packets. + Originator DELBA transmission handlers now receive the full Packet so this + identity is retained. Custom handler implementations and callers must adopt + the new signatures and return types; a null result means the DELBA did not + remove an agreement. The originator policy parameter `addbaFailureTimeout` was renamed to `addbaResponseTimeout`; update custom configurations accordingly. diff --git a/doc/src/users-guide/ch-diffserv.rst b/doc/src/users-guide/ch-diffserv.rst index 03302b6e30c..ac601d0d30e 100644 --- a/doc/src/users-guide/ch-diffserv.rst +++ b/doc/src/users-guide/ch-diffserv.rst @@ -282,9 +282,11 @@ interface is ready to transmit one. They have several input gates and one output gate. Modules that are connected to the inputs of a scheduler must implement -the :cpp:`IPacketQueue` C++ interface. Schedulers also implement the -:cpp:`IPacketQueue` interface, so they can be cascaded to other -schedulers and used as the output module of :ned:`IPacketQueue`'s. +the :cpp:`IPassivePacketSource` C++ interface. Collection and predicate +extraction operations additionally require the corresponding input provider +to implement :cpp:`IPacketCollection` and :cpp:`IPacketExtractor`, respectively. +Schedulers can be cascaded and used as the output module of compound packet +queues when those additional interfaces are available. There are several possible scheduling disciplines (first come/first served, priority, weighted fair, weighted round-robin, deadline-based, @@ -589,4 +591,3 @@ implement three different drop priorities within the class. BE packets are stored in a drop tail queue. Packets from AFxy and BE queues are scheduled by a WRR scheduler, which ensures that the remaining bandwidth is allocated among the classes according to the specified weights. - diff --git a/src/inet/linklayer/ieee80211/mac/aggregation/BasicMsduAggregationPolicy.cc b/src/inet/linklayer/ieee80211/mac/aggregation/BasicMsduAggregationPolicy.cc index 2d06484c6a4..1f667253f6b 100644 --- a/src/inet/linklayer/ieee80211/mac/aggregation/BasicMsduAggregationPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/aggregation/BasicMsduAggregationPolicy.cc @@ -56,40 +56,52 @@ bool BasicMsduAggregationPolicy::isEligible(Packet *packet, const Ptr *BasicMsduAggregationPolicy::computeAggregateFrames(queueing::IPacketQueue *queue) +std::vector *BasicMsduAggregationPolicy::computeAggregateFrames(queueing::IPacketQueue *queue, Packet *candidate, const std::function& isFrameEligible) { Enter_Method("computeAggregateFrames"); - ASSERT(!queue->isEmpty()); + ASSERT(candidate != nullptr); + ASSERT(queue->findPacket([candidate](const Packet *packet) { return packet == candidate; }) == candidate); b aMsduLength = b(0); - Ptr firstHeader = nullptr; - auto frames = new std::vector(); + int candidateIndex = -1; for (int i = 0; i < queue->getNumPackets(); i++) { - auto dataPacket = queue->getPacket(i); + if (queue->getPacket(i) == candidate) { candidateIndex = i; break; } + } + if (candidateIndex == -1) + return nullptr; + const auto& firstHeader = dynamicPtrCast(candidate->peekAtFront()); + if (firstHeader == nullptr || !isFrameEligible(candidate)) + return nullptr; + auto frames = new std::vector(); + auto appendIfEligible = [&](Packet *dataPacket) { + if (!isFrameEligible(dataPacket)) + return false; const auto& dataHeader = dynamicPtrCast(dataPacket->peekAtFront()); if (dataHeader == nullptr) - break; - if (firstHeader == nullptr) - firstHeader = dataHeader; + return false; const auto& dataTrailer = dataPacket->peekAtBack(B(4)); - if (!isEligible(dataPacket, staticPtrCast(dataHeader), dataTrailer, firstHeader, aMsduLength)) { - EV_TRACE << "Queued " << *dataPacket << " is not eligible for A-MSDU aggregation.\n"; - break; - } - EV_TRACE << "Queued " << *dataPacket << " is eligible for A-MSDU aggregation.\n"; + if (!isEligible(dataPacket, dataHeader, dataTrailer, firstHeader, aMsduLength)) + return false; frames->push_back(dataPacket); - aMsduLength += dataPacket->getDataLength() - dataHeader->getChunkLength() - dataTrailer->getChunkLength() + b(LENGTH_A_MSDU_SUBFRAME_HEADER); // sum of MSDU lengths + subframe header + aMsduLength += dataPacket->getDataLength() - dataHeader->getChunkLength() - dataTrailer->getChunkLength() + b(LENGTH_A_MSDU_SUBFRAME_HEADER); + return true; + }; + if (!appendIfEligible(candidate)) { + delete frames; + return nullptr; + } + int numPackets = queue->getNumPackets(); + for (int offset = 1; offset < numPackets; offset++) { + auto dataPacket = queue->getPacket((candidateIndex + offset) % numPackets); + appendIfEligible(dataPacket); } if (frames->size() <= 1 || !isAggregationPossible(frames->size(), aMsduLength.get())) { EV_DEBUG << "A-MSDU aggregation is not possible, collected " << frames->size() << " packets.\n"; delete frames; return nullptr; } - else { - EV_DEBUG << "A-MSDU aggregation is possible, collected " << frames->size() << " packets.\n"; - return frames; - } + EV_DEBUG << "A-MSDU aggregation is possible, collected " << frames->size() << " packets.\n"; + return frames; } } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/src/inet/linklayer/ieee80211/mac/aggregation/BasicMsduAggregationPolicy.h b/src/inet/linklayer/ieee80211/mac/aggregation/BasicMsduAggregationPolicy.h index 0534914eaf4..22ddab978ea 100644 --- a/src/inet/linklayer/ieee80211/mac/aggregation/BasicMsduAggregationPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/aggregation/BasicMsduAggregationPolicy.h @@ -28,7 +28,7 @@ class INET_API BasicMsduAggregationPolicy : public IMsduAggregationPolicy, publi virtual bool isEligible(Packet *packet, const Ptr& header, const Ptr& trailer, const Ptr& testHeader, b aMsduLength); public: - virtual std::vector *computeAggregateFrames(queueing::IPacketQueue *queue) override; + virtual std::vector *computeAggregateFrames(queueing::IPacketQueue *queue, Packet *candidate, const std::function& isFrameEligible) override; }; } /* namespace ieee80211 */ diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h index 89ad4021dc8..8099d7c1bdb 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h @@ -38,7 +38,6 @@ class INET_API OriginatorBlockAckAgreement : public cObject simtime_t addbaResponseDeadline = -1; simtime_t blockAckTimeoutValue = -1; simtime_t expirationTime = -1; - bool teardownPending = false; public: OriginatorBlockAckAgreement(MacAddress receiverAddr, Tid tid, SequenceNumberCyclic startingSequenceNumber, int bufferSize, bool isAMsduSupported, bool isDelayedBlockAckPolicySupported, uint8_t dialogToken, uint64_t transactionId) : @@ -78,12 +77,10 @@ class INET_API OriginatorBlockAckAgreement : public cObject virtual void setIsDelayedBlockAckPolicySupported(bool isDelayedBlockAckPolicySupported) { this->isDelayedBlockAckPolicySupported = isDelayedBlockAckPolicySupported; } virtual void setBlockAckTimeoutValue(const simtime_t blockAckTimeoutValue) { this->blockAckTimeoutValue = blockAckTimeoutValue; } virtual void setAddbaResponseDeadline(simtime_t addbaResponseDeadline) { this->addbaResponseDeadline = addbaResponseDeadline; } - virtual void setTeardownPending(bool teardownPending) { this->teardownPending = teardownPending; } virtual void baPolicyFrameSent() { numSentBaPolicyFrames++; } virtual void calculateExpirationTime() { expirationTime = blockAckTimeoutValue == 0 ? SIMTIME_MAX : simTime() + blockAckTimeoutValue; } virtual simtime_t getExpirationTime() { return expirationTime; } - virtual bool isTeardownPending() const { return teardownPending; } }; } /* namespace ieee80211 */ diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc index ff0d20d49be..37b8ab47a84 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc @@ -100,7 +100,7 @@ void OriginatorBlockAckAgreementHandler::blockAckAgreementExpired(IProcedureCall simtime_t now = simTime(); for (auto id : blockAckAgreements) { auto agreement = id.second; - if (agreement->getExpirationTime() == now && !agreement->isTeardownPending()) { + if (agreement->getExpirationTime() == now) { MacAddress receiverAddr = id.first.first; Tid tid = id.first.second; const auto& delba = buildDelba(receiverAddr, tid, 39); @@ -199,22 +199,31 @@ void OriginatorBlockAckAgreementHandler::terminateAgreement(MacAddress originato delete removeAgreement(originatorAddr, tid); } -void OriginatorBlockAckAgreementHandler::processAcknowledgedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback) +uint64_t OriginatorBlockAckAgreementHandler::processAcknowledgedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback) { // IEEE Std 802.11-2024, Table 9-466: the Starting Sequence Number identifies // the first or next MSDU/A-MSDU sent under the agreement. Wait until the // final fragment is acknowledged so no remaining fragment precedes the SSN. if (dataHeader->getMoreFragments()) - return; + return 0; auto receiverAddr = dataHeader->getReceiverAddress(); auto tid = dataHeader->getTid(); auto agreementId = std::make_pair(receiverAddr, tid); auto agreement = getAgreement(receiverAddr, tid); auto retryIt = addbaRetryDeadlines.find(agreementId); bool retryAllowed = retryIt == addbaRetryDeadlines.end() || retryIt->second <= simTime(); + uint64_t obsoleteTeardownTransactionId = 0; if (blockAckAgreementPolicy->isAddbaReqNeeded(packet, dataHeader) && agreement == nullptr && retryAllowed) { if (retryIt != addbaRetryDeadlines.end()) addbaRetryDeadlines.erase(retryIt); + // A replacement agreement makes an older best-effort DELBA stale. Drop + // its eligibility instead of waiting indefinitely for a queue callback + // that every custom packet provider may not deliver. + auto teardownIt = pendingTeardownTransactionIds.find(agreementId); + if (teardownIt != pendingTeardownTransactionIds.end()) { + obsoleteTeardownTransactionId = teardownIt->second; + pendingTeardownTransactionIds.erase(teardownIt); + } // IEEE Std 802.11-2024, 10.25.2 and 11.5.2.2: Normal Ack data is // permitted before an agreement exists, and the requested SSN starts // after the acknowledged trigger MPDU. @@ -225,6 +234,7 @@ void OriginatorBlockAckAgreementHandler::processAcknowledgedDataFrame(Packet *pa addbaPacket->addTag()->setTransactionId(transactionId); procedureCallback->processMgmtFrame(addbaPacket, addbaReq); } + return obsoleteTeardownTransactionId; } OriginatorBlockAckAgreementResponse OriginatorBlockAckAgreementHandler::processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) @@ -247,14 +257,17 @@ OriginatorBlockAckAgreementResponse OriginatorBlockAckAgreementHandler::processR scheduleAddbaResponseTimer(callback); callback->cancelAddbaTransaction(transactionId, nullptr); if (!acceptedByLocalPolicy) { - // IEEE Std 802.11-2024, 10.25.2 and Figure 11-32: SUCCESS - // establishes the peer agreement. If local policy rejects the - // accepted parameters, tear it down with an initiator DELBA; - // retain the established state until that DELBA is transmitted. - agreement->setTeardownPending(true); + // IEEE Std 802.11-2024, 10.25.2 Note 3: delete a successful + // agreement rejected by local policy and continue with Normal Ack. response.teardownDelba = buildDelba(addbaResp->getTransmitterAddress(), addbaResp->getTid(), RC_END_BA); + pendingTeardownTransactionIds[std::make_pair(addbaResp->getTransmitterAddress(), addbaResp->getTid())] = transactionId; + response.agreement = removeAgreement(addbaResp->getTransmitterAddress(), addbaResp->getTid()); + if (response.agreement != nullptr) + response.terminatedAgreement.reset(response.agreement); + scheduleInactivityTimer(callback); } - response.agreement = agreement; + else + response.agreement = agreement; return response; } else { @@ -283,6 +296,17 @@ bool OriginatorBlockAckAgreementHandler::isAddbaRequestPending(const Packet *pac it->second->getDialogToken() == addbaReq->getDialogToken() && it->second->getTransactionId() == transactionTag->getTransactionId(); } +bool OriginatorBlockAckAgreementHandler::isDelbaPending(const Packet *packet, const Ptr& delba) const +{ + if (!delba->getInitiator()) + return true; + auto transactionTag = packet->findTag(); + if (transactionTag == nullptr) + return true; + auto it = pendingTeardownTransactionIds.find(std::make_pair(delba->getReceiverAddress(), delba->getTid())); + return it != pendingTeardownTransactionIds.end() && it->second == transactionTag->getTransactionId(); +} + void OriginatorBlockAckAgreementHandler::processTransmittedAddbaReq(Packet *packet, const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) { auto agreement = getAgreement(addbaReq->getReceiverAddress(), addbaReq->getTid()); @@ -306,10 +330,18 @@ void OriginatorBlockAckAgreementHandler::processDroppedAddbaReq(Packet *packet, } } -std::unique_ptr OriginatorBlockAckAgreementHandler::processTransmittedDelba(const Ptr& delba, IBlockAckAgreementHandlerCallback *callback) +std::unique_ptr OriginatorBlockAckAgreementHandler::processTransmittedDelba(Packet *packet, IBlockAckAgreementHandlerCallback *callback) { - // IEEE Std 802.11-2024, 11.5.3.2: the originator tears down its local - // agreement when its initiator DELBA is transmitted. + auto delba = packet->peekAtFront(); + auto transactionTag = packet->findTag(); + if (transactionTag != nullptr) { + auto it = pendingTeardownTransactionIds.find(std::make_pair(delba->getReceiverAddress(), delba->getTid())); + if (it == pendingTeardownTransactionIds.end() || it->second != transactionTag->getTransactionId()) + return nullptr; + if (!delba->getMoreFragments()) + pendingTeardownTransactionIds.erase(it); + return nullptr; + } auto agreement = getAgreement(delba->getReceiverAddress(), delba->getTid()); bool cancelPendingTransaction = agreement != nullptr && agreement->isPending(); auto transactionId = cancelPendingTransaction ? agreement->getTransactionId() : 0; @@ -325,12 +357,16 @@ std::unique_ptr OriginatorBlockAckAgreementHandler: auto delba = packet->peekAtFront(); if (!delba->getInitiator()) return nullptr; - auto agreement = getAgreement(delba->getReceiverAddress(), delba->getTid()); - if (agreement == nullptr || !agreement->isTeardownPending()) - return nullptr; - std::unique_ptr terminatedAgreement(removeAgreement(delba->getReceiverAddress(), delba->getTid())); - scheduleAddbaResponseTimer(callback); - return terminatedAgreement; + auto transactionTag = packet->findTag(); + if (transactionTag != nullptr) { + auto it = pendingTeardownTransactionIds.find(std::make_pair(delba->getReceiverAddress(), delba->getTid())); + if (it != pendingTeardownTransactionIds.end() && it->second == transactionTag->getTransactionId()) { + auto transactionId = it->second; + pendingTeardownTransactionIds.erase(it); + callback->cancelAddbaTransaction(transactionId, packet); + } + } + return nullptr; } std::unique_ptr OriginatorBlockAckAgreementHandler::processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h index 3d174e4564f..899cb722d61 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h @@ -22,6 +22,7 @@ class INET_API OriginatorBlockAckAgreementHandler : public IOriginatorBlockAckAg protected: std::map, OriginatorBlockAckAgreement *> blockAckAgreements; std::map, simtime_t> addbaRetryDeadlines; + std::map, uint64_t> pendingTeardownTransactionIds; uint8_t nextDialogToken = 1; uint64_t nextTransactionId = 1; @@ -44,11 +45,11 @@ class INET_API OriginatorBlockAckAgreementHandler : public IOriginatorBlockAckAg virtual ~OriginatorBlockAckAgreementHandler(); virtual void processTransmittedAddbaReq(Packet *packet, const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; virtual void processDroppedAddbaReq(Packet *packet, const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; - virtual void processAcknowledgedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback) override; + virtual uint64_t processAcknowledgedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback) override; virtual void processReceivedBlockAck(const Ptr& blockAck, IBlockAckAgreementHandlerCallback *callback) override; virtual OriginatorBlockAckAgreementResponse processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; virtual std::unique_ptr processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; - virtual std::unique_ptr processTransmittedDelba(const Ptr& delba, IBlockAckAgreementHandlerCallback *callback) override; + virtual std::unique_ptr processTransmittedDelba(Packet *packet, IBlockAckAgreementHandlerCallback *callback) override; virtual std::unique_ptr processAbortedDelba(Packet *packet, IBlockAckAgreementHandlerCallback *callback) override; virtual void blockAckAgreementExpired(IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) override; virtual void addbaResponseTimeoutExpired(IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; @@ -56,6 +57,7 @@ class INET_API OriginatorBlockAckAgreementHandler : public IOriginatorBlockAckAg virtual OriginatorBlockAckAgreement *getAgreement(MacAddress receiverAddr, Tid tid) override; virtual bool isAddbaResponsePending(MacAddress receiverAddr, Tid tid) const override; virtual bool isAddbaRequestPending(const Packet *packet, const Ptr& addbaReq) const override; + virtual bool isDelbaPending(const Packet *packet, const Ptr& delba) const override; }; } // namespace ieee80211 diff --git a/src/inet/linklayer/ieee80211/mac/contract/IMsduAggregationPolicy.h b/src/inet/linklayer/ieee80211/mac/contract/IMsduAggregationPolicy.h index 8260a067cef..3a08e3a8cbe 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IMsduAggregationPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IMsduAggregationPolicy.h @@ -8,6 +8,8 @@ #ifndef __INET_IMSDUAGGREGATIONPOLICY_H #define __INET_IMSDUAGGREGATIONPOLICY_H +#include + #include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" #include "inet/queueing/contract/IPacketQueue.h" @@ -19,11 +21,12 @@ class INET_API IMsduAggregationPolicy public: virtual ~IMsduAggregationPolicy() {} - virtual std::vector *computeAggregateFrames(queueing::IPacketQueue *queue) = 0; + // A non-null result is caller-owned and must contain at least two unique, + // discoverable, eligible frames with candidate as its first element. + virtual std::vector *computeAggregateFrames(queueing::IPacketQueue *queue, Packet *candidate, const std::function& isFrameEligible) = 0; }; } // namespace ieee80211 } // namespace inet #endif - diff --git a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h index 4f59066f817..4e488b62996 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h @@ -13,6 +13,7 @@ #include "inet/common/packet/Packet.h" #include "inet/linklayer/common/MacAddress.h" #include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" +#include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h" #include "inet/linklayer/ieee80211/mac/common/Ieee80211Defs.h" #include "inet/linklayer/ieee80211/mac/contract/IBlockAckAgreementHandlerCallback.h" #include "inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementPolicy.h" @@ -25,6 +26,7 @@ struct INET_API OriginatorBlockAckAgreementResponse { OriginatorBlockAckAgreement *agreement = nullptr; Ptr teardownDelba; + std::unique_ptr terminatedAgreement; }; class INET_API IOriginatorBlockAckAgreementHandler @@ -35,10 +37,12 @@ class INET_API IOriginatorBlockAckAgreementHandler virtual void processReceivedBlockAck(const Ptr& blockAck, IBlockAckAgreementHandlerCallback *callback) = 0; virtual void processTransmittedAddbaReq(Packet *packet, const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; virtual void processDroppedAddbaReq(Packet *packet, const Ptr& addbaReq, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; - virtual void processAcknowledgedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback) = 0; + // Returns the transaction identity of an obsolete teardown whose packets + // must be cancelled by the caller, or 0 when there is none. + virtual uint64_t processAcknowledgedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback) = 0; virtual OriginatorBlockAckAgreementResponse processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; virtual std::unique_ptr processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; - virtual std::unique_ptr processTransmittedDelba(const Ptr& delba, IBlockAckAgreementHandlerCallback *callback) = 0; + virtual std::unique_ptr processTransmittedDelba(Packet *packet, IBlockAckAgreementHandlerCallback *callback) = 0; virtual std::unique_ptr processAbortedDelba(Packet *packet, IBlockAckAgreementHandlerCallback *callback) = 0; virtual void blockAckAgreementExpired(IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) = 0; virtual void addbaResponseTimeoutExpired(IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; @@ -46,6 +50,7 @@ class INET_API IOriginatorBlockAckAgreementHandler virtual OriginatorBlockAckAgreement *getAgreement(MacAddress receiverAddr, Tid tid) = 0; virtual bool isAddbaResponsePending(MacAddress receiverAddr, Tid tid) const = 0; virtual bool isAddbaRequestPending(const Packet *packet, const Ptr& addbaReq) const = 0; + virtual bool isDelbaPending(const Packet *packet, const Ptr& delba) const { return true; } }; } // namespace ieee80211 diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc index ef70dc00147..0c944c02d4a 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc @@ -66,9 +66,10 @@ void Hcf::initialize(int stage) originatorDataService->setFrameEligibilityFunction([this](const Packet *packet) { if (auto addbaReq = dynamicPtrCast(packet->peekAtFront())) return originatorBlockAckAgreementHandler->isAddbaRequestPending(packet, addbaReq); + if (auto delba = dynamicPtrCast(packet->peekAtFront())) + return originatorBlockAckAgreementHandler->isDelbaPending(packet, delba); auto dataHeader = dynamicPtrCast(packet->peekAtFront()); - auto agreement = dataHeader == nullptr ? nullptr : originatorBlockAckAgreementHandler->getAgreement(dataHeader->getReceiverAddress(), dataHeader->getTid()); - return dataHeader == nullptr || dataHeader->getType() != ST_DATA_WITH_QOS || ((agreement == nullptr || !agreement->isTeardownPending()) && !originatorBlockAckAgreementHandler->isAddbaResponsePending(dataHeader->getReceiverAddress(), dataHeader->getTid())); + return dataHeader == nullptr || dataHeader->getType() != ST_DATA_WITH_QOS || (!originatorBlockAckAgreementHandler->isAddbaResponsePending(dataHeader->getReceiverAddress(), dataHeader->getTid())); }); } } @@ -543,10 +544,14 @@ void Hcf::recipientProcessReceivedManagementFrame(const PtrprocessReceivedAddbaResp(addbaResp, originatorBlockAckAgreementPolicy, this); if (wasPending && !originatorBlockAckAgreementHandler->isAddbaResponsePending(addbaResp->getTransmitterAddress(), addbaResp->getTid())) rebuildPendingFrameEligibility(); - if (response.agreement != nullptr) + if (response.agreement != nullptr) { emit(blockAckAgreementAddedSignal, response.agreement); + if (response.teardownDelba != nullptr) + emit(blockAckAgreementDeletedSignal, response.agreement); + } if (response.teardownDelba != nullptr) { auto delbaPacket = new Packet("Delba", response.teardownDelba); + delbaPacket->addTag()->setTransactionId(response.agreement == nullptr ? 0 : response.agreement->getTransactionId()); processMgmtFrame(delbaPacket, response.teardownDelba); } resumeEligibleChannelAccess(); @@ -688,7 +693,7 @@ void Hcf::originatorProcessTransmittedManagementFrame(Packet *packet, const Ptr< else if (auto delba = dynamicPtrCast(mgmtHeader)) { if (delba->getInitiator()) { bool wasPending = originatorBlockAckAgreementHandler->isAddbaResponsePending(delba->getReceiverAddress(), delba->getTid()); - auto agreement = originatorBlockAckAgreementHandler->processTransmittedDelba(delba, this); + auto agreement = originatorBlockAckAgreementHandler->processTransmittedDelba(packet, this); if (wasPending) rebuildPendingFrameEligibility(); if (agreement != nullptr && agreement->getIsAddbaResponseReceived()) @@ -845,7 +850,9 @@ void Hcf::originatorProcessReceivedControlFrame(Packet *packet, const Ptr(lastTransmittedHeader)) { if (originatorBlockAckAgreementHandler) { bool wasPending = originatorBlockAckAgreementHandler->isAddbaResponsePending(dataHeader->getReceiverAddress(), dataHeader->getTid()); - originatorBlockAckAgreementHandler->processAcknowledgedDataFrame(lastTransmittedPacket, dataHeader, originatorBlockAckAgreementPolicy, this); + auto obsoleteTeardownTransactionId = originatorBlockAckAgreementHandler->processAcknowledgedDataFrame(lastTransmittedPacket, dataHeader, originatorBlockAckAgreementPolicy, this); + if (obsoleteTeardownTransactionId != 0) + cancelAddbaTransaction(obsoleteTeardownTransactionId, nullptr); if (!wasPending && originatorBlockAckAgreementHandler->isAddbaResponsePending(dataHeader->getReceiverAddress(), dataHeader->getTid())) rebuildPendingFrameEligibility(); } @@ -930,8 +937,6 @@ void Hcf::transmitFrame(Packet *packet, simtime_t ifs) OriginatorBlockAckAgreement *agreement = nullptr; if (originatorBlockAckAgreementHandler) agreement = originatorBlockAckAgreementHandler->getAgreement(dataFrame->getReceiverAddress(), dataFrame->getTid()); - if (agreement != nullptr && agreement->isTeardownPending()) - agreement = nullptr; auto ackPolicy = originatorAckPolicy->computeAckPolicy(packet, dataFrame, agreement); auto dataHeader = packet->removeAtFront(); dataHeader->setAckPolicy(ackPolicy); diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc index f09e0c12ee2..25d9ff71b41 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc @@ -8,6 +8,7 @@ #include "inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.h" #include +#include #include "inet/linklayer/ieee80211/mac/aggregation/MpduAggregation.h" #include "inet/linklayer/ieee80211/mac/aggregation/MsduAggregation.h" @@ -33,20 +34,45 @@ void OriginatorQosMacDataService::initialize() fragmentation = new Fragmentation(); } -Packet *OriginatorQosMacDataService::aMsduAggregateIfNeeded(queueing::IPacketQueue *pendingQueue) +Packet *OriginatorQosMacDataService::aMsduAggregateIfNeeded(queueing::IPacketQueue *pendingQueue, Packet *candidate) { - auto subframes = aMsduAggregationPolicy->computeAggregateFrames(pendingQueue); + auto predicate = [this](const Packet *packet) { return isFrameEligible(packet); }; + auto subframes = aMsduAggregationPolicy->computeAggregateFrames(pendingQueue, candidate, predicate); if (subframes) { - if (!std::all_of(subframes->begin(), subframes->end(), [this](const Packet *packet) { return isFrameEligible(packet); })) { + if (subframes->size() < 2 || subframes->front() != candidate) { delete subframes; - return nullptr; + throw cRuntimeError("A-MSDU policy must return at least two frames with the selected candidate first"); + } + std::set uniqueFrames; + for (auto subframe : *subframes) { + if (subframe == nullptr || !uniqueFrames.insert(subframe).second || !isFrameEligible(subframe) || pendingQueue->findPacket([subframe](const Packet *packet) { return packet == subframe; }) != subframe) { + delete subframes; + throw cRuntimeError("A-MSDU policy returned a frame that is unavailable, ineligible, or duplicated"); + } } + struct AggregateFrameState { Tid tid; MacAddress receiver; MacAddress transmitter; MacAddress address3; MacAddress address4; int type; bool toDS; bool fromDS; b dataLength; b headerLength; b trailerLength; }; + std::vector states; for (auto subframe : *subframes) { + auto dataHeader = dynamicPtrCast(subframe->peekAtFront()); + auto dataTrailer = subframe->peekAtBack(B(4)); + if (dataHeader == nullptr || dataTrailer == nullptr) { + delete subframes; + throw cRuntimeError("A-MSDU policy selected a frame without a valid data header/trailer"); + } + states.push_back({static_cast(dataHeader->getTid()), dataHeader->getReceiverAddress(), dataHeader->getTransmitterAddress(), dataHeader->getAddress3(), dataHeader->getAddress4(), dataHeader->getType(), dataHeader->getToDS(), dataHeader->getFromDS(), subframe->getDataLength(), dataHeader->getChunkLength(), dataTrailer->getChunkLength()}); auto dequeuedSubframe = pendingQueue->dequeuePacket([subframe](const Packet *packet) { return packet == subframe; }); if (dequeuedSubframe != subframe) throw cRuntimeError("A-MSDU policy-selected subframe is no longer available in scheduling order"); take(dequeuedSubframe); } + for (size_t i = 0; i < subframes->size(); i++) { + auto dataHeader = dynamicPtrCast((*subframes)[i]->peekAtFront()); + auto dataTrailer = (*subframes)[i]->peekAtBack(B(4)); + if (dataHeader == nullptr || dataTrailer == nullptr || dataHeader->getTid() != states[i].tid || dataHeader->getReceiverAddress() != states[i].receiver || dataHeader->getTransmitterAddress() != states[i].transmitter || dataHeader->getAddress3() != states[i].address3 || dataHeader->getAddress4() != states[i].address4 || dataHeader->getType() != states[i].type || dataHeader->getToDS() != states[i].toDS || dataHeader->getFromDS() != states[i].fromDS || (*subframes)[i]->getDataLength() != states[i].dataLength || dataHeader->getChunkLength() != states[i].headerLength || dataTrailer->getChunkLength() != states[i].trailerLength) { + delete subframes; + throw cRuntimeError("A-MSDU provider changed aggregation-critical frame fields during extraction"); + } + } auto aggregatedFrame = aMsduAggregation->aggregateFrames(subframes); emit(packetAggregatedSignal, aggregatedFrame); delete subframes; @@ -112,11 +138,10 @@ std::vector *OriginatorQosMacDataService::extractFramesToTransmit(queu // if (msduRateLimiting) // txRateLimitingIfNeeded(); Packet *packet = nullptr; - // The current A-MSDU policy enumerates the queue when selecting all - // aggregate members. Use it only if enumeration is guaranteed to be - // the provider's scheduling order for the entire aggregate. - if (aMsduAggregationPolicy && pendingQueue->isPacketOrderPreserved() && pendingQueue->getNumPackets() != 0 && pendingQueue->getPacket(0) == candidate) - packet = aMsduAggregateIfNeeded(pendingQueue); + // Scheduling selects the anchor; the policy may select additional + // eligible members, which are all extracted through the provider. + if (aMsduAggregationPolicy) + packet = aMsduAggregateIfNeeded(pendingQueue, candidate); if (!packet) { packet = pendingQueue->dequeuePacket(predicate); ASSERT(packet == candidate); diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.h b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.h index eb3258fe545..8ba2b581f0b 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.h +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.h @@ -45,7 +45,7 @@ class INET_API OriginatorQosMacDataService : public IOriginatorMacDataService, p virtual void assignSequenceNumber(const Ptr& header); virtual std::vector *fragmentIfNeeded(Packet *frame); - virtual Packet *aMsduAggregateIfNeeded(queueing::IPacketQueue *pendingQueue); + virtual Packet *aMsduAggregateIfNeeded(queueing::IPacketQueue *pendingQueue, Packet *candidate); virtual Packet *aMpduAggregateIfNeeded(std::vector *fragments); public: diff --git a/src/inet/queueing/base/PacketFlowBase.h b/src/inet/queueing/base/PacketFlowBase.h index 27f86e03e5c..ffaa505784f 100644 --- a/src/inet/queueing/base/PacketFlowBase.h +++ b/src/inet/queueing/base/PacketFlowBase.h @@ -84,10 +84,6 @@ class INET_API PacketFlowBase : public PacketProcessorBase, public virtual IPack virtual Packet *getPacket(int index) const override { return collection->getPacket(index); } virtual bool isEmpty() const override { return collection->isEmpty(); } virtual void removePacket(Packet *packet) override { collection->removePacket(packet); } - // Pull-side processing runs after a packet is selected and may change its - // contents, so collection order before processing does not guarantee stable - // packet properties after extraction. - virtual bool isPacketOrderPreserved() const override { return false; } virtual Packet *findPacket(const PacketPredicate& predicate) const override { return packetExtractor->findPacket(predicate); } virtual Packet *dequeuePacket(const PacketPredicate& predicate) override; virtual void removeAllPackets() override { collection->removeAllPackets(); } diff --git a/src/inet/queueing/contract/IPacketExtractor.h b/src/inet/queueing/contract/IPacketExtractor.h index 48f43f292a4..cf2287ceeed 100644 --- a/src/inet/queueing/contract/IPacketExtractor.h +++ b/src/inet/queueing/contract/IPacketExtractor.h @@ -28,13 +28,6 @@ class INET_API IPacketExtractor public: virtual ~IPacketExtractor() {} - /** - * Returns true when IPacketCollection::getPacket() enumeration order on - * the same object is also the order in which this extractor selects - * packets. The conservative default prevents clients from bypassing a - * provider-specific scheduling policy. - */ - virtual bool isPacketOrderPreserved() const { return false; } virtual Packet *findPacket(const PacketPredicate& predicate) const = 0; virtual Packet *dequeuePacket(const PacketPredicate& predicate) = 0; }; diff --git a/src/inet/queueing/queue/CompoundPacketQueueBase.h b/src/inet/queueing/queue/CompoundPacketQueueBase.h index cd2632379b4..89f8c70f47a 100644 --- a/src/inet/queueing/queue/CompoundPacketQueueBase.h +++ b/src/inet/queueing/queue/CompoundPacketQueueBase.h @@ -52,7 +52,6 @@ class INET_API CompoundPacketQueueBase : public PacketQueueBase, public cListene virtual bool isEmpty() const override { return collection->isEmpty(); } virtual Packet *getPacket(int index) const override { return collection->getPacket(index); } virtual void removePacket(Packet *packet) override; - virtual bool isPacketOrderPreserved() const override { return packetExtractor->isPacketOrderPreserved(); } virtual Packet *findPacket(const PacketPredicate& predicate) const override; virtual Packet *dequeuePacket(const PacketPredicate& predicate) override; virtual void removeAllPackets() override; diff --git a/src/inet/queueing/queue/PacketQueue.h b/src/inet/queueing/queue/PacketQueue.h index 5c32a9156e5..2892da932ec 100644 --- a/src/inet/queueing/queue/PacketQueue.h +++ b/src/inet/queueing/queue/PacketQueue.h @@ -60,7 +60,6 @@ class INET_API PacketQueue : public PacketQueueBase, public IPacketBuffer::ICall virtual bool isEmpty() const override { return getNumPackets() == 0; } virtual Packet *getPacket(int index) const override; virtual void removePacket(Packet *packet) override; - virtual bool isPacketOrderPreserved() const override { return true; } virtual Packet *findPacket(const PacketPredicate& predicate) const override; virtual Packet *dequeuePacket(const PacketPredicate& predicate) override; virtual void removeAllPackets() override; diff --git a/src/inet/queueing/scheduler/LabelScheduler.cc b/src/inet/queueing/scheduler/LabelScheduler.cc index 41a983eb0c2..eaea811bbab 100644 --- a/src/inet/queueing/scheduler/LabelScheduler.cc +++ b/src/inet/queueing/scheduler/LabelScheduler.cc @@ -24,8 +24,6 @@ void LabelScheduler::initialize(int stage) auto provider = providers[i].get(); auto collection = dynamic_cast(provider); auto packetExtractor = dynamic_cast(provider); - if (collection == nullptr || packetExtractor == nullptr) - throw cRuntimeError("Input provider at gate index %d must implement both IPacketCollection and IPacketExtractor", (int)i); collections.push_back(collection); packetExtractors.push_back(packetExtractor); } @@ -35,23 +33,35 @@ void LabelScheduler::initialize(int stage) int LabelScheduler::getNumPackets() const { int size = 0; - for (auto collection : collections) + for (size_t i = 0; i < collections.size(); i++) { + auto collection = collections[i]; + if (collection == nullptr) + throw cRuntimeError("Cannot getNumPackets(): input provider at gate index %d does not implement IPacketCollection", (int)i); size += collection->getNumPackets(); + } return size; } b LabelScheduler::getTotalLength() const { b totalLength(0); - for (auto collection : collections) + for (size_t i = 0; i < collections.size(); i++) { + auto collection = collections[i]; + if (collection == nullptr) + throw cRuntimeError("Cannot getTotalLength(): input provider at gate index %d does not implement IPacketCollection", (int)i); totalLength += collection->getTotalLength(); + } return totalLength; } Packet *LabelScheduler::getPacket(int index) const { int originalIndex = index; - for (auto collection : collections) { + for (size_t i = 0; i < collections.size(); i++) + if (collections[i] == nullptr) + throw cRuntimeError("Cannot getPacket(): input provider at gate index %d does not implement IPacketCollection", (int)i); + for (size_t i = 0; i < collections.size(); i++) { + auto collection = collections[i]; auto numPackets = collection->getNumPackets(); if (index < numPackets) return collection->getPacket(index); @@ -62,7 +72,11 @@ Packet *LabelScheduler::getPacket(int index) const void LabelScheduler::removePacket(Packet *packet) { - for (auto collection : collections) { + for (size_t i = 0; i < collections.size(); i++) + if (collections[i] == nullptr) + throw cRuntimeError("Cannot removePacket(): input provider at gate index %d does not implement IPacketCollection", (int)i); + for (size_t i = 0; i < collections.size(); i++) { + auto collection = collections[i]; for (int i = 0; i < collection->getNumPackets(); i++) { if (collection->getPacket(i) == packet) { collection->removePacket(packet); @@ -76,8 +90,11 @@ void LabelScheduler::removePacket(Packet *packet) int LabelScheduler::findInput(const PacketPredicate& predicate) const { std::vector candidates; - for (auto packetExtractor : packetExtractors) - candidates.push_back(packetExtractor->findPacket(predicate)); + for (size_t i = 0; i < packetExtractors.size(); i++) { + if (packetExtractors[i] == nullptr) + throw cRuntimeError("Cannot findPacket(): input provider at gate index %d does not implement IPacketExtractor", (int)i); + candidates.push_back(packetExtractors[i]->findPacket(predicate)); + } for (auto label : labels) { for (size_t i = 0; i < candidates.size(); i++) { auto packet = candidates[i]; @@ -103,6 +120,9 @@ Packet *LabelScheduler::findPacket(const PacketPredicate& predicate) const Packet *LabelScheduler::dequeuePacket(const PacketPredicate& predicate) { Enter_Method("dequeuePacket"); + for (size_t i = 0; i < packetExtractors.size(); i++) + if (packetExtractors[i] == nullptr) + throw cRuntimeError("Cannot dequeuePacket(): input provider at gate index %d does not implement IPacketExtractor", (int)i); auto index = findInput(predicate); if (index == -1) return nullptr; @@ -118,6 +138,10 @@ Packet *LabelScheduler::dequeuePacket(const PacketPredicate& predicate) void LabelScheduler::removeAllPackets() { Enter_Method("removeAllPackets"); + for (size_t i = 0; i < collections.size(); i++) { + if (collections[i] == nullptr) + throw cRuntimeError("Cannot removeAllPackets(): input provider at gate index %d does not implement IPacketCollection", (int)i); + } for (auto collection : collections) collection->removeAllPackets(); } diff --git a/src/inet/queueing/scheduler/PriorityScheduler.cc b/src/inet/queueing/scheduler/PriorityScheduler.cc index be5925cf13a..1f69ae4aa09 100644 --- a/src/inet/queueing/scheduler/PriorityScheduler.cc +++ b/src/inet/queueing/scheduler/PriorityScheduler.cc @@ -20,8 +20,6 @@ void PriorityScheduler::initialize(int stage) auto provider = providers[i].get(); collections.push_back(dynamic_cast(provider)); auto packetExtractor = dynamic_cast(provider); - if (packetExtractor == nullptr) - throw cRuntimeError("Input provider at gate index %d must implement IPacketExtractor", (int)i); packetExtractors.push_back(packetExtractor); } } @@ -30,29 +28,35 @@ void PriorityScheduler::initialize(int stage) int PriorityScheduler::getNumPackets() const { int size = 0; - for (auto collection : collections) - if (collection != nullptr) - size += collection->getNumPackets(); - else - return -1; + for (size_t i = 0; i < collections.size(); i++) { + auto collection = collections[i]; + if (collection == nullptr) + throw cRuntimeError("Cannot getNumPackets(): input provider at gate index %d does not implement IPacketCollection", (int)i); + size += collection->getNumPackets(); + } return size; } b PriorityScheduler::getTotalLength() const { b totalLength(0); - for (auto collection : collections) - if (collection != nullptr) - totalLength += collection->getTotalLength(); - else - return b(-1); + for (size_t i = 0; i < collections.size(); i++) { + auto collection = collections[i]; + if (collection == nullptr) + throw cRuntimeError("Cannot getTotalLength(): input provider at gate index %d does not implement IPacketCollection", (int)i); + totalLength += collection->getTotalLength(); + } return totalLength; } Packet *PriorityScheduler::getPacket(int index) const { int origIndex = index; - for (auto collection : collections) { + for (size_t i = 0; i < collections.size(); i++) + if (collections[i] == nullptr) + throw cRuntimeError("Cannot getPacket(): input provider at gate index %d does not implement IPacketCollection", (int)i); + for (size_t i = 0; i < collections.size(); i++) { + auto collection = collections[i]; auto numPackets = collection->getNumPackets(); if (index < numPackets) return collection->getPacket(index); @@ -65,7 +69,11 @@ Packet *PriorityScheduler::getPacket(int index) const void PriorityScheduler::removePacket(Packet *packet) { Enter_Method("removePacket"); - for (auto collection : collections) { + for (size_t i = 0; i < collections.size(); i++) + if (collections[i] == nullptr) + throw cRuntimeError("Cannot removePacket(): input provider at gate index %d does not implement IPacketCollection", (int)i); + for (size_t i = 0; i < collections.size(); i++) { + auto collection = collections[i]; int numPackets = collection->getNumPackets(); for (int j = 0; j < numPackets; j++) { if (collection->getPacket(j) == packet) { @@ -77,21 +85,15 @@ void PriorityScheduler::removePacket(Packet *packet) throw cRuntimeError("Cannot find packet"); } -bool PriorityScheduler::isPacketOrderPreserved() const -{ - if (reverseOrder) - return false; - for (auto packetExtractor : packetExtractors) { - if (!packetExtractor->isPacketOrderPreserved()) - return false; - } - return true; -} - Packet *PriorityScheduler::findPacket(const PacketPredicate& predicate) const { + for (size_t i = 0; i < packetExtractors.size(); i++) + if (packetExtractors[i] == nullptr) + throw cRuntimeError("Cannot findPacket(): input provider at gate index %d does not implement IPacketExtractor", (int)i); for (size_t i = 0; i < collections.size(); i++) { auto index = reverseOrder ? collections.size() - i - 1 : i; + if (packetExtractors[index] == nullptr) + throw cRuntimeError("Cannot findPacket(): input provider at gate index %d does not implement IPacketExtractor", (int)index); auto packet = packetExtractors[index]->findPacket(predicate); if (packet != nullptr) return packet; @@ -102,8 +104,13 @@ Packet *PriorityScheduler::findPacket(const PacketPredicate& predicate) const Packet *PriorityScheduler::dequeuePacket(const PacketPredicate& predicate) { Enter_Method("dequeuePacket"); + for (size_t i = 0; i < packetExtractors.size(); i++) + if (packetExtractors[i] == nullptr) + throw cRuntimeError("Cannot dequeuePacket(): input provider at gate index %d does not implement IPacketExtractor", (int)i); for (size_t i = 0; i < collections.size(); i++) { auto index = reverseOrder ? collections.size() - i - 1 : i; + if (packetExtractors[index] == nullptr) + throw cRuntimeError("Cannot dequeuePacket(): input provider at gate index %d does not implement IPacketExtractor", (int)index); auto packet = packetExtractors[index]->dequeuePacket(predicate); if (packet == nullptr) continue; @@ -119,6 +126,10 @@ Packet *PriorityScheduler::dequeuePacket(const PacketPredicate& predicate) void PriorityScheduler::removeAllPackets() { Enter_Method("removeAllPackets"); + for (size_t i = 0; i < collections.size(); i++) { + if (collections[i] == nullptr) + throw cRuntimeError("Cannot removeAllPackets(): input provider at gate index %d does not implement IPacketCollection", (int)i); + } for (auto collection : collections) collection->removeAllPackets(); } diff --git a/src/inet/queueing/scheduler/PriorityScheduler.h b/src/inet/queueing/scheduler/PriorityScheduler.h index c9ecf2032a5..703cc6a0ada 100644 --- a/src/inet/queueing/scheduler/PriorityScheduler.h +++ b/src/inet/queueing/scheduler/PriorityScheduler.h @@ -35,7 +35,6 @@ class INET_API PriorityScheduler : public PacketSchedulerBase, public virtual IP virtual bool isEmpty() const override { return getNumPackets() == 0; } virtual Packet *getPacket(int index) const override; virtual void removePacket(Packet *packet) override; - virtual bool isPacketOrderPreserved() const override; virtual Packet *findPacket(const PacketPredicate& predicate) const override; virtual Packet *dequeuePacket(const PacketPredicate& predicate) override; virtual void removeAllPackets() override; diff --git a/src/inet/queueing/scheduler/WrrScheduler.cc b/src/inet/queueing/scheduler/WrrScheduler.cc index 1484befe521..2552cf4de5a 100644 --- a/src/inet/queueing/scheduler/WrrScheduler.cc +++ b/src/inet/queueing/scheduler/WrrScheduler.cc @@ -41,8 +41,6 @@ void WrrScheduler::initialize(int stage) auto provider = providers[i].get(); auto collection = dynamic_cast(provider); auto packetExtractor = dynamic_cast(provider); - if (collection == nullptr || packetExtractor == nullptr) - throw cRuntimeError("Input provider at gate index %d must implement both IPacketCollection and IPacketExtractor", (int)i); collections.push_back(collection); packetExtractors.push_back(packetExtractor); } @@ -52,23 +50,35 @@ void WrrScheduler::initialize(int stage) int WrrScheduler::getNumPackets() const { int size = 0; - for (auto collection : collections) + for (size_t i = 0; i < collections.size(); i++) { + auto collection = collections[i]; + if (collection == nullptr) + throw cRuntimeError("Cannot getNumPackets(): input provider at gate index %d does not implement IPacketCollection", (int)i); size += collection->getNumPackets(); + } return size; } b WrrScheduler::getTotalLength() const { b totalLength(0); - for (auto collection : collections) + for (size_t i = 0; i < collections.size(); i++) { + auto collection = collections[i]; + if (collection == nullptr) + throw cRuntimeError("Cannot getTotalLength(): input provider at gate index %d does not implement IPacketCollection", (int)i); totalLength += collection->getTotalLength(); + } return totalLength; } Packet *WrrScheduler::getPacket(int index) const { int originalIndex = index; - for (auto collection : collections) { + for (size_t i = 0; i < collections.size(); i++) + if (collections[i] == nullptr) + throw cRuntimeError("Cannot getPacket(): input provider at gate index %d does not implement IPacketCollection", (int)i); + for (size_t i = 0; i < collections.size(); i++) { + auto collection = collections[i]; auto numPackets = collection->getNumPackets(); if (index < numPackets) return collection->getPacket(index); @@ -79,7 +89,11 @@ Packet *WrrScheduler::getPacket(int index) const void WrrScheduler::removePacket(Packet *packet) { - for (auto collection : collections) { + for (size_t i = 0; i < collections.size(); i++) + if (collections[i] == nullptr) + throw cRuntimeError("Cannot removePacket(): input provider at gate index %d does not implement IPacketCollection", (int)i); + for (size_t i = 0; i < collections.size(); i++) { + auto collection = collections[i]; for (int i = 0; i < collection->getNumPackets(); i++) { if (collection->getPacket(i) == packet) { collection->removePacket(packet); @@ -95,6 +109,8 @@ int WrrScheduler::findInput(const PacketPredicate& predicate) const int firstWeighted = -1; int firstNonWeighted = -1; for (size_t i = 0; i < collections.size(); ++i) { + if (packetExtractors[i] == nullptr) + throw cRuntimeError("Cannot findPacket(): input provider at gate index %d does not implement IPacketExtractor", (int)i); if (packetExtractors[i]->findPacket(predicate) != nullptr) { if (buckets[i] > 0) return i; @@ -121,6 +137,9 @@ void WrrScheduler::consumeBucket(int index) Packet *WrrScheduler::findPacket(const PacketPredicate& predicate) const { + for (size_t i = 0; i < packetExtractors.size(); i++) + if (packetExtractors[i] == nullptr) + throw cRuntimeError("Cannot findPacket(): input provider at gate index %d does not implement IPacketExtractor", (int)i); auto index = findInput(predicate); return index == -1 ? nullptr : packetExtractors[index]->findPacket(predicate); } @@ -128,6 +147,9 @@ Packet *WrrScheduler::findPacket(const PacketPredicate& predicate) const Packet *WrrScheduler::dequeuePacket(const PacketPredicate& predicate) { Enter_Method("dequeuePacket"); + for (size_t i = 0; i < packetExtractors.size(); i++) + if (packetExtractors[i] == nullptr) + throw cRuntimeError("Cannot dequeuePacket(): input provider at gate index %d does not implement IPacketExtractor", (int)i); auto index = findInput(predicate); if (index == -1) return nullptr; @@ -144,6 +166,10 @@ Packet *WrrScheduler::dequeuePacket(const PacketPredicate& predicate) void WrrScheduler::removeAllPackets() { Enter_Method("removeAllPackets"); + for (size_t i = 0; i < collections.size(); i++) { + if (collections[i] == nullptr) + throw cRuntimeError("Cannot removeAllPackets(): input provider at gate index %d does not implement IPacketCollection", (int)i); + } for (auto collection : collections) collection->removeAllPackets(); } diff --git a/tests/unit/Ieee80211AddbaTransaction_1.test b/tests/unit/Ieee80211AddbaTransaction_1.test index 331d99fae17..b4d0da4d240 100644 --- a/tests/unit/Ieee80211AddbaTransaction_1.test +++ b/tests/unit/Ieee80211AddbaTransaction_1.test @@ -10,6 +10,7 @@ Test IEEE 802.11 ADDBA transactions, reordering reset, fragmentation cleanup, DC #include "inet/common/TimeTag_m.h" #include "inet/common/LabelsTag_m.h" #include "inet/common/packet/chunk/ByteCountChunk.h" +#include "inet/linklayer/ieee80211/mac/aggregation/BasicMsduAggregationPolicy.h" #include "inet/linklayer/ieee80211/mac/aggregation/MsduAggregation.h" #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h" #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h" @@ -115,6 +116,7 @@ class TestOriginatorHandler : public OriginatorBlockAckAgreementHandler void setNextDialogToken(uint8_t token) { nextDialogToken = token; } void expireNow(MacAddress receiverAddress, Tid tid) { getAgreement(receiverAddress, tid)->setAddbaResponseDeadline(simTime()); } void allowRetryNow(MacAddress receiverAddress, Tid tid) { addbaRetryDeadlines[std::make_pair(receiverAddress, tid)] = simTime(); } + void setPendingTeardownTransaction(MacAddress receiverAddress, Tid tid, uint64_t transactionId) { pendingTeardownTransactionIds[std::make_pair(receiverAddress, tid)] = transactionId; } simtime_t getRetryDeadline(MacAddress receiverAddress, Tid tid) const { auto it = addbaRetryDeadlines.find(std::make_pair(receiverAddress, tid)); return it == addbaRetryDeadlines.end() ? SIMTIME_MAX : it->second; } }; @@ -151,7 +153,6 @@ class TestPacketQueue : public queueing::IPacketQueue packets.erase(std::find(packets.begin(), packets.end(), packet)); notifyPacketRemoved(packet, PacketRemovalReason::REMOVED); } - virtual bool isPacketOrderPreserved() const override { return true; } virtual void removeAllPackets() override { auto removedPackets = packets; packets.clear(); @@ -261,8 +262,11 @@ class TestWrrScheduler : public queueing::WrrScheduler this->weights[i] = weights[i]; this->buckets[i] = buckets[i]; } + numProcessedPackets = 0; + processedTotalLength = b(0); } unsigned int getBucket(int index) const { return buckets[index]; } + int getNumProcessedPackets() const { return numProcessedPackets; } void release(Packet *packet) { take(packet); drop(packet); } }; @@ -328,6 +332,14 @@ class TestSignalListener : public cListener virtual void receiveSignal(cComponent *, simsignal_t, cObject *, cObject *) override { numSignals++; } }; +class TestOrderedSignalListener : public cListener +{ + public: + std::vector signals; + + virtual void receiveSignal(cComponent *, simsignal_t signal, cObject *, cObject *) override { signals.push_back(signal); } +}; + class TestPacketDropSignalListener : public cListener { public: @@ -533,15 +545,27 @@ class TestMsduAggregationPolicy : public IMsduAggregationPolicy public: int numCalls = 0; - virtual std::vector *computeAggregateFrames(queueing::IPacketQueue *) override { numCalls++; return nullptr; } + virtual std::vector *computeAggregateFrames(queueing::IPacketQueue *, Packet *, const std::function&) override { numCalls++; return nullptr; } }; class TestSelectedMsduAggregationPolicy : public IMsduAggregationPolicy { public: + int numCalls = 0; std::vector selectedPackets; - virtual std::vector *computeAggregateFrames(queueing::IPacketQueue *) override { return new std::vector(selectedPackets); } + virtual std::vector *computeAggregateFrames(queueing::IPacketQueue *, Packet *, const std::function&) override { numCalls++; return new std::vector(selectedPackets); } +}; + +class TestBasicMsduAggregationPolicy : public BasicMsduAggregationPolicy +{ + public: + void configure(b maximumSize) { + qOsCheck = true; + subframeNumThreshold = -1; + aggregationLengthThreshold = -1; + maxAMsduSize = maximumSize; + } }; class TestNonQosRecoveryProcedure : public NonQosRecoveryProcedure @@ -573,7 +597,6 @@ class TestOnlyPacketExtractor : public queueing::IPacketExtractor public: std::vector packets; - virtual bool isPacketOrderPreserved() const override { return true; } virtual Packet *findPacket(const PacketPredicate& predicate) const override { auto it = std::find_if(packets.begin(), packets.end(), predicate); return it == packets.end() ? nullptr : *it; @@ -768,6 +791,40 @@ auto triggerHeader = makeQosHeader(peer1, 3, SequenceNumberCyclic(4095)); ASSERT(fragmentedDataRequest->getStartingSequenceNumber() == SequenceNumberCyclic(101)); } +// A fragmented, transaction-tagged DELBA remains eligible through every +// nonfinal fragment. Aborting one fragment invalidates the transaction and asks +// the HCF callback to remove its sibling fragments. +{ + TestOriginatorHandler fragmentedDelbaHandler; + TestCallback fragmentedDelbaCallback; + auto firstDelba = makeShared(); + firstDelba->setReceiverAddress(peer1); + firstDelba->setTid(6); + firstDelba->setInitiator(true); + firstDelba->setFragmentNumber(0); + firstDelba->setMoreFragments(true); + auto finalDelba = staticPtrCast(firstDelba->dupShared()); + finalDelba->setFragmentNumber(1); + finalDelba->setMoreFragments(false); + Packet firstDelbaPacket("firstDelbaFragment", firstDelba); + Packet finalDelbaPacket("finalDelbaFragment", finalDelba); + firstDelbaPacket.addTag()->setTransactionId(77); + finalDelbaPacket.addTag()->setTransactionId(77); + fragmentedDelbaHandler.setPendingTeardownTransaction(peer1, 6, 77); + ASSERT(fragmentedDelbaHandler.isDelbaPending(&firstDelbaPacket, firstDelba)); + ASSERT(fragmentedDelbaHandler.processTransmittedDelba(&firstDelbaPacket, &fragmentedDelbaCallback) == nullptr); + ASSERT(fragmentedDelbaHandler.isDelbaPending(&finalDelbaPacket, finalDelba)); + ASSERT(fragmentedDelbaHandler.processTransmittedDelba(&finalDelbaPacket, &fragmentedDelbaCallback) == nullptr); + ASSERT(!fragmentedDelbaHandler.isDelbaPending(&finalDelbaPacket, finalDelba)); + + fragmentedDelbaHandler.setPendingTeardownTransaction(peer1, 6, 78); + firstDelbaPacket.getTagForUpdate()->setTransactionId(78); + ASSERT(fragmentedDelbaHandler.processAbortedDelba(&firstDelbaPacket, &fragmentedDelbaCallback) == nullptr); + ASSERT(!fragmentedDelbaHandler.isDelbaPending(&firstDelbaPacket, firstDelba)); + ASSERT(fragmentedDelbaCallback.cancelledTransactionIds.back() == 78); + ASSERT(fragmentedDelbaCallback.excludedPackets.back() == &firstDelbaPacket); +} + handler.processAcknowledgedDataFrame(&triggerPacket, triggerHeader, &originatorPolicy, &callback); auto request = dynamicPtrCast(callback.managementHeader); ASSERT(request != nullptr); @@ -797,7 +854,8 @@ ASSERT(handler.processReceivedAddbaResp(success, &originatorPolicy, &callback).a // IEEE Std 802.11-2024, 10.25.2 and Figure 11-32: a successful matching // response establishes the agreement even when local policy rejects the -// negotiated parameters; the initiator then tears it down with END_BA. +// negotiated parameters; the originator immediately deletes its local state +// and sends END_BA on a best-effort basis. { TestOriginatorHandler vetoHandler; TestCallback vetoCallback; @@ -807,22 +865,24 @@ ASSERT(handler.processReceivedAddbaResp(success, &originatorPolicy, &callback).a auto vetoHeader = makeQosHeader(peer3, 6, SequenceNumberCyclic(30)); vetoHandler.processAcknowledgedDataFrame(&vetoTrigger, vetoHeader, &vetoPolicy, &vetoCallback); auto vetoRequest = dynamicPtrCast(vetoCallback.managementHeader); + auto vetoTransactionId = vetoCallback.managementPacket->getTag()->getTransactionId(); vetoHandler.processTransmittedAddbaReq(vetoCallback.managementPacket, vetoRequest, &vetoPolicy, &vetoCallback); auto vetoResponse = makeResponse(peer3, 6, vetoRequest->getDialogToken(), 0); auto vetoResponseOutcome = vetoHandler.processReceivedAddbaResp(vetoResponse, &vetoPolicy, &vetoCallback); auto vetoAgreement = vetoResponseOutcome.agreement; ASSERT(vetoAgreement != nullptr); + ASSERT(vetoResponseOutcome.terminatedAgreement.get() == vetoAgreement); ASSERT(vetoAgreement->getIsAddbaResponseReceived()); - ASSERT(vetoAgreement->isTeardownPending()); + ASSERT(vetoHandler.getAgreement(peer3, 6) == nullptr); auto vetoDelba = vetoResponseOutcome.teardownDelba; ASSERT(vetoDelba != nullptr); ASSERT(vetoDelba->getInitiator()); ASSERT(vetoDelba->getReceiverAddress() == peer3); ASSERT(vetoDelba->getTid() == 6); ASSERT(vetoDelba->getReasonCode() == RC_END_BA); - auto vetoTerminated = vetoHandler.processTransmittedDelba(vetoDelba, &vetoCallback); - ASSERT(vetoTerminated != nullptr); - ASSERT(vetoTerminated->getIsAddbaResponseReceived()); + Packet vetoDelbaPacket("vetoDelba", vetoDelba); + vetoDelbaPacket.addTag()->setTransactionId(vetoTransactionId); + ASSERT(vetoHandler.isDelbaPending(&vetoDelbaPacket, vetoDelba)); ASSERT(vetoHandler.getAgreement(peer3, 6) == nullptr); ASSERT(vetoHandler.getRetryDeadline(peer3, 6) > simTime()); TestCallback vetoRetryCallback; @@ -831,8 +891,18 @@ ASSERT(handler.processReceivedAddbaResp(success, &originatorPolicy, &callback).a vetoHandler.processAcknowledgedDataFrame(&vetoRetryTrigger, vetoRetryHeader, &vetoPolicy, &vetoRetryCallback); ASSERT(vetoRetryCallback.managementPacket == nullptr); vetoHandler.allowRetryNow(peer3, 6); - vetoHandler.processAcknowledgedDataFrame(&vetoRetryTrigger, vetoRetryHeader, &vetoPolicy, &vetoRetryCallback); + auto obsoleteVetoTransactionId = vetoHandler.processAcknowledgedDataFrame(&vetoRetryTrigger, vetoRetryHeader, &vetoPolicy, &vetoRetryCallback); + ASSERT(obsoleteVetoTransactionId == vetoTransactionId); ASSERT(vetoRetryCallback.managementPacket != nullptr); + auto newerAgreement = vetoHandler.getAgreement(peer3, 6); + ASSERT(newerAgreement != nullptr); + ASSERT(newerAgreement->getTransactionId() != vetoTransactionId); + ASSERT(!vetoHandler.isDelbaPending(&vetoDelbaPacket, vetoDelba)); + ASSERT(vetoHandler.processAbortedDelba(&vetoDelbaPacket, &vetoCallback) == nullptr); + ASSERT(!vetoHandler.isDelbaPending(&vetoDelbaPacket, vetoDelba)); + auto vetoTerminated = vetoHandler.processTransmittedDelba(&vetoDelbaPacket, &vetoCallback); + ASSERT(vetoTerminated == nullptr); + ASSERT(vetoHandler.getAgreement(peer3, 6) == newerAgreement); } Packet rejectedTrigger("rejectedTrigger"); @@ -1054,7 +1124,8 @@ ASSERT(callback.excludedPackets.back() == unsentRequestPacket); auto transmittedDelba = makeShared(); transmittedDelba->setReceiverAddress(peer2); transmittedDelba->setTid(2); - auto terminatedPendingAgreement = transmittedDelbaHandler.processTransmittedDelba(transmittedDelba, &transmittedDelbaCallback); + Packet transmittedDelbaPacket("transmittedDelba", transmittedDelba); + auto terminatedPendingAgreement = transmittedDelbaHandler.processTransmittedDelba(&transmittedDelbaPacket, &transmittedDelbaCallback); ASSERT(terminatedPendingAgreement != nullptr); ASSERT(terminatedPendingAgreement->isPending()); if (transmittedDelbaHandler.getAgreement(peer2, 2) != nullptr || transmittedDelbaCallback.cancelledTransactionIds.size() != 1 || transmittedDelbaCallback.cancelledTransactionIds.back() != transmittedDelbaTransactionId) @@ -1500,9 +1571,89 @@ ASSERT(immediateRecipientHandler.getAgreement(6, peer1) == replacementImmediateA delete heldPacket; } -// A locally vetoed successful response emits Added before its initiator DELBA -// enters the queue. IEEE 802.11 teardown is terminal if that frame is dropped -// or removed synchronously, and the transition is idempotent. +// A locally vetoed successful response immediately restores data eligibility +// even while its best-effort DELBA remains queued. Once retry backoff expires, +// a replacement ADDBA invalidates the old DELBA without requiring a disposal +// callback from the queue. +{ + TestPacketQueue heldTeardownQueue; + auto heldTeardownOriginatorHandler = new TestOriginatorHandler(); + auto heldTeardownRecipientHandler = new RecipientBlockAckAgreementHandler(); + TestOriginatorPolicy heldTeardownOriginatorPolicy; + heldTeardownOriginatorPolicy.addbaAccepted = false; + TestRecipientPolicy heldTeardownRecipientPolicy; + TestOriginatorQosMacDataService heldTeardownDataService; + heldTeardownDataService.setFrameEligibilityFunction([heldTeardownOriginatorHandler](const Packet *packet) { + auto header = packet->peekAtFront(); + if (auto addbaReq = dynamicPtrCast(header)) + return heldTeardownOriginatorHandler->isAddbaRequestPending(packet, addbaReq); + if (auto delba = dynamicPtrCast(header)) + return heldTeardownOriginatorHandler->isDelbaPending(packet, delba); + auto dataHeader = dynamicPtrCast(header); + return dataHeader == nullptr || !heldTeardownOriginatorHandler->isAddbaResponsePending(dataHeader->getReceiverAddress(), dataHeader->getTid()); + }); + QosAckHandler heldTeardownAckHandler; + TestInProgressFrames heldTeardownInProgressFrames; + heldTeardownInProgressFrames.configure(&heldTeardownDataService, &heldTeardownAckHandler, &heldTeardownQueue); + TestEdcaf heldTeardownEdcaf; + heldTeardownEdcaf.pendingQueue = &heldTeardownQueue; + heldTeardownEdcaf.inProgressFrames = &heldTeardownInProgressFrames; + heldTeardownEdcaf.qosAckHandler = &heldTeardownAckHandler; + TestEdca heldTeardownEdca; + heldTeardownEdca.edcaf = &heldTeardownEdcaf; + TestHcf heldTeardownHcf; + heldTeardownHcf.configureBlockAckHandlers(heldTeardownOriginatorHandler, &heldTeardownOriginatorPolicy, heldTeardownRecipientHandler, &heldTeardownRecipientPolicy); + heldTeardownHcf.configureEligibilityIndex(&heldTeardownEdca, &heldTeardownDataService); + heldTeardownHcf.observePendingQueue(&heldTeardownQueue); + TestCallback heldTeardownCallback; + Packet heldTeardownTrigger("heldTeardownTrigger"); + auto heldTeardownTriggerHeader = makeQosHeader(peer1, 7, SequenceNumberCyclic(210)); + heldTeardownOriginatorHandler->processAcknowledgedDataFrame(&heldTeardownTrigger, heldTeardownTriggerHeader, &heldTeardownOriginatorPolicy, &heldTeardownCallback); + auto heldTeardownRequest = dynamicPtrCast(heldTeardownCallback.managementHeader); + heldTeardownOriginatorHandler->processTransmittedAddbaReq(heldTeardownCallback.managementPacket, heldTeardownRequest, &heldTeardownOriginatorPolicy, &heldTeardownCallback); + auto heldDataPacket = new Packet("heldTeardownData", makeQosHeader(peer1, 7, SequenceNumberCyclic(211))); + heldTeardownQueue.enqueuePacket(heldDataPacket); + heldTeardownHcf.trackFrame(heldDataPacket, AC_BK); + ASSERT(!heldTeardownDataService.isFrameEligible(heldDataPacket)); + TestOrderedSignalListener transitionListener; + heldTeardownHcf.subscribe(Hcf::blockAckAgreementAddedSignal, &transitionListener); + heldTeardownHcf.subscribe(Hcf::blockAckAgreementDeletedSignal, &transitionListener); + auto heldTeardownResponse = makeResponse(peer1, 7, heldTeardownRequest->getDialogToken(), 0); + heldTeardownHcf.processReceivedManagementFrame(heldTeardownResponse); + ASSERT(transitionListener.signals == std::vector({ Hcf::blockAckAgreementAddedSignal, Hcf::blockAckAgreementDeletedSignal })); + ASSERT(heldTeardownOriginatorHandler->getAgreement(peer1, 7) == nullptr); + ASSERT(heldTeardownHcf.numRebuildEligibilityCalls > 0); + ASSERT(heldTeardownDataService.isFrameEligible(heldDataPacket)); + ASSERT(heldTeardownHcf.hasFrame(AC_BK)); + ASSERT(heldTeardownQueue.getNumPackets() == 2); + auto heldDelbaPacket = heldTeardownQueue.getPacket(1); + auto heldDelba = dynamicPtrCast(heldDelbaPacket->peekAtFront()); + ASSERT(heldDelba != nullptr); + ASSERT(heldTeardownOriginatorHandler->isDelbaPending(heldDelbaPacket, heldDelba)); + OriginatorQosAckPolicy heldTeardownAckPolicy; + ASSERT(heldTeardownAckPolicy.computeAckPolicy(heldDataPacket, heldDataPacket->peekAtFront(), heldTeardownOriginatorHandler->getAgreement(peer1, 7)) == NORMAL_ACK); + heldTeardownOriginatorHandler->allowRetryNow(peer1, 7); + TestCallback heldTeardownRetryCallback; + Packet heldTeardownRetryTrigger("heldTeardownRetryTrigger"); + auto heldTeardownRetryHeader = makeQosHeader(peer1, 7, SequenceNumberCyclic(212)); + auto obsoleteHeldTeardownTransactionId = heldTeardownOriginatorHandler->processAcknowledgedDataFrame(&heldTeardownRetryTrigger, heldTeardownRetryHeader, &heldTeardownOriginatorPolicy, &heldTeardownRetryCallback); + ASSERT(heldTeardownRetryCallback.managementPacket != nullptr); + ASSERT(obsoleteHeldTeardownTransactionId == heldDelbaPacket->getTag()->getTransactionId()); + heldTeardownHcf.cancelTransaction(obsoleteHeldTeardownTransactionId); + ASSERT(heldTeardownQueue.getNumPackets() == 1); + ASSERT(heldTeardownQueue.getPacket(0) == heldDataPacket); + heldTeardownHcf.rebuildEligibilityIndex(); + heldTeardownHcf.unsubscribe(Hcf::blockAckAgreementAddedSignal, &transitionListener); + heldTeardownHcf.unsubscribe(Hcf::blockAckAgreementDeletedSignal, &transitionListener); + while (!heldTeardownQueue.isEmpty()) { + auto packet = heldTeardownQueue.getPacket(0); + heldTeardownQueue.removePacket(packet); + delete packet; + } +} + +// Synchronous DELBA drop/removal clears the separate teardown transaction, and +// the already-completed agreement transition remains idempotent. for (auto removalReason : { queueing::IPacketQueue::PacketRemovalReason::DROPPED, queueing::IPacketQueue::PacketRemovalReason::REMOVED }) { TestImmediateRemovalQueue teardownQueue; teardownQueue.removalReason = removalReason; @@ -1919,7 +2070,14 @@ ASSERT(!dataService.hasEligibleFrame(&mixedQueue)); packetExtractor.packets = { packet }; TestPriorityScheduler scheduler; scheduler.configure(std::vector({ nullptr }), std::vector({ &packetExtractor })); - ASSERT(scheduler.getNumPackets() == -1); + bool aggregateQueryFailed = false; + try { + scheduler.getNumPackets(); + } + catch (cRuntimeError& error) { + aggregateQueryFailed = std::string(error.what()).find("getNumPackets") != std::string::npos && std::string(error.what()).find("IPacketCollection") != std::string::npos; + } + ASSERT(aggregateQueryFailed); ASSERT(scheduler.findPacket([](const Packet *) { return true; }) == packet); auto dequeuedPacket = scheduler.dequeuePacket([](const Packet *) { return true; }); ASSERT(dequeuedPacket == packet); @@ -1927,6 +2085,31 @@ ASSERT(!dataService.hasEligibleFrame(&mixedQueue)); scheduler.release(packet); delete packet; } +// Collection-only providers remain usable for aggregate queries and fail only +// when predicate extraction is actually requested. +{ + TestPacketQueue firstCollection; + TestPacketQueue collectionOnly; + auto firstPacket = new Packet("extractablePacket", makeShared(B(1))); + auto collectionOnlyPacket = new Packet("collectionOnlyPacket", makeShared(B(1))); + firstCollection.packets = { firstPacket }; + collectionOnly.packets = { collectionOnlyPacket }; + TestPriorityScheduler scheduler; + scheduler.configure(std::vector({ &firstCollection, &collectionOnly }), std::vector({ &firstCollection, nullptr })); + ASSERT(scheduler.getNumPackets() == 2); + bool extractionFailed = false; + try { + scheduler.findPacket([](const Packet *) { return true; }); + } + catch (cRuntimeError& error) { + extractionFailed = std::string(error.what()).find("findPacket") != std::string::npos && std::string(error.what()).find("IPacketExtractor") != std::string::npos; + } + ASSERT(extractionFailed); + firstCollection.removePacket(firstPacket); + collectionOnly.removePacket(collectionOnlyPacket); + delete firstPacket; + delete collectionOnlyPacket; +} { TestPacketQueue firstQueue; TestPacketQueue secondQueue; @@ -2079,9 +2262,45 @@ ASSERT(!dataService.hasEligibleFrame(&mixedQueue)); delete frames; } -// PacketFlowBase cannot advertise A-MSDU-safe order because processing happens -// after selection and may transform aggregation-relevant packet contents. -// Ordinary predicate extraction still runs the flow processing exactly once. +// The built-in policy is anchored on the provider-selected candidate. Held +// enumeration members do not hide later compatible frames, while an oversized +// anchor falls back to ordinary dequeue without aggregating its followers. +{ + auto makeAggregatePacket = [&](const char *name, SequenceNumberCyclic sequenceNumber, int payloadLength) { + auto header = makeQosHeader(peer1, 1, sequenceNumber); + header->setTransmitterAddress(peer2); + auto packet = new Packet(name, header); + packet->insertAtBack(makeShared(B(payloadLength))); + packet->insertAtBack(makeShared()); + return packet; + }; + TestPacketQueue queue; + auto anchor = makeAggregatePacket("basicPolicyAnchor", SequenceNumberCyclic(20), 8); + auto held = makeAggregatePacket("basicPolicyHeld", SequenceNumberCyclic(21), 8); + auto compatible = makeAggregatePacket("basicPolicyCompatible", SequenceNumberCyclic(22), 8); + queue.packets = { anchor, held, compatible }; + TestBasicMsduAggregationPolicy policy; + policy.configure(B(4065)); + auto selected = policy.computeAggregateFrames(&queue, anchor, [held](const Packet *packet) { return packet != held; }); + ASSERT(selected != nullptr); + ASSERT(selected->size() == 2); + ASSERT(selected->at(0) == anchor); + ASSERT(selected->at(1) == compatible); + delete selected; + auto oversizedAnchor = makeAggregatePacket("basicPolicyOversizedAnchor", SequenceNumberCyclic(23), 64); + auto smallFollower1 = makeAggregatePacket("basicPolicySmallFollower1", SequenceNumberCyclic(24), 1); + auto smallFollower2 = makeAggregatePacket("basicPolicySmallFollower2", SequenceNumberCyclic(25), 1); + queue.packets = { oversizedAnchor, smallFollower1, smallFollower2 }; + policy.configure(B(30)); + ASSERT(policy.computeAggregateFrames(&queue, oversizedAnchor, [](const Packet *) { return true; }) == nullptr); + queue.packets.clear(); + for (auto packet : { anchor, held, compatible, oversizedAnchor, smallFollower1, smallFollower2 }) + delete packet; +} + +// A flow provider may expose an enumeration order different from its +// scheduling behavior. Candidate-aware aggregation still extracts every +// selected member through the provider and runs its processing exactly once. { auto leafQueue = createPacketQueue("aggregationFlowLeaf"); auto flowModule = cModuleType::get("inet.queueing.common.BackPressureBarrier")->create("aggregationFlow", this); @@ -2105,20 +2324,16 @@ ASSERT(!dataService.hasEligibleFrame(&mixedQueue)); auto flowExtractor = check_and_cast(flowModule); TestCompoundPacketQueue compoundQueue; compoundQueue.configure(flowCollection, flowExtractor); - TestMsduAggregationPolicy aggregationPolicy; + TestSelectedMsduAggregationPolicy aggregationPolicy; + aggregationPolicy.selectedPackets = { firstSubframe, secondSubframe }; TestOriginatorQosMacDataService service; service.enableMsduAggregationPolicy(&aggregationPolicy); auto frames = service.extractFramesToTransmit(&compoundQueue); - if (frames == nullptr || frames->size() != 1 || frames->front() != firstSubframe || aggregationPolicy.numCalls != 0 || flowListener.numSignals != 1 || leafQueue->getNumPackets() != 1) - throw cRuntimeError("PacketFlowBase incorrectly advertised A-MSDU-safe ordering"); + if (frames == nullptr || frames->size() != 1 || !frames->front()->peekAtFront()->getAMsduPresent() || aggregationPolicy.numCalls != 1 || flowListener.numSignals != 2 || leafQueue->getNumPackets() != 0) + throw cRuntimeError("A-MSDU aggregation bypassed PacketFlowBase extraction"); service.release(frames->front()); delete frames->front(); delete frames; - auto remainingPacket = flowExtractor->dequeuePacket([secondSubframe](const Packet *packet) { return packet == secondSubframe; }); - if (remainingPacket != secondSubframe || flowListener.numSignals != 2 || leafQueue->getNumPackets() != 0) - throw cRuntimeError("Predicate dequeue bypassed PacketFlowBase processing"); - take(remainingPacket); - delete remainingPacket; flowModule->unsubscribe(packetPulledOutSignal, &flowListener); flowModule->callFinish(); flowModule->deleteModule(); @@ -2126,45 +2341,63 @@ ASSERT(!dataService.hasEligibleFrame(&mixedQueue)); leafQueue->deleteModule(); } -// End-to-end compound extraction uses the provider's reverse-priority choice. -// A-MSDU aggregation is conservatively skipped when queue enumeration has a -// different head, so it cannot remove a held frame behind the scheduler. +// End-to-end compound extraction uses the provider's reverse-priority choice +// as the aggregate anchor while leaving an ineligible higher-level frame held. { + auto makeAggregatePacket = [&](const char *name, SequenceNumberCyclic sequenceNumber) { + auto header = makeQosHeader(peer1, 1, sequenceNumber); + header->setTransmitterAddress(peer2); + auto packet = new Packet(name, header); + packet->insertAtBack(makeShared(B(8))); + packet->insertAtBack(makeShared()); + return packet; + }; TestPacketQueue firstQueue; TestPacketQueue secondQueue; - auto heldPacket = new Packet("compoundHeldPacket", makeQosHeader(peer1, 1, SequenceNumberCyclic(1))); - auto selectedPacket = new Packet("compoundSelectedPacket", makeQosHeader(peer1, 1, SequenceNumberCyclic(2))); + auto heldPacket = makeAggregatePacket("compoundHeldPacket", SequenceNumberCyclic(1)); + auto selectedPacket = makeAggregatePacket("compoundSelectedPacket", SequenceNumberCyclic(2)); + auto companionPacket = makeAggregatePacket("compoundCompanionPacket", SequenceNumberCyclic(3)); firstQueue.packets = { heldPacket }; - secondQueue.packets = { selectedPacket }; + secondQueue.packets = { selectedPacket, companionPacket }; TestPriorityScheduler scheduler; scheduler.configure({ &firstQueue, &secondQueue }, true); TestCompoundPacketQueue compoundQueue; compoundQueue.configure(&scheduler, &scheduler); - TestMsduAggregationPolicy aggregationPolicy; + TestSelectedMsduAggregationPolicy aggregationPolicy; + aggregationPolicy.selectedPackets = { selectedPacket, companionPacket }; TestOriginatorQosMacDataService service; service.enableMsduAggregationPolicy(&aggregationPolicy); service.setFrameEligibilityFunction([heldPacket](const Packet *packet) { return packet != heldPacket; }); auto frames = service.extractFramesToTransmit(&compoundQueue); ASSERT(frames != nullptr); ASSERT(frames->size() == 1); - ASSERT(frames->front() == selectedPacket); - ASSERT(aggregationPolicy.numCalls == 0); + ASSERT(frames->front()->peekAtFront()->getAMsduPresent()); + ASSERT(aggregationPolicy.numCalls == 1); ASSERT(firstQueue.getNumPackets() == 1); - service.release(selectedPacket); - delete selectedPacket; + ASSERT(secondQueue.isEmpty()); + service.release(frames->front()); + delete frames->front(); delete frames; firstQueue.removePacket(heldPacket); delete heldPacket; } -// Even when a WRR provider's current choice is the collection head, A-MSDU -// aggregation must not enumerate subsequent members outside WRR policy. +// A-MSDU aggregation may select multiple WRR members, but each exact dequeue +// must still update WRR accounting. { + auto makeAggregatePacket = [&](const char *name, SequenceNumberCyclic sequenceNumber) { + auto header = makeQosHeader(peer1, 1, sequenceNumber); + header->setTransmitterAddress(peer2); + auto packet = new Packet(name, header); + packet->insertAtBack(makeShared(B(8))); + packet->insertAtBack(makeShared()); + return packet; + }; TestPacketQueue firstQueue; TestPacketQueue secondQueue; - auto selectedPacket = new Packet("wrrAggregateSelectedPacket", makeQosHeader(peer1, 1, SequenceNumberCyclic(3))); - auto sameInputPacket = new Packet("wrrAggregateSameInputPacket", makeQosHeader(peer1, 1, SequenceNumberCyclic(4))); - auto otherInputPacket = new Packet("wrrAggregateOtherInputPacket", makeQosHeader(peer1, 1, SequenceNumberCyclic(5))); + auto selectedPacket = makeAggregatePacket("wrrAggregateSelectedPacket", SequenceNumberCyclic(3)); + auto sameInputPacket = makeAggregatePacket("wrrAggregateSameInputPacket", SequenceNumberCyclic(4)); + auto otherInputPacket = makeAggregatePacket("wrrAggregateOtherInputPacket", SequenceNumberCyclic(5)); firstQueue.packets = { selectedPacket, sameInputPacket }; secondQueue.packets = { otherInputPacket }; TestWrrScheduler scheduler; @@ -2172,23 +2405,24 @@ ASSERT(!dataService.hasEligibleFrame(&mixedQueue)); TestCompoundPacketQueue compoundQueue; compoundQueue.configure(&scheduler, &scheduler); ASSERT(compoundQueue.getPacket(0) == selectedPacket); - ASSERT(!compoundQueue.isPacketOrderPreserved()); - TestMsduAggregationPolicy aggregationPolicy; + TestSelectedMsduAggregationPolicy aggregationPolicy; + aggregationPolicy.selectedPackets = { selectedPacket, sameInputPacket }; TestOriginatorQosMacDataService service; service.enableMsduAggregationPolicy(&aggregationPolicy); auto frames = service.extractFramesToTransmit(&compoundQueue); ASSERT(frames != nullptr); ASSERT(frames->size() == 1); - ASSERT(frames->front() == selectedPacket); - ASSERT(aggregationPolicy.numCalls == 0); - ASSERT(firstQueue.getNumPackets() == 1); + ASSERT(frames->front()->peekAtFront()->getAMsduPresent()); + ASSERT(aggregationPolicy.numCalls == 1); + ASSERT(firstQueue.isEmpty()); ASSERT(secondQueue.getNumPackets() == 1); - service.release(selectedPacket); - delete selectedPacket; + ASSERT(scheduler.getNumProcessedPackets() == 2); + ASSERT(scheduler.getBucket(0) == 0); + ASSERT(scheduler.getBucket(1) == 1); + service.release(frames->front()); + delete frames->front(); delete frames; - firstQueue.removePacket(sameInputPacket); secondQueue.removePacket(otherInputPacket); - delete sameInputPacket; delete otherInputPacket; } From e029b1d07480ea753fb8f2581f0d18883b9e9c10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Wed, 19 Aug 2026 14:13:14 +0200 Subject: [PATCH 09/14] Fix Block Ack retry and queue eligibility edge cases Keep transaction-tagged initiator DELBA frames eligible until a final-fragment acknowledgement or terminal abort, allowing failed management transmissions to follow the normal retry and retry-limit paths. Make teardown abort outcomes explicit in HCF and clean up sibling frames and acknowledgement state exactly once. Preserve same-flow MSDU ordering during A-MSDU selection, make extraction contract validation exception-safe, propagate destructive drops through nested compound queues exactly once, reject unsupported PacketBuffer ownership before mutation, and make fragment tag propagation robust. Document the scheduler aggregate-query compatibility change. Add focused coverage for DELBA retry, acknowledgement and retry-limit cleanup; malformed packet extractors; nested queue callbacks; PacketBuffer ownership; scheduler capabilities; and fragment tag propagation. Validated with release and debug builds, focused Ieee80211AddbaTransaction_1 tests in both modes, git diff --check, and independent architecture and IEEE 802.11 semantic review. Fingerprint baselines are intentionally unchanged. --- WHATSNEW | 20 +- .../aggregation/BasicMsduAggregationPolicy.cc | 27 +- .../OriginatorBlockAckAgreementHandler.cc | 32 +- .../OriginatorBlockAckAgreementHandler.h | 3 +- .../IOriginatorBlockAckAgreementHandler.h | 5 +- .../ieee80211/mac/coordinationfunction/Hcf.cc | 14 +- .../originator/OriginatorMacDataService.cc | 2 +- .../originator/OriginatorQosMacDataService.cc | 46 +- src/inet/queueing/buffer/PacketBuffer.cc | 7 +- .../queueing/queue/CompoundPacketQueueBase.cc | 32 +- .../queueing/queue/CompoundPacketQueueBase.h | 4 + tests/unit/Ieee80211AddbaTransaction_1.test | 443 +++++++++++++++++- 12 files changed, 575 insertions(+), 60 deletions(-) diff --git a/WHATSNEW b/WHATSNEW index 94556e59e6c..f835ae6b4c6 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -136,13 +136,22 @@ Notable backward incompatible changes are the following: ordinary IPassivePacketSource inputs; they require IPacketCollection only when collection/aggregate access is used and IPacketExtractor only when predicate extraction is used, reporting the unsupported operation lazily. + PriorityScheduler aggregate queries no longer return -1 when an input lacks + IPacketCollection; getNumPackets() and getTotalLength() now report that + unsupported operation with cRuntimeError. Update callers that treated -1 as + an unknown aggregate size, or connect collection-capable providers. A-MSDU policies now receive the provider-selected anchor and frame-eligibility predicate. Additional policy-selected members are removed through exact predicate dequeues, preserving scheduler/flow accounting without requiring collection enumeration order to match scheduling order. + BasicMsduAggregationPolicy conservatively considers only collection members + after the anchor and never bypasses an earlier blocked same-flow member. IPacketBuffer::ICallback also provides an optional handlePacketDropped() notification. Shared buffers invoke it only after all victims selected by one - overload operation have been detached from their owning queues. + overload operation have been detached from their owning queues. Compound + queues propagate destructive drops through arbitrary non-queue wrappers and + nested compound queues exactly once. PacketBuffer rejects packets owned by a + cPacketQueue whose owner cannot participate in the buffer callback contract. 8. Block Ack DELBA agreement ownership @@ -161,9 +170,12 @@ Notable backward incompatible changes are the following: terminate a newer peer/TID agreement. The replacement setup reports that obsolete identity so Hcf also removes all of its queued or in-progress packets. Originator DELBA transmission handlers now receive the full Packet so this - identity is retained. Custom handler implementations and callers must adopt - the new signatures and return types; a null result means the DELBA did not - remove an agreement. + identity is retained. A transaction-tagged initiator DELBA remains eligible + across MAC retries and is retired only when its final fragment is acknowledged + or the transaction is terminally aborted. Custom handler implementations and + callers must adopt processAcknowledgedDelba() and the boolean + processAbortedDelba() outcome; processTransmittedDelba() still returns the + agreement removed by an untagged DELBA, or null when none was removed. The originator policy parameter `addbaFailureTimeout` was renamed to `addbaResponseTimeout`; update custom configurations accordingly. diff --git a/src/inet/linklayer/ieee80211/mac/aggregation/BasicMsduAggregationPolicy.cc b/src/inet/linklayer/ieee80211/mac/aggregation/BasicMsduAggregationPolicy.cc index 1f667253f6b..951f011c5ee 100644 --- a/src/inet/linklayer/ieee80211/mac/aggregation/BasicMsduAggregationPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/aggregation/BasicMsduAggregationPolicy.cc @@ -60,7 +60,6 @@ std::vector *BasicMsduAggregationPolicy::computeAggregateFrames(queuei { Enter_Method("computeAggregateFrames"); ASSERT(candidate != nullptr); - ASSERT(queue->findPacket([candidate](const Packet *packet) { return packet == candidate; }) == candidate); b aMsduLength = b(0); int candidateIndex = -1; for (int i = 0; i < queue->getNumPackets(); i++) { @@ -72,11 +71,21 @@ std::vector *BasicMsduAggregationPolicy::computeAggregateFrames(queuei if (firstHeader == nullptr || !isFrameEligible(candidate)) return nullptr; auto frames = new std::vector(); + auto hasSameFlow = [&](const Ptr& dataHeader) { + return dataHeader != nullptr && dataHeader->getTid() == firstHeader->getTid() && + dataHeader->getReceiverAddress() == firstHeader->getReceiverAddress() && + dataHeader->getTransmitterAddress() == firstHeader->getTransmitterAddress(); + }; auto appendIfEligible = [&](Packet *dataPacket) { - if (!isFrameEligible(dataPacket)) - return false; const auto& dataHeader = dynamicPtrCast(dataPacket->peekAtFront()); - if (dataHeader == nullptr) + if (!hasSameFlow(dataHeader)) + return true; + // IEEE Std 802.11-2024, 5.1.3: preserve the ordering of MSDUs with the + // same traffic identifier. Enumeration order is the conservative + // intra-flow order for this built-in policy. Never overtake a + // same-flow packet which is held by transaction eligibility or cannot + // fit in the current A-MSDU. + if (!isFrameEligible(dataPacket)) return false; const auto& dataTrailer = dataPacket->peekAtBack(B(4)); if (!isEligible(dataPacket, dataHeader, dataTrailer, firstHeader, aMsduLength)) @@ -89,11 +98,11 @@ std::vector *BasicMsduAggregationPolicy::computeAggregateFrames(queuei delete frames; return nullptr; } - int numPackets = queue->getNumPackets(); - for (int offset = 1; offset < numPackets; offset++) { - auto dataPacket = queue->getPacket((candidateIndex + offset) % numPackets); - appendIfEligible(dataPacket); - } + // Do not wrap around: providers may schedule in an order different from + // their IPacketCollection enumeration (for example reverse priority). + for (int i = candidateIndex + 1; i < queue->getNumPackets(); i++) + if (!appendIfEligible(queue->getPacket(i))) + break; if (frames->size() <= 1 || !isAggregationPossible(frames->size(), aMsduLength.get())) { EV_DEBUG << "A-MSDU aggregation is not possible, collected " << frames->size() << " packets.\n"; delete frames; diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc index 37b8ab47a84..b4b19afa972 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc @@ -338,8 +338,12 @@ std::unique_ptr OriginatorBlockAckAgreementHandler: auto it = pendingTeardownTransactionIds.find(std::make_pair(delba->getReceiverAddress(), delba->getTid())); if (it == pendingTeardownTransactionIds.end() || it->second != transactionTag->getTransactionId()) return nullptr; - if (!delba->getMoreFragments()) - pendingTeardownTransactionIds.erase(it); + // IEEE Std 802.11-2024, 11.5.3.2: teardown is performed by + // transmitting DELBA. IEEE Std 802.11-2024, 10.23.2.12.1 and + // 10.3.4.4 require unsuccessful MMPDU attempts to be retried until + // success or the applicable retry limit. Keep the local transaction + // live across ordinary MAC retries and retire it only after the final + // fragment is acknowledged or the frame is terminally aborted. return nullptr; } auto agreement = getAgreement(delba->getReceiverAddress(), delba->getTid()); @@ -352,11 +356,28 @@ std::unique_ptr OriginatorBlockAckAgreementHandler: return terminatedAgreement; } -std::unique_ptr OriginatorBlockAckAgreementHandler::processAbortedDelba(Packet *packet, IBlockAckAgreementHandlerCallback *callback) +bool OriginatorBlockAckAgreementHandler::processAcknowledgedDelba(Packet *packet, IBlockAckAgreementHandlerCallback *callback) +{ + auto delba = packet->peekAtFront(); + if (!delba->getInitiator() || delba->getMoreFragments()) + return false; + auto transactionTag = packet->findTag(); + if (transactionTag == nullptr) + return false; + auto it = pendingTeardownTransactionIds.find(std::make_pair(delba->getReceiverAddress(), delba->getTid())); + if (it == pendingTeardownTransactionIds.end() || it->second != transactionTag->getTransactionId()) + return false; + auto transactionId = it->second; + pendingTeardownTransactionIds.erase(it); + callback->cancelAddbaTransaction(transactionId, packet); + return true; +} + +bool OriginatorBlockAckAgreementHandler::processAbortedDelba(Packet *packet, IBlockAckAgreementHandlerCallback *callback) { auto delba = packet->peekAtFront(); if (!delba->getInitiator()) - return nullptr; + return false; auto transactionTag = packet->findTag(); if (transactionTag != nullptr) { auto it = pendingTeardownTransactionIds.find(std::make_pair(delba->getReceiverAddress(), delba->getTid())); @@ -364,9 +385,10 @@ std::unique_ptr OriginatorBlockAckAgreementHandler: auto transactionId = it->second; pendingTeardownTransactionIds.erase(it); callback->cancelAddbaTransaction(transactionId, packet); + return true; } } - return nullptr; + return false; } std::unique_ptr OriginatorBlockAckAgreementHandler::processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h index 899cb722d61..8be627c5135 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h @@ -50,7 +50,8 @@ class INET_API OriginatorBlockAckAgreementHandler : public IOriginatorBlockAckAg virtual OriginatorBlockAckAgreementResponse processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; virtual std::unique_ptr processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; virtual std::unique_ptr processTransmittedDelba(Packet *packet, IBlockAckAgreementHandlerCallback *callback) override; - virtual std::unique_ptr processAbortedDelba(Packet *packet, IBlockAckAgreementHandlerCallback *callback) override; + virtual bool processAcknowledgedDelba(Packet *packet, IBlockAckAgreementHandlerCallback *callback) override; + virtual bool processAbortedDelba(Packet *packet, IBlockAckAgreementHandlerCallback *callback) override; virtual void blockAckAgreementExpired(IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) override; virtual void addbaResponseTimeoutExpired(IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) override; diff --git a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h index 4e488b62996..b00e79a3e72 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h @@ -43,7 +43,10 @@ class INET_API IOriginatorBlockAckAgreementHandler virtual OriginatorBlockAckAgreementResponse processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; virtual std::unique_ptr processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; virtual std::unique_ptr processTransmittedDelba(Packet *packet, IBlockAckAgreementHandlerCallback *callback) = 0; - virtual std::unique_ptr processAbortedDelba(Packet *packet, IBlockAckAgreementHandlerCallback *callback) = 0; + // Returns true when the packet completed or aborted its tagged teardown + // transaction and sibling packets were cancelled through the callback. + virtual bool processAcknowledgedDelba(Packet *packet, IBlockAckAgreementHandlerCallback *callback) = 0; + virtual bool processAbortedDelba(Packet *packet, IBlockAckAgreementHandlerCallback *callback) = 0; virtual void blockAckAgreementExpired(IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) = 0; virtual void addbaResponseTimeoutExpired(IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) = 0; diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc index 0c944c02d4a..bae8e004f06 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc @@ -136,13 +136,9 @@ bool Hcf::processDroppedBlockAckTeardownFrame(Packet *packet) { if (originatorBlockAckAgreementHandler) { auto delba = dynamicPtrCast(packet->peekAtFront()); - if (delba != nullptr) { - auto agreement = originatorBlockAckAgreementHandler->processAbortedDelba(packet, this); - if (agreement != nullptr) { - emit(blockAckAgreementDeletedSignal, agreement.get()); - rebuildPendingFrameEligibility(); - return true; - } + if (delba != nullptr && originatorBlockAckAgreementHandler->processAbortedDelba(packet, this)) { + rebuildPendingFrameEligibility(); + return true; } } return false; @@ -845,6 +841,10 @@ void Hcf::originatorProcessReceivedControlFrame(Packet *packet, const Ptr(lastTransmittedHeader); edcaf->getAckHandler()->processReceivedAck(ackFrame, lastTransmittedDataOrMgmtHeader); + if (auto delba = dynamicPtrCast(lastTransmittedHeader)) { + if (delba->getInitiator() && originatorBlockAckAgreementHandler != nullptr && originatorBlockAckAgreementHandler->processAcknowledgedDelba(lastTransmittedPacket, this)) + rebuildPendingFrameEligibility(); + } edcaf->getInProgressFrames()->dropFrame(lastTransmittedPacket); edcaf->getAckHandler()->dropFrame(lastTransmittedDataOrMgmtHeader); if (auto dataHeader = dynamicPtrCast(lastTransmittedHeader)) { diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.cc b/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.cc index 4db58ed7e98..4bbfc0ea6d3 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorMacDataService.cc @@ -39,7 +39,7 @@ std::vector *OriginatorMacDataService::fragmentIfNeeded(Packet *frame) auto fragmentFrames = fragmentation->fragmentFrame(frame, fragmentSizes); if (hasTransactionTag) for (auto fragment : *fragmentFrames) - fragment->addTag()->setTransactionId(transactionId); + fragment->addTagIfAbsent()->setTransactionId(transactionId); return fragmentFrames; } return nullptr; diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc index 25d9ff71b41..87318d18c30 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc @@ -8,7 +8,8 @@ #include "inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.h" #include -#include +#include +#include #include "inet/linklayer/ieee80211/mac/aggregation/MpduAggregation.h" #include "inet/linklayer/ieee80211/mac/aggregation/MsduAggregation.h" @@ -37,45 +38,52 @@ void OriginatorQosMacDataService::initialize() Packet *OriginatorQosMacDataService::aMsduAggregateIfNeeded(queueing::IPacketQueue *pendingQueue, Packet *candidate) { auto predicate = [this](const Packet *packet) { return isFrameEligible(packet); }; - auto subframes = aMsduAggregationPolicy->computeAggregateFrames(pendingQueue, candidate, predicate); + std::unique_ptr> subframes(aMsduAggregationPolicy->computeAggregateFrames(pendingQueue, candidate, predicate)); if (subframes) { - if (subframes->size() < 2 || subframes->front() != candidate) { - delete subframes; + if (subframes->size() < 2 || subframes->front() != candidate) throw cRuntimeError("A-MSDU policy must return at least two frames with the selected candidate first"); - } - std::set uniqueFrames; + std::unordered_set availableFrames; + for (int i = 0; i < pendingQueue->getNumPackets(); i++) + availableFrames.insert(pendingQueue->getPacket(i)); + std::unordered_set uniqueFrames; for (auto subframe : *subframes) { - if (subframe == nullptr || !uniqueFrames.insert(subframe).second || !isFrameEligible(subframe) || pendingQueue->findPacket([subframe](const Packet *packet) { return packet == subframe; }) != subframe) { - delete subframes; + if (subframe == nullptr || !uniqueFrames.insert(subframe).second || availableFrames.find(subframe) == availableFrames.end() || !isFrameEligible(subframe)) throw cRuntimeError("A-MSDU policy returned a frame that is unavailable, ineligible, or duplicated"); - } } struct AggregateFrameState { Tid tid; MacAddress receiver; MacAddress transmitter; MacAddress address3; MacAddress address4; int type; bool toDS; bool fromDS; b dataLength; b headerLength; b trailerLength; }; std::vector states; for (auto subframe : *subframes) { auto dataHeader = dynamicPtrCast(subframe->peekAtFront()); auto dataTrailer = subframe->peekAtBack(B(4)); - if (dataHeader == nullptr || dataTrailer == nullptr) { - delete subframes; + if (dataHeader == nullptr || dataTrailer == nullptr) throw cRuntimeError("A-MSDU policy selected a frame without a valid data header/trailer"); - } states.push_back({static_cast(dataHeader->getTid()), dataHeader->getReceiverAddress(), dataHeader->getTransmitterAddress(), dataHeader->getAddress3(), dataHeader->getAddress4(), dataHeader->getType(), dataHeader->getToDS(), dataHeader->getFromDS(), subframe->getDataLength(), dataHeader->getChunkLength(), dataTrailer->getChunkLength()}); + } + std::vector> extractedSubframes; + extractedSubframes.reserve(subframes->size()); + for (auto subframe : *subframes) { auto dequeuedSubframe = pendingQueue->dequeuePacket([subframe](const Packet *packet) { return packet == subframe; }); - if (dequeuedSubframe != subframe) + if (dequeuedSubframe != subframe) { + bool alreadyExtracted = std::any_of(extractedSubframes.begin(), extractedSubframes.end(), [dequeuedSubframe](const auto& extractedSubframe) { return extractedSubframe.get() == dequeuedSubframe; }); + if (dequeuedSubframe != nullptr && !alreadyExtracted) { + take(dequeuedSubframe); + extractedSubframes.emplace_back(dequeuedSubframe); + } throw cRuntimeError("A-MSDU policy-selected subframe is no longer available in scheduling order"); + } take(dequeuedSubframe); + extractedSubframes.emplace_back(dequeuedSubframe); } for (size_t i = 0; i < subframes->size(); i++) { auto dataHeader = dynamicPtrCast((*subframes)[i]->peekAtFront()); auto dataTrailer = (*subframes)[i]->peekAtBack(B(4)); - if (dataHeader == nullptr || dataTrailer == nullptr || dataHeader->getTid() != states[i].tid || dataHeader->getReceiverAddress() != states[i].receiver || dataHeader->getTransmitterAddress() != states[i].transmitter || dataHeader->getAddress3() != states[i].address3 || dataHeader->getAddress4() != states[i].address4 || dataHeader->getType() != states[i].type || dataHeader->getToDS() != states[i].toDS || dataHeader->getFromDS() != states[i].fromDS || (*subframes)[i]->getDataLength() != states[i].dataLength || dataHeader->getChunkLength() != states[i].headerLength || dataTrailer->getChunkLength() != states[i].trailerLength) { - delete subframes; + if (dataHeader == nullptr || dataTrailer == nullptr || dataHeader->getTid() != states[i].tid || dataHeader->getReceiverAddress() != states[i].receiver || dataHeader->getTransmitterAddress() != states[i].transmitter || dataHeader->getAddress3() != states[i].address3 || dataHeader->getAddress4() != states[i].address4 || dataHeader->getType() != states[i].type || dataHeader->getToDS() != states[i].toDS || dataHeader->getFromDS() != states[i].fromDS || (*subframes)[i]->getDataLength() != states[i].dataLength || dataHeader->getChunkLength() != states[i].headerLength || dataTrailer->getChunkLength() != states[i].trailerLength) throw cRuntimeError("A-MSDU provider changed aggregation-critical frame fields during extraction"); - } } - auto aggregatedFrame = aMsduAggregation->aggregateFrames(subframes); + for (auto& subframe : extractedSubframes) + subframe.release(); + auto aggregatedFrame = aMsduAggregation->aggregateFrames(subframes.get()); emit(packetAggregatedSignal, aggregatedFrame); - delete subframes; return aggregatedFrame; } return nullptr; @@ -111,7 +119,7 @@ std::vector *OriginatorQosMacDataService::fragmentIfNeeded(Packet *fra auto fragmentFrames = fragmentation->fragmentFrame(frame, fragmentSizes); if (hasTransactionTag) for (auto fragment : *fragmentFrames) - fragment->addTag()->setTransactionId(transactionId); + fragment->addTagIfAbsent()->setTransactionId(transactionId); return fragmentFrames; } return nullptr; diff --git a/src/inet/queueing/buffer/PacketBuffer.cc b/src/inet/queueing/buffer/PacketBuffer.cc index 2509ced7fa3..f554f5f12f9 100644 --- a/src/inet/queueing/buffer/PacketBuffer.cc +++ b/src/inet/queueing/buffer/PacketBuffer.cc @@ -53,6 +53,9 @@ b PacketBuffer::getTotalLength() const void PacketBuffer::addPacket(Packet *packet) { Enter_Method("addPacket"); + auto ownerQueue = dynamic_cast(packet->getOwner()); + if (ownerQueue != nullptr && dynamic_cast(ownerQueue->getOwner()) == nullptr) + throw cRuntimeError("Cannot buffer packet owned by cPacketQueue whose owner does not implement IPacketBuffer::ICallback"); EV_INFO << "Adding packet" << EV_FIELD(packet) << EV_ENDL; emit(packetAddedSignal, packet); packets.push_back(packet); @@ -63,10 +66,8 @@ void PacketBuffer::addPacket(Packet *packet) auto packet = packetDropperFunction->selectPacket(this); EV_INFO << "Dropping packet" << EV_FIELD(packet) << EV_ENDL; packets.erase(find(packets, packet)); - ICallback *callback = nullptr; auto queue = dynamic_cast(packet->getOwner()); - if (queue != nullptr) - callback = dynamic_cast(queue->getOwner()); + auto callback = queue != nullptr ? check_and_cast(queue->getOwner()) : nullptr; droppedPackets.emplace_back(packet, callback); } for (auto& [packet, callback] : droppedPackets) diff --git a/src/inet/queueing/queue/CompoundPacketQueueBase.cc b/src/inet/queueing/queue/CompoundPacketQueueBase.cc index f2cdcbe30b8..077c2ade951 100644 --- a/src/inet/queueing/queue/CompoundPacketQueueBase.cc +++ b/src/inet/queueing/queue/CompoundPacketQueueBase.cc @@ -24,13 +24,10 @@ void CompoundPacketQueueBase::initialize(int stage) provider.reference(outputGate, true, -1); collection = check_and_cast(provider.get()); packetExtractor = check_and_cast(provider.get()); - for (cModule::SubmoduleIterator it(this); !it.end(); it++) { - auto childQueue = dynamic_cast(*it); - if (childQueue != nullptr) { - childQueues.push_back(childQueue); - childQueue->addPacketCallback(this); - } - } + // Observe the nearest queue on every descendant branch. Nested + // compound queues forward their own frontier, so stopping at a queue + // avoids duplicate notifications while traversing non-queue wrappers. + registerQueueFrontier(this); packetDropperFunction = createDropperFunction(par("dropperClass")); subscribe(packetDroppedSignal, this); subscribe(packetCreatedSignal, this); @@ -42,6 +39,27 @@ void CompoundPacketQueueBase::initialize(int stage) } } +void CompoundPacketQueueBase::registerQueueFrontier(cModule *module) +{ + for (cModule::SubmoduleIterator it(module); !it.end(); it++) { + auto childModule = *it; + auto childQueue = dynamic_cast(childModule); + if (childQueue != nullptr) { + childQueues.push_back(childQueue); + childQueue->addPacketCallback(this); + } + else + registerQueueFrontier(childModule); + } +} + +void CompoundPacketQueueBase::finish() +{ + for (auto childQueue : childQueues) + childQueue->removePacketCallback(this); + childQueues.clear(); +} + IPacketDropperFunction *CompoundPacketQueueBase::createDropperFunction(const char *dropperClass) const { if (strlen(dropperClass) == 0) diff --git a/src/inet/queueing/queue/CompoundPacketQueueBase.h b/src/inet/queueing/queue/CompoundPacketQueueBase.h index 89f8c70f47a..dc1c6e69164 100644 --- a/src/inet/queueing/queue/CompoundPacketQueueBase.h +++ b/src/inet/queueing/queue/CompoundPacketQueueBase.h @@ -32,7 +32,11 @@ class INET_API CompoundPacketQueueBase : public PacketQueueBase, public cListene IPacketDropperFunction *packetDropperFunction = nullptr; protected: + using cListener::finish; + virtual void initialize(int stage) override; + virtual void finish() override; + virtual void registerQueueFrontier(cModule *module); virtual IPacketDropperFunction *createDropperFunction(const char *dropperClass) const; diff --git a/tests/unit/Ieee80211AddbaTransaction_1.test b/tests/unit/Ieee80211AddbaTransaction_1.test index b4d0da4d240..908528f9a2a 100644 --- a/tests/unit/Ieee80211AddbaTransaction_1.test +++ b/tests/unit/Ieee80211AddbaTransaction_1.test @@ -213,6 +213,36 @@ class TestImmediateRemovalQueue : public TestPacketQueue } }; +class TestWrongDequeueQueue : public TestPacketQueue +{ + public: + Packet *wrongPacket = nullptr; + int numPredicateDequeues = 0; + + virtual Packet *dequeuePacket(const PacketPredicate& predicate) override { + if (numPredicateDequeues++ == 0) + return TestPacketQueue::dequeuePacket(predicate); + packets.erase(std::find(packets.begin(), packets.end(), wrongPacket)); + notifyPacketRemoved(wrongPacket, PacketRemovalReason::DEQUEUED); + return wrongPacket; + } +}; + +class TestRepeatedDequeueQueue : public TestPacketQueue +{ + public: + Packet *repeatedPacket = nullptr; + int numPredicateDequeues = 0; + + virtual Packet *dequeuePacket(const PacketPredicate& predicate) override { + if (numPredicateDequeues++ == 0) { + repeatedPacket = TestPacketQueue::dequeuePacket(predicate); + return repeatedPacket; + } + return repeatedPacket; + } +}; + class TestCompoundPacketQueue : public queueing::CompoundPacketQueueBase { public: @@ -224,6 +254,15 @@ class TestCompoundPacketQueue : public queueing::CompoundPacketQueueBase take(packet); drop(packet); } + void observeDescendantQueues(cModule *module) { registerQueueFrontier(module); } + void observeQueue(queueing::IPacketQueue *queue) { childQueues.push_back(queue); queue->addPacketCallback(this); } + void addObserver(queueing::IPacketQueue::ICallback *callback) { packetCallbacks.push_back(callback); } + void stopObserving() { + for (auto queue : childQueues) + queue->removePacketCallback(this); + childQueues.clear(); + packetCallbacks.clear(); + } }; class TestPriorityScheduler : public queueing::PriorityScheduler @@ -362,6 +401,15 @@ class TestAckHandler : public IAckHandler virtual void frameGotInProgress(const Ptr&) override {} }; +class TestQosAckHandler : public QosAckHandler +{ + public: + bool hasMgmtEntry(const Ptr& header) const { + auto id = std::make_pair(header->getReceiverAddress(), SequenceControlField(header->getSequenceNumber().get(), header->getFragmentNumber())); + return mgmtAckStatuses.find(id) != mgmtAckStatuses.end(); + } +}; + class TestInProgressFrames : public InProgressFrames { public: @@ -501,6 +549,12 @@ class TestHcf : public Hcf void cancelTransaction(uint64_t transactionId) { cancelAddbaTransaction(transactionId, nullptr); } void processReceivedManagementFrame(const Ptr& header) { recipientProcessReceivedManagementFrame(header); } void processRtsProtectionFailure(Packet *packet) { originatorProcessRtsProtectionFailed(packet); } + void processFailedFrame(Packet *packet) { originatorProcessFailedFrame(packet); } + void processReceivedAck(Packet *packet, Packet *lastTransmittedPacket, AccessCategory accessCategory) { + auto header = packet->peekAtFront(); + auto lastTransmittedHeader = lastTransmittedPacket->peekAtFront(); + originatorProcessReceivedControlFrame(packet, header, lastTransmittedPacket, lastTransmittedHeader, accessCategory); + } protected: virtual bool processDroppedBlockAckSetupFrame(Packet *packet) override { numProcessedDroppedSetupFrames++; return delegateDroppedSetupHandling ? Hcf::processDroppedBlockAckSetupFrame(packet) : droppedSetupCancelled; } @@ -697,6 +751,20 @@ static Ptr makeQosHeader(MacAddress receiverAddress, Tid ti return header; } +static Packet *makeTaggedDelbaPacket(const char *name, MacAddress receiverAddress, Tid tid, SequenceNumberCyclic sequenceNumber, int fragmentNumber, bool moreFragments, uint64_t transactionId) +{ + auto delba = makeShared(); + delba->setReceiverAddress(receiverAddress); + delba->setTid(tid); + delba->setInitiator(true); + delba->setSequenceNumber(sequenceNumber); + delba->setFragmentNumber(fragmentNumber); + delba->setMoreFragments(moreFragments); + auto packet = new Packet(name, delba); + packet->addTag()->setTransactionId(transactionId); + return packet; +} + static Ptr makeResponse(MacAddress transmitterAddress, Tid tid, uint8_t dialogToken, uint16_t statusCode) { auto response = makeShared(); @@ -815,16 +883,181 @@ auto triggerHeader = makeQosHeader(peer1, 3, SequenceNumberCyclic(4095)); ASSERT(fragmentedDelbaHandler.processTransmittedDelba(&firstDelbaPacket, &fragmentedDelbaCallback) == nullptr); ASSERT(fragmentedDelbaHandler.isDelbaPending(&finalDelbaPacket, finalDelba)); ASSERT(fragmentedDelbaHandler.processTransmittedDelba(&finalDelbaPacket, &fragmentedDelbaCallback) == nullptr); + // A failed attempt remains eligible for retransmission. + ASSERT(fragmentedDelbaHandler.isDelbaPending(&finalDelbaPacket, finalDelba)); + ASSERT(fragmentedDelbaHandler.processTransmittedDelba(&finalDelbaPacket, &fragmentedDelbaCallback) == nullptr); + ASSERT(fragmentedDelbaHandler.isDelbaPending(&finalDelbaPacket, finalDelba)); + ASSERT(!fragmentedDelbaHandler.processAcknowledgedDelba(&firstDelbaPacket, &fragmentedDelbaCallback)); + ASSERT(fragmentedDelbaHandler.processAcknowledgedDelba(&finalDelbaPacket, &fragmentedDelbaCallback)); ASSERT(!fragmentedDelbaHandler.isDelbaPending(&finalDelbaPacket, finalDelba)); + ASSERT(fragmentedDelbaCallback.cancelledTransactionIds.back() == 77); + ASSERT(fragmentedDelbaCallback.excludedPackets.back() == &finalDelbaPacket); fragmentedDelbaHandler.setPendingTeardownTransaction(peer1, 6, 78); firstDelbaPacket.getTagForUpdate()->setTransactionId(78); - ASSERT(fragmentedDelbaHandler.processAbortedDelba(&firstDelbaPacket, &fragmentedDelbaCallback) == nullptr); + ASSERT(fragmentedDelbaHandler.processAbortedDelba(&firstDelbaPacket, &fragmentedDelbaCallback)); ASSERT(!fragmentedDelbaHandler.isDelbaPending(&firstDelbaPacket, firstDelba)); ASSERT(fragmentedDelbaCallback.cancelledTransactionIds.back() == 78); ASSERT(fragmentedDelbaCallback.excludedPackets.back() == &firstDelbaPacket); } +// IEEE Std 802.11-2024, 10.23.2.12.1 and 11.5.3.2: the HCF retry path keeps a +// transaction-tagged DELBA selectable and preserves its negative ACK state. +{ + constexpr uint64_t transactionId = 79; + TestCwCalculator cwCalculator; + TestNonQosRecoveryProcedure recoveryProcedure; + recoveryProcedure.configureRts(INT_MAX, 2, &cwCalculator); + TestQosAckHandler ackHandler; + TestPacketQueue pendingQueue; + TestOriginatorQosMacDataService dataService; + auto originatorHandler = new TestOriginatorHandler(); + dataService.setFrameEligibilityFunction([originatorHandler](const Packet *packet) { + auto delba = dynamicPtrCast(packet->peekAtFront()); + return delba == nullptr || originatorHandler->isDelbaPending(packet, delba); + }); + TestInProgressFrames inProgressFrames; + inProgressFrames.configure(&dataService, &ackHandler, &pendingQueue); + TestEdcaf edcaf; + edcaf.pendingQueue = &pendingQueue; + edcaf.inProgressFrames = &inProgressFrames; + edcaf.qosAckHandler = &ackHandler; + TestEdca edca; + edca.edcaf = &edcaf; + edca.nonQosRecoveryProcedure = &recoveryProcedure; + TestOriginatorPolicy policy; + OriginatorQosAckPolicy ackPolicy; + TestHcf hcf; + hcf.configureTransmittedData(&edca, originatorHandler, &policy); + hcf.configureTransmittedManagement(&edca, &ackPolicy); + + auto packet = makeTaggedDelbaPacket("failedTransactionalDelba", peer1, 6, SequenceNumberCyclic(203), 0, false, transactionId); + auto delba = packet->peekAtFront(); + originatorHandler->setPendingTeardownTransaction(peer1, 6, transactionId); + inProgressFrames.addOwnedFrame(packet); + ackHandler.frameGotInProgress(delba); + hcf.processTransmittedManagement(packet, delba, AC_BE); + ASSERT(ackHandler.getMgmtOrNonQoSAckStatus(delba) == QosAckHandler::Status::WAITING_FOR_NORMAL_ACK); + hcf.processFailedFrame(packet); + ASSERT(originatorHandler->isDelbaPending(packet, packet->peekAtFront())); + ASSERT(ackHandler.getMgmtOrNonQoSAckStatus(delba) == QosAckHandler::Status::NORMAL_ACK_NOT_ARRIVED); + ASSERT(ackHandler.hasMgmtEntry(delba)); + ASSERT(inProgressFrames.getFrameToTransmit() == packet); + ASSERT(inProgressFrames.getNumDroppedFrames() == 0); + auto releasedFrames = inProgressFrames.releaseFrames(); + ASSERT(releasedFrames == std::vector({ packet })); + delete packet; +} + +// A final-fragment ACK retires the DELBA transaction through HCF exactly once, +// removes its in-progress sibling, and clears both ACK-handler entries. +{ + constexpr uint64_t transactionId = 80; + TestCwCalculator cwCalculator; + TestNonQosRecoveryProcedure recoveryProcedure; + recoveryProcedure.configureRts(INT_MAX, 2, &cwCalculator); + TestQosAckHandler ackHandler; + TestPacketQueue pendingQueue; + TestOriginatorQosMacDataService dataService; + auto originatorHandler = new TestOriginatorHandler(); + dataService.setFrameEligibilityFunction([originatorHandler](const Packet *packet) { + auto delba = dynamicPtrCast(packet->peekAtFront()); + return delba == nullptr || originatorHandler->isDelbaPending(packet, delba); + }); + TestInProgressFrames inProgressFrames; + inProgressFrames.configure(&dataService, &ackHandler, &pendingQueue); + TestEdcaf edcaf; + edcaf.pendingQueue = &pendingQueue; + edcaf.inProgressFrames = &inProgressFrames; + edcaf.qosAckHandler = &ackHandler; + TestEdca edca; + edca.edcaf = &edcaf; + edca.nonQosRecoveryProcedure = &recoveryProcedure; + TestOriginatorPolicy policy; + OriginatorQosAckPolicy ackPolicy; + TestHcf hcf; + hcf.configureTransmittedData(&edca, originatorHandler, &policy); + hcf.configureTransmittedManagement(&edca, &ackPolicy); + + auto sibling = makeTaggedDelbaPacket("acknowledgedDelbaSibling", peer1, 6, SequenceNumberCyclic(204), 0, true, transactionId); + auto finalPacket = makeTaggedDelbaPacket("acknowledgedFinalDelba", peer1, 6, SequenceNumberCyclic(204), 1, false, transactionId); + auto siblingHeader = sibling->peekAtFront(); + auto finalHeader = finalPacket->peekAtFront(); + originatorHandler->setPendingTeardownTransaction(peer1, 6, transactionId); + inProgressFrames.addOwnedFrame(sibling); + inProgressFrames.addOwnedFrame(finalPacket); + ackHandler.frameGotInProgress(siblingHeader); + ackHandler.frameGotInProgress(finalHeader); + hcf.processTransmittedManagement(finalPacket, finalHeader, AC_BE); + Packet ackPacket("transactionalDelbaAck", makeShared()); + hcf.processReceivedAck(&ackPacket, finalPacket, AC_BE); + ASSERT(!originatorHandler->isDelbaPending(finalPacket, finalHeader)); + ASSERT(hcf.numCancelledAddbaTransactions == 1); + ASSERT(inProgressFrames.getLength() == 0); + ASSERT(inProgressFrames.getNumDroppedFrames() == 1); + ASSERT(!ackHandler.hasMgmtEntry(siblingHeader)); + ASSERT(!ackHandler.hasMgmtEntry(finalHeader)); + ASSERT(!originatorHandler->processAcknowledgedDelba(finalPacket, &hcf)); + ASSERT(hcf.numCancelledAddbaTransactions == 1); + auto releasedFrames = inProgressFrames.releaseFrames(); + ASSERT(releasedFrames == std::vector({ finalPacket })); + delete finalPacket; +} + +// Reaching the retry limit aborts the teardown through HCF, clears its sibling +// and both ACK-handler entries, and records one terminal cancellation. +{ + constexpr uint64_t transactionId = 81; + TestCwCalculator cwCalculator; + TestNonQosRecoveryProcedure recoveryProcedure; + recoveryProcedure.configureRts(INT_MAX, 1, &cwCalculator); + TestQosAckHandler ackHandler; + TestPacketQueue pendingQueue; + TestOriginatorQosMacDataService dataService; + auto originatorHandler = new TestOriginatorHandler(); + dataService.setFrameEligibilityFunction([originatorHandler](const Packet *packet) { + auto delba = dynamicPtrCast(packet->peekAtFront()); + return delba == nullptr || originatorHandler->isDelbaPending(packet, delba); + }); + TestInProgressFrames inProgressFrames; + inProgressFrames.configure(&dataService, &ackHandler, &pendingQueue); + TestEdcaf edcaf; + edcaf.pendingQueue = &pendingQueue; + edcaf.inProgressFrames = &inProgressFrames; + edcaf.qosAckHandler = &ackHandler; + TestEdca edca; + edca.edcaf = &edcaf; + edca.nonQosRecoveryProcedure = &recoveryProcedure; + TestOriginatorPolicy policy; + OriginatorQosAckPolicy ackPolicy; + TestHcf hcf; + hcf.configureTransmittedData(&edca, originatorHandler, &policy); + hcf.configureTransmittedManagement(&edca, &ackPolicy); + + auto sibling = makeTaggedDelbaPacket("retryLimitedDelbaSibling", peer1, 6, SequenceNumberCyclic(205), 0, true, transactionId); + auto finalPacket = makeTaggedDelbaPacket("retryLimitedFinalDelba", peer1, 6, SequenceNumberCyclic(205), 1, false, transactionId); + auto siblingHeader = sibling->peekAtFront(); + auto finalHeader = finalPacket->peekAtFront(); + originatorHandler->setPendingTeardownTransaction(peer1, 6, transactionId); + inProgressFrames.addOwnedFrame(sibling); + inProgressFrames.addOwnedFrame(finalPacket); + ackHandler.frameGotInProgress(siblingHeader); + ackHandler.frameGotInProgress(finalHeader); + hcf.processTransmittedManagement(finalPacket, finalHeader, AC_BE); + hcf.processFailedFrame(finalPacket); + ASSERT(!originatorHandler->isDelbaPending(finalPacket, finalHeader)); + ASSERT(hcf.numCancelledAddbaTransactions == 1); + ASSERT(inProgressFrames.getLength() == 0); + ASSERT(inProgressFrames.getNumDroppedFrames() == 1); + ASSERT(!ackHandler.hasMgmtEntry(siblingHeader)); + ASSERT(!ackHandler.hasMgmtEntry(finalHeader)); + ASSERT(!originatorHandler->processAbortedDelba(finalPacket, &hcf)); + ASSERT(hcf.numCancelledAddbaTransactions == 1); + auto releasedFrames = inProgressFrames.releaseFrames(); + ASSERT(releasedFrames == std::vector({ finalPacket })); + delete finalPacket; +} + handler.processAcknowledgedDataFrame(&triggerPacket, triggerHeader, &originatorPolicy, &callback); auto request = dynamicPtrCast(callback.managementHeader); ASSERT(request != nullptr); @@ -898,7 +1131,7 @@ ASSERT(handler.processReceivedAddbaResp(success, &originatorPolicy, &callback).a ASSERT(newerAgreement != nullptr); ASSERT(newerAgreement->getTransactionId() != vetoTransactionId); ASSERT(!vetoHandler.isDelbaPending(&vetoDelbaPacket, vetoDelba)); - ASSERT(vetoHandler.processAbortedDelba(&vetoDelbaPacket, &vetoCallback) == nullptr); + ASSERT(!vetoHandler.processAbortedDelba(&vetoDelbaPacket, &vetoCallback)); ASSERT(!vetoHandler.isDelbaPending(&vetoDelbaPacket, vetoDelba)); auto vetoTerminated = vetoHandler.processTransmittedDelba(&vetoDelbaPacket, &vetoCallback); ASSERT(vetoTerminated == nullptr); @@ -2078,6 +2311,14 @@ ASSERT(!dataService.hasEligibleFrame(&mixedQueue)); aggregateQueryFailed = std::string(error.what()).find("getNumPackets") != std::string::npos && std::string(error.what()).find("IPacketCollection") != std::string::npos; } ASSERT(aggregateQueryFailed); + bool aggregateLengthQueryFailed = false; + try { + scheduler.getTotalLength(); + } + catch (cRuntimeError& error) { + aggregateLengthQueryFailed = std::string(error.what()).find("getTotalLength") != std::string::npos && std::string(error.what()).find("IPacketCollection") != std::string::npos; + } + ASSERT(aggregateLengthQueryFailed); ASSERT(scheduler.findPacket([](const Packet *) { return true; }) == packet); auto dequeuedPacket = scheduler.dequeuePacket([](const Packet *) { return true; }); ASSERT(dequeuedPacket == packet); @@ -2278,6 +2519,9 @@ ASSERT(!dataService.hasEligibleFrame(&mixedQueue)); auto anchor = makeAggregatePacket("basicPolicyAnchor", SequenceNumberCyclic(20), 8); auto held = makeAggregatePacket("basicPolicyHeld", SequenceNumberCyclic(21), 8); auto compatible = makeAggregatePacket("basicPolicyCompatible", SequenceNumberCyclic(22), 8); + auto heldHeader = held->removeAtFront(); + heldHeader->setTid(2); + held->insertAtFront(heldHeader); queue.packets = { anchor, held, compatible }; TestBasicMsduAggregationPolicy policy; policy.configure(B(4065)); @@ -2293,11 +2537,137 @@ ASSERT(!dataService.hasEligibleFrame(&mixedQueue)); queue.packets = { oversizedAnchor, smallFollower1, smallFollower2 }; policy.configure(B(30)); ASSERT(policy.computeAggregateFrames(&queue, oversizedAnchor, [](const Packet *) { return true; }) == nullptr); + auto prefix = makeAggregatePacket("basicPolicyPrefix", SequenceNumberCyclic(26), 8); + auto middleAnchor = makeAggregatePacket("basicPolicyMiddleAnchor", SequenceNumberCyclic(27), 8); + auto suffix = makeAggregatePacket("basicPolicySuffix", SequenceNumberCyclic(28), 8); + queue.packets = { prefix, middleAnchor, suffix }; + policy.configure(B(4065)); + selected = policy.computeAggregateFrames(&queue, middleAnchor, [prefix](const Packet *packet) { return packet != prefix; }); + ASSERT(selected != nullptr); + ASSERT(selected->size() == 2); + ASSERT(selected->at(0) == middleAnchor); + ASSERT(selected->at(1) == suffix); + delete selected; + auto capacityAnchor = makeAggregatePacket("basicPolicyCapacityAnchor", SequenceNumberCyclic(29), 8); + auto oversizedMiddle = makeAggregatePacket("basicPolicyOversizedMiddle", SequenceNumberCyclic(30), 64); + auto smallSuffix = makeAggregatePacket("basicPolicySmallSuffix", SequenceNumberCyclic(31), 1); + queue.packets = { capacityAnchor, oversizedMiddle, smallSuffix }; + policy.configure(B(40)); + ASSERT(policy.computeAggregateFrames(&queue, capacityAnchor, [](const Packet *) { return true; }) == nullptr); queue.packets.clear(); - for (auto packet : { anchor, held, compatible, oversizedAnchor, smallFollower1, smallFollower2 }) + for (auto packet : { anchor, held, compatible, oversizedAnchor, smallFollower1, smallFollower2, prefix, middleAnchor, suffix, capacityAnchor, oversizedMiddle, smallSuffix }) delete packet; } +// Contract validation owns both already-extracted frames and an unexpected +// non-null provider result, so a caught provider error leaves only the frame +// which was never extracted in the queue. +{ + auto makeAggregatePacket = [&](const char *name, SequenceNumberCyclic sequenceNumber) { + auto header = makeQosHeader(peer1, 1, sequenceNumber); + header->setTransmitterAddress(peer2); + auto packet = new Packet(name, header); + packet->insertAtBack(makeShared(B(8))); + packet->insertAtBack(makeShared()); + return packet; + }; + TestWrongDequeueQueue queue; + auto first = makeAggregatePacket("badProviderFirst", SequenceNumberCyclic(35)); + auto second = makeAggregatePacket("badProviderSecond", SequenceNumberCyclic(36)); + auto wrong = makeAggregatePacket("badProviderWrong", SequenceNumberCyclic(37)); + queue.packets = { first, second, wrong }; + queue.wrongPacket = wrong; + TestSelectedMsduAggregationPolicy aggregationPolicy; + aggregationPolicy.selectedPackets = { first, second }; + TestOriginatorQosMacDataService service; + service.enableMsduAggregationPolicy(&aggregationPolicy); + bool contractError = false; + try { + service.extractFramesToTransmit(&queue); + } + catch (cRuntimeError& error) { + contractError = std::string(error.what()).find("no longer available") != std::string::npos; + } + ASSERT(contractError); + ASSERT(queue.packets.size() == 1); + ASSERT(queue.packets.front() == second); + queue.packets.clear(); + delete second; +} + +// Returning an already-extracted pointer for a later exact dequeue is also a +// provider contract violation, but the cleanup guard must adopt it only once. +{ + auto makeAggregatePacket = [&](const char *name, SequenceNumberCyclic sequenceNumber) { + auto header = makeQosHeader(peer1, 1, sequenceNumber); + header->setTransmitterAddress(peer2); + auto packet = new Packet(name, header); + packet->insertAtBack(makeShared(B(8))); + packet->insertAtBack(makeShared()); + return packet; + }; + TestRepeatedDequeueQueue queue; + auto first = makeAggregatePacket("repeatedProviderFirst", SequenceNumberCyclic(38)); + auto second = makeAggregatePacket("repeatedProviderSecond", SequenceNumberCyclic(39)); + queue.packets = { first, second }; + TestSelectedMsduAggregationPolicy aggregationPolicy; + aggregationPolicy.selectedPackets = { first, second }; + TestOriginatorQosMacDataService service; + service.enableMsduAggregationPolicy(&aggregationPolicy); + bool contractError = false; + try { + service.extractFramesToTransmit(&queue); + } + catch (cRuntimeError& error) { + contractError = std::string(error.what()).find("no longer available") != std::string::npos; + } + ASSERT(contractError); + ASSERT(queue.packets.size() == 1); + ASSERT(queue.packets.front() == second); + queue.packets.clear(); + delete second; +} + +// Reverse-priority scheduling may select an anchor after an eligible packet in +// collection enumeration. The built-in policy must never wrap and append that +// earlier packet after the anchor. +{ + auto makeAggregatePacket = [&](const char *name, SequenceNumberCyclic sequenceNumber) { + auto header = makeQosHeader(peer1, 1, sequenceNumber); + header->setTransmitterAddress(peer2); + auto packet = new Packet(name, header); + packet->insertAtBack(makeShared(B(8))); + packet->insertAtBack(makeShared()); + return packet; + }; + TestPacketQueue firstQueue; + TestPacketQueue secondQueue; + auto earlierPacket = makeAggregatePacket("reverseEarlier", SequenceNumberCyclic(32)); + auto reverseAnchor = makeAggregatePacket("reverseAnchor", SequenceNumberCyclic(33)); + auto reverseSuffix = makeAggregatePacket("reverseSuffix", SequenceNumberCyclic(34)); + firstQueue.packets = { earlierPacket }; + secondQueue.packets = { reverseAnchor, reverseSuffix }; + TestPriorityScheduler scheduler; + scheduler.configure({ &firstQueue, &secondQueue }, true); + TestCompoundPacketQueue compoundQueue; + compoundQueue.configure(&scheduler, &scheduler); + auto candidate = compoundQueue.findPacket([](const Packet *) { return true; }); + ASSERT(candidate == reverseAnchor); + TestBasicMsduAggregationPolicy policy; + policy.configure(B(4065)); + auto selected = policy.computeAggregateFrames(&compoundQueue, candidate, [](const Packet *) { return true; }); + ASSERT(selected != nullptr); + ASSERT(selected->size() == 2); + ASSERT(selected->at(0) == reverseAnchor); + ASSERT(selected->at(1) == reverseSuffix); + delete selected; + firstQueue.packets.clear(); + secondQueue.packets.clear(); + delete earlierPacket; + delete reverseAnchor; + delete reverseSuffix; +} + // A flow provider may expose an enumeration order different from its // scheduling behavior. Candidate-aware aggregation still extracts every // selected member through the provider and runs its processing exactly once. @@ -2751,6 +3121,32 @@ delete compoundPacket; bufferModule->deleteModule(); } +// A shared buffer rejects cPacketQueue ownership it cannot detach before any +// mutation, preventing a later overload drop from corrupting the owner queue. +{ + auto bufferModule = cModuleType::get("inet.queueing.buffer.PacketBuffer")->create("unsupportedOwnerBuffer", this); + bufferModule->callInitialize(); + auto buffer = check_and_cast(bufferModule); + cPacketQueue unsupportedOwnerQueue("unsupportedOwnerQueue"); + auto packet = new Packet("unsupportedOwnerPacket", makeShared(B(1))); + unsupportedOwnerQueue.insert(packet); + bool unsupportedOwnerRejected = false; + try { + buffer->addPacket(packet); + } + catch (cRuntimeError& error) { + unsupportedOwnerRejected = std::string(error.what()).find("IPacketBuffer::ICallback") != std::string::npos; + } + ASSERT(unsupportedOwnerRejected); + ASSERT(buffer->getNumPackets() == 0); + ASSERT(unsupportedOwnerQueue.contains(packet)); + unsupportedOwnerQueue.remove(packet); + take(packet); + delete packet; + bufferModule->callFinish(); + bufferModule->deleteModule(); +} + // Internal overflow invokes the typed queue callback once for the destructive // discard and never for the packet later removed normally. { @@ -2772,6 +3168,47 @@ delete compoundPacket; overflowQueue->deleteModule(); } +// A compound's direct frontier and a nested compound boundary each forward a +// destructive removal exactly once. +{ + auto pendingQueueModule = cModuleType::get("inet.linklayer.ieee80211.mac.queue.CompoundPendingQueue")->create("callbackPendingQueue", this); + pendingQueueModule->callInitialize(); + auto pendingQueue = check_and_cast(pendingQueueModule); + auto directLeaf = check_and_cast(pendingQueueModule->getSubmodule("managementQueue")); + TestPacketDropCallback directCallback; + TestPacketDropCallback nestedCallback; + pendingQueue->addPacketCallback(&directCallback); + TestCompoundPacketQueue outerCompound; + outerCompound.observeQueue(pendingQueue); + outerCompound.addObserver(&nestedCallback); + auto directPacket = new Packet("directCompoundDrop", makeShared(B(1))); + directLeaf->handlePacketDropped(directPacket); + ASSERT(directCallback.numDroppedPackets == 1); + ASSERT(nestedCallback.numDroppedPackets == 1); + delete directPacket; + outerCompound.stopObserving(); + pendingQueue->removePacketCallback(&directCallback); + pendingQueueModule->callFinish(); + pendingQueueModule->deleteModule(); + + // Starting the frontier at the enclosing module forces traversal through + // PacketShaper, which is not an IPacketQueue, before reaching its leaf. + auto packetShaperModule = cModuleType::get("inet.queueing.shaper.PacketShaper")->create("callbackPacketShaper", this); + packetShaperModule->callInitialize(); + auto wrappedLeaf = check_and_cast(packetShaperModule->getSubmodule("queue")); + TestPacketDropCallback wrappedCallback; + TestCompoundPacketQueue wrappedCompound; + wrappedCompound.observeDescendantQueues(this); + wrappedCompound.addObserver(&wrappedCallback); + auto wrappedPacket = new Packet("wrappedCompoundDrop", makeShared(B(1))); + wrappedLeaf->handlePacketDropped(wrappedPacket); + ASSERT(wrappedCallback.numDroppedPackets == 1); + delete wrappedPacket; + wrappedCompound.stopObserving(); + packetShaperModule->callFinish(); + packetShaperModule->deleteModule(); +} + // A shared buffer detaches every victim from its owning queue before the first // typed drop callback. This keeps protocol cleanup from reentering while a // later victim is still selectable from another queue. From 9c48302ff6e4d790bef3260a4706ca8c1c971457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Wed, 19 Aug 2026 18:48:13 +0200 Subject: [PATCH 10/14] fix: preserve block ack state for duplicate ADDBA requests Propagate MAC duplicate detection from the recipient QoS data service into HCF so retransmitted management frames are not processed as new negotiations. Cache the exact ADDBA response per originator and TID and replay it for a recognized duplicate while preserving the existing agreement, reorder window, buffered MPDUs, inactivity timer, and agreement signals. Fresh MAC identities still perform normal renegotiation, including when their ADDBA parameters are unchanged. Clear replay state during agreement teardown and avoid retaining responses for initial rejected requests without an agreement. Add focused transaction coverage for accepted and rejected requests, duplicate response replay, genuine renegotiation, reorder-buffer preservation, DELBA handling, and cache lifecycle. --- .../RecipientBlockAckAgreementHandler.cc | 29 +- .../RecipientBlockAckAgreementHandler.h | 2 + .../IRecipientBlockAckAgreementHandler.h | 1 + .../contract/IRecipientQosMacDataService.h | 10 +- .../ieee80211/mac/coordinationfunction/Hcf.cc | 14 +- .../ieee80211/mac/coordinationfunction/Hcf.h | 2 +- .../recipient/RecipientQosMacDataService.cc | 12 +- .../recipient/RecipientQosMacDataService.h | 2 +- tests/unit/Ieee80211AddbaTransaction_1.test | 270 +++++++++++++++++- 9 files changed, 324 insertions(+), 18 deletions(-) diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc index 044da6d2014..8956059d80c 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc @@ -100,6 +100,7 @@ const Ptr RecipientBlockAckAgreementHandler::buildAddbaR RecipientBlockAckAgreement *RecipientBlockAckAgreementHandler::removeAgreement(MacAddress originatorAddr, Tid tid) { auto agreementId = std::make_pair(originatorAddr, tid); + lastAddbaResponses.erase(agreementId); auto it = blockAckAgreements.find(agreementId); if (it != blockAckAgreements.end()) { auto agreement = it->second; @@ -122,13 +123,21 @@ RecipientBlockAckAgreement *RecipientBlockAckAgreementHandler::processReceivedAd bool accepted = addbaRequest->getDialogToken() != 0 && blockAckAgreementPolicy->isAddbaReqAccepted(addbaRequest); EV_DETAIL << "Building Addba Response" << endl; auto addbaResponse = buildAddbaResponse(addbaRequest, blockAckAgreementPolicy, accepted); + auto id = std::make_pair(addbaRequest->getTransmitterAddress(), addbaRequest->getTid()); + bool hadAgreement = blockAckAgreements.find(id) != blockAckAgreements.end(); + // Keep the immutable response body that corresponds to the most recently + // processed request identity. This is response replay state, not a second + // duplicate detector; RecipientQosMacDataService remains authoritative. + if (accepted || hadAgreement) + lastAddbaResponses[id] = addbaResponse; + else + lastAddbaResponses.erase(id); auto addbaResponsePacket = new Packet("AddbaResponse", addbaResponse); RecipientBlockAckAgreement *agreement = nullptr; if (accepted) { // IEEE Std 802.11-2024, 10.25.2 and 11.5.2.3: accepting the // request establishes or modifies the recipient agreement when the // successful response is formed; transmission is not a state gate. - auto id = std::make_pair(addbaRequest->getTransmitterAddress(), addbaRequest->getTid()); agreement = new RecipientBlockAckAgreement(addbaRequest->getTransmitterAddress(), addbaRequest->getTid(), addbaRequest->getStartingSequenceNumber(), addbaResponse->getBufferSize(), addbaResponse->getBlockAckTimeoutValue()); auto it = blockAckAgreements.find(id); if (it != blockAckAgreements.end()) { @@ -143,6 +152,24 @@ RecipientBlockAckAgreement *RecipientBlockAckAgreementHandler::processReceivedAd return agreement; } +void RecipientBlockAckAgreementHandler::processDuplicateAddbaRequest(const Ptr& addbaRequest, IProcedureCallback *procedureCallback) +{ + auto agreement = getAgreement(addbaRequest->getTid(), addbaRequest->getTransmitterAddress()); + if (agreement != nullptr) { + // IEEE Std 802.11-2024, 10.3.2.14.3 normally discards duplicate management bodies. + // Replaying the already generated response is an explicit robustness/model + // extension; it does not modify the agreement, reorder window, or inactivity timer. + auto id = std::make_pair(addbaRequest->getTransmitterAddress(), addbaRequest->getTid()); + auto it = lastAddbaResponses.find(id); + if (it == lastAddbaResponses.end()) + return; + // Copy the immutable snapshot so outbound sequence assignment uses COW + // and cannot modify the cached body used by a later retransmission. + auto addbaResponse = staticPtrCast(it->second->dupShared()); + procedureCallback->processMgmtFrame(new Packet("AddbaResponse", addbaResponse), addbaResponse); + } +} + std::unique_ptr RecipientBlockAckAgreementHandler::processTransmittedDelba(const Ptr& delba) { return std::unique_ptr(removeAgreement(delba->getReceiverAddress(), delba->getTid())); diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h index 7d95b02a6a7..f642c0a06b9 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h @@ -26,6 +26,7 @@ class INET_API RecipientBlockAckAgreementHandler : public IRecipientBlockAckAgre { protected: std::map, RecipientBlockAckAgreement *> blockAckAgreements; + std::map, Ptr> lastAddbaResponses; protected: virtual RecipientBlockAckAgreement *removeAgreement(MacAddress originatorAddr, Tid tid); @@ -37,6 +38,7 @@ class INET_API RecipientBlockAckAgreementHandler : public IRecipientBlockAckAgre public: virtual ~RecipientBlockAckAgreementHandler(); virtual RecipientBlockAckAgreement *processReceivedAddbaRequest(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) override; + virtual void processDuplicateAddbaRequest(const Ptr& addbaRequest, IProcedureCallback *procedureCallback) override; virtual std::unique_ptr processReceivedDelba(const Ptr& delba, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy) override; virtual void qosFrameReceived(const Ptr& qosHeader, IBlockAckAgreementHandlerCallback *callback) override; virtual std::unique_ptr processTransmittedDelba(const Ptr& delba) override; diff --git a/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h index 25761d25d0d..4eba49314c1 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h @@ -27,6 +27,7 @@ class INET_API IRecipientBlockAckAgreementHandler virtual ~IRecipientBlockAckAgreementHandler() {} virtual RecipientBlockAckAgreement *processReceivedAddbaRequest(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *procedureCallback, IBlockAckAgreementHandlerCallback *agreementHandlerCallback) = 0; + virtual void processDuplicateAddbaRequest(const Ptr& addbaRequest, IProcedureCallback *procedureCallback) = 0; virtual std::unique_ptr processReceivedDelba(const Ptr& delba, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy) = 0; virtual std::unique_ptr processTransmittedDelba(const Ptr& delba) = 0; virtual void qosFrameReceived(const Ptr& qosHeader, IBlockAckAgreementHandlerCallback *callback) = 0; diff --git a/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosMacDataService.h b/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosMacDataService.h index f401256e355..108a499727f 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosMacDataService.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosMacDataService.h @@ -18,6 +18,14 @@ namespace ieee80211 { class INET_API IRecipientQosMacDataService { public: + // A duplicate has already been consumed and must not be processed as a + // management body; the coordination function may apply a subtype-specific + // response rule using the duplicate flag. + struct ManagementFrameReceptionResult { + std::vector completeFrames; + bool duplicate = false; + }; + static simsignal_t packetDefragmentedSignal; static simsignal_t packetDeaggregatedSignal; @@ -26,7 +34,7 @@ class INET_API IRecipientQosMacDataService virtual std::vector dataFrameReceived(Packet *dataPacket, const Ptr& dataHeader, IRecipientBlockAckAgreementHandler *blockAckAgreementHandler) = 0; virtual std::vector controlFrameReceived(Packet *controlPacket, const Ptr& controlHeader, IRecipientBlockAckAgreementHandler *blockAckAgreementHandler) = 0; - virtual std::vector managementFrameReceived(Packet *mgmtPacket, const Ptr& mgmtHeader) = 0; + virtual ManagementFrameReceptionResult managementFrameReceived(Packet *mgmtPacket, const Ptr& mgmtHeader) = 0; virtual void resetBlockAckReordering(Tid tid, MacAddress originatorAddr) = 0; }; diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc index bae8e004f06..e7897da5a86 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc @@ -496,8 +496,9 @@ void Hcf::recipientProcessReceivedFrame(Packet *packet, const PtrdataFrameReceived(packet, dataHeader, recipientBlockAckAgreementHandler)); } else if (auto mgmtHeader = dynamicPtrCast(header)) { - sendUp(recipientDataService->managementFrameReceived(packet, mgmtHeader)); - recipientProcessReceivedManagementFrame(mgmtHeader); + auto receptionResult = recipientDataService->managementFrameReceived(packet, mgmtHeader); + sendUp(receptionResult.completeFrames); + recipientProcessReceivedManagementFrame(mgmtHeader, receptionResult.duplicate); } else { // TODO else if (auto ctrlFrame = dynamic_cast(frame)) sendUp(recipientDataService->controlFrameReceived(packet, header, recipientBlockAckAgreementHandler)); @@ -520,8 +521,15 @@ void Hcf::recipientProcessReceivedControlFrame(Packet *packet, const Ptr& header) +void Hcf::recipientProcessReceivedManagementFrame(const Ptr& header, bool duplicate) { + if (duplicate) { + if (recipientBlockAckAgreementHandler) { + if (auto addbaRequest = dynamicPtrCast(header)) + recipientBlockAckAgreementHandler->processDuplicateAddbaRequest(addbaRequest, this); + } + return; + } if (recipientBlockAckAgreementHandler && originatorBlockAckAgreementHandler) { if (auto addbaRequest = dynamicPtrCast(header)) { bool hadAgreement = recipientBlockAckAgreementHandler->getAgreement(addbaRequest->getTid(), addbaRequest->getTransmitterAddress()) != nullptr; diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h index 9585b31906b..e4fc822dc3d 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h @@ -141,7 +141,7 @@ class INET_API Hcf : public ICoordinationFunction, public IFrameSequenceHandler: // Recipient virtual void recipientProcessReceivedFrame(Packet *packet, const Ptr& header); virtual void recipientProcessReceivedControlFrame(Packet *packet, const Ptr& header); - virtual void recipientProcessReceivedManagementFrame(const Ptr& header); + virtual void recipientProcessReceivedManagementFrame(const Ptr& header, bool duplicate); virtual void recipientProcessTransmittedControlResponseFrame(Packet *packet, const Ptr& header); // Originator diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc index 6f1f87cc3a3..00dbfe970e0 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc @@ -127,23 +127,25 @@ std::vector RecipientQosMacDataService::dataFrameReceived(Packet *data return deaggregatedFrames; } -std::vector RecipientQosMacDataService::managementFrameReceived(Packet *mgmtPacket, const Ptr& mgmtHeader) +IRecipientQosMacDataService::ManagementFrameReceptionResult RecipientQosMacDataService::managementFrameReceived(Packet *mgmtPacket, const Ptr& mgmtHeader) { Enter_Method("managementFrameReceived"); take(mgmtPacket); // TODO MPDU Header+FCS Validation, Address1 Filtering, Duplicate Removal, MPDU Decryption - if (duplicateRemoval && duplicateRemoval->isDuplicate(mgmtHeader)) - return std::vector(); + if (duplicateRemoval && duplicateRemoval->isDuplicate(mgmtHeader)) { + delete mgmtPacket; + return { {}, true }; + } if (basicReassembly) { // FIXME defragmentation mgmtPacket = defragment(mgmtPacket); } // TODO Defrag, MSDU Integrity, Replay Detection, RX MSDU Rate Limiting if (dynamicPtrCast(mgmtHeader)) { delete mgmtPacket; - return std::vector(); + return { {}, false }; } else - return std::vector({ mgmtPacket }); + return { { mgmtPacket }, false }; } std::vector RecipientQosMacDataService::controlFrameReceived(Packet *controlPacket, const Ptr& controlHeader, IRecipientBlockAckAgreementHandler *blockAckAgreementHandler) diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.h b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.h index 91a245645fc..cb27ff09543 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.h +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.h @@ -48,7 +48,7 @@ class INET_API RecipientQosMacDataService : public IRecipientQosMacDataService, public: virtual std::vector dataFrameReceived(Packet *dataPacket, const Ptr& dataHeader, IRecipientBlockAckAgreementHandler *blockAckAgreementHandler) override; virtual std::vector controlFrameReceived(Packet *controlPacket, const Ptr& controlHeader, IRecipientBlockAckAgreementHandler *blockAckAgreementHandler) override; - virtual std::vector managementFrameReceived(Packet *mgmtPacket, const Ptr& mgmtHeader) override; + virtual ManagementFrameReceptionResult managementFrameReceived(Packet *mgmtPacket, const Ptr& mgmtHeader) override; virtual void resetBlockAckReordering(Tid tid, MacAddress originatorAddr) override; }; diff --git a/tests/unit/Ieee80211AddbaTransaction_1.test b/tests/unit/Ieee80211AddbaTransaction_1.test index 908528f9a2a..b3ac0b66507 100644 --- a/tests/unit/Ieee80211AddbaTransaction_1.test +++ b/tests/unit/Ieee80211AddbaTransaction_1.test @@ -69,13 +69,17 @@ class TestRecipientPolicy : public IRecipientBlockAckAgreementPolicy public: bool accepted = true; bool delbaAccepted = true; + simtime_t blockAckTimeoutValue = 0; + bool aMsduSupportedValue = true; + bool delayedBlockAckPolicySupportedValue = false; + int maximumAllowedBufferSize = 64; virtual bool isAddbaReqAccepted(const Ptr&) override { return accepted; } virtual bool isDelbaAccepted(const Ptr&) override { return delbaAccepted; } - virtual simtime_t getBlockAckTimeoutValue() const override { return 0; } - virtual bool aMsduSupported() const override { return true; } - virtual bool delayedBlockAckPolicySupported() const override { return false; } - virtual int getMaximumAllowedBufferSize() const override { return 64; } + virtual simtime_t getBlockAckTimeoutValue() const override { return blockAckTimeoutValue; } + virtual bool aMsduSupported() const override { return aMsduSupportedValue; } + virtual bool delayedBlockAckPolicySupported() const override { return delayedBlockAckPolicySupportedValue; } + virtual int getMaximumAllowedBufferSize() const override { return maximumAllowedBufferSize; } }; class TestCallback : public IProcedureCallback, public IBlockAckAgreementHandlerCallback @@ -120,6 +124,12 @@ class TestOriginatorHandler : public OriginatorBlockAckAgreementHandler simtime_t getRetryDeadline(MacAddress receiverAddress, Tid tid) const { auto it = addbaRetryDeadlines.find(std::make_pair(receiverAddress, tid)); return it == addbaRetryDeadlines.end() ? SIMTIME_MAX : it->second; } }; +class TestRecipientHandler : public RecipientBlockAckAgreementHandler +{ + public: + int getNumCachedAddbaResponses() const { return lastAddbaResponses.size(); } +}; + class TestBlockAckReordering : public BlockAckReordering { public: @@ -487,7 +497,7 @@ class TestRecipientDataService : public IRecipientQosMacDataService virtual std::vector dataFrameReceived(Packet *, const Ptr&, IRecipientBlockAckAgreementHandler *) override { return {}; } virtual std::vector controlFrameReceived(Packet *, const Ptr&, IRecipientBlockAckAgreementHandler *) override { return {}; } - virtual std::vector managementFrameReceived(Packet *, const Ptr&) override { return {}; } + virtual ManagementFrameReceptionResult managementFrameReceived(Packet *, const Ptr&) override { return {}; } virtual void resetBlockAckReordering(Tid tid, MacAddress originatorAddress) override { numReorderingResets++; lastTid = tid; @@ -504,6 +514,7 @@ class TestHcf : public Hcf int numRebuildEligibilityCalls = 0; bool droppedSetupCancelled = false; bool delegateDroppedSetupHandling = false; + TestCallback *managementCallback = nullptr; void configure(Edca *edca) { this->edca = edca; } void configureMac(Ieee80211Mac *mac) { this->mac = mac; } @@ -526,6 +537,7 @@ class TestHcf : public Hcf } void configureFrameSequenceHandler(IFrameSequenceHandler *frameSequenceHandler) { this->frameSequenceHandler = frameSequenceHandler; } void configureRecipientDataService(IRecipientQosMacDataService *recipientDataService) { this->recipientDataService = recipientDataService; } + void configureManagementCallback(TestCallback *managementCallback) { this->managementCallback = managementCallback; } void configureTransmittedData(Edca *edca, IOriginatorBlockAckAgreementHandler *handler, IOriginatorBlockAckAgreementPolicy *policy) { this->edca = edca; originatorBlockAckAgreementHandler = handler; @@ -547,7 +559,7 @@ class TestHcf : public Hcf void removePacket(Packet *packet) { handlePacketRemoved(packet, queueing::IPacketQueue::PacketRemovalReason::REMOVED); } void dequeuePacket(Packet *packet) { handlePacketRemoved(packet, queueing::IPacketQueue::PacketRemovalReason::DEQUEUED); } void cancelTransaction(uint64_t transactionId) { cancelAddbaTransaction(transactionId, nullptr); } - void processReceivedManagementFrame(const Ptr& header) { recipientProcessReceivedManagementFrame(header); } + void processReceivedManagementFrame(const Ptr& header, bool duplicate = false) { recipientProcessReceivedManagementFrame(header, duplicate); } void processRtsProtectionFailure(Packet *packet) { originatorProcessRtsProtectionFailed(packet); } void processFailedFrame(Packet *packet) { originatorProcessFailedFrame(packet); } void processReceivedAck(Packet *packet, Packet *lastTransmittedPacket, AccessCategory accessCategory) { @@ -557,10 +569,17 @@ class TestHcf : public Hcf } protected: + virtual void processMgmtFrame(Packet *packet, const Ptr& header) override { + if (managementCallback != nullptr) + managementCallback->processMgmtFrame(packet, header); + else + Hcf::processMgmtFrame(packet, header); + } virtual bool processDroppedBlockAckSetupFrame(Packet *packet) override { numProcessedDroppedSetupFrames++; return delegateDroppedSetupHandling ? Hcf::processDroppedBlockAckSetupFrame(packet) : droppedSetupCancelled; } virtual void cancelAddbaTransaction(uint64_t transactionId, Packet *excludedPacket) override { numCancelledAddbaTransactions++; Hcf::cancelAddbaTransaction(transactionId, excludedPacket); } virtual void rebuildPendingFrameEligibility() override { numRebuildEligibilityCalls++; Hcf::rebuildPendingFrameEligibility(); } virtual void resumeEligibleChannelAccess() override { numResumedEligibleChannelAccess++; } + virtual void scheduleInactivityTimer(simtime_t) override {} virtual void scheduleAddbaResponseTimer(simtime_t) override {} }; @@ -1438,6 +1457,245 @@ ASSERT(callback.excludedPackets.back() == unsentRequestPacket); ASSERT(threw); } +// An initially rejected request has no agreement, so its response is never +// replayable and must not consume replay-cache state. +{ + auto recipientDataServiceModule = cModuleType::get("inet.linklayer.ieee80211.mac.recipient.RecipientQosMacDataService")->create("initiallyRejectedAddbaDataService", this); + recipientDataServiceModule->callInitialize(); + auto recipientDataService = check_and_cast(recipientDataServiceModule); + auto originatorHandler = new TestOriginatorHandler(); + auto recipientHandler = new TestRecipientHandler(); + TestRecipientPolicy rejectingPolicy; + rejectingPolicy.accepted = false; + TestCallback responseCallback; + TestHcf hcf; + hcf.configureBlockAckHandlers(originatorHandler, &originatorPolicy, recipientHandler, &rejectingPolicy); + hcf.configureRecipientDataService(recipientDataService); + hcf.configureManagementCallback(&responseCallback); + + auto rejectedRequest = makeRequest(peer3, 7, 89, SequenceNumberCyclic(90), 16); + rejectedRequest->setSequenceNumber(SequenceNumberCyclic(40)); + auto rejectedReception = recipientDataService->managementFrameReceived(new Packet("initiallyRejectedAddbaRequest", rejectedRequest), rejectedRequest); + ASSERT(!rejectedReception.duplicate); + hcf.processReceivedManagementFrame(rejectedRequest, rejectedReception.duplicate); + ASSERT(recipientHandler->getAgreement(7, peer3) == nullptr); + ASSERT(recipientHandler->getNumCachedAddbaResponses() == 0); + ASSERT(responseCallback.managementPackets.size() == 1); + ASSERT(dynamicPtrCast(responseCallback.managementHeader)->getStatusCode() != 0); + + auto duplicateRejectedRequest = makeRequest(peer3, 7, 89, SequenceNumberCyclic(90), 16); + duplicateRejectedRequest->setSequenceNumber(SequenceNumberCyclic(40)); + duplicateRejectedRequest->setRetry(true); + auto duplicateReception = recipientDataService->managementFrameReceived(new Packet("duplicateInitiallyRejectedAddbaRequest", duplicateRejectedRequest), duplicateRejectedRequest); + ASSERT(duplicateReception.duplicate); + hcf.processReceivedManagementFrame(duplicateRejectedRequest, duplicateReception.duplicate); + ASSERT(recipientHandler->getNumCachedAddbaResponses() == 0); + ASSERT(responseCallback.managementPackets.size() == 1); + + recipientDataServiceModule->callFinish(); + recipientDataServiceModule->deleteModule(); +} + +// IEEE Std 802.11-2024, 9.2.4.1.6 and 10.3.2.14.3/Table 10-6: +// duplicate identity comes from Retry + TA + MAC sequence/fragment, not from +// equal ADDBA parameters. Re-sending the cached exact response is an +// explicit robustness/model extension; it leaves recipient state untouched. +{ + auto recipientDataServiceModule = cModuleType::get("inet.linklayer.ieee80211.mac.recipient.RecipientQosMacDataService")->create("duplicateAddbaDataService", this); + recipientDataServiceModule->callInitialize(); + auto recipientDataService = check_and_cast(recipientDataServiceModule); + TestPacketDropSignalListener reorderDropListener; + recipientDataService->subscribe(packetDroppedSignal, &reorderDropListener); + + auto originatorHandler = new TestOriginatorHandler(); + auto recipientHandler = new TestRecipientHandler(); + TestRecipientPolicy duplicatePolicy; + duplicatePolicy.blockAckTimeoutValue = 1; + TestCallback responseCallback; + TestHcf hcf; + hcf.configureBlockAckHandlers(originatorHandler, &originatorPolicy, recipientHandler, &duplicatePolicy); + hcf.configureRecipientDataService(recipientDataService); + hcf.configureManagementCallback(&responseCallback); + TestSignalListener addedListener; + TestSignalListener changedListener; + hcf.subscribe(Hcf::blockAckAgreementAddedSignal, &addedListener); + hcf.subscribe(Hcf::blockAckAgreementChangedSignal, &changedListener); + + auto firstRequest = makeRequest(peer2, 6, 90, SequenceNumberCyclic(100), 32); + firstRequest->setSequenceNumber(SequenceNumberCyclic(50)); + firstRequest->setBlockAckTimeoutValue(3); + auto firstReception = recipientDataService->managementFrameReceived(new Packet("firstAddbaRequest", firstRequest), firstRequest); + ASSERT(!firstReception.duplicate); + ASSERT(firstReception.completeFrames.empty()); + hcf.processReceivedManagementFrame(firstRequest, firstReception.duplicate); + auto firstAgreement = recipientHandler->getAgreement(6, peer2); + ASSERT(firstAgreement != nullptr); + ASSERT(recipientHandler->getNumCachedAddbaResponses() == 1); + auto firstBlockAckRecord = firstAgreement->getBlockAckRecord(); + auto firstExpirationTime = firstAgreement->getExpirationTime(); + ASSERT(addedListener.numSignals == 1); + ASSERT(changedListener.numSignals == 0); + ASSERT(responseCallback.managementPackets.size() == 1); + auto firstResponse = dynamicPtrCast(responseCallback.managementHeader); + ASSERT(firstResponse != nullptr); + ASSERT(firstResponse->getDialogToken() == 90); + ASSERT(firstResponse->getStatusCode() == 0); + ASSERT(firstResponse->getTid() == 6); + ASSERT(firstResponse->getBlockAckPolicy()); + ASSERT(firstResponse->getBufferSize() == 32); + ASSERT(firstResponse->getBlockAckTimeoutValue() == 3); + ASSERT(firstResponse->getAMsduSupported()); + + auto bufferedHeader = makeQosHeader(peer1, 6, SequenceNumberCyclic(101)); + bufferedHeader->setTransmitterAddress(peer2); + auto bufferedPacket = new Packet("duplicateAddbaBufferedData", bufferedHeader); + ASSERT(recipientDataService->dataFrameReceived(bufferedPacket, bufferedHeader, recipientHandler).empty()); + + // Mutable policy is deliberately changed after negotiation. A true MAC + // duplicate must replay the original successful response, not rebuild it. + duplicatePolicy.accepted = false; + duplicatePolicy.blockAckTimeoutValue = 0; + duplicatePolicy.aMsduSupportedValue = false; + duplicatePolicy.maximumAllowedBufferSize = 1; + auto duplicateRequest = makeRequest(peer2, 6, 90, SequenceNumberCyclic(100), 32); + duplicateRequest->setSequenceNumber(SequenceNumberCyclic(50)); + duplicateRequest->setRetry(true); + duplicateRequest->setBlockAckTimeoutValue(3); + auto duplicateReception = recipientDataService->managementFrameReceived(new Packet("duplicateAddbaRequest", duplicateRequest), duplicateRequest); + ASSERT(duplicateReception.duplicate); + ASSERT(duplicateReception.completeFrames.empty()); + hcf.processReceivedManagementFrame(duplicateRequest, duplicateReception.duplicate); + ASSERT(recipientHandler->getAgreement(6, peer2) == firstAgreement); + ASSERT(firstAgreement->getBlockAckRecord() == firstBlockAckRecord); + ASSERT(firstAgreement->getExpirationTime() == firstExpirationTime); + ASSERT(addedListener.numSignals == 1); + ASSERT(changedListener.numSignals == 0); + ASSERT(reorderDropListener.numSignals == 0); + ASSERT(responseCallback.managementPackets.size() == 2); + auto duplicateResponse = dynamicPtrCast(responseCallback.managementHeader); + ASSERT(duplicateResponse->getDialogToken() == firstResponse->getDialogToken()); + ASSERT(duplicateResponse->getStatusCode() == firstResponse->getStatusCode()); + ASSERT(duplicateResponse->getTid() == firstResponse->getTid()); + ASSERT(duplicateResponse->getBlockAckPolicy() == firstResponse->getBlockAckPolicy()); + ASSERT(duplicateResponse->getBufferSize() == firstResponse->getBufferSize()); + ASSERT(duplicateResponse->getBlockAckTimeoutValue() == firstResponse->getBlockAckTimeoutValue()); + ASSERT(duplicateResponse->getAMsduSupported() == firstResponse->getAMsduSupported()); + + // A rejected fresh renegotiation leaves agreement A in place, but its + // retransmission must replay B's rejection rather than A's old success. + duplicatePolicy.blockAckTimeoutValue = 1; + auto rejectedRequest = makeRequest(peer2, 6, 91, SequenceNumberCyclic(200), 16); + rejectedRequest->setSequenceNumber(SequenceNumberCyclic(51)); + rejectedRequest->setBlockAckTimeoutValue(4); + auto rejectedReception = recipientDataService->managementFrameReceived(new Packet("rejectedAddbaRequest", rejectedRequest), rejectedRequest); + ASSERT(!rejectedReception.duplicate); + hcf.processReceivedManagementFrame(rejectedRequest, rejectedReception.duplicate); + ASSERT(recipientHandler->getAgreement(6, peer2) == firstAgreement); + ASSERT(changedListener.numSignals == 0); + ASSERT(reorderDropListener.numSignals == 0); + ASSERT(responseCallback.managementPackets.size() == 3); + ASSERT(recipientHandler->getNumCachedAddbaResponses() == 1); + auto rejectedResponse = dynamicPtrCast(responseCallback.managementHeader); + ASSERT(rejectedResponse->getDialogToken() == 91); + ASSERT(rejectedResponse->getStatusCode() != 0); + ASSERT(rejectedResponse->getTid() == 6); + ASSERT(rejectedResponse->getBufferSize() == 1); + ASSERT(rejectedResponse->getBlockAckTimeoutValue() == 4); + ASSERT(!rejectedResponse->getAMsduSupported()); + + auto duplicateRejectedRequest = makeRequest(peer2, 6, 91, SequenceNumberCyclic(200), 16); + duplicateRejectedRequest->setSequenceNumber(SequenceNumberCyclic(51)); + duplicateRejectedRequest->setRetry(true); + duplicateRejectedRequest->setBlockAckTimeoutValue(4); + auto duplicateRejectedReception = recipientDataService->managementFrameReceived(new Packet("duplicateRejectedAddbaRequest", duplicateRejectedRequest), duplicateRejectedRequest); + ASSERT(duplicateRejectedReception.duplicate); + hcf.processReceivedManagementFrame(duplicateRejectedRequest, duplicateRejectedReception.duplicate); + ASSERT(recipientHandler->getAgreement(6, peer2) == firstAgreement); + ASSERT(recipientHandler->getNumCachedAddbaResponses() == 1); + ASSERT(changedListener.numSignals == 0); + ASSERT(reorderDropListener.numSignals == 0); + ASSERT(responseCallback.managementPackets.size() == 4); + auto duplicateRejectedResponse = dynamicPtrCast(responseCallback.managementHeader); + ASSERT(duplicateRejectedResponse->getDialogToken() == rejectedResponse->getDialogToken()); + ASSERT(duplicateRejectedResponse->getStatusCode() == rejectedResponse->getStatusCode()); + ASSERT(duplicateRejectedResponse->getTid() == rejectedResponse->getTid()); + ASSERT(duplicateRejectedResponse->getBlockAckPolicy() == rejectedResponse->getBlockAckPolicy()); + ASSERT(duplicateRejectedResponse->getBufferSize() == rejectedResponse->getBufferSize()); + ASSERT(duplicateRejectedResponse->getBlockAckTimeoutValue() == rejectedResponse->getBlockAckTimeoutValue()); + ASSERT(duplicateRejectedResponse->getAMsduSupported() == rejectedResponse->getAMsduSupported()); + + auto expectedHeader = makeQosHeader(peer1, 6, SequenceNumberCyclic(100)); + expectedHeader->setTransmitterAddress(peer2); + auto expectedPacket = new Packet("duplicateAddbaExpectedData", expectedHeader); + auto expectedFrames = recipientDataService->dataFrameReceived(expectedPacket, expectedHeader, recipientHandler); + ASSERT(expectedFrames.size() == 1); + ASSERT(expectedFrames.front() == expectedPacket); + auto followingHeader = makeQosHeader(peer1, 6, SequenceNumberCyclic(102)); + followingHeader->setTransmitterAddress(peer2); + auto followingPacket = new Packet("duplicateAddbaFollowingData", followingHeader); + auto followingFrames = recipientDataService->dataFrameReceived(followingPacket, followingHeader, recipientHandler); + ASSERT(followingFrames.size() == 1); + ASSERT(followingFrames.front() == bufferedPacket); + for (auto packet : expectedFrames) { + take(packet); + drop(packet); + delete packet; + } + for (auto packet : followingFrames) { + take(packet); + drop(packet); + delete packet; + } + + // A new MAC identity with the exact same ADDBA body is a genuine 10.25.2 + // renegotiation: replace the agreement and reset the reorder window, + // which still contains followingPacket (sequence number 102). + duplicatePolicy.accepted = true; + duplicatePolicy.blockAckTimeoutValue = 1; + duplicatePolicy.aMsduSupportedValue = true; + duplicatePolicy.maximumAllowedBufferSize = 64; + auto replacementRequest = makeRequest(peer2, 6, 90, SequenceNumberCyclic(100), 32); + replacementRequest->setSequenceNumber(SequenceNumberCyclic(52)); + replacementRequest->setBlockAckTimeoutValue(3); + auto replacementReception = recipientDataService->managementFrameReceived(new Packet("replacementAddbaRequest", replacementRequest), replacementRequest); + ASSERT(!replacementReception.duplicate); + ASSERT(replacementReception.completeFrames.empty()); + hcf.processReceivedManagementFrame(replacementRequest, replacementReception.duplicate); + ASSERT(recipientHandler->getAgreement(6, peer2) != firstAgreement); + ASSERT(addedListener.numSignals == 1); + ASSERT(changedListener.numSignals == 1); + ASSERT(reorderDropListener.numSignals == 1); + ASSERT(reorderDropListener.numOtherPacketDrops == 1); + ASSERT(responseCallback.managementPackets.size() == 5); + + auto duplicateWithoutAgreement = makeRequest(peer3, 7, 91, SequenceNumberCyclic(200), 16); + hcf.processReceivedManagementFrame(duplicateWithoutAgreement, true); + ASSERT(responseCallback.managementPackets.size() == 5); + + auto duplicateDelba = makeShared(); + duplicateDelba->setTransmitterAddress(peer2); + duplicateDelba->setTid(6); + duplicateDelba->setInitiator(true); + auto replacementAgreement = recipientHandler->getAgreement(6, peer2); + hcf.processReceivedManagementFrame(duplicateDelba, true); + ASSERT(recipientHandler->getAgreement(6, peer2) == replacementAgreement); + + hcf.processReceivedManagementFrame(duplicateDelba, false); + ASSERT(recipientHandler->getAgreement(6, peer2) == nullptr); + ASSERT(recipientHandler->getNumCachedAddbaResponses() == 0); + auto duplicateAfterTeardown = makeRequest(peer2, 6, 90, SequenceNumberCyclic(100), 32); + duplicateAfterTeardown->setSequenceNumber(SequenceNumberCyclic(52)); + duplicateAfterTeardown->setRetry(true); + hcf.processReceivedManagementFrame(duplicateAfterTeardown, true); + ASSERT(responseCallback.managementPackets.size() == 5); + + hcf.unsubscribe(Hcf::blockAckAgreementAddedSignal, &addedListener); + hcf.unsubscribe(Hcf::blockAckAgreementChangedSignal, &changedListener); + recipientDataService->unsubscribe(packetDroppedSignal, &reorderDropListener); + recipientDataServiceModule->callFinish(); + recipientDataServiceModule->deleteModule(); +} + // IEEE Std 802.11-2024, 9.6.4.2, 10.25.2, and 11.5.2.3. RecipientBlockAckAgreementHandler immediateRecipientHandler; TestRecipientPolicy recipientPolicy; From 2d525ec0f6731417fc6b813d2769439ba414ac5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Wed, 19 Aug 2026 19:41:52 +0200 Subject: [PATCH 11/14] Fix compound queue removal callback propagation Forward REMOVED callbacks from descendant queues through CompoundPacketQueueBase so observers are notified whenever a packet leaves the enclosing logical queue, including leaf-initiated and shared-buffer removals. Track the packet currently removed at the compound boundary with a scoped save-and-restore guard. Suppress only the matching descendant callback during boundary removal and overflow victim detachment, preserving exactly-once delivery and the intended DROPPED reason while allowing nested or reentrant removals of other packets to propagate. Extend the ADDBA transaction unit coverage for direct and nested compounds, bulk and shared-buffer removal, boundary remove/dequeue/pull paths, reentrant removal, and compound capacity drops. Validation: debug build; focused Ieee80211AddbaTransaction_1 unit test; focused PriorityQueue and EthernetQosQueue fingerprints; focused architecture check; independent semantic review. --- .../queueing/queue/CompoundPacketQueueBase.cc | 29 +++- .../queueing/queue/CompoundPacketQueueBase.h | 1 + tests/unit/Ieee80211AddbaTransaction_1.test | 124 +++++++++++++++++- 3 files changed, 149 insertions(+), 5 deletions(-) diff --git a/src/inet/queueing/queue/CompoundPacketQueueBase.cc b/src/inet/queueing/queue/CompoundPacketQueueBase.cc index 077c2ade951..90dba13f477 100644 --- a/src/inet/queueing/queue/CompoundPacketQueueBase.cc +++ b/src/inet/queueing/queue/CompoundPacketQueueBase.cc @@ -14,6 +14,22 @@ namespace queueing { Define_Module(CompoundPacketQueueBase); +class ScopedPacketRemoval +{ + protected: + Packet *&packetBeingRemoved; + Packet *previousPacket; + + public: + ScopedPacketRemoval(Packet *&packetBeingRemoved, Packet *packet) : + packetBeingRemoved(packetBeingRemoved), previousPacket(packetBeingRemoved) + { + packetBeingRemoved = packet; + } + + ~ScopedPacketRemoval() { packetBeingRemoved = previousPacket; } +}; + void CompoundPacketQueueBase::initialize(int stage) { PacketQueueBase::initialize(stage); @@ -88,7 +104,10 @@ void CompoundPacketQueueBase::pushPacket(Packet *packet, const cGate *gate) while (isOverloaded()) { auto packet = packetDropperFunction->selectPacket(this); EV_INFO << "Dropping packet" << EV_FIELD(packet) << EV_ENDL; - collection->removePacket(packet); + { + ScopedPacketRemoval scopedPacketRemoval(packetBeingRemoved, packet); + collection->removePacket(packet); + } emit(packetRemovedSignal, packet); droppedPackets.push_back(packet); } @@ -115,7 +134,10 @@ Packet *CompoundPacketQueueBase::pullPacket(const cGate *gate) void CompoundPacketQueueBase::removePacket(Packet *packet) { Enter_Method("removePacket"); - collection->removePacket(packet); + { + ScopedPacketRemoval scopedPacketRemoval(packetBeingRemoved, packet); + collection->removePacket(packet); + } notifyPacketRemoved(packet, IPacketQueue::PacketRemovalReason::REMOVED); emit(packetRemovedSignal, packet); } @@ -187,7 +209,8 @@ void CompoundPacketQueueBase::receiveSignal(cComponent *source, simsignal_t sign void CompoundPacketQueueBase::handlePacketRemoved(Packet *packet, IPacketQueue::PacketRemovalReason reason) { Enter_Method("handlePacketRemoved"); - if (reason == IPacketQueue::PacketRemovalReason::DROPPED) + if (reason == IPacketQueue::PacketRemovalReason::DROPPED || + (reason == IPacketQueue::PacketRemovalReason::REMOVED && packet != packetBeingRemoved)) notifyPacketRemoved(packet, reason); } diff --git a/src/inet/queueing/queue/CompoundPacketQueueBase.h b/src/inet/queueing/queue/CompoundPacketQueueBase.h index dc1c6e69164..e93dbb8005c 100644 --- a/src/inet/queueing/queue/CompoundPacketQueueBase.h +++ b/src/inet/queueing/queue/CompoundPacketQueueBase.h @@ -28,6 +28,7 @@ class INET_API CompoundPacketQueueBase : public PacketQueueBase, public cListene IPacketCollection *collection = nullptr; IPacketExtractor *packetExtractor = nullptr; std::vector childQueues; + Packet *packetBeingRemoved = nullptr; IPacketDropperFunction *packetDropperFunction = nullptr; diff --git a/tests/unit/Ieee80211AddbaTransaction_1.test b/tests/unit/Ieee80211AddbaTransaction_1.test index b3ac0b66507..5fe6d79c370 100644 --- a/tests/unit/Ieee80211AddbaTransaction_1.test +++ b/tests/unit/Ieee80211AddbaTransaction_1.test @@ -357,6 +357,22 @@ class TestPacketDropCallback : public queueing::IPacketQueue::ICallback } }; +class TestReentrantRemovalCallback : public queueing::IPacketQueue::ICallback +{ + public: + queueing::IPacketQueue *queue = nullptr; + Packet *triggerPacket = nullptr; + Packet *packetToRemove = nullptr; + bool removedPacket = false; + + virtual void handlePacketRemoved(Packet *packet, queueing::IPacketQueue::PacketRemovalReason reason) override { + if (!removedPacket && packet == triggerPacket && reason == queueing::IPacketQueue::PacketRemovalReason::REMOVED) { + removedPacket = true; + queue->removePacket(packetToRemove); + } + } +}; + class TestPacketPulledListener : public cListener { public: @@ -3349,6 +3365,10 @@ delete compoundPacket; auto secondQueue = createBufferedQueue("sharedRemovalSecondQueue"); TestPacketDropCallback firstCallback; TestPacketDropCallback secondCallback; + TestPacketDropCallback compoundCallback; + TestCompoundPacketQueue bufferedCompound; + bufferedCompound.observeQueue(secondQueue); + bufferedCompound.addObserver(&compoundCallback); firstQueue->addPacketCallback(&firstCallback); secondQueue->addPacketCallback(&secondCallback); firstQueue->pushPacket(new Packet("sharedRemovalFirstA", makeShared(B(1))), nullptr); @@ -3367,8 +3387,11 @@ delete compoundPacket; ASSERT(buffer->getNumPackets() == 0); ASSERT(secondCallback.numRemovedPackets == 1); ASSERT(secondCallback.numDroppedPackets == 0); + ASSERT(compoundCallback.numRemovedPackets == 1); + ASSERT(compoundCallback.numDroppedPackets == 0); take(retainedPacket); delete retainedPacket; + bufferedCompound.stopObserving(); firstQueue->removePacketCallback(&firstCallback); secondQueue->removePacketCallback(&secondCallback); firstQueue->callFinish(); @@ -3426,8 +3449,10 @@ delete compoundPacket; overflowQueue->deleteModule(); } -// A compound's direct frontier and a nested compound boundary each forward a -// destructive removal exactly once. +// A compound's direct frontier and a nested compound boundary each forward +// descendant removals exactly once. Boundary-initiated removals suppress the +// descendant callback for the same packet, including during reentrant removal +// of a different packet. { auto pendingQueueModule = cModuleType::get("inet.linklayer.ieee80211.mac.queue.CompoundPendingQueue")->create("callbackPendingQueue", this); pendingQueueModule->callInitialize(); @@ -3439,11 +3464,78 @@ delete compoundPacket; TestCompoundPacketQueue outerCompound; outerCompound.observeQueue(pendingQueue); outerCompound.addObserver(&nestedCallback); + auto directPacket = new Packet("directCompoundDrop", makeShared(B(1))); directLeaf->handlePacketDropped(directPacket); ASSERT(directCallback.numDroppedPackets == 1); ASSERT(nestedCallback.numDroppedPackets == 1); delete directPacket; + + auto leafRemovedPacket = new Packet("leafRemovedPacket", makeShared(B(1))); + directLeaf->pushPacket(leafRemovedPacket, nullptr); + directLeaf->removePacket(leafRemovedPacket); + ASSERT(directCallback.numRemovedPackets == 1); + ASSERT(nestedCallback.numRemovedPackets == 1); + take(leafRemovedPacket); + delete leafRemovedPacket; + + directLeaf->pushPacket(new Packet("leafBulkRemovedFirst", makeShared(B(1))), nullptr); + directLeaf->pushPacket(new Packet("leafBulkRemovedSecond", makeShared(B(1))), nullptr); + directLeaf->removeAllPackets(); + ASSERT(directCallback.numRemovedPackets == 3); + ASSERT(nestedCallback.numRemovedPackets == 3); + + auto boundaryRemovedPacket = new Packet("boundaryRemovedPacket", makeShared(B(1))); + directLeaf->pushPacket(boundaryRemovedPacket, nullptr); + pendingQueue->removePacket(boundaryRemovedPacket); + ASSERT(directCallback.numRemovedPackets == 4); + ASSERT(nestedCallback.numRemovedPackets == 4); + take(boundaryRemovedPacket); + delete boundaryRemovedPacket; + + directLeaf->pushPacket(new Packet("boundaryBulkRemovedFirst", makeShared(B(1))), nullptr); + directLeaf->pushPacket(new Packet("boundaryBulkRemovedSecond", makeShared(B(1))), nullptr); + pendingQueue->removeAllPackets(); + ASSERT(directCallback.numRemovedPackets == 6); + ASSERT(nestedCallback.numRemovedPackets == 6); + + auto boundaryDequeuedPacket = new Packet("boundaryDequeuedPacket", makeShared(B(1))); + directLeaf->pushPacket(boundaryDequeuedPacket, nullptr); + auto dequeuedPacket = pendingQueue->dequeuePacket([boundaryDequeuedPacket](const Packet *packet) { return packet == boundaryDequeuedPacket; }); + ASSERT(dequeuedPacket == boundaryDequeuedPacket); + ASSERT(directCallback.numDequeuedPackets == 1); + ASSERT(nestedCallback.numDequeuedPackets == 0); + take(dequeuedPacket); + delete dequeuedPacket; + + auto boundaryPulledPacket = new Packet("boundaryPulledPacket", makeShared(B(1))); + directLeaf->pushPacket(boundaryPulledPacket, nullptr); + auto pulledPacket = pendingQueue->pullPacket(nullptr); + ASSERT(pulledPacket == boundaryPulledPacket); + ASSERT(directCallback.numDequeuedPackets == 2); + ASSERT(nestedCallback.numDequeuedPackets == 0); + take(pulledPacket); + delete pulledPacket; + + auto reentrantTriggerPacket = new Packet("reentrantTriggerPacket", makeShared(B(1))); + auto reentrantRemovedPacket = new Packet("reentrantRemovedPacket", makeShared(B(1))); + directLeaf->pushPacket(reentrantTriggerPacket, nullptr); + directLeaf->pushPacket(reentrantRemovedPacket, nullptr); + TestReentrantRemovalCallback reentrantCallback; + reentrantCallback.queue = directLeaf; + reentrantCallback.triggerPacket = reentrantTriggerPacket; + reentrantCallback.packetToRemove = reentrantRemovedPacket; + directLeaf->addPacketCallback(&reentrantCallback); + pendingQueue->removePacket(reentrantTriggerPacket); + ASSERT(reentrantCallback.removedPacket); + ASSERT(directCallback.numRemovedPackets == 8); + ASSERT(nestedCallback.numRemovedPackets == 8); + directLeaf->removePacketCallback(&reentrantCallback); + take(reentrantTriggerPacket); + take(reentrantRemovedPacket); + delete reentrantTriggerPacket; + delete reentrantRemovedPacket; + outerCompound.stopObserving(); pendingQueue->removePacketCallback(&directCallback); pendingQueueModule->callFinish(); @@ -3467,6 +3559,34 @@ delete compoundPacket; packetShaperModule->deleteModule(); } +// Compound capacity enforcement detaches its leaf victim without leaking a +// REMOVED callback before reporting the single destructive departure. +{ + auto pendingQueueModule = cModuleType::get("inet.linklayer.ieee80211.mac.queue.CompoundPendingQueue")->create("callbackOverflowPendingQueue", this); + pendingQueueModule->par("packetCapacity").setIntValue(1); + pendingQueueModule->par("dropperClass").setStringValue("inet::queueing::PacketAtCollectionEndDropper"); + pendingQueueModule->callInitialize(); + auto pendingQueue = check_and_cast(pendingQueueModule); + TestPacketDropCallback directCallback; + TestPacketDropCallback nestedCallback; + pendingQueue->addPacketCallback(&directCallback); + TestCompoundPacketQueue outerCompound; + outerCompound.observeQueue(pendingQueue); + outerCompound.addObserver(&nestedCallback); + pendingQueue->pushPacket(new Packet("compoundRetainedPacket", makeQosHeader(peer1, 1, SequenceNumberCyclic(1))), nullptr); + pendingQueue->pushPacket(new Packet("compoundOverflowPacket", makeQosHeader(peer1, 1, SequenceNumberCyclic(2))), nullptr); + ASSERT(pendingQueue->getNumPackets() == 1); + ASSERT(directCallback.numDroppedPackets == 1); + ASSERT(directCallback.numRemovedPackets == 0); + ASSERT(nestedCallback.numDroppedPackets == 1); + ASSERT(nestedCallback.numRemovedPackets == 0); + outerCompound.stopObserving(); + pendingQueue->removePacketCallback(&directCallback); + pendingQueue->removeAllPackets(); + pendingQueueModule->callFinish(); + pendingQueueModule->deleteModule(); +} + // A shared buffer detaches every victim from its owning queue before the first // typed drop callback. This keeps protocol cleanup from reentering while a // later victim is still selectable from another queue. From bbe72efcdaab707749219a54d8f666598ed2a82c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Wed, 19 Aug 2026 20:53:14 +0200 Subject: [PATCH 12/14] Fix compound queue callback teardown --- .../queueing/queue/CompoundPacketQueueBase.cc | 12 ++++++++ .../queueing/queue/CompoundPacketQueueBase.h | 2 ++ tests/unit/Ieee80211AddbaTransaction_1.test | 28 +++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/src/inet/queueing/queue/CompoundPacketQueueBase.cc b/src/inet/queueing/queue/CompoundPacketQueueBase.cc index 90dba13f477..77e323656e8 100644 --- a/src/inet/queueing/queue/CompoundPacketQueueBase.cc +++ b/src/inet/queueing/queue/CompoundPacketQueueBase.cc @@ -70,6 +70,18 @@ void CompoundPacketQueueBase::registerQueueFrontier(cModule *module) } void CompoundPacketQueueBase::finish() +{ + unregisterChildQueueCallbacks(); + PacketQueueBase::finish(); +} + +void CompoundPacketQueueBase::preDelete(cComponent *root) +{ + unregisterChildQueueCallbacks(); + PacketQueueBase::preDelete(root); +} + +void CompoundPacketQueueBase::unregisterChildQueueCallbacks() { for (auto childQueue : childQueues) childQueue->removePacketCallback(this); diff --git a/src/inet/queueing/queue/CompoundPacketQueueBase.h b/src/inet/queueing/queue/CompoundPacketQueueBase.h index e93dbb8005c..4ecd2972447 100644 --- a/src/inet/queueing/queue/CompoundPacketQueueBase.h +++ b/src/inet/queueing/queue/CompoundPacketQueueBase.h @@ -37,7 +37,9 @@ class INET_API CompoundPacketQueueBase : public PacketQueueBase, public cListene virtual void initialize(int stage) override; virtual void finish() override; + virtual void preDelete(cComponent *root) override; virtual void registerQueueFrontier(cModule *module); + virtual void unregisterChildQueueCallbacks(); virtual IPacketDropperFunction *createDropperFunction(const char *dropperClass) const; diff --git a/tests/unit/Ieee80211AddbaTransaction_1.test b/tests/unit/Ieee80211AddbaTransaction_1.test index 5fe6d79c370..764499be3e3 100644 --- a/tests/unit/Ieee80211AddbaTransaction_1.test +++ b/tests/unit/Ieee80211AddbaTransaction_1.test @@ -3449,6 +3449,34 @@ delete compoundPacket; overflowQueue->deleteModule(); } +// A compound unregisters from child queues during pre-delete even when finish +// is skipped, and repeated lifecycle cleanup is harmless. +{ + auto pendingQueueModule = cModuleType::get("inet.linklayer.ieee80211.mac.queue.CompoundPendingQueue")->create("preDeletePendingQueue", this); + pendingQueueModule->callInitialize(); + auto pendingQueue = check_and_cast(pendingQueueModule); + auto leafQueue = check_and_cast(pendingQueueModule->getSubmodule("managementQueue")); + TestPacketDropCallback callback; + pendingQueue->addPacketCallback(&callback); + + auto observedPacket = new Packet("preDeleteObservedPacket", makeShared(B(1))); + leafQueue->pushPacket(observedPacket, nullptr); + leafQueue->removePacket(observedPacket); + ASSERT(callback.numRemovedPackets == 1); + take(observedPacket); + delete observedPacket; + + pendingQueueModule->callPreDelete(pendingQueueModule); + auto unobservedPacket = new Packet("preDeleteUnobservedPacket", makeShared(B(1))); + leafQueue->pushPacket(unobservedPacket, nullptr); + leafQueue->removePacket(unobservedPacket); + ASSERT(callback.numRemovedPackets == 1); + take(unobservedPacket); + delete unobservedPacket; + + pendingQueueModule->deleteModule(); +} + // A compound's direct frontier and a nested compound boundary each forward // descendant removals exactly once. Boundary-initiated removals suppress the // descendant callback for the same packet, including during reentrant removal From 1b6d28a3d1f0259d6d58e6d02203538ac3bfb581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Thu, 20 Aug 2026 12:46:25 +0200 Subject: [PATCH 13/14] Fix ADDBA transaction lifecycle handling Clarify agreement ownership, harden reentrant cleanup, restore queue extraction animation, and add focused regressions for the reviewed edge cases. --- WHATSNEW | 6 +- .../OriginatorBlockAckAgreementHandler.cc | 16 ++- .../IOriginatorBlockAckAgreementHandler.h | 7 +- .../ieee80211/mac/coordinationfunction/Dcf.cc | 1 + .../ieee80211/mac/coordinationfunction/Hcf.cc | 21 ++- .../ieee80211/mac/framesequence/HcfFs.cc | 18 ++- src/inet/queueing/base/PacketFlowBase.cc | 2 + src/inet/queueing/contract/IPacketQueue.h | 6 +- src/inet/queueing/queue/PacketQueue.cc | 2 + src/inet/queueing/scheduler/LabelScheduler.cc | 2 + .../queueing/scheduler/PriorityScheduler.cc | 2 + src/inet/queueing/scheduler/WrrScheduler.cc | 2 + tests/unit/Ieee80211AddbaTransaction_1.test | 135 +++++++++++++++--- 13 files changed, 173 insertions(+), 47 deletions(-) diff --git a/WHATSNEW b/WHATSNEW index f835ae6b4c6..2fa93288f7b 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -177,8 +177,10 @@ Notable backward incompatible changes are the following: processAbortedDelba() outcome; processTransmittedDelba() still returns the agreement removed by an untagged DELBA, or null when none was removed. - The originator policy parameter `addbaFailureTimeout` was renamed to - `addbaResponseTimeout`; update custom configurations accordingly. + OriginatorBlockAckAgreementPolicy now exposes the new `addbaResponseTimeout` + and `addbaRetryBackoff` NED parameters. C++ implementations of + IOriginatorBlockAckAgreementPolicy must replace computeAddbaFailureTimeout() + with getAddbaResponseTimeout() and implement computeAddbaRetryBackoff(). Notable backward compatible changes are the following: diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc index b4b19afa972..d03bae7b498 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc @@ -7,6 +7,8 @@ #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h" +#include + #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h" #include "inet/linklayer/ieee80211/mac/blockack/Ieee80211AddbaTransactionTag_m.h" #include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame_m.h" @@ -75,6 +77,7 @@ void OriginatorBlockAckAgreementHandler::recordAddbaFailure(MacAddress receiverA void OriginatorBlockAckAgreementHandler::addbaResponseTimeoutExpired(IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback) { auto now = simTime(); + std::vector expiredTransactionIds; for (auto it = blockAckAgreements.begin(); it != blockAckAgreements.end();) { auto agreement = it->second; if (agreement->isPending() && agreement->getIsAddbaRequestSent() && agreement->getAddbaResponseDeadline() <= now) { @@ -83,11 +86,13 @@ void OriginatorBlockAckAgreementHandler::addbaResponseTimeoutExpired(IOriginator recordAddbaFailure(agreement->getReceiverAddr(), agreement->getTid(), blockAckAgreementPolicy); it = blockAckAgreements.erase(it); delete agreement; - callback->cancelAddbaTransaction(transactionId, nullptr); + expiredTransactionIds.push_back(transactionId); } else it++; } + for (auto transactionId : expiredTransactionIds) + callback->cancelAddbaTransaction(transactionId, nullptr); scheduleAddbaResponseTimer(callback); } @@ -259,15 +264,16 @@ OriginatorBlockAckAgreementResponse OriginatorBlockAckAgreementHandler::processR if (!acceptedByLocalPolicy) { // IEEE Std 802.11-2024, 10.25.2 Note 3: delete a successful // agreement rejected by local policy and continue with Normal Ack. + response.terminatedAgreement.reset(removeAgreement(addbaResp->getTransmitterAddress(), addbaResp->getTid())); + if (response.terminatedAgreement == nullptr) + throw cRuntimeError("Cannot terminate locally vetoed Block Ack agreement"); response.teardownDelba = buildDelba(addbaResp->getTransmitterAddress(), addbaResp->getTid(), RC_END_BA); pendingTeardownTransactionIds[std::make_pair(addbaResp->getTransmitterAddress(), addbaResp->getTid())] = transactionId; - response.agreement = removeAgreement(addbaResp->getTransmitterAddress(), addbaResp->getTid()); - if (response.agreement != nullptr) - response.terminatedAgreement.reset(response.agreement); + response.teardownTransactionId = transactionId; scheduleInactivityTimer(callback); } else - response.agreement = agreement; + response.establishedAgreement = agreement; return response; } else { diff --git a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h index b00e79a3e72..fbe0918749a 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h @@ -24,9 +24,12 @@ namespace ieee80211 { struct INET_API OriginatorBlockAckAgreementResponse { - OriginatorBlockAckAgreement *agreement = nullptr; - Ptr teardownDelba; + // Borrowed from the handler; valid while the established agreement remains installed. + OriginatorBlockAckAgreement *establishedAgreement = nullptr; + // Owns the agreement that was established and immediately terminated after a local veto. std::unique_ptr terminatedAgreement; + Ptr teardownDelba; + uint64_t teardownTransactionId = 0; }; class INET_API IOriginatorBlockAckAgreementHandler diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc index dd07a97ee0e..e2f35e4bbc5 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc @@ -75,6 +75,7 @@ void Dcf::channelGranted(IChannelAccess *channelAccess) if (this->channelAccess->getInProgressFrames()->getFrameToTransmit() == nullptr) { EV_DETAIL << "Releasing channel because no frame is available.\n"; channelAccess->releaseChannel(this); + mac->sendDownPendingRadioConfigMsg(); return; } frameSequenceHandler->startFrameSequence(new DcfFs(), buildContext(), this); diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc index e7897da5a86..8bfe80eaa4d 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc @@ -69,6 +69,9 @@ void Hcf::initialize(int stage) if (auto delba = dynamicPtrCast(packet->peekAtFront())) return originatorBlockAckAgreementHandler->isDelbaPending(packet, delba); auto dataHeader = dynamicPtrCast(packet->peekAtFront()); + // Hold this peer/TID while its ADDBA response is pending so no + // already-sequenced MPDU can precede the advertised SSN. The + // response timeout starts only after the request is transmitted. return dataHeader == nullptr || dataHeader->getType() != ST_DATA_WITH_QOS || (!originatorBlockAckAgreementHandler->isAddbaResponsePending(dataHeader->getReceiverAddress(), dataHeader->getTid())); }); } @@ -149,6 +152,8 @@ void Hcf::handlePacketRemoved(Packet *packet, queueing::IPacketQueue::PacketRemo Enter_Method("handlePacketRemoved"); untrackPendingFrame(packet); bool shouldResume = false; + // HCF treats explicit REMOVED notifications as terminal transaction + // disposal; code relocating a packet must use dequeuePacket(). if (reason == queueing::IPacketQueue::PacketRemovalReason::DROPPED || reason == queueing::IPacketQueue::PacketRemovalReason::REMOVED) { shouldResume |= processDroppedBlockAckSetupFrame(packet); shouldResume |= processDroppedBlockAckTeardownFrame(packet); @@ -292,6 +297,8 @@ void Hcf::scheduleAddbaResponseTimer(simtime_t deadline) void Hcf::cancelAddbaTransaction(uint64_t transactionId, Packet *excludedPacket) { Enter_Method("cancelAddbaTransaction"); + // Frames borrowed by the active sequence cannot be removed here. The + // sequence's failure paths detect their now-stale transaction and discard them. auto belongsToTransaction = [this, transactionId, excludedPacket](Packet *packet) { auto transactionTag = packet->findTag(); return packet != excludedPacket && !isPacketReferencedByCurrentFrameSequence(packet) && transactionTag != nullptr && transactionTag->getTransactionId() == transactionId; @@ -384,6 +391,7 @@ void Hcf::channelGranted(IChannelAccess *channelAccess) if (!hasFrameToTransmit(ac)) { EV_DETAIL << "Releasing channel because no eligible frame is available.\n"; edcaf->releaseChannel(this); + mac->sendDownPendingRadioConfigMsg(); return; } edcaf->getTxopProcedure()->startTxop(ac); @@ -548,14 +556,17 @@ void Hcf::recipientProcessReceivedManagementFrame(const PtrprocessReceivedAddbaResp(addbaResp, originatorBlockAckAgreementPolicy, this); if (wasPending && !originatorBlockAckAgreementHandler->isAddbaResponsePending(addbaResp->getTransmitterAddress(), addbaResp->getTid())) rebuildPendingFrameEligibility(); - if (response.agreement != nullptr) { - emit(blockAckAgreementAddedSignal, response.agreement); - if (response.teardownDelba != nullptr) - emit(blockAckAgreementDeletedSignal, response.agreement); + if (response.teardownDelba != nullptr && (response.terminatedAgreement == nullptr || response.teardownTransactionId == 0)) + throw cRuntimeError("Invalid locally vetoed ADDBA response outcome"); + if (response.establishedAgreement != nullptr) + emit(blockAckAgreementAddedSignal, response.establishedAgreement); + if (response.terminatedAgreement != nullptr) { + emit(blockAckAgreementAddedSignal, response.terminatedAgreement.get()); + emit(blockAckAgreementDeletedSignal, response.terminatedAgreement.get()); } if (response.teardownDelba != nullptr) { auto delbaPacket = new Packet("Delba", response.teardownDelba); - delbaPacket->addTag()->setTransactionId(response.agreement == nullptr ? 0 : response.agreement->getTransactionId()); + delbaPacket->addTag()->setTransactionId(response.teardownTransactionId); processMgmtFrame(delbaPacket, response.teardownDelba); } resumeEligibleChannelAccess(); diff --git a/src/inet/linklayer/ieee80211/mac/framesequence/HcfFs.cc b/src/inet/linklayer/ieee80211/mac/framesequence/HcfFs.cc index af6b96ea350..2ae6c036527 100644 --- a/src/inet/linklayer/ieee80211/mac/framesequence/HcfFs.cc +++ b/src/inet/linklayer/ieee80211/mac/framesequence/HcfFs.cc @@ -13,6 +13,14 @@ namespace inet { namespace ieee80211 { +static bool hasMoreTxOpsForFrame(RepeatingFs *frameSequence, FrameSequenceContext *context, Packet *nextFrameToTransmit) +{ + if (nextFrameToTransmit == nullptr) + return false; + const auto& nextHeader = nextFrameToTransmit->peekAtFront(); + return frameSequence->getCount() == 0 || (!nextHeader->getReceiverAddress().isMulticast() && context->getQoSContext()->txopProcedure->getRemaining() > 0); +} + HcfFs::HcfFs() : // G.3 EDCA and HCCA sequences // hcf-sequence = @@ -61,18 +69,14 @@ bool HcfFs::hasMoreTxOps(RepeatingFs *frameSequence, FrameSequenceContext *conte // This predicate is the TXOP continuation boundary, so it may materialize // an eligible pending frame. Availability queries elsewhere remain pure. auto nextFrameToTransmit = context->getInProgressFrames()->getFrameToTransmit(); - if (nextFrameToTransmit != nullptr) { - const auto& nextHeader = nextFrameToTransmit->peekAtFront(); - return frameSequence->getCount() == 0 || (!nextHeader->getReceiverAddress().isMulticast() && context->getQoSContext()->txopProcedure->getRemaining() > 0); - } - return false; + return hasMoreTxOpsForFrame(frameSequence, context, nextFrameToTransmit); } bool HcfFs::hasMoreTxOpsAndMulticast(RepeatingFs *frameSequence, FrameSequenceContext *context) { - return hasMoreTxOps(frameSequence, context) && context->getInProgressFrames()->getFrameToTransmit()->peekAtFront()->getReceiverAddress().isMulticast(); + auto nextFrameToTransmit = context->getInProgressFrames()->getFrameToTransmit(); + return hasMoreTxOpsForFrame(frameSequence, context, nextFrameToTransmit) && nextFrameToTransmit->peekAtFront()->getReceiverAddress().isMulticast(); } } // namespace ieee80211 } // namespace inet - diff --git a/src/inet/queueing/base/PacketFlowBase.cc b/src/inet/queueing/base/PacketFlowBase.cc index a9500a99a8d..96eb2ca9716 100644 --- a/src/inet/queueing/base/PacketFlowBase.cc +++ b/src/inet/queueing/base/PacketFlowBase.cc @@ -181,6 +181,8 @@ Packet *PacketFlowBase::dequeuePacket(const PacketPredicate& predicate) processPacket(packet); handlePacketProcessed(packet); emit(packetPulledOutSignal, packet); + if (collector != nullptr) + animatePullPacket(packet, outputGate, collector.getReferencedGate()); drop(packet); return packet; } diff --git a/src/inet/queueing/contract/IPacketQueue.h b/src/inet/queueing/contract/IPacketQueue.h index 15c62bcb13d..2e6a7fbe5ef 100644 --- a/src/inet/queueing/contract/IPacketQueue.h +++ b/src/inet/queueing/contract/IPacketQueue.h @@ -23,9 +23,9 @@ class INET_API IPacketQueue : public virtual IPacketCollection, public virtual I { public: enum class PacketRemovalReason { - DEQUEUED, - REMOVED, - DROPPED, + DEQUEUED, // Normal queue processing transferred ownership out of the queue. + REMOVED, // Explicit removal outside normal queue processing. + DROPPED, // The queue destructively removed the packet. }; class INET_API ICallback { diff --git a/src/inet/queueing/queue/PacketQueue.cc b/src/inet/queueing/queue/PacketQueue.cc index c0374894510..9a6f553ce48 100644 --- a/src/inet/queueing/queue/PacketQueue.cc +++ b/src/inet/queueing/queue/PacketQueue.cc @@ -158,6 +158,8 @@ Packet *PacketQueue::dequeuePacket(const PacketPredicate& predicate) buffer->removePacket(packet); notifyPacketRemoved(packet, IPacketQueue::PacketRemovalReason::DEQUEUED); recordPacketDequeued(packet); + if (collector != nullptr) + animatePullPacket(packet, outputGate, collector.getReferencedGate()); drop(packet); return packet; } diff --git a/src/inet/queueing/scheduler/LabelScheduler.cc b/src/inet/queueing/scheduler/LabelScheduler.cc index eaea811bbab..f46dd2d2794 100644 --- a/src/inet/queueing/scheduler/LabelScheduler.cc +++ b/src/inet/queueing/scheduler/LabelScheduler.cc @@ -131,6 +131,8 @@ Packet *LabelScheduler::dequeuePacket(const PacketPredicate& predicate) take(packet); handlePacketProcessed(packet); emit(packetPulledSignal, packet); + if (collector != nullptr) + animatePullPacket(packet, outputGate, collector.getReferencedGate()); drop(packet); return packet; } diff --git a/src/inet/queueing/scheduler/PriorityScheduler.cc b/src/inet/queueing/scheduler/PriorityScheduler.cc index 1f69ae4aa09..be772b00d99 100644 --- a/src/inet/queueing/scheduler/PriorityScheduler.cc +++ b/src/inet/queueing/scheduler/PriorityScheduler.cc @@ -117,6 +117,8 @@ Packet *PriorityScheduler::dequeuePacket(const PacketPredicate& predicate) take(packet); handlePacketProcessed(packet); emit(packetPulledSignal, packet); + if (collector != nullptr) + animatePullPacket(packet, outputGate, collector.getReferencedGate()); drop(packet); return packet; } diff --git a/src/inet/queueing/scheduler/WrrScheduler.cc b/src/inet/queueing/scheduler/WrrScheduler.cc index 2552cf4de5a..fbe6535eca2 100644 --- a/src/inet/queueing/scheduler/WrrScheduler.cc +++ b/src/inet/queueing/scheduler/WrrScheduler.cc @@ -159,6 +159,8 @@ Packet *WrrScheduler::dequeuePacket(const PacketPredicate& predicate) take(packet); handlePacketProcessed(packet); emit(packetPulledSignal, packet); + if (collector != nullptr) + animatePullPacket(packet, outputGate, collector.getReferencedGate()); drop(packet); return packet; } diff --git a/tests/unit/Ieee80211AddbaTransaction_1.test b/tests/unit/Ieee80211AddbaTransaction_1.test index 764499be3e3..2ce24ff2d5c 100644 --- a/tests/unit/Ieee80211AddbaTransaction_1.test +++ b/tests/unit/Ieee80211AddbaTransaction_1.test @@ -124,6 +124,20 @@ class TestOriginatorHandler : public OriginatorBlockAckAgreementHandler simtime_t getRetryDeadline(MacAddress receiverAddress, Tid tid) const { auto it = addbaRetryDeadlines.find(std::make_pair(receiverAddress, tid)); return it == addbaRetryDeadlines.end() ? SIMTIME_MAX : it->second; } }; +class TestTimeoutCancellationCallback : public TestCallback +{ + public: + TestOriginatorHandler *handler = nullptr; + std::vector> expiredAgreementIds; + bool allExpiredAgreementsGoneBeforeCallbacks = true; + + virtual void cancelAddbaTransaction(uint64_t transactionId, Packet *excludedPacket) override { + for (const auto& agreementId : expiredAgreementIds) + allExpiredAgreementsGoneBeforeCallbacks &= handler->getAgreement(agreementId.first, agreementId.second) == nullptr; + TestCallback::cancelAddbaTransaction(transactionId, excludedPacket); + } +}; + class TestRecipientHandler : public RecipientBlockAckAgreementHandler { public: @@ -363,10 +377,11 @@ class TestReentrantRemovalCallback : public queueing::IPacketQueue::ICallback queueing::IPacketQueue *queue = nullptr; Packet *triggerPacket = nullptr; Packet *packetToRemove = nullptr; + queueing::IPacketQueue::PacketRemovalReason triggerReason = queueing::IPacketQueue::PacketRemovalReason::REMOVED; bool removedPacket = false; virtual void handlePacketRemoved(Packet *packet, queueing::IPacketQueue::PacketRemovalReason reason) override { - if (!removedPacket && packet == triggerPacket && reason == queueing::IPacketQueue::PacketRemovalReason::REMOVED) { + if (!removedPacket && packet == triggerPacket && reason == triggerReason) { removedPacket = true; queue->removePacket(packetToRemove); } @@ -762,8 +777,9 @@ class TestDcaf : public Dcaf class TestDcf : public Dcf { public: - TestFrameSequenceHandler *configure(TestDcaf *channelAccess) { + TestFrameSequenceHandler *configure(TestDcaf *channelAccess, Ieee80211Mac *mac) { this->channelAccess = channelAccess; + this->mac = mac; auto handler = new TestFrameSequenceHandler(); frameSequenceHandler = handler; return handler; @@ -1103,22 +1119,25 @@ ASSERT(callback.addbaDeadline == simTime() + 1); auto requestTransactionId = callback.managementPacket->getTag()->getTransactionId(); auto wrongToken = makeResponse(peer1, 3, request->getDialogToken() + 1, 0); -ASSERT(handler.processReceivedAddbaResp(wrongToken, &originatorPolicy, &callback).agreement == nullptr); +ASSERT(handler.processReceivedAddbaResp(wrongToken, &originatorPolicy, &callback).establishedAgreement == nullptr); ASSERT(handler.isAddbaResponsePending(peer1, 3)); auto wrongTid = makeResponse(peer1, 4, request->getDialogToken(), 0); -ASSERT(handler.processReceivedAddbaResp(wrongTid, &originatorPolicy, &callback).agreement == nullptr); +ASSERT(handler.processReceivedAddbaResp(wrongTid, &originatorPolicy, &callback).establishedAgreement == nullptr); auto wrongPeer = makeResponse(peer2, 3, request->getDialogToken(), 0); -ASSERT(handler.processReceivedAddbaResp(wrongPeer, &originatorPolicy, &callback).agreement == nullptr); +ASSERT(handler.processReceivedAddbaResp(wrongPeer, &originatorPolicy, &callback).establishedAgreement == nullptr); auto success = makeResponse(peer1, 3, request->getDialogToken(), 0); auto establishedResponse = handler.processReceivedAddbaResp(success, &originatorPolicy, &callback); -auto established = establishedResponse.agreement; +auto established = establishedResponse.establishedAgreement; ASSERT(established != nullptr); +ASSERT(establishedResponse.terminatedAgreement == nullptr); +ASSERT(establishedResponse.teardownDelba == nullptr); +ASSERT(establishedResponse.teardownTransactionId == 0); ASSERT(callback.cancelledTransactionIds.back() == requestTransactionId); ASSERT(callback.excludedPackets.back() == nullptr); ASSERT(established->getStartingSequenceNumber() == SequenceNumberCyclic(0)); ASSERT(!handler.isAddbaResponsePending(peer1, 3)); -ASSERT(handler.processReceivedAddbaResp(success, &originatorPolicy, &callback).agreement == nullptr); +ASSERT(handler.processReceivedAddbaResp(success, &originatorPolicy, &callback).establishedAgreement == nullptr); // IEEE Std 802.11-2024, 10.25.2 and Figure 11-32: a successful matching // response establishes the agreement even when local policy rejects the @@ -1137,9 +1156,10 @@ ASSERT(handler.processReceivedAddbaResp(success, &originatorPolicy, &callback).a vetoHandler.processTransmittedAddbaReq(vetoCallback.managementPacket, vetoRequest, &vetoPolicy, &vetoCallback); auto vetoResponse = makeResponse(peer3, 6, vetoRequest->getDialogToken(), 0); auto vetoResponseOutcome = vetoHandler.processReceivedAddbaResp(vetoResponse, &vetoPolicy, &vetoCallback); - auto vetoAgreement = vetoResponseOutcome.agreement; + auto vetoAgreement = vetoResponseOutcome.terminatedAgreement.get(); ASSERT(vetoAgreement != nullptr); - ASSERT(vetoResponseOutcome.terminatedAgreement.get() == vetoAgreement); + ASSERT(vetoResponseOutcome.establishedAgreement == nullptr); + ASSERT(vetoResponseOutcome.teardownTransactionId == vetoTransactionId); ASSERT(vetoAgreement->getIsAddbaResponseReceived()); ASSERT(vetoHandler.getAgreement(peer3, 6) == nullptr); auto vetoDelba = vetoResponseOutcome.teardownDelba; @@ -1149,7 +1169,7 @@ ASSERT(handler.processReceivedAddbaResp(success, &originatorPolicy, &callback).a ASSERT(vetoDelba->getTid() == 6); ASSERT(vetoDelba->getReasonCode() == RC_END_BA); Packet vetoDelbaPacket("vetoDelba", vetoDelba); - vetoDelbaPacket.addTag()->setTransactionId(vetoTransactionId); + vetoDelbaPacket.addTag()->setTransactionId(vetoResponseOutcome.teardownTransactionId); ASSERT(vetoHandler.isDelbaPending(&vetoDelbaPacket, vetoDelba)); ASSERT(vetoHandler.getAgreement(peer3, 6) == nullptr); ASSERT(vetoHandler.getRetryDeadline(peer3, 6) > simTime()); @@ -1179,7 +1199,7 @@ handler.processAcknowledgedDataFrame(&rejectedTrigger, rejectedHeader, &originat auto rejectedRequest = dynamicPtrCast(callback.managementHeader); handler.processTransmittedAddbaReq(callback.managementPacket, rejectedRequest, &originatorPolicy, &callback); auto rejection = makeResponse(peer2, 4, rejectedRequest->getDialogToken(), 1); -ASSERT(handler.processReceivedAddbaResp(rejection, &originatorPolicy, &callback).agreement == nullptr); +ASSERT(handler.processReceivedAddbaResp(rejection, &originatorPolicy, &callback).establishedAgreement == nullptr); ASSERT(handler.getAgreement(peer2, 4) == nullptr); handler.processAcknowledgedDataFrame(&rejectedTrigger, rejectedHeader, &originatorPolicy, &callback); ASSERT(handler.getAgreement(peer2, 4) == nullptr); @@ -1202,13 +1222,48 @@ ASSERT(callback.excludedPackets.back() == nullptr); handler.processTransmittedAddbaReq(timedRequestPacket, timedRequest, &originatorPolicy, &callback); ASSERT(handler.getAgreement(peer3, 5) == nullptr); auto staleResponse = makeResponse(peer3, 5, timedRequest->getDialogToken(), 0); -ASSERT(handler.processReceivedAddbaResp(staleResponse, &originatorPolicy, &callback).agreement == nullptr); +ASSERT(handler.processReceivedAddbaResp(staleResponse, &originatorPolicy, &callback).establishedAgreement == nullptr); handler.processAcknowledgedDataFrame(&timeoutTrigger, timeoutHeader, &originatorPolicy, &callback); ASSERT(handler.getAgreement(peer3, 5) == nullptr); handler.allowRetryNow(peer3, 5); handler.processAcknowledgedDataFrame(&timeoutTrigger, timeoutHeader, &originatorPolicy, &callback); ASSERT(handler.isAddbaResponsePending(peer3, 5)); +// All simultaneously expired agreements are erased before cancellation can +// reenter the MAC and query or mutate transaction state. +{ + TestOriginatorHandler timeoutHandler; + TestOriginatorPolicy timeoutPolicy; + TestTimeoutCancellationCallback timeoutCallback; + timeoutCallback.handler = &timeoutHandler; + timeoutCallback.expiredAgreementIds = { { peer1, 1 }, { peer2, 2 } }; + + Packet firstTimeoutTrigger("firstTimeoutTrigger"); + auto firstTimeoutHeader = makeQosHeader(peer1, 1, SequenceNumberCyclic(40)); + timeoutHandler.processAcknowledgedDataFrame(&firstTimeoutTrigger, firstTimeoutHeader, &timeoutPolicy, &timeoutCallback); + auto firstTimeoutRequestPacket = timeoutCallback.managementPacket; + auto firstTimeoutRequest = dynamicPtrCast(timeoutCallback.managementHeader); + auto firstTimeoutTransactionId = firstTimeoutRequestPacket->getTag()->getTransactionId(); + timeoutHandler.processTransmittedAddbaReq(firstTimeoutRequestPacket, firstTimeoutRequest, &timeoutPolicy, &timeoutCallback); + + Packet secondTimeoutTrigger("secondTimeoutTrigger"); + auto secondTimeoutHeader = makeQosHeader(peer2, 2, SequenceNumberCyclic(50)); + timeoutHandler.processAcknowledgedDataFrame(&secondTimeoutTrigger, secondTimeoutHeader, &timeoutPolicy, &timeoutCallback); + auto secondTimeoutRequestPacket = timeoutCallback.managementPacket; + auto secondTimeoutRequest = dynamicPtrCast(timeoutCallback.managementHeader); + auto secondTimeoutTransactionId = secondTimeoutRequestPacket->getTag()->getTransactionId(); + timeoutHandler.processTransmittedAddbaReq(secondTimeoutRequestPacket, secondTimeoutRequest, &timeoutPolicy, &timeoutCallback); + + timeoutHandler.expireNow(peer1, 1); + timeoutHandler.expireNow(peer2, 2); + timeoutHandler.addbaResponseTimeoutExpired(&timeoutPolicy, &timeoutCallback); + ASSERT(timeoutCallback.allExpiredAgreementsGoneBeforeCallbacks); + ASSERT(timeoutCallback.cancelledTransactionIds.size() == 2); + ASSERT(std::find(timeoutCallback.cancelledTransactionIds.begin(), timeoutCallback.cancelledTransactionIds.end(), firstTimeoutTransactionId) != timeoutCallback.cancelledTransactionIds.end()); + ASSERT(std::find(timeoutCallback.cancelledTransactionIds.begin(), timeoutCallback.cancelledTransactionIds.end(), secondTimeoutTransactionId) != timeoutCallback.cancelledTransactionIds.end()); + ASSERT(timeoutCallback.addbaDeadline == SIMTIME_MAX); +} + // Transaction cancellation leaves packets borrowed by the active frame // sequence under that sequence's ownership, including RTS-protected packets. { @@ -2474,10 +2529,13 @@ ASSERT(!dataService.hasEligibleFrame(&mixedQueue)); grantedEdca.internallyCollidedEdcafs = { &grantedEdcaf }; TestHcf grantedHcf; grantedHcf.configure(&grantedEdca); + TestMac grantedMac; + grantedHcf.configureMac(&grantedMac); TestSignalListener collisionListener; grantedHcf.subscribe(Hcf::edcaCollisionDetectedSignal, &collisionListener); grantedHcf.grantChannel(&grantedEdcaf); ASSERT(grantedEdcaf.numReleaseChannelCalls == 1); + ASSERT(grantedMac.numPendingRadioConfigSends == 1); ASSERT(collisionListener.numSignals == 0); grantedHcf.unsubscribe(Hcf::edcaCollisionDetectedSignal, &collisionListener); } @@ -3635,17 +3693,22 @@ delete compoundPacket; dropCallback.expectedPacketCountsAtFirstDrop = { { firstQueue, 1 }, { secondQueue, 0 } }; firstQueue->addPacketCallback(&dropCallback); secondQueue->addPacketCallback(&dropCallback); - firstQueue->pushPacket(new Packet("sharedFirstVictim", makeShared(B(1))), nullptr); - secondQueue->pushPacket(new Packet("sharedSecondVictim", makeShared(B(1))), nullptr); + auto firstVictim = new Packet("sharedFirstVictim", makeShared(B(1))); auto retainedPacket = new Packet("sharedRetainedPacket", makeShared(B(3))); + TestReentrantRemovalCallback reentrantCallback; + reentrantCallback.queue = firstQueue; + reentrantCallback.triggerPacket = firstVictim; + reentrantCallback.packetToRemove = retainedPacket; + reentrantCallback.triggerReason = queueing::IPacketQueue::PacketRemovalReason::DROPPED; + firstQueue->addPacketCallback(&reentrantCallback); + firstQueue->pushPacket(firstVictim, nullptr); + secondQueue->pushPacket(new Packet("sharedSecondVictim", makeShared(B(1))), nullptr); firstQueue->pushPacket(retainedPacket, nullptr); - if (dropCallback.numDroppedPackets != 2 || dropCallback.numRemovedPackets != 0 || !dropCallback.allExpectedPacketsDetachedAtFirstDrop || firstQueue->getNumPackets() != 1 || secondQueue->getNumPackets() != 0) - throw cRuntimeError("Shared buffer notified a drop before detaching the full victim batch"); - auto dequeuedPacket = firstQueue->dequeuePacket([retainedPacket](const Packet *packet) { return packet == retainedPacket; }); - if (dequeuedPacket != retainedPacket) - throw cRuntimeError("Shared buffer retained the wrong packet after overload"); - take(dequeuedPacket); - delete dequeuedPacket; + if (dropCallback.numDroppedPackets != 2 || dropCallback.numRemovedPackets != 1 || !dropCallback.allExpectedPacketsDetachedAtFirstDrop || !reentrantCallback.removedPacket || firstQueue->getNumPackets() != 0 || secondQueue->getNumPackets() != 0) + throw cRuntimeError("Shared buffer reentrant removal violated the detached victim batch contract"); + take(retainedPacket); + delete retainedPacket; + firstQueue->removePacketCallback(&reentrantCallback); firstQueue->removePacketCallback(&dropCallback); secondQueue->removePacketCallback(&dropCallback); firstQueue->callFinish(); @@ -3656,6 +3719,28 @@ delete compoundPacket; bufferModule->deleteModule(); } +// The multicast continuation predicate materializes its candidate once and +// reuses that result for both the TXOP and receiver-address decisions. +{ + TestOriginatorQosMacDataService multicastDataService; + TestPacketQueue multicastPendingQueue; + TestAckHandler multicastAckHandler; + auto multicastFrame = new Packet("multicastTxopFrame", makeQosHeader(MacAddress::BROADCAST_ADDRESS, 1, SequenceNumberCyclic(4))); + multicastPendingQueue.packets = { multicastFrame }; + TestInProgressFrames multicastInProgressFrames; + multicastInProgressFrames.configure(&multicastDataService, &multicastAckHandler, &multicastPendingQueue); + TestTxopProcedure multicastTxopProcedure; + FrameSequenceContext multicastContext(MacAddress::UNSPECIFIED_ADDRESS, nullptr, &multicastInProgressFrames, nullptr, nullptr, nullptr, new QoSContext(nullptr, nullptr, nullptr, &multicastTxopProcedure)); + HcfFs multicastHcfFs; + RepeatingFs multicastRepeatingFs(new TestEmptyFrameSequence(), [](RepeatingFs *, FrameSequenceContext *) { return false; }); + ASSERT(multicastHcfFs.hasMoreTxOpsAndMulticast(&multicastRepeatingFs, &multicastContext)); + ASSERT(multicastPendingQueue.numSelectedDequeues == 1); + ASSERT(multicastPendingQueue.getNumPackets() == 0); + auto multicastFrames = multicastInProgressFrames.releaseFrames(); + ASSERT(multicastFrames == std::vector({ multicastFrame })); + delete multicastFrame; +} + // At the TXOP continuation boundary, an eligible pending frame is // materialized by the real repeating-sequence lifecycle. { @@ -3696,9 +3781,11 @@ delete compoundPacket; TestDcaf dcaf; dcaf.configure(&dcfInProgressFrames); TestDcf dcf; - auto dcfHandler = dcf.configure(&dcaf); + TestMac dcfMac; + auto dcfHandler = dcf.configure(&dcaf, &dcfMac); dcf.grant(&dcaf); ASSERT(dcaf.numReleasedChannels == 1); + ASSERT(dcfMac.numPendingRadioConfigSends == 1); ASSERT(dcfHandler->numStartedSequences == 0); } { @@ -3711,9 +3798,11 @@ delete compoundPacket; TestDcaf dcaf; dcaf.configure(&dcfInProgressFrames); TestDcf dcf; - auto dcfHandler = dcf.configure(&dcaf); + TestMac dcfMac; + auto dcfHandler = dcf.configure(&dcaf, &dcfMac); dcf.grant(&dcaf); ASSERT(dcaf.numReleasedChannels == 0); + ASSERT(dcfMac.numPendingRadioConfigSends == 0); ASSERT(dcfHandler->numStartedSequences == 1); ASSERT(dcfPendingQueue.getNumPackets() == 0); ASSERT(dcfInProgressFrames.getFrameToTransmit() == firstDcfFrame); From 35c306035553f39699669adb35ddf254118abe06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Thu, 20 Aug 2026 13:56:43 +0200 Subject: [PATCH 14/14] Add predicate dequeue animation regression --- tests/unit/Ieee80211AddbaTransaction_1.test | 126 +++++++++++++++++--- 1 file changed, 108 insertions(+), 18 deletions(-) diff --git a/tests/unit/Ieee80211AddbaTransaction_1.test b/tests/unit/Ieee80211AddbaTransaction_1.test index 2ce24ff2d5c..a5ae26b2616 100644 --- a/tests/unit/Ieee80211AddbaTransaction_1.test +++ b/tests/unit/Ieee80211AddbaTransaction_1.test @@ -412,6 +412,18 @@ class TestSignalListener : public cListener virtual void receiveSignal(cComponent *, simsignal_t, cObject *, cObject *) override { numSignals++; } }; +class TestPacketArrivalListener : public cListener +{ + public: + int numSignals = 0; + cGate *lastArrivalGate = nullptr; + + virtual void receiveSignal(cComponent *, simsignal_t, cObject *value, cObject *) override { + numSignals++; + lastArrivalGate = check_and_cast(value)->getArrivalGate(); + } +}; + class TestOrderedSignalListener : public cListener { public: @@ -3311,46 +3323,124 @@ auto deferredFrames = deferredInProgressFrames.releaseFrames(); ASSERT(deferredFrames.size() == 1); delete deferredFrames.at(0); -// A simulation-registered real queue records selected-dequeue residence and -// emits its semantic pull exactly once. -Packet *compoundPacket = nullptr; +// A real queue with a downstream collector predicate-dequeues a non-front +// packet, records its residence and semantic pull once, and animates it to the +// collector while retaining the front packet. { TestPacketPulledListener pullListener; - auto leafQueue = createPacketQueue("selectedDequeueQueue"); + TestPacketDropCallback removalCallback; + auto leafQueueModule = cModuleType::get("inet.queueing.queue.PacketQueue")->create("selectedDequeueQueue", this); + auto sinkModule = cModuleType::get("inet.queueing.sink.FullPacketSink")->create("selectedDequeueSink", this); + leafQueueModule->gate("out")->connectTo(sinkModule->gate("in")); + leafQueueModule->callInitialize(); + sinkModule->callInitialize(); + auto leafQueue = check_and_cast(leafQueueModule); pullListener.leaf = leafQueue; leafQueue->subscribe(packetPulledSignal, &pullListener); - compoundPacket = new Packet("compoundPacket", makeQosHeader(peer1, 1, SequenceNumberCyclic(1))); - auto queueingTimeTag = compoundPacket->addRegionTag(); + leafQueue->addPacketCallback(&removalCallback); + auto frontPacket = new Packet("selectedDequeueFront", makeShared(B(1))); + auto selectedPacket = new Packet("selectedDequeueMatch", makeQosHeader(peer1, 1, SequenceNumberCyclic(1))); + auto queueingTimeTag = selectedPacket->addRegionTag(); queueingTimeTag->setBitTotalTimesArraySize(1); queueingTimeTag->setBitTotalTimes(0, 0); queueingTimeTag->setPacketTotalTimesArraySize(1); queueingTimeTag->setPacketTotalTimes(0, 0); - compoundPacket->addRegionTag(); - compoundPacket->setArrivalTime(simTime() - 1); - leafQueue->pushPacket(compoundPacket, nullptr); - auto dequeuedPacket = leafQueue->dequeuePacket([compoundPacket](const Packet *packet) { return packet == compoundPacket; }); - ASSERT(dequeuedPacket == compoundPacket); + selectedPacket->addRegionTag(); + selectedPacket->setArrivalTime(simTime() - 1); + leafQueue->pushPacket(frontPacket, nullptr); + leafQueue->pushPacket(selectedPacket, nullptr); + auto dequeuedPacket = leafQueue->dequeuePacket([selectedPacket](const Packet *packet) { return packet == selectedPacket; }); + ASSERT(dequeuedPacket == selectedPacket); int numQueuedEvents = 0; - compoundPacket->mapAllRegionTags(b(0), compoundPacket->getTotalLength(), [&](b, b, const Ptr& tag) { + selectedPacket->mapAllRegionTags(b(0), selectedPacket->getTotalLength(), [&](b, b, const Ptr& tag) { for (size_t i = 0; i < tag->getPacketEventsArraySize(); i++) if (tag->getPacketEvents(i)->getKind() == PEK_QUEUED) numQueuedEvents++; }); simtime_t totalQueueingTime = -1; - compoundPacket->mapAllRegionTags(b(0), compoundPacket->getTotalLength(), [&](b, b, const Ptr& tag) { + selectedPacket->mapAllRegionTags(b(0), selectedPacket->getTotalLength(), [&](b, b, const Ptr& tag) { ASSERT(tag->getPacketTotalTimesArraySize() == 1); totalQueueingTime = tag->getPacketTotalTimes(0); }); ASSERT(numQueuedEvents == 1); ASSERT(totalQueueingTime == 1); - ASSERT(leafQueue->getNumPackets() == 0); + ASSERT(leafQueue->getNumPackets() == 1); + ASSERT(leafQueue->getPacket(0) == frontPacket); ASSERT(pullListener.numLeafPulls == 1); + ASSERT(removalCallback.numDequeuedPackets == 1); + ASSERT(selectedPacket->getArrivalGate() == sinkModule->gate("in")); leafQueue->unsubscribe(packetPulledSignal, &pullListener); - leafQueue->callFinish(); - leafQueue->deleteModule(); - compoundPacket = nullptr; + leafQueue->removePacketCallback(&removalCallback); + leafQueue->removePacket(frontPacket); + take(frontPacket); + take(selectedPacket); + delete frontPacket; + delete selectedPacket; + sinkModule->callFinish(); + leafQueueModule->callFinish(); + sinkModule->deleteModule(); + leafQueueModule->deleteModule(); +} + +// Predicate extraction through a concrete PacketFlowBase module processes a +// non-front packet once. The queue animates it to the flow input before the +// flow signals, and the flow then animates it to the final collector. +{ + TestPacketPulledListener pullListener; + TestPacketDropCallback removalCallback; + TestPacketArrivalListener flowInputListener; + TestPacketArrivalListener flowOutputListener; + auto leafQueueModule = cModuleType::get("inet.queueing.queue.PacketQueue")->create("selectedFlowLeaf", this); + auto gateModule = cModuleType::get("inet.queueing.gate.PacketGate")->create("selectedFlowGate", this); + auto sinkModule = cModuleType::get("inet.queueing.sink.FullPacketSink")->create("selectedFlowSink", this); + gateModule->par("initiallyOpen").setBoolValue(true); + gateModule->par("openTime").setDoubleValue(10); + gateModule->par("closeTime").setDoubleValue(20); + leafQueueModule->gate("out")->connectTo(gateModule->gate("in")); + gateModule->gate("out")->connectTo(sinkModule->gate("in")); + leafQueueModule->callInitialize(); + gateModule->callInitialize(); + sinkModule->callInitialize(); + auto leafQueue = check_and_cast(leafQueueModule); + auto packetGate = check_and_cast(gateModule); + auto packetExtractor = check_and_cast(gateModule); + pullListener.leaf = leafQueue; + leafQueue->subscribe(packetPulledSignal, &pullListener); + leafQueue->addPacketCallback(&removalCallback); + gateModule->subscribe(packetPulledInSignal, &flowInputListener); + gateModule->subscribe(packetPulledOutSignal, &flowOutputListener); + auto frontPacket = new Packet("selectedFlowFront", makeShared(B(1))); + auto selectedPacket = new Packet("selectedFlowMatch", makeShared(B(1))); + leafQueue->pushPacket(frontPacket, nullptr); + leafQueue->pushPacket(selectedPacket, nullptr); + auto dequeuedPacket = packetExtractor->dequeuePacket([selectedPacket](const Packet *packet) { return packet == selectedPacket; }); + ASSERT(dequeuedPacket == selectedPacket); + ASSERT(leafQueue->getNumPackets() == 1); + ASSERT(leafQueue->getPacket(0) == frontPacket); + ASSERT(pullListener.numLeafPulls == 1); + ASSERT(removalCallback.numDequeuedPackets == 1); + ASSERT(flowInputListener.numSignals == 1); + ASSERT(flowInputListener.lastArrivalGate == gateModule->gate("in")); + ASSERT(flowOutputListener.numSignals == 1); + ASSERT(flowOutputListener.lastArrivalGate == gateModule->gate("in")); + ASSERT(packetGate->resolveDirective('p') == "1"); + ASSERT(selectedPacket->getArrivalGate() == sinkModule->gate("in")); + leafQueue->unsubscribe(packetPulledSignal, &pullListener); + leafQueue->removePacketCallback(&removalCallback); + gateModule->unsubscribe(packetPulledInSignal, &flowInputListener); + gateModule->unsubscribe(packetPulledOutSignal, &flowOutputListener); + leafQueue->removePacket(frontPacket); + take(frontPacket); + take(selectedPacket); + delete frontPacket; + delete selectedPacket; + sinkModule->callFinish(); + gateModule->callFinish(); + leafQueueModule->callFinish(); + sinkModule->deleteModule(); + gateModule->deleteModule(); + leafQueueModule->deleteModule(); } -delete compoundPacket; // Ordinary queue overflow must not trigger channel-access re-evaluation, while // cancelling a dropped setup transaction must immediately release held traffic.