From 4b34eeec14fb411a9d49f67fb9ccc0a0d8584dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Sat, 15 Aug 2026 20:33:01 +0200 Subject: [PATCH 1/8] feat(ieee80211): add HT compressed Block Ack Implement one-TID compressed BlockAckReq and 64-bit compressed BlockAck exchanges across the QoS MAC path. Correct BAR and BA Control and Starting Sequence Control serialization, including the Basic BAR wire length, little-endian fields, fragment-number packing, and compressed frame lengths. Extend originator and recipient agreement handling, HCF dispatch, frame sequences, rate selection, protection timing, reordering, and acknowledgment processing. Preserve immediate versus delayed ADDBA policy and return the required all-zero compressed Block Ack when no matching recipient state exists. Keep selection default-off behind the documented assumePeerSupportsCompressedBlockAck model assumption. Reject fragmented and delayed-policy exchanges, maintain the recipient acknowledgment-window boundary, and preserve leading holes including sequence-number wraparound. Add byte-exact unit coverage for Basic and compressed variants, bitmap and wraparound behavior, capability and agreement gates, plus a deterministic HT runtime exchange test. --- .../ieee80211/mac/Ieee80211Frame.msg | 7 +- .../mac/Ieee80211MacHeaderSerializer.cc | 115 ++++--- .../ieee80211/mac/blockack/BlockAckRecord.cc | 15 +- .../ieee80211/mac/blockack/BlockAckRecord.h | 5 +- .../OriginatorBlockAckAgreementHandler.cc | 10 +- .../blockack/OriginatorBlockAckProcedure.cc | 13 +- .../blockack/RecipientBlockAckAgreement.cc | 3 +- .../mac/blockack/RecipientBlockAckAgreement.h | 5 +- .../RecipientBlockAckAgreementHandler.cc | 2 +- .../blockack/RecipientBlockAckProcedure.cc | 30 +- .../blockackreordering/BlockAckReordering.cc | 3 +- .../mac/contract/IOriginatorQoSAckPolicy.h | 2 +- .../mac/contract/IRecipientQosAckPolicy.h | 3 +- .../ieee80211/mac/coordinationfunction/Hcf.cc | 11 +- .../framesequence/PrimitiveFrameSequences.cc | 11 +- .../mac/originator/OriginatorQosAckPolicy.cc | 32 +- .../mac/originator/OriginatorQosAckPolicy.h | 6 +- .../mac/originator/OriginatorQosAckPolicy.ned | 4 +- .../SingleProtectionMechanism.cc | 7 +- .../mac/rateselection/QosRateSelection.cc | 3 +- .../mac/recipient/RecipientQosAckPolicy.cc | 25 +- .../mac/recipient/RecipientQosAckPolicy.h | 7 +- .../mac/recipient/RecipientQosAckPolicy.ned | 4 +- .../recipient/RecipientQosMacDataService.cc | 11 +- .../Ieee80211CompressedBlockAckRuntime.test | 37 ++ tests/unit/Ieee80211CompressedBlockAck_1.test | 319 ++++++++++++++++++ 26 files changed, 581 insertions(+), 109 deletions(-) create mode 100644 tests/module/Ieee80211CompressedBlockAckRuntime.test create mode 100644 tests/unit/Ieee80211CompressedBlockAck_1.test diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211Frame.msg b/src/inet/linklayer/ieee80211/mac/Ieee80211Frame.msg index 033ee7141be..9ff66966df9 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211Frame.msg +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Frame.msg @@ -42,6 +42,7 @@ const b LENGTH_ADDBAREQ = LENGTH_MGMT + B(9); // mgmt length + action body lengt const b LENGTH_ADDBARESP = LENGTH_MGMT + B(9); // mgmt length + action body length const b LENGTH_DELBA = LENGTH_MGMT + B(6); // mgmt length + action body length const b LENGTH_BASIC_BLOCKACK = B(16 + 2 + (2 + 128) + 4); // header + ba control + ba information + fcs +const b LENGTH_COMPRESSED_BLOCKACK = B(16 + 2 + (2 + 8) + 4); // header + ba control + ba information + fcs const b DATAFRAME_HEADER_MINLENGTH = B(2 + 2 + 3 * 6 + 2); //bits without QoS, without Address4: 2 + 2 + 3*6(addresses) + 2 const b QOSCONTROL_PART_LENGTH = b(2 * 8); // QoS Control field length (bits) const short int MAX_NUM_FRAGMENTS = 16; @@ -345,7 +346,7 @@ class Ieee80211BlockAckReq extends Ieee80211TwoAddressHeader class Ieee80211BasicBlockAckReq extends Ieee80211BlockAckReq { - chunkLength = B(38); + chunkLength = B(20); int tidInfo; // The TID_INFO subfield of the BAR Control field of the Basic BlockAckReq frame contains the TID for which a Basic BlockAck frame is requested. @@ -362,7 +363,7 @@ class Ieee80211BasicBlockAckReq extends Ieee80211BlockAckReq class Ieee80211CompressedBlockAckReq extends Ieee80211BlockAckReq { - chunkLength = B(38); + chunkLength = B(20); int tidInfo; // The TID_INFO subfield of the BAR Control field of the Compressed BlockAckReq frame contains the TID for which a BlockAck frame is requested. // The BAR Information field of the Compressed BlockAckReq frame contains the Block Ack Starting @@ -444,7 +445,7 @@ class Ieee80211BasicBlockAck extends Ieee80211BlockAck // class Ieee80211CompressedBlockAck extends Ieee80211BlockAck { - // chunkLength TODO + chunkLength = LENGTH_COMPRESSED_BLOCKACK - B(4); multiTid = 0; compressedBitmap = 1; diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc b/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc index 026419c7b79..01077e3f648 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc @@ -76,6 +76,24 @@ void readSequenceControl(MemoryInputStream& stream, int& fragmentNumber, ieee802 sequenceNumber = ieee80211::SequenceNumberCyclic((sequenceControl >> 4) & 0xFFF); } +uint16_t packBlockAckControl(bool ackPolicy, bool multiTid, bool compressedBitmap, uint16_t reserved, uint8_t tidInfo) +{ + return (ackPolicy ? 0x0001 : 0) | + (multiTid ? 0x0002 : 0) | + (compressedBitmap ? 0x0004 : 0) | + ((reserved & 0x1FF) << 3) | + ((tidInfo & 0xF) << 12); +} + +void unpackBlockAckControl(uint16_t control, bool& ackPolicy, bool& multiTid, bool& compressedBitmap, uint16_t& reserved, uint8_t& tidInfo) +{ + ackPolicy = (control & 0x0001) != 0; + multiTid = (control & 0x0002) != 0; + compressedBitmap = (control & 0x0004) != 0; + reserved = (control >> 3) & 0x1FF; + tidInfo = (control >> 12) & 0xF; +} + } // namespace namespace ieee80211 { @@ -266,26 +284,20 @@ void Ieee80211MacHeaderSerializer::serialize(MemoryOutputStream& stream, const P stream.writeUint16Le(blockAckReq->getDurationField().inUnit(SIMTIME_US)); stream.writeMacAddress(blockAckReq->getReceiverAddress()); stream.writeMacAddress(blockAckReq->getTransmitterAddress()); - stream.writeBit(blockAckReq->getBarAckPolicy()); bool multiTid = blockAckReq->getMultiTid(); bool compressedBitmap = blockAckReq->getCompressedBitmap(); - stream.writeBit(multiTid); - stream.writeBit(compressedBitmap); - stream.writeNBitsOfUint64Be(blockAckReq->getReserved(), 9); if (!multiTid && !compressedBitmap) { auto basicBlockAckReq = dynamicPtrCast(chunk); - stream.writeUint4(basicBlockAckReq->getTidInfo()); - stream.writeUint32Be(basicBlockAckReq->getFragmentNumber()); - stream.writeUint64Be(0); - stream.writeUint64Be(basicBlockAckReq->getStartingSequenceNumber().get()); + stream.writeUint16Le(packBlockAckControl(blockAckReq->getBarAckPolicy(), multiTid, compressedBitmap, blockAckReq->getReserved(), basicBlockAckReq->getTidInfo())); + writeSequenceControl(stream, basicBlockAckReq->getFragmentNumber(), basicBlockAckReq->getStartingSequenceNumber().get()); ASSERT(stream.getLength() - startPos == basicBlockAckReq->getChunkLength()); } else if (!multiTid && compressedBitmap) { auto compressedBlockAckReq = dynamicPtrCast(chunk); - stream.writeUint4(compressedBlockAckReq->getTidInfo()); - stream.writeUint32Be(compressedBlockAckReq->getFragmentNumber()); - stream.writeUint64Be(0); - stream.writeUint64Be(compressedBlockAckReq->getStartingSequenceNumber().get()); + // IEEE Std 802.11-2024, 9.3.1.7.2: one-TID compressed BAR Control and + // Starting Sequence Control are little-endian 16-bit on-wire fields. + stream.writeUint16Le(packBlockAckControl(blockAckReq->getBarAckPolicy(), multiTid, compressedBitmap, blockAckReq->getReserved(), compressedBlockAckReq->getTidInfo())); + writeSequenceControl(stream, compressedBlockAckReq->getFragmentNumber(), compressedBlockAckReq->getStartingSequenceNumber().get()); ASSERT(stream.getLength() - startPos == compressedBlockAckReq->getChunkLength()); } else if (multiTid && compressedBitmap) { @@ -300,16 +312,12 @@ void Ieee80211MacHeaderSerializer::serialize(MemoryOutputStream& stream, const P stream.writeUint16Le(blockAck->getDurationField().inUnit(SIMTIME_US)); stream.writeMacAddress(blockAck->getReceiverAddress()); stream.writeMacAddress(blockAck->getTransmitterAddress()); - stream.writeBit(blockAck->getBlockAckPolicy()); bool multiTid = blockAck->getMultiTid(); bool compressedBitmap = blockAck->getCompressedBitmap(); - stream.writeBit(multiTid); - stream.writeBit(compressedBitmap); - stream.writeNBitsOfUint64Be(blockAck->getReserved(), 9); if (!multiTid && !compressedBitmap) { auto basicBlockAck = dynamicPtrCast(chunk); - stream.writeUint4(basicBlockAck->getTidInfo()); - stream.writeUint16Be(basicBlockAck->getStartingSequenceNumber().get()); + stream.writeUint16Le(packBlockAckControl(blockAck->getBlockAckPolicy(), multiTid, compressedBitmap, blockAck->getReserved(), basicBlockAck->getTidInfo())); + writeSequenceControl(stream, basicBlockAck->getFragmentNumber(), basicBlockAck->getStartingSequenceNumber().get()); for (size_t i = 0; i < 64; ++i) { stream.writeByte(basicBlockAck->getBlockAckBitmap(i).getBytes()[0]); stream.writeByte(basicBlockAck->getBlockAckBitmap(i).getBytes()[1]); @@ -318,8 +326,10 @@ void Ieee80211MacHeaderSerializer::serialize(MemoryOutputStream& stream, const P } else if (!multiTid && compressedBitmap) { auto compressedBlockAck = dynamicPtrCast(chunk); - stream.writeUint4(compressedBlockAck->getTidInfo()); - stream.writeUint16Be(compressedBlockAck->getStartingSequenceNumber().get()); + // IEEE Std 802.11-2024, 9.3.1.8.2: the non-HE compressed BA carries + // one Starting Sequence Control field followed by a 64-bit bitmap. + stream.writeUint16Le(packBlockAckControl(blockAck->getBlockAckPolicy(), multiTid, compressedBitmap, blockAck->getReserved(), compressedBlockAck->getTidInfo())); + writeSequenceControl(stream, compressedBlockAck->getFragmentNumber(), compressedBlockAck->getStartingSequenceNumber().get()); for (size_t i = 0; i < 8; ++i) { stream.writeByte(compressedBlockAck->getBlockAckBitmap().getBytes()[i]); } @@ -507,30 +517,38 @@ const Ptr Ieee80211MacHeaderSerializer::deserialize(MemoryInputStream& st blockAckReq->setDurationField(SimTime(stream.readUint16Le(), SIMTIME_US)); blockAckReq->setReceiverAddress(stream.readMacAddress()); blockAckReq->setTransmitterAddress(stream.readMacAddress()); - blockAckReq->setBarAckPolicy(stream.readBit()); - bool multiTid = stream.readBit(); - bool compressedBitmap = stream.readBit(); + bool barAckPolicy; + bool multiTid; + bool compressedBitmap; + uint16_t reserved; + uint8_t tidInfo; + unpackBlockAckControl(stream.readUint16Le(), barAckPolicy, multiTid, compressedBitmap, reserved, tidInfo); + blockAckReq->setBarAckPolicy(barAckPolicy); blockAckReq->setMultiTid(multiTid); blockAckReq->setCompressedBitmap(compressedBitmap); - blockAckReq->setReserved(stream.readNBitsToUint64Be(9)); + blockAckReq->setReserved(reserved); if (!multiTid && !compressedBitmap) { auto basicBlockAckReq = makeShared(); copyBasicFields(basicBlockAckReq, macHeader); copyBlockAckReqFrameFields(basicBlockAckReq, blockAckReq); - basicBlockAckReq->setTidInfo(stream.readUint4()); - basicBlockAckReq->setFragmentNumber(stream.readUint32Be()); - stream.readUint64Be(); - basicBlockAckReq->setStartingSequenceNumber(SequenceNumberCyclic(stream.readUint64Be())); + basicBlockAckReq->setTidInfo(tidInfo); + int fragmentNumber; + SequenceNumberCyclic sequenceNumber; + readSequenceControl(stream, fragmentNumber, sequenceNumber); + basicBlockAckReq->setFragmentNumber(fragmentNumber); + basicBlockAckReq->setStartingSequenceNumber(sequenceNumber); return basicBlockAckReq; } else if (!multiTid && compressedBitmap) { auto compressedBlockAckReq = makeShared(); copyBasicFields(compressedBlockAckReq, macHeader); copyBlockAckReqFrameFields(compressedBlockAckReq, blockAckReq); - compressedBlockAckReq->setTidInfo(stream.readUint4()); - compressedBlockAckReq->setFragmentNumber(stream.readUint32Be()); - stream.readUint64Be(); - compressedBlockAckReq->setStartingSequenceNumber(SequenceNumberCyclic(stream.readUint64Be())); + compressedBlockAckReq->setTidInfo(tidInfo); + int fragmentNumber; + SequenceNumberCyclic sequenceNumber; + readSequenceControl(stream, fragmentNumber, sequenceNumber); + compressedBlockAckReq->setFragmentNumber(fragmentNumber); + compressedBlockAckReq->setStartingSequenceNumber(sequenceNumber); return compressedBlockAckReq; } else @@ -543,24 +561,31 @@ const Ptr Ieee80211MacHeaderSerializer::deserialize(MemoryInputStream& st blockAck->setDurationField(SimTime(stream.readUint16Le(), SIMTIME_US)); blockAck->setReceiverAddress(stream.readMacAddress()); blockAck->setTransmitterAddress(stream.readMacAddress()); - blockAck->setBlockAckPolicy(stream.readBit()); - bool multiTid = stream.readBit(); - bool compressedBitmap = stream.readBit(); + bool blockAckPolicy; + bool multiTid; + bool compressedBitmap; + uint16_t reserved; + uint8_t tidInfo; + unpackBlockAckControl(stream.readUint16Le(), blockAckPolicy, multiTid, compressedBitmap, reserved, tidInfo); + blockAck->setBlockAckPolicy(blockAckPolicy); blockAck->setMultiTid(multiTid); blockAck->setCompressedBitmap(compressedBitmap); - blockAck->setReserved(stream.readNBitsToUint64Be(9)); + blockAck->setReserved(reserved); if (!multiTid && !compressedBitmap) { auto basicBlockAck = makeShared(); copyBasicFields(basicBlockAck, macHeader); copyBlockAckFrameFields(basicBlockAck, blockAck); - basicBlockAck->setTidInfo(stream.readUint4()); - basicBlockAck->setStartingSequenceNumber(SequenceNumberCyclic(stream.readUint16Be())); + basicBlockAck->setTidInfo(tidInfo); + int fragmentNumber; + SequenceNumberCyclic sequenceNumber; + readSequenceControl(stream, fragmentNumber, sequenceNumber); + basicBlockAck->setFragmentNumber(fragmentNumber); + basicBlockAck->setStartingSequenceNumber(sequenceNumber); for (size_t i = 0; i < 64; ++i) { std::vector bytes; bytes.push_back(stream.readByte()); bytes.push_back(stream.readByte()); - BitVector *blockAckBitmap = new BitVector(bytes); - basicBlockAck->setBlockAckBitmap(i, *blockAckBitmap); + basicBlockAck->setBlockAckBitmap(i, BitVector(bytes)); } return basicBlockAck; } @@ -569,13 +594,17 @@ const Ptr Ieee80211MacHeaderSerializer::deserialize(MemoryInputStream& st copyBasicFields(compressedBlockAck, macHeader); copyBlockAckFrameFields(compressedBlockAck, blockAck); - compressedBlockAck->setTidInfo(stream.readUint4()); - compressedBlockAck->setStartingSequenceNumber(SequenceNumberCyclic(stream.readUint16Be())); + compressedBlockAck->setTidInfo(tidInfo); + int fragmentNumber; + SequenceNumberCyclic sequenceNumber; + readSequenceControl(stream, fragmentNumber, sequenceNumber); + compressedBlockAck->setFragmentNumber(fragmentNumber); + compressedBlockAck->setStartingSequenceNumber(sequenceNumber); std::vector bytes; for (size_t i = 0; i < 8; ++i) { bytes.push_back(stream.readByte()); } - compressedBlockAck->setBlockAckBitmap(*(new BitVector(bytes))); + compressedBlockAck->setBlockAckBitmap(BitVector(bytes)); return compressedBlockAck; } else { diff --git a/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc b/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc index 919bd813a6f..7dce744fdec 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc @@ -12,9 +12,10 @@ namespace inet { namespace ieee80211 { -BlockAckRecord::BlockAckRecord(MacAddress originatorAddress, Tid tid) : +BlockAckRecord::BlockAckRecord(MacAddress originatorAddress, Tid tid, SequenceNumberCyclic startingSequenceNumber) : originatorAddress(originatorAddress), - tid(tid) + tid(tid), + startingSequenceNumber(startingSequenceNumber) { } @@ -42,6 +43,13 @@ bool BlockAckRecord::getAckState(SequenceNumberCyclic sequenceNumber, FragmentNu } } +bool BlockAckRecord::getCompressedAckState(SequenceNumberCyclic sequenceNumber) +{ + // IEEE Std 802.11-2024, 10.25.6.1: bits preceding the maintained + // receive-window range are one; missing MPDUs within the range are zero. + return containsKey(acknowledgmentState, SequenceControlField(sequenceNumber.get(), 0)) || sequenceNumber < startingSequenceNumber; +} + void BlockAckRecord::removeAckStates(SequenceNumberCyclic sequenceNumber) { auto it = acknowledgmentState.begin(); @@ -51,8 +59,9 @@ void BlockAckRecord::removeAckStates(SequenceNumberCyclic sequenceNumber) else it++; } + if (startingSequenceNumber <= sequenceNumber) + startingSequenceNumber = sequenceNumber + 1; } } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.h b/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.h index 971be8a8af9..3eb8f9bec46 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.h @@ -24,14 +24,16 @@ class INET_API BlockAckRecord protected: MacAddress originatorAddress = MacAddress::UNSPECIFIED_ADDRESS; Tid tid = -1; + SequenceNumberCyclic startingSequenceNumber; std::map acknowledgmentState; public: - BlockAckRecord(MacAddress originatorAddress, Tid tid); + BlockAckRecord(MacAddress originatorAddress, Tid tid, SequenceNumberCyclic startingSequenceNumber); virtual ~BlockAckRecord() {} void blockAckPolicyFrameReceived(const Ptr& header); bool getAckState(SequenceNumberCyclic sequenceNumber, FragmentNumber fragmentNumber); + bool getCompressedAckState(SequenceNumberCyclic sequenceNumber); void removeAckStates(SequenceNumberCyclic sequenceNumber); MacAddress getOriginatorAddress() { return originatorAddress; } @@ -42,4 +44,3 @@ class INET_API BlockAckRecord } /* 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..bab4a75ce29 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc @@ -81,6 +81,14 @@ void OriginatorBlockAckAgreementHandler::processReceivedBlockAck(const Ptr(blockAck)) { + auto agreement = getAgreement(compressedBlockAck->getTransmitterAddress(), compressedBlockAck->getTidInfo()); + if (agreement) { + agreement->setStartingSequenceNumber(compressedBlockAck->getStartingSequenceNumber()); + agreement->calculateExpirationTime(); + scheduleInactivityTimer(callback); + } + } else throw cRuntimeError("Unsupported BlockAck"); } @@ -147,6 +155,7 @@ void OriginatorBlockAckAgreementHandler::processReceivedAddbaResp(const Ptr& addbaResp) { agreement->setIsAddbaResponseReceived(true); + agreement->setIsDelayedBlockAckPolicySupported(addbaResp->getBlockAckPolicy() == 0); agreement->setBufferSize(addbaResp->getBufferSize()); agreement->setBlockAckTimeoutValue(addbaResp->getBlockAckTimeoutValue()); agreement->calculateExpirationTime(); @@ -180,4 +189,3 @@ OriginatorBlockAckAgreementHandler::~OriginatorBlockAckAgreementHandler() } // namespace ieee80211 } // namespace inet - diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckProcedure.cc b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckProcedure.cc index d02e412b644..6a468e94e02 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckProcedure.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckProcedure.cc @@ -12,13 +12,11 @@ namespace ieee80211 { const Ptr OriginatorBlockAckProcedure::buildCompressedBlockAckReqFrame(const MacAddress& receiverAddress, Tid tid, SequenceNumberCyclic startingSequenceNumber) const { - throw cRuntimeError("Unsupported feature"); - // TODO implement - // auto blockAckReq = makeShared(); - // blockAckReq->setReceiverAddress(receiverAddress); - // blockAckReq->setStartingSequenceNumber(startingSequenceNumber); - // blockAckReq->setTidInfo(tid); - // return blockAckReq; + auto blockAckReq = makeShared(); + blockAckReq->setReceiverAddress(receiverAddress); + blockAckReq->setStartingSequenceNumber(startingSequenceNumber); + blockAckReq->setTidInfo(tid); + return blockAckReq; } const Ptr OriginatorBlockAckProcedure::buildBasicBlockAckReqFrame(const MacAddress& receiverAddress, Tid tid, SequenceNumberCyclic startingSequenceNumber) const @@ -32,4 +30,3 @@ const Ptr OriginatorBlockAckProcedure::buildBasicBlockAckR } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.cc b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.cc index d93711d2839..38e4eea2944 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.cc @@ -18,7 +18,7 @@ RecipientBlockAckAgreement::RecipientBlockAckAgreement(MacAddress originatorAddr blockAckTimeoutValue(lastUsedTime) { calculateExpirationTime(); - blockAckRecord = new BlockAckRecord(originatorAddress, tid); + blockAckRecord = new BlockAckRecord(originatorAddress, tid, startingSequenceNumber); } void RecipientBlockAckAgreement::blockAckPolicyFrameReceived(const Ptr& header) @@ -39,4 +39,3 @@ std::ostream& operator<<(std::ostream& os, const RecipientBlockAckAgreement& agr } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h index 40a4186fead..cdb41395dbf 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h @@ -22,6 +22,7 @@ class INET_API RecipientBlockAckAgreement : public cObject int bufferSize = -1; simtime_t blockAckTimeoutValue = 0; bool isAddbaResponseSent = false; + bool isDelayedBlockAckPolicySupported = false; simtime_t expirationTime = -1; public: @@ -34,8 +35,11 @@ class INET_API RecipientBlockAckAgreement : public cObject virtual simtime_t getBlockAckTimeoutValue() const { return blockAckTimeoutValue; } virtual int getBufferSize() const { return bufferSize; } virtual SequenceNumberCyclic getStartingSequenceNumber() const { return startingSequenceNumber; } + virtual bool getIsAddbaResponseSent() const { return isAddbaResponseSent; } + virtual bool getIsDelayedBlockAckPolicySupported() const { return isDelayedBlockAckPolicySupported; } virtual void addbaResposneSent() { isAddbaResponseSent = true; } + virtual void setIsDelayedBlockAckPolicySupported(bool isDelayedBlockAckPolicySupported) { this->isDelayedBlockAckPolicySupported = isDelayedBlockAckPolicySupported; } 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 +49,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 6219ac77140..a1d8e0f5d0f 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc @@ -123,6 +123,7 @@ void RecipientBlockAckAgreementHandler::updateAgreement(const Ptrsecond; agreement->addbaResposneSent(); + agreement->setIsDelayedBlockAckPolicySupported(addbaResponse->getBlockAckPolicy() == 0); } else throw cRuntimeError("Agreement is not found"); @@ -185,4 +186,3 @@ RecipientBlockAckAgreementHandler::~RecipientBlockAckAgreementHandler() } // namespace ieee80211 } // namespace inet - diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckProcedure.cc b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckProcedure.cc index ab7cb050e5f..8a6eace3298 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckProcedure.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckProcedure.cc @@ -24,13 +24,24 @@ void RecipientBlockAckProcedure::processReceivedBlockAckReq(Packet *blockAckPack auto agreement = blockAckAgreementHandler->getAgreement(basicBlockAckReq->getTidInfo(), basicBlockAckReq->getTransmitterAddress()); if (ackPolicy->isBlockAckNeeded(basicBlockAckReq, agreement)) { auto blockAck = buildBlockAck(basicBlockAckReq, agreement); - auto duration = ackPolicy->computeBasicBlockAckDurationField(blockAckPacketReq, basicBlockAckReq); + auto duration = ackPolicy->computeBlockAckDurationField(blockAckPacketReq, basicBlockAckReq); blockAck->setDurationField(duration); auto blockAckPacket = new Packet("BasicBlockAck", blockAck); EV_DEBUG << "Duration for " << blockAckPacket->getName() << " is set to " << duration << " s.\n"; callback->transmitControlResponseFrame(blockAckPacket, blockAck, blockAckPacketReq, basicBlockAckReq); } } + else if (auto compressedBlockAckReq = dynamicPtrCast(blockAckReq)) { + auto agreement = blockAckAgreementHandler->getAgreement(compressedBlockAckReq->getTidInfo(), compressedBlockAckReq->getTransmitterAddress()); + if (ackPolicy->isBlockAckNeeded(compressedBlockAckReq, agreement)) { + auto blockAck = buildBlockAck(compressedBlockAckReq, agreement); + auto duration = ackPolicy->computeBlockAckDurationField(blockAckPacketReq, compressedBlockAckReq); + blockAck->setDurationField(duration); + auto blockAckPacket = new Packet("CompressedBlockAck", blockAck); + EV_DEBUG << "Duration for " << blockAckPacket->getName() << " is set to " << duration << " s.\n"; + callback->transmitControlResponseFrame(blockAckPacket, blockAck, blockAckPacketReq, compressedBlockAckReq); + } + } else throw cRuntimeError("Unsupported BlockAckReq"); } @@ -65,10 +76,25 @@ const Ptr RecipientBlockAckProcedure::buildBlockAck(const Ptr blockAck->setTidInfo(basicBlockAckReq->getTidInfo()); return blockAck; } + else if (auto compressedBlockAckReq = dynamicPtrCast(blockAckReq)) { + auto blockAck = makeShared(); + auto startingSequenceNumber = compressedBlockAckReq->getStartingSequenceNumber(); + BitVector bitmap(std::vector(8, 0)); + if (agreement != nullptr) { + // IEEE Std 802.11-2024, 10.25.6.1 and 10.25.6.5: a non-HE + // compressed BA reports 64 consecutive, unfragmented MPDUs. + for (int i = 0; i < 64; i++) + bitmap.setBit(i, agreement->getBlockAckRecord()->getCompressedAckState(startingSequenceNumber + i)); + } + blockAck->setReceiverAddress(blockAckReq->getTransmitterAddress()); + blockAck->setStartingSequenceNumber(startingSequenceNumber); + blockAck->setTidInfo(compressedBlockAckReq->getTidInfo()); + blockAck->setBlockAckBitmap(bitmap); + return blockAck; + } else throw cRuntimeError("Unsupported Block Ack Request"); } } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc index 47837ead38a..224c8b2b1e5 100644 --- a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc +++ b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc @@ -61,7 +61,7 @@ BlockAckReordering::ReorderBuffer BlockAckReordering::processReceivedBlockAckReq tid = basicReq->getTidInfo(); startingSequenceNumber = basicReq->getStartingSequenceNumber(); } - else if (auto compressedReq = dynamicPtrCast(blockAckReq)) { + else if (auto compressedReq = dynamicPtrCast(blockAckReq)) { tid = compressedReq->getTidInfo(); startingSequenceNumber = compressedReq->getStartingSequenceNumber(); } @@ -244,4 +244,3 @@ BlockAckReordering::~BlockAckReordering() } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorQoSAckPolicy.h b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorQoSAckPolicy.h index 8f03c2fd052..7b7f5aac443 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorQoSAckPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorQoSAckPolicy.h @@ -25,6 +25,7 @@ class INET_API IOriginatorQoSAckPolicy virtual AckPolicy computeAckPolicy(Packet *packet, const Ptr& header, OriginatorBlockAckAgreement *agreement) const = 0; virtual bool isBlockAckReqNeeded(InProgressFrames *inProgressFrames, TxopProcedure *txopProcedure) const = 0; virtual bool isBlockAckPolicyEligibleFrame(Packet *packet, const Ptr& header) const = 0; + virtual bool isCompressedBlockAckReq(const std::vector& outstandingFrames, OriginatorBlockAckAgreement *agreement) const = 0; virtual std::tuple computeBlockAckReqParameters(InProgressFrames *inProgressFrames, TxopProcedure *txopProcedure) const = 0; virtual simtime_t getAckTimeout(Packet *packet, const Ptr& dataOrMgmtHeader) const = 0; @@ -35,4 +36,3 @@ class INET_API IOriginatorQoSAckPolicy } // namespace inet #endif - diff --git a/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosAckPolicy.h b/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosAckPolicy.h index ccf47cc5610..b0525470502 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosAckPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosAckPolicy.h @@ -25,11 +25,10 @@ class INET_API IRecipientQosAckPolicy virtual bool isBlockAckNeeded(const Ptr& blockAckReq, RecipientBlockAckAgreement *agreement) const = 0; virtual simtime_t computeAckDurationField(Packet *packet, const Ptr& header) const = 0; - virtual simtime_t computeBasicBlockAckDurationField(Packet *packet, const Ptr& basicBlockAckReq) const = 0; + virtual simtime_t computeBlockAckDurationField(Packet *packet, const Ptr& blockAckReq) const = 0; }; } // namespace ieee80211 } // 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..ea44357fac3 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc @@ -322,7 +322,7 @@ void Hcf::recipientProcessReceivedControlFrame(Packet *packet, const Ptr(header)) ctsProcedure->processReceivedRts(packet, rtsFrame, ctsPolicy, this); - else if (auto blockAckRequest = dynamicPtrCast(header)) { + else if (auto blockAckRequest = dynamicPtrCast(header)) { if (recipientBlockAckProcedure) recipientBlockAckProcedure->processReceivedBlockAckReq(packet, blockAckRequest, recipientAckPolicy, recipientBlockAckAgreementHandler, this); } @@ -597,8 +597,8 @@ void Hcf::originatorProcessReceivedControlFrame(Packet *packet, const PtrgetInProgressFrames()->dropFrame(lastTransmittedPacket); edcaf->getAckHandler()->dropFrame(lastTransmittedDataOrMgmtHeader); } - else if (auto blockAck = dynamicPtrCast(header)) { - EV_INFO << "BasicBlockAck has arrived" << std::endl; + else if (auto blockAck = dynamicPtrCast(header)) { + EV_INFO << blockAck->getClassName() << " has arrived" << std::endl; edcaf->getRecoveryProcedure()->blockAckFrameReceived(); auto ackedSeqAndFragNums = edcaf->getAckHandler()->processReceivedBlockAck(blockAck); if (originatorBlockAckAgreementHandler) @@ -615,7 +615,7 @@ void Hcf::originatorProcessReceivedControlFrame(Packet *packet, const PtrgetRecoveryProcedure()->ctsFrameReceived(); else if (header->getType() == ST_DATA_WITH_QOS) ; // void - else if (dynamicPtrCast(header)) + else if (dynamicPtrCast(header)) ; // void else throw cRuntimeError("Unknown control frame"); @@ -696,7 +696,7 @@ void Hcf::transmitControlResponseFrame(Packet *responsePacket, const Ptr(receivedHeader)) responseMode = rateSelection->computeResponseCtsFrameMode(receivedPacket, rtsFrame); - else if (auto blockAckReq = dynamicPtrCast(receivedHeader)) + else if (auto blockAckReq = dynamicPtrCast(receivedHeader)) responseMode = rateSelection->computeResponseBlockAckFrameMode(receivedPacket, blockAckReq); else if (auto dataOrMgmtHeader = dynamicPtrCast(receivedHeader)) responseMode = rateSelection->computeResponseAckFrameMode(receivedPacket, dataOrMgmtHeader); @@ -784,4 +784,3 @@ Hcf::~Hcf() } // namespace ieee80211 } // namespace inet - diff --git a/src/inet/linklayer/ieee80211/mac/framesequence/PrimitiveFrameSequences.cc b/src/inet/linklayer/ieee80211/mac/framesequence/PrimitiveFrameSequences.cc index 9ad68ed2969..fa76676eeff 100644 --- a/src/inet/linklayer/ieee80211/mac/framesequence/PrimitiveFrameSequences.cc +++ b/src/inet/linklayer/ieee80211/mac/framesequence/PrimitiveFrameSequences.cc @@ -396,8 +396,14 @@ IFrameSequenceStep *BlockAckReqBlockAckFs::prepareStep(FrameSequenceContext *con auto receiverAddr = std::get<0>(blockAckReqParams); auto startingSequenceNumber = std::get<1>(blockAckReqParams); auto tid = std::get<2>(blockAckReqParams); - auto blockAckReq = context->getQoSContext()->blockAckProcedure->buildBasicBlockAckReqFrame(receiverAddr, tid, startingSequenceNumber); - auto blockAckPacket = new Packet("BasicBlockAckReq", blockAckReq); + auto agreementHandler = context->getQoSContext()->blockAckAgreementHandler; + auto agreement = agreementHandler == nullptr ? nullptr : agreementHandler->getAgreement(receiverAddr, tid); + auto outstandingFrames = context->getInProgressFrames()->getOutstandingFrames(); + bool useCompressedBlockAck = context->getQoSContext()->ackPolicy->isCompressedBlockAckReq(outstandingFrames, agreement); + auto blockAckReq = useCompressedBlockAck ? + context->getQoSContext()->blockAckProcedure->buildCompressedBlockAckReqFrame(receiverAddr, tid, startingSequenceNumber) : + context->getQoSContext()->blockAckProcedure->buildBasicBlockAckReqFrame(receiverAddr, tid, startingSequenceNumber); + auto blockAckPacket = new Packet(useCompressedBlockAck ? "CompressedBlockAckReq" : "BasicBlockAckReq", blockAckReq); blockAckPacket->insertAtBack(makeShared()); return new TransmitStep(blockAckPacket, context->getIfs(), true); } @@ -434,4 +440,3 @@ bool BlockAckReqBlockAckFs::completeStep(FrameSequenceContext *context) } // namespace ieee80211 } // namespace inet - diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.cc b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.cc index 90cc7602199..92bf70024ec 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.cc @@ -21,6 +21,7 @@ void OriginatorQosAckPolicy::initialize(int stage) rateSelection = check_and_cast(getModuleByPath(par("rateSelectionModule"))); maxBlockAckPolicyFrameLength = par("maxBlockAckPolicyFrameLength"); blockAckReqThreshold = par("blockAckReqThreshold"); + assumePeerSupportsCompressedBlockAck = par("assumePeerSupportsCompressedBlockAck"); blockAckTimeout = par("blockAckTimeout"); ackTimeout = par("ackTimeout"); } @@ -52,15 +53,29 @@ SequenceNumberCyclic OriginatorQosAckPolicy::computeStartingSequenceNumber(const return startingSequenceNumber; } -bool OriginatorQosAckPolicy::isCompressedBlockAckReq(const std::vector& outstandingFrames, int startingSequenceNumber) const +bool OriginatorQosAckPolicy::isCompressedBlockAckReq(const std::vector& outstandingFrames, OriginatorBlockAckAgreement *agreement) const { - // The Compressed Bitmap subfield of the BA Control field or BAR Control field shall be set to 1 in all - // BlockAck and BlockAckReq frames sent from one HT STA to another HT STA and shall be set to 0 otherwise. - return false; // non-HT STA -// for (auto frame : outstandingFrames) -// if (frame->getSequenceNumber() >= startingSequenceNumber && frame->getFragmentNumber() > 0) -// return false; -// return true; + return isCompressedBlockAckReqNeeded(outstandingFrames, agreement, assumePeerSupportsCompressedBlockAck); +} + +bool OriginatorQosAckPolicy::isCompressedBlockAckReqNeeded(const std::vector& outstandingFrames, OriginatorBlockAckAgreement *agreement, bool assumePeerSupportsCompressedBlockAck) +{ + // IEEE Std 802.11-2024, Table 11-8 and 10.25.6.1: use the compressed + // variant only for an established immediate HT Block Ack agreement. + // Peer HT capability is not represented by the baseline agreement contract; + // the parameter is an explicit assumption supplied by the configuration. + if (!assumePeerSupportsCompressedBlockAck || agreement == nullptr || !agreement->getIsAddbaResponseReceived() || agreement->getIsDelayedBlockAckPolicySupported()) + return false; + bool hasMatchingOutstandingFrame = false; + for (auto frame : outstandingFrames) { + auto header = dynamicPtrCast(frame->peekAtFront()); + if (header == nullptr || header->getReceiverAddress() != agreement->getReceiverAddr() || header->getTid() != agreement->getTid()) + continue; + hasMatchingOutstandingFrame = true; + if (header->getFragmentNumber() != 0 || header->getMoreFragments()) + return false; + } + return hasMatchingOutstandingFrame; } // FIXME @@ -140,4 +155,3 @@ simtime_t OriginatorQosAckPolicy::getBlockAckTimeout(Packet *packet, const Ptr& header, OriginatorBlockAckAgreement *agreement) const; virtual std::map> getOutstandingFramesPerReceiver(InProgressFrames *inProgressFrames) const; virtual SequenceNumberCyclic computeStartingSequenceNumber(const std::vector& outstandingFrames) const; - virtual bool isCompressedBlockAckReq(const std::vector& outstandingFrames, int startingSequenceNumber) const; - + static bool isCompressedBlockAckReqNeeded(const std::vector& outstandingFrames, OriginatorBlockAckAgreement *agreement, bool assumePeerSupportsCompressedBlockAck); public: virtual bool isAckNeeded(const Ptr& header) const override; virtual AckPolicy computeAckPolicy(Packet *packet, const Ptr& header, OriginatorBlockAckAgreement *agreement) const override; virtual bool isBlockAckPolicyEligibleFrame(Packet *packet, const Ptr& header) const override; virtual bool isBlockAckReqNeeded(InProgressFrames *inProgressFrames, TxopProcedure *txopProcedure) const override; + virtual bool isCompressedBlockAckReq(const std::vector& outstandingFrames, OriginatorBlockAckAgreement *agreement) const override; virtual std::tuple computeBlockAckReqParameters(InProgressFrames *inProgressFrames, TxopProcedure *txopProcedure) const override; virtual simtime_t getAckTimeout(Packet *packet, const Ptr& dataOrMgmtHeader) const override; @@ -50,4 +51,3 @@ class INET_API OriginatorQosAckPolicy : public ModeSetListener, public IOriginat } /* namespace inet */ #endif - diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.ned b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.ned index eb2f9236ab1..6575d5290b2 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.ned +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.ned @@ -21,9 +21,11 @@ simple OriginatorQosAckPolicy extends SimpleModule like IOriginatorQosAckPolicy int blockAckReqThreshold = default(5); int maxBlockAckPolicyFrameLength @unit(B) = default(1000B); + // Set only for an HT-or-later local STA when peer capability management establishes that the peer supports Compressed Block Ack. + // This explicit assumption is needed because peer HT capabilities are not represented by the baseline agreement contract. + bool assumePeerSupportsCompressedBlockAck = default(false); double blockAckTimeout @unit(s) = default(-1s); double ackTimeout @unit(s) = default(-1s); @display("i=block/control"); } - diff --git a/src/inet/linklayer/ieee80211/mac/protectionmechanism/SingleProtectionMechanism.cc b/src/inet/linklayer/ieee80211/mac/protectionmechanism/SingleProtectionMechanism.cc index 4b38b531005..3739079f674 100644 --- a/src/inet/linklayer/ieee80211/mac/protectionmechanism/SingleProtectionMechanism.cc +++ b/src/inet/linklayer/ieee80211/mac/protectionmechanism/SingleProtectionMechanism.cc @@ -74,8 +74,12 @@ simtime_t SingleProtectionMechanism::computeBlockAckReqDurationField(Packet *pac simtime_t blockAckReqDurationPerId = blockAckFrameDuration + modeSet->getSifsTime(); return blockAckReqDurationPerId; } + else if (dynamicPtrCast(blockAckReq)) { + simtime_t blockAckFrameDuration = rateSelection->computeResponseBlockAckFrameMode(packet, blockAckReq)->getDuration(LENGTH_COMPRESSED_BLOCKACK); + return blockAckFrameDuration + modeSet->getSifsTime(); + } else - throw cRuntimeError("Compressed and Multi-Tid Block Ack Requests are not supported"); + throw cRuntimeError("Multi-Tid Block Ack Requests are not supported"); } // @@ -183,4 +187,3 @@ simtime_t SingleProtectionMechanism::computeDurationField(Packet *packet, const } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc index c8579ac4424..76cc273fd7a 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc @@ -111,7 +111,7 @@ const IIeee80211Mode *QosRateSelection::computeResponseCtsFrameMode(Packet *pack // const IIeee80211Mode *QosRateSelection::computeResponseBlockAckFrameMode(Packet *packet, const Ptr& blockAckReq) { - if (dynamicPtrCast(blockAckReq)) + if (dynamicPtrCast(blockAckReq) || dynamicPtrCast(blockAckReq)) return responseBlockAckFrameMode ? responseBlockAckFrameMode : getMode(packet, blockAckReq); else throw cRuntimeError("Unknown BlockAckReq frame type"); @@ -248,4 +248,3 @@ void QosRateSelection::frameTransmitted(Packet *packet, const Ptr(getModuleByPath(par("rateSelectionModule"))); + assumePeerSupportsCompressedBlockAck = par("assumePeerSupportsCompressedBlockAck"); } } -simtime_t RecipientQosAckPolicy::computeBasicBlockAckDuration(Packet *packet, const Ptr& blockAckReq) const +simtime_t RecipientQosAckPolicy::computeBlockAckDuration(Packet *packet, const Ptr& blockAckReq) const { - return rateSelection->computeResponseBlockAckFrameMode(packet, blockAckReq)->getDuration(LENGTH_BASIC_BLOCKACK); + b length = dynamicPtrCast(blockAckReq) ? LENGTH_COMPRESSED_BLOCKACK : LENGTH_BASIC_BLOCKACK; + return rateSelection->computeResponseBlockAckFrameMode(packet, blockAckReq)->getDuration(length); } simtime_t RecipientQosAckPolicy::computeAckDuration(Packet *packet, const Ptr& dataOrMgmtHeader) const @@ -66,10 +69,23 @@ bool RecipientQosAckPolicy::isBlockAckNeeded(const Ptr(blockAckReq)) + return isCompressedBlockAckNeeded(compressedBlockAckReq, agreement, assumePeerSupportsCompressedBlockAck); else throw cRuntimeError("Unsupported BlockAckReq"); } +bool RecipientQosAckPolicy::isCompressedBlockAckNeeded(const Ptr& blockAckReq, RecipientBlockAckAgreement *agreement, bool assumePeerSupportsCompressedBlockAck) +{ + // IEEE Std 802.11-2024, 9.3.1.7.2 and 10.25.6.5. Both endpoint policies + // must carry the explicit peer-capability assumption because this baseline + // does not model negotiated peer HT capability. + if (!assumePeerSupportsCompressedBlockAck || blockAckReq->getFragmentNumber() != 0) + return false; + // A missing partial state still elicits the mandatory null compressed BA. + return agreement == nullptr || (agreement->getIsAddbaResponseSent() && !agreement->getIsDelayedBlockAckPolicySupported()); +} + // // 8.2.5.7 Setting for control response frames // For an ACK frame, the Duration/ID field is set to the value obtained from the Duration/ID field of the frame @@ -89,11 +105,10 @@ simtime_t RecipientQosAckPolicy::computeAckDurationField(Packet *packet, const P // the PPDU carrying the frame that elicited the response and the end of the PPDU carrying the BlockAck // frame. // -simtime_t RecipientQosAckPolicy::computeBasicBlockAckDurationField(Packet *packet, const Ptr& basicBlockAckReq) const +simtime_t RecipientQosAckPolicy::computeBlockAckDurationField(Packet *packet, const Ptr& blockAckReq) const { - return basicBlockAckReq->getDurationField() - modeSet->getSifsTime() - computeBasicBlockAckDuration(packet, basicBlockAckReq); + return blockAckReq->getDurationField() - modeSet->getSifsTime() - computeBlockAckDuration(packet, blockAckReq); } } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h index 9f4ce456784..e1ec8677c86 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h @@ -20,12 +20,14 @@ class INET_API RecipientQosAckPolicy : public ModeSetListener, public IRecipient { protected: IQosRateSelection *rateSelection = nullptr; + bool assumePeerSupportsCompressedBlockAck = false; protected: virtual int numInitStages() const override { return NUM_INIT_STAGES; } virtual void initialize(int stage) override; - simtime_t computeBasicBlockAckDuration(Packet *packet, const Ptr& blockAckReq) const; + static bool isCompressedBlockAckNeeded(const Ptr& blockAckReq, RecipientBlockAckAgreement *agreement, bool assumePeerSupportsCompressedBlockAck); + simtime_t computeBlockAckDuration(Packet *packet, const Ptr& blockAckReq) const; simtime_t computeAckDuration(Packet *packet, const Ptr& dataOrMgmtHeader) const; public: @@ -33,11 +35,10 @@ class INET_API RecipientQosAckPolicy : public ModeSetListener, public IRecipient virtual bool isBlockAckNeeded(const Ptr& blockAckReq, RecipientBlockAckAgreement *agreement) const override; virtual simtime_t computeAckDurationField(Packet *packet, const Ptr& header) const override; - virtual simtime_t computeBasicBlockAckDurationField(Packet *packet, const Ptr& basicBlockAckReq) const override; + virtual simtime_t computeBlockAckDurationField(Packet *packet, const Ptr& blockAckReq) const override; }; } /* namespace ieee80211 */ } /* namespace inet */ #endif - diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.ned b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.ned index 50971923a80..02369ccfd24 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.ned +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.ned @@ -18,6 +18,8 @@ simple RecipientQosAckPolicy extends SimpleModule like IRecipientQosAckPolicy parameters: @class(RecipientQosAckPolicy); string rateSelectionModule; + // Set only for an HT-or-later local STA when peer capability management establishes that the peer supports Compressed Block Ack. + // This explicit assumption is needed because peer HT capabilities are not represented by the baseline agreement contract. + bool assumePeerSupportsCompressedBlockAck = default(false); @display("i=block/control"); } - diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc index 77e677bb147..3fd9f29682b 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc @@ -136,10 +136,16 @@ std::vector RecipientQosMacDataService::managementFrameReceived(Packet std::vector RecipientQosMacDataService::controlFrameReceived(Packet *controlPacket, const Ptr& controlHeader, IRecipientBlockAckAgreementHandler *blockAckAgreementHandler) { Enter_Method("controlFrameReceived"); - if (auto blockAckReq = dynamicPtrCast(controlHeader)) { + if (auto blockAckReq = dynamicPtrCast(controlHeader)) { BlockAckReordering::ReorderBuffer frames; if (blockAckReordering) { - Tid tid = blockAckReq->getTidInfo(); + Tid tid = -1; + if (auto basicBlockAckReq = dynamicPtrCast(blockAckReq)) + tid = basicBlockAckReq->getTidInfo(); + else if (auto compressedBlockAckReq = dynamicPtrCast(blockAckReq)) + tid = compressedBlockAckReq->getTidInfo(); + else + return std::vector(); MacAddress originatorAddr = blockAckReq->getTransmitterAddress(); RecipientBlockAckAgreement *agreement = blockAckAgreementHandler->getAgreement(tid, originatorAddr); if (agreement) @@ -196,4 +202,3 @@ RecipientQosMacDataService::~RecipientQosMacDataService() } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/tests/module/Ieee80211CompressedBlockAckRuntime.test b/tests/module/Ieee80211CompressedBlockAckRuntime.test new file mode 100644 index 00000000000..ea7c4dbdc24 --- /dev/null +++ b/tests/module/Ieee80211CompressedBlockAckRuntime.test @@ -0,0 +1,37 @@ +%description: + +Checks that an explicitly enabled one-TID HT-immediate Block Ack agreement uses +Compressed BlockAckReq and receives the SIFS Compressed BlockAck response. + +%extraargs: -c CompressedBlockAckRuntime -r 0 + +%inifile: omnetpp.ini + +include ../../../../examples/wireless/qos/omnetpp.ini + +[General] +ned-path = .;../../../../src;../../../../examples;../../lib +cmdenv-express-mode = false +record-vector-results = false +record-scalar-results = false +seed-set = 0 + +[Config CompressedBlockAckRuntime] +extends = MacQosWithBlockAck +abstract = false +sim-time-limit = 1.05s + +**.opMode = "n(mixed-2.4Ghz)" +# Baseline capability management is absent, so both endpoint policies explicitly +# opt in to compressed Block Ack; the production default remains false. +**.assumePeerSupportsCompressedBlockAck = true +**.cmdenv-log-level = info + +%contains: stdout +Processing Addba Request from + +%contains: stdout +Processing transmitted frame CompressedBlockAckReq as originator in frame sequence. + +%contains: stdout +Ieee80211CompressedBlockAck has arrived diff --git a/tests/unit/Ieee80211CompressedBlockAck_1.test b/tests/unit/Ieee80211CompressedBlockAck_1.test new file mode 100644 index 00000000000..eeb7f6fb503 --- /dev/null +++ b/tests/unit/Ieee80211CompressedBlockAck_1.test @@ -0,0 +1,319 @@ +%description: +Validate one-TID HT Compressed Block Ack wire encoding and recipient bitmap construction. +IEEE Std 802.11-2024, 9.3.1.7.2, 9.3.1.8.2, 10.25.6.1, and 10.25.6.5. + +%includes: +#include "inet/common/packet/Packet.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" +#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/RecipientBlockAckProcedure.h" +#include "inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.h" +#include "inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h" + +%global: +using namespace inet; +using namespace inet::ieee80211; + +class TestRecipientBlockAckProcedure : public RecipientBlockAckProcedure +{ + public: + using RecipientBlockAckProcedure::buildBlockAck; +}; + +class TestOriginatorBlockAckAgreementHandler : public OriginatorBlockAckAgreementHandler +{ + public: + using OriginatorBlockAckAgreementHandler::updateAgreement; +}; + +class TestOriginatorQosAckPolicy : public OriginatorQosAckPolicy +{ + public: + static bool isCompressedRequestNeeded(const std::vector& outstandingFrames, OriginatorBlockAckAgreement *agreement, bool assumePeerSupport) + { + return isCompressedBlockAckReqNeeded(outstandingFrames, agreement, assumePeerSupport); + } +}; + +class TestRecipientQosAckPolicy : public RecipientQosAckPolicy +{ + public: + static bool isCompressedResponseNeeded(const Ptr& request, RecipientBlockAckAgreement *agreement, bool enabled) + { + return isCompressedBlockAckNeeded(request, agreement, enabled); + } +}; + +template +Ptr roundTrip(const Ptr& original) +{ + Packet packet("original", original); + auto bytes = packet.peekAllAsBytes(); + Packet decodedPacket("decoded", bytes); + auto decoded = decodedPacket.popAtFront(); + return dynamicPtrCast(decoded); +} + +static Ptr makeCompressedBlockAckReq() +{ + auto request = makeShared(); + request->setDurationField(SIMTIME_ZERO); + request->setReceiverAddress(MacAddress("10:20:30:40:50:60")); + request->setTransmitterAddress(MacAddress("11:22:33:44:55:66")); + request->setBarAckPolicy(true); + request->setTidInfo(5); + request->setFragmentNumber(0); + request->setStartingSequenceNumber(SequenceNumberCyclic(0xABC)); + return request; +} + +static Packet *makeOutstandingQosFrame(MacAddress receiverAddress, Tid tid, FragmentNumber fragmentNumber = 0, bool moreFragments = false) +{ + auto header = makeShared(); + header->setType(ST_DATA_WITH_QOS); + header->setReceiverAddress(receiverAddress); + header->setTid(tid); + header->setFragmentNumber(fragmentNumber); + header->setMoreFragments(moreFragments); + return new Packet("outstanding", header); +} + +%activity: +{ + auto request = makeCompressedBlockAckReq(); + Packet packet("compressedBar", request); + auto bytes = packet.peekAllAsBytes(); + ASSERT(bytes->getChunkLength() == B(20)); + ASSERT(bytes->getByte(16) == 0x05 && bytes->getByte(17) == 0x50); // BAR Control: policy=1, compressed=1, TID=5 + ASSERT(bytes->getByte(18) == 0xC0 && bytes->getByte(19) == 0xAB); // SSC: fragment=0, sequence=0xABC + auto decoded = roundTrip(request); + ASSERT(decoded != nullptr); + ASSERT(decoded->getBarAckPolicy() && decoded->getCompressedBitmap() && !decoded->getMultiTid()); + ASSERT(decoded->getTidInfo() == 5 && decoded->getFragmentNumber() == 0); + ASSERT(decoded->getStartingSequenceNumber() == SequenceNumberCyclic(0xABC)); + EV << "Compressed BAR encoding and round-trip passed.\n"; +} + +{ + auto blockAck = makeShared(); + blockAck->setDurationField(SIMTIME_ZERO); + blockAck->setReceiverAddress(MacAddress("11:22:33:44:55:66")); + blockAck->setTransmitterAddress(MacAddress("10:20:30:40:50:60")); + blockAck->setTidInfo(5); + blockAck->setFragmentNumber(0); + blockAck->setStartingSequenceNumber(SequenceNumberCyclic(0xABC)); + BitVector bitmap(std::vector({0x05, 0, 0, 0, 0, 0, 0, 0})); + blockAck->setBlockAckBitmap(bitmap); + Packet packet("compressedBa", blockAck); + auto bytes = packet.peekAllAsBytes(); + ASSERT(bytes->getChunkLength() == B(28)); + ASSERT(bytes->getByte(16) == 0x04 && bytes->getByte(17) == 0x50); // BA Control: compressed=1, TID=5 + ASSERT(bytes->getByte(18) == 0xC0 && bytes->getByte(19) == 0xAB); + ASSERT(bytes->getByte(20) == 0x05); + auto decoded = roundTrip(blockAck); + ASSERT(decoded != nullptr); + ASSERT(decoded->getBlockAckBitmap().getBit(0)); + ASSERT(!decoded->getBlockAckBitmap().getBit(1)); + ASSERT(decoded->getBlockAckBitmap().getBit(2)); + EV << "Compressed BA encoding and round-trip passed.\n"; +} + +{ + TestRecipientBlockAckProcedure procedure; + auto request = makeCompressedBlockAckReq(); + request->setStartingSequenceNumber(SequenceNumberCyclic(100)); + auto response = dynamicPtrCast(procedure.buildBlockAck(request, nullptr)); + ASSERT(response != nullptr && response->getChunkLength() == B(28)); + for (int i = 0; i < 64; i++) + ASSERT(!response->getBlockAckBitmap().getBit(i)); + EV << "Missing recipient state produces a null compressed BA.\n"; +} + +{ + TestRecipientBlockAckProcedure procedure; + RecipientBlockAckAgreement emptyAgreement(MacAddress("11:22:33:44:55:66"), 5, SequenceNumberCyclic(100), 64, SIMTIME_ZERO); + auto request = makeCompressedBlockAckReq(); + request->setStartingSequenceNumber(SequenceNumberCyclic(100)); + auto response = dynamicPtrCast(procedure.buildBlockAck(request, &emptyAgreement)); + for (int i = 0; i < 64; i++) + ASSERT(!response->getBlockAckBitmap().getBit(i)); + EV << "Empty recipient record produces a null compressed BA.\n"; +} + +{ + TestRecipientBlockAckProcedure procedure; + RecipientBlockAckAgreement agreement(MacAddress("11:22:33:44:55:66"), 5, SequenceNumberCyclic(101), 64, SIMTIME_ZERO); + for (int sequenceNumber : {101, 103}) { + auto data = makeShared(); + data->setType(ST_DATA_WITH_QOS); + data->setAckPolicy(BLOCK_ACK); + data->setTid(5); + data->setSequenceNumber(SequenceNumberCyclic(sequenceNumber)); + data->setFragmentNumber(0); + agreement.blockAckPolicyFrameReceived(data); + } + auto request = makeCompressedBlockAckReq(); + request->setStartingSequenceNumber(SequenceNumberCyclic(101)); + auto response = dynamicPtrCast(procedure.buildBlockAck(request, &agreement)); + ASSERT(response->getBlockAckBitmap().getBit(0)); + ASSERT(!response->getBlockAckBitmap().getBit(1)); + ASSERT(response->getBlockAckBitmap().getBit(2)); + EV << "Established agreement produces the expected 64-bit bitmap.\n"; +} + +{ + TestRecipientBlockAckProcedure procedure; + RecipientBlockAckAgreement agreement(MacAddress("11:22:33:44:55:66"), 5, SequenceNumberCyclic(100), 64, SIMTIME_ZERO); + auto data = makeShared(); + data->setType(ST_DATA_WITH_QOS); + data->setAckPolicy(BLOCK_ACK); + data->setTid(5); + data->setSequenceNumber(SequenceNumberCyclic(102)); + data->setFragmentNumber(0); + agreement.blockAckPolicyFrameReceived(data); + auto request = makeCompressedBlockAckReq(); + request->setStartingSequenceNumber(SequenceNumberCyclic(100)); + auto response = dynamicPtrCast(procedure.buildBlockAck(request, &agreement)); + ASSERT(!response->getBlockAckBitmap().getBit(0)); + ASSERT(!response->getBlockAckBitmap().getBit(1)); + ASSERT(response->getBlockAckBitmap().getBit(2)); + EV << "Compressed bitmap preserves leading receive-window holes.\n"; +} + +{ + TestRecipientBlockAckProcedure procedure; + RecipientBlockAckAgreement agreement(MacAddress("11:22:33:44:55:66"), 5, SequenceNumberCyclic(4095), 64, SIMTIME_ZERO); + for (int sequenceNumber : {4095, 0}) { + auto data = makeShared(); + data->setType(ST_DATA_WITH_QOS); + data->setAckPolicy(BLOCK_ACK); + data->setTid(5); + data->setSequenceNumber(SequenceNumberCyclic(sequenceNumber)); + data->setFragmentNumber(0); + agreement.blockAckPolicyFrameReceived(data); + } + auto request = makeCompressedBlockAckReq(); + request->setStartingSequenceNumber(SequenceNumberCyclic(4095)); + auto response = dynamicPtrCast(procedure.buildBlockAck(request, &agreement)); + ASSERT(response->getBlockAckBitmap().getBit(0)); + ASSERT(response->getBlockAckBitmap().getBit(1)); + ASSERT(!response->getBlockAckBitmap().getBit(2)); + EV << "Compressed bitmap wraps from sequence 4095 to 0.\n"; +} + +{ + TestRecipientBlockAckProcedure procedure; + RecipientBlockAckAgreement agreement(MacAddress("11:22:33:44:55:66"), 5, SequenceNumberCyclic(4095), 64, SIMTIME_ZERO); + auto data = makeShared(); + data->setType(ST_DATA_WITH_QOS); + data->setAckPolicy(BLOCK_ACK); + data->setTid(5); + data->setSequenceNumber(SequenceNumberCyclic(1)); + data->setFragmentNumber(0); + agreement.blockAckPolicyFrameReceived(data); + auto request = makeCompressedBlockAckReq(); + request->setStartingSequenceNumber(SequenceNumberCyclic(4095)); + auto response = dynamicPtrCast(procedure.buildBlockAck(request, &agreement)); + ASSERT(!response->getBlockAckBitmap().getBit(0)); + ASSERT(!response->getBlockAckBitmap().getBit(1)); + ASSERT(response->getBlockAckBitmap().getBit(2)); + EV << "Compressed bitmap preserves leading holes across sequence wrap.\n"; +} + +{ + auto request = makeCompressedBlockAckReq(); + ASSERT(!TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, nullptr, false)); + ASSERT(TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, nullptr, true)); + request->setFragmentNumber(1); + ASSERT(!TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, nullptr, true)); + request->setFragmentNumber(0); + RecipientBlockAckAgreement delayedAgreement(MacAddress("11:22:33:44:55:66"), 5, SequenceNumberCyclic(100), 64, SIMTIME_ZERO); + delayedAgreement.addbaResposneSent(); + delayedAgreement.setIsDelayedBlockAckPolicySupported(true); + ASSERT(!TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, &delayedAgreement, true)); + delayedAgreement.setIsDelayedBlockAckPolicySupported(false); + ASSERT(TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, &delayedAgreement, true)); + EV << "Recipient capability, fragment, and agreement-policy gates passed.\n"; +} + +{ + MacAddress receiverAddress("10:20:30:40:50:60"); + OriginatorBlockAckAgreement immediateAgreement(receiverAddress, 5, SequenceNumberCyclic(100), 64, false, false); + immediateAgreement.setIsAddbaResponseReceived(true); + std::vector outstandingFrames { makeOutstandingQosFrame(receiverAddress, 5) }; + ASSERT(!TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, &immediateAgreement, false)); + ASSERT(!TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, nullptr, true)); + OriginatorBlockAckAgreement delayedAgreement(receiverAddress, 5, SequenceNumberCyclic(100), 64, false, true); + delayedAgreement.setIsAddbaResponseReceived(true); + ASSERT(!TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, &delayedAgreement, true)); + ASSERT(TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, &immediateAgreement, true)); + delete outstandingFrames[0]; + outstandingFrames = { makeOutstandingQosFrame(receiverAddress, 5, 1) }; + ASSERT(!TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, &immediateAgreement, true)); + delete outstandingFrames[0]; + outstandingFrames = { makeOutstandingQosFrame(receiverAddress, 5, 0, true) }; + ASSERT(!TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, &immediateAgreement, true)); + delete outstandingFrames[0]; + EV << "Originator capability, agreement, and fragmentation gates passed.\n"; +} + +{ + auto basicRequest = makeShared(); + basicRequest->setDurationField(SIMTIME_ZERO); + basicRequest->setReceiverAddress(MacAddress("10:20:30:40:50:60")); + basicRequest->setTransmitterAddress(MacAddress("11:22:33:44:55:66")); + basicRequest->setTidInfo(5); + basicRequest->setStartingSequenceNumber(SequenceNumberCyclic(0xABC)); + Packet packet("basicBar", basicRequest); + auto bytes = packet.peekAllAsBytes(); + ASSERT(bytes->getChunkLength() == B(20)); + ASSERT(bytes->getByte(16) == 0x00 && bytes->getByte(17) == 0x50); + ASSERT(roundTrip(basicRequest) != nullptr); + auto basicBlockAck = makeShared(); + basicBlockAck->setDurationField(SIMTIME_ZERO); + basicBlockAck->setReceiverAddress(MacAddress("11:22:33:44:55:66")); + basicBlockAck->setTransmitterAddress(MacAddress("10:20:30:40:50:60")); + basicBlockAck->setTidInfo(5); + basicBlockAck->setStartingSequenceNumber(SequenceNumberCyclic(0xABC)); + for (int i = 0; i < 64; i++) + basicBlockAck->setBlockAckBitmap(i, BitVector(std::vector(2, 0))); + Packet blockAckPacket("basicBa", basicBlockAck); + auto blockAckBytes = blockAckPacket.peekAllAsBytes(); + ASSERT(blockAckBytes->getChunkLength() == B(148)); + ASSERT(blockAckBytes->getByte(16) == 0x00 && blockAckBytes->getByte(17) == 0x50); + ASSERT(blockAckBytes->getByte(18) == 0xC0 && blockAckBytes->getByte(19) == 0xAB); + ASSERT(roundTrip(basicBlockAck) != nullptr); + EV << "Legacy Basic BAR and BA remain byte-exact.\n"; +} + +{ + TestOriginatorBlockAckAgreementHandler handler; + OriginatorBlockAckAgreement agreement(MacAddress("10:20:30:40:50:60"), 5, SequenceNumberCyclic(100), 64, false, false); + auto delayedResponse = makeShared(); + delayedResponse->setBlockAckPolicy(false); + delayedResponse->setBufferSize(64); + delayedResponse->setBlockAckTimeoutValue(SIMTIME_ZERO); + handler.updateAgreement(&agreement, delayedResponse); + ASSERT(agreement.getIsAddbaResponseReceived()); + ASSERT(agreement.getIsDelayedBlockAckPolicySupported()); + EV << "Accepted delayed agreement is retained for Basic BAR fallback.\n"; +} + +EV << ".\n"; + +%contains: stdout +Compressed BAR encoding and round-trip passed. +Compressed BA encoding and round-trip passed. +Missing recipient state produces a null compressed BA. +Empty recipient record produces a null compressed BA. +Established agreement produces the expected 64-bit bitmap. +Compressed bitmap preserves leading receive-window holes. +Compressed bitmap wraps from sequence 4095 to 0. +Compressed bitmap preserves leading holes across sequence wrap. +Recipient capability, fragment, and agreement-policy gates passed. +Originator capability, agreement, and fragmentation gates passed. +Legacy Basic BAR and BA remain byte-exact. +Accepted delayed agreement is retained for Basic BAR fallback. +. From 84916eb16474dcfdb71df2baab395de202f0226c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Sat, 15 Aug 2026 20:33:36 +0200 Subject: [PATCH 2/8] test(fingerprint): update Block Ack trajectories Update the affected QoS, Block Ack, fragmentation, and TXOP fingerprints after correcting the Basic BlockAckReq wire size from 42 bytes to 24 bytes. The first changed event is the same BAR transmission with the corrected frame length. Its shorter airtime shifts subsequent packet and timing ingredients while preserving the Block Ack exchange and SIFS response. Cover the QoS Block Ack example, all three Block Ack showcase configurations, HCF fragmentation with Block Ack, the TXOP showcase, and the Block Ack-enabled ad hoc QoS run. Preserve existing graphical tyf ingredients, which are excluded from the focused campaign. The focused debug fingerprint campaign passes all six newly identified rows with tyf excluded; the previously updated QoS Block Ack row also passes its focused release check. --- tests/fingerprint/examples.csv | 4 ++-- tests/fingerprint/showcases.csv | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/fingerprint/examples.csv b/tests/fingerprint/examples.csv index 283915ab865..8d31686982e 100644 --- a/tests/fingerprint/examples.csv +++ b/tests/fingerprint/examples.csv @@ -7,7 +7,7 @@ # /examples/adhoc/ieee80211/, -f omnetpp.ini -c Ping2 -r 0, 100s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND, ERROR, wireless adhoc # [Config Ping2] # interactive config, needed a *.numHosts parameter /examples/adhoc/qos/, -f omnetpp.ini -c MacNonQos -r 0, 10s, 5749-0281/tplx;f38a-cb93/~tNl;fd9b-683f/~tND;0eb8-3e3b/tyf, PASS, wireless adhoc Ipv4 /examples/adhoc/qos/, -f omnetpp.ini -c MacQos -r 0, 10s, 8d50-a4b1/tplx;d392-4369/~tNl;8da4-91dc/~tND;2c1e-66e5/tyf, PASS, wireless adhoc Ipv4 -/examples/adhoc/qos/, -f omnetpp.ini -c MacQos -r 1, 10s, ed2f-2d62/tplx;8a06-c4b3/~tNl;cddb-a568/~tND;a587-3a4d/tyf, PASS, wireless adhoc Ipv4 +/examples/adhoc/qos/, -f omnetpp.ini -c MacQos -r 1, 10s, 5a48-dd94/tplx;45f0-2ffe/~tNl;8d6d-ad67/~tND;a587-3a4d/tyf, PASS, wireless adhoc Ipv4 /examples/adhoc/qos/, -f omnetpp.ini -c Fragmentation, 10s, 9c66-e6f0/tplx;cc9e-d1a6/~tNl;53c8-e1b0/~tND;33b7-00f9/tyf, PASS, wireless adhoc Ipv4 /examples/adhoc/qos/, -f omnetpp.ini -c MsduAggregation, 10s, 6139-16b9/tplx;cfcf-1e52/~tNl;31df-c237/~tND;df04-18f7/tyf, PASS, wireless adhoc Ipv4 @@ -658,7 +658,7 @@ /examples/wireless/qos/, -f omnetpp.ini -c MacQos -r 0, 10s, 5e57-04bd/tplx;68a7-f409/~tNl;53ea-a840/~tND;9f3c-4512/tyf, PASS, wireless Ipv4 /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 MacQosWithBlockAck -r 0, 10s, cf83-e60c/tplx;51b0-197f/~tNl;9291-3bf1/~tND;431e-d96c/tyf, 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/fingerprint/showcases.csv b/tests/fingerprint/showcases.csv index c483f40c8a3..efa9fe7bf5f 100644 --- a/tests/fingerprint/showcases.csv +++ b/tests/fingerprint/showcases.csv @@ -199,9 +199,9 @@ /showcases/wireless/analogmodel/, -f omnetpp.ini -c Distance -r 0, 2.5s, 1e75-270e/tplx;6d7a-d84c/~tNl;b380-6cd5/~tND;5575-fd8f/tyf, PASS, wireless Ipv4 /showcases/wireless/analogmodel/, -f omnetpp.ini -c Noise -r 0, 0.1s, dd08-a63c/tplx;e167-9c84/~tNl;643d-41a6/~tND;0d1f-df73/tyf, PASS, wireless Ipv4 -/showcases/wireless/blockack/, -f omnetpp.ini -c NoFragmentation -r 0, 1s, aa2d-5d35/tplx;2094-1f2a/~tNl;1470-1e1b/~tND, PASS, wireless Ipv4 -/showcases/wireless/blockack/, -f omnetpp.ini -c Fragmentation -r 0, 1s, 7ae9-e07d/tplx;db8b-3b81/~tNl;9c41-dc97/~tND, PASS, wireless Ipv4 -/showcases/wireless/blockack/, -f omnetpp.ini -c MixedTraffic -r 0, 1s, 462d-10c7/tplx;727b-d26a/~tNl;62c4-cbc2/~tND, PASS, wireless Ipv4 +/showcases/wireless/blockack/, -f omnetpp.ini -c NoFragmentation -r 0, 1s, 5b0b-f055/tplx;8511-cc76/~tNl;5847-83de/~tND, PASS, wireless Ipv4 +/showcases/wireless/blockack/, -f omnetpp.ini -c Fragmentation -r 0, 1s, 7f81-62c8/tplx;2638-6c00/~tNl;82ef-52a1/~tND, PASS, wireless Ipv4 +/showcases/wireless/blockack/, -f omnetpp.ini -c MixedTraffic -r 0, 1s, 26b2-73e4/tplx;d7bc-6c9e/~tNl;cfda-783c/~tND, PASS, wireless Ipv4 /showcases/wireless/crosstalk/, -f omnetpp.ini -c CompletelyOverlappingFrequencyBands -r 0, 1s, d0d7-43e0/tplx;867a-07a4/~tNl;df78-8445/~tND;3ea3-43da/tyf, PASS, wireless Ipv4 /showcases/wireless/crosstalk/, -f omnetpp.ini -c IndependentFrequencyBandsOneRadioMediumModule -r 0, 1s, 70c6-72b6/tplx;cf96-5e4d/~tNl;3cba-ae59/~tND;d1b2-9fd4/tyf, PASS, wireless Ipv4 @@ -267,7 +267,7 @@ /showcases/wireless/fragmentation/, -f omnetpp.ini -c DCFnofrag -r 0, 1s, 52b9-628f/tplx;3fec-74a2/~tNl;6073-4582/~tND;8871-1dd1/tyf, PASS, wireless Ipv4 /showcases/wireless/fragmentation/, -f omnetpp.ini -c DCFfrag -r 0, 1s, 57ee-7ddf/tplx;dab9-5e8d/~tNl;7f9b-00fc/~tND;f985-34fb/tyf, PASS, wireless Ipv4 /showcases/wireless/fragmentation/, -f omnetpp.ini -c HCFfrag -r 0, 1s, 73ec-f869/tplx;2366-9a07/~tNl;dcb8-5554/~tND;335d-6687/tyf, PASS, wireless Ipv4 -/showcases/wireless/fragmentation/, -f omnetpp.ini -c HCFfragblockack -r 0, 1s, 41c4-d741/tplx;db22-9b02/~tNl;523f-f640/~tND;6f6e-b101/tyf, PASS, wireless Ipv4 +/showcases/wireless/fragmentation/, -f omnetpp.ini -c HCFfragblockack -r 0, 1s, 598f-9c58/tplx;0010-a266/~tNl;0533-8742/~tND;6f6e-b101/tyf, PASS, wireless Ipv4 /showcases/wireless/handover/, -f omnetpp.ini -c General -r 0, 250s, 47b6-4dfd/tplx;a78b-61da/~tNl;b639-081a/~tND;02e9-9ad3/tyf, PASS, wireless @@ -341,5 +341,5 @@ /showcases/wireless/throughput/, -f omnetpp.ini -c General -r 0, 1s, 030e-c416/tplx;66e6-0bca/~tNl;3cb6-43bd/~tND;e6a5-bde0/tyf, PASS, wireless Ipv4 -/showcases/wireless/txop/, -f omnetpp.ini -c General -r 0, 5s, 9fdc-f33e/tplx;5ee8-bcc1/~tNl;c87d-3f3a/tyf, PASS, wireless Ipv4 +/showcases/wireless/txop/, -f omnetpp.ini -c General -r 0, 5s, b599-60c7/tplx;b680-00ca/~tNl;c87d-3f3a/tyf, PASS, wireless Ipv4 From c86205007e3e36023cd02090051d94a58391b145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Sat, 15 Aug 2026 21:51:34 +0200 Subject: [PATCH 3/8] fix(ieee80211): select Block Ack rates by base variant Classify BlockAck and BlockAckReq frames through their base classes in QosRateSelection so compressed requests follow the same control-rate policy as Basic requests. This prevents an originated Compressed BlockAckReq from falling through to the ordinary control-frame path and reusing the last transmitted data mode when the mandatory Block Ack fallback should be selected. Add focused coverage with distinct mandatory and last-transmitted modes, verifying Basic and Compressed BAR symmetry while preserving RTS behavior. --- .../mac/rateselection/QosRateSelection.cc | 9 ++--- tests/unit/Ieee80211CompressedBlockAck_1.test | 35 +++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc index 76cc273fd7a..9448f7b0d5c 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc @@ -170,17 +170,18 @@ const IIeee80211Mode *QosRateSelection::computeControlFrameMode(const Ptr(header) != nullptr || dynamicPtrCast(header) != nullptr; // This subclause describes the rate selection rules for control frames that initiate a TXOP and that are not carried // in an A-MPDU. if (txopProcedure->isTxopInitiator(header)) { - // If a control frame other than a Basic BlockAckReq or Basic BlockAck is carried in a non-HT PPDU, the + // If a control frame other than a BlockAckReq or BlockAck is carried in a non-HT PPDU, the // transmitting STA shall transmit the frame using one of the rates in the BSSBasicRateSet parameter or a rate // from the mandatory rate set of the attached PHY if the BSSBasicRateSet is empty. - if (!dynamicPtrCast(header) && !dynamicPtrCast(header)) { + if (!isBlockAckFrame) { // TODO BSSBasicRateSet return fastestMandatoryMode; } - // If a Basic BlockAckReq or Basic BlockAck frame is carried in a non-HT PPDU, the transmitting STA shall + // If a BlockAckReq or BlockAck frame is carried in a non-HT PPDU, the transmitting STA shall // transmit the frame using a rate supported by the receiver STA, if known (as reported in the Supported Rates // element and/or Extended Supported Rates element in frames transmitted by that STA). If the supported rate set // of the receiving STA or STAs is not known, the transmitting STA shall transmit using a rate from the @@ -202,7 +203,7 @@ const IIeee80211Mode *QosRateSelection::computeControlFrameMode(const Ptr(header) && !dynamicPtrCast(header)) { + if (!isBlockAckFrame) { // TODO frame sequence context auto it = lastTransmittedFrameMode.find(header->getReceiverAddress()); return (it != lastTransmittedFrameMode.end()) ? it->second : fastestMandatoryMode; diff --git a/tests/unit/Ieee80211CompressedBlockAck_1.test b/tests/unit/Ieee80211CompressedBlockAck_1.test index eeb7f6fb503..2fd6b9732b5 100644 --- a/tests/unit/Ieee80211CompressedBlockAck_1.test +++ b/tests/unit/Ieee80211CompressedBlockAck_1.test @@ -10,7 +10,10 @@ IEEE Std 802.11-2024, 9.3.1.7.2, 9.3.1.8.2, 10.25.6.1, and 10.25.6.5. #include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h" #include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckProcedure.h" #include "inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.h" +#include "inet/linklayer/ieee80211/mac/originator/TxopProcedure.h" +#include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" #include "inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211OfdmMode.h" %global: using namespace inet; @@ -46,6 +49,14 @@ class TestRecipientQosAckPolicy : public RecipientQosAckPolicy } }; +class TestQosRateSelection : public QosRateSelection +{ + public: + void setFastestMandatoryMode(const physicallayer::IIeee80211Mode *mode) { fastestMandatoryMode = mode; } + void setLastTransmittedFrameMode(const MacAddress& receiverAddress, const physicallayer::IIeee80211Mode *mode) { lastTransmittedFrameMode[receiverAddress] = mode; } + using QosRateSelection::computeControlFrameMode; +}; + template Ptr roundTrip(const Ptr& original) { @@ -301,6 +312,29 @@ static Packet *makeOutstandingQosFrame(MacAddress receiverAddress, Tid tid, Frag EV << "Accepted delayed agreement is retained for Basic BAR fallback.\n"; } +{ + // IEEE Std 802.11-2024, 10.6.6.4 applies the control-frame rate rule to BlockAckReq and BlockAck variants. + const auto mandatoryMode = &physicallayer::Ieee80211OfdmCompliantModes::ofdmMode6MbpsCS20MHz; + const auto lastTransmittedMode = &physicallayer::Ieee80211OfdmCompliantModes::ofdmMode54Mbps; + MacAddress receiverAddress("10:20:30:40:50:60"); + TestQosRateSelection rateSelection; + TxopProcedure txopProcedure; + rateSelection.setFastestMandatoryMode(mandatoryMode); + rateSelection.setLastTransmittedFrameMode(receiverAddress, lastTransmittedMode); + + auto basicRequest = makeShared(); + basicRequest->setReceiverAddress(receiverAddress); + auto compressedRequest = makeCompressedBlockAckReq(); + compressedRequest->setReceiverAddress(receiverAddress); + ASSERT(rateSelection.computeControlFrameMode(basicRequest, &txopProcedure) == mandatoryMode); + ASSERT(rateSelection.computeControlFrameMode(compressedRequest, &txopProcedure) == mandatoryMode); + + auto rtsFrame = makeShared(); + rtsFrame->setReceiverAddress(receiverAddress); + ASSERT(rateSelection.computeControlFrameMode(rtsFrame, &txopProcedure) == lastTransmittedMode); + EV << "Basic and Compressed BAR rate selection is variant-agnostic.\n"; +} + EV << ".\n"; %contains: stdout @@ -316,4 +350,5 @@ Recipient capability, fragment, and agreement-policy gates passed. Originator capability, agreement, and fragmentation gates passed. Legacy Basic BAR and BA remain byte-exact. Accepted delayed agreement is retained for Basic BAR fallback. +Basic and Compressed BAR rate selection is variant-agnostic. . From 67145b74ece806c5b04c5355def860954c254205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Sun, 16 Aug 2026 01:25:44 +0200 Subject: [PATCH 4/8] fix(ieee80211): answer valid compressed block ack requests Remove the recipient-side peer capability assumption from compressed Block Ack response selection. An addressed, syntactically valid one-TID compressed BlockAckReq must receive a compressed BlockAck after SIFS, including the all-zero response when no matching partial state exists, as required by IEEE 802.11-2024 sections 9.3.1.7.2 and 10.25.6.5. Keep the originator-side capability assumption as the explicit opt-in for selecting compressed BlockAckReq frames until per-peer HT capability management is modeled. Continue rejecting nonzero fragment numbers and suppressing immediate responses for unaccepted or delayed agreements. Update the focused unit and runtime coverage to verify null-state responses, malformed fragment rejection, agreement-policy gates, and asymmetric endpoint configuration. --- .../mac/recipient/RecipientQosAckPolicy.cc | 15 ++++++--------- .../mac/recipient/RecipientQosAckPolicy.h | 3 +-- .../mac/recipient/RecipientQosAckPolicy.ned | 3 --- .../Ieee80211CompressedBlockAckRuntime.test | 6 +++--- tests/unit/Ieee80211CompressedBlockAck_1.test | 18 +++++++++--------- 5 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc index 7c301b6e92d..4c918c5995b 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc @@ -18,10 +18,8 @@ Define_Module(RecipientQosAckPolicy); void RecipientQosAckPolicy::initialize(int stage) { ModeSetListener::initialize(stage); - if (stage == INITSTAGE_LOCAL) { + if (stage == INITSTAGE_LOCAL) rateSelection = check_and_cast(getModuleByPath(par("rateSelectionModule"))); - assumePeerSupportsCompressedBlockAck = par("assumePeerSupportsCompressedBlockAck"); - } } simtime_t RecipientQosAckPolicy::computeBlockAckDuration(Packet *packet, const Ptr& blockAckReq) const @@ -70,17 +68,16 @@ bool RecipientQosAckPolicy::isBlockAckNeeded(const Ptr(blockAckReq)) - return isCompressedBlockAckNeeded(compressedBlockAckReq, agreement, assumePeerSupportsCompressedBlockAck); + return isCompressedBlockAckNeeded(compressedBlockAckReq, agreement); else throw cRuntimeError("Unsupported BlockAckReq"); } -bool RecipientQosAckPolicy::isCompressedBlockAckNeeded(const Ptr& blockAckReq, RecipientBlockAckAgreement *agreement, bool assumePeerSupportsCompressedBlockAck) +bool RecipientQosAckPolicy::isCompressedBlockAckNeeded(const Ptr& blockAckReq, RecipientBlockAckAgreement *agreement) { - // IEEE Std 802.11-2024, 9.3.1.7.2 and 10.25.6.5. Both endpoint policies - // must carry the explicit peer-capability assumption because this baseline - // does not model negotiated peer HT capability. - if (!assumePeerSupportsCompressedBlockAck || blockAckReq->getFragmentNumber() != 0) + // IEEE Std 802.11-2024, 9.3.1.7.2 and 10.25.6.5: an addressed, syntactically + // valid Compressed BlockAckReq elicits a Compressed BlockAck, including a null response. + if (blockAckReq->getFragmentNumber() != 0) return false; // A missing partial state still elicits the mandatory null compressed BA. return agreement == nullptr || (agreement->getIsAddbaResponseSent() && !agreement->getIsDelayedBlockAckPolicySupported()); diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h index e1ec8677c86..d74e0ecc962 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h @@ -20,13 +20,12 @@ class INET_API RecipientQosAckPolicy : public ModeSetListener, public IRecipient { protected: IQosRateSelection *rateSelection = nullptr; - bool assumePeerSupportsCompressedBlockAck = false; protected: virtual int numInitStages() const override { return NUM_INIT_STAGES; } virtual void initialize(int stage) override; - static bool isCompressedBlockAckNeeded(const Ptr& blockAckReq, RecipientBlockAckAgreement *agreement, bool assumePeerSupportsCompressedBlockAck); + static bool isCompressedBlockAckNeeded(const Ptr& blockAckReq, RecipientBlockAckAgreement *agreement); simtime_t computeBlockAckDuration(Packet *packet, const Ptr& blockAckReq) const; simtime_t computeAckDuration(Packet *packet, const Ptr& dataOrMgmtHeader) const; diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.ned b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.ned index 02369ccfd24..6fb9639b69e 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.ned +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.ned @@ -18,8 +18,5 @@ simple RecipientQosAckPolicy extends SimpleModule like IRecipientQosAckPolicy parameters: @class(RecipientQosAckPolicy); string rateSelectionModule; - // Set only for an HT-or-later local STA when peer capability management establishes that the peer supports Compressed Block Ack. - // This explicit assumption is needed because peer HT capabilities are not represented by the baseline agreement contract. - bool assumePeerSupportsCompressedBlockAck = default(false); @display("i=block/control"); } diff --git a/tests/module/Ieee80211CompressedBlockAckRuntime.test b/tests/module/Ieee80211CompressedBlockAckRuntime.test index ea7c4dbdc24..8dde5158a5c 100644 --- a/tests/module/Ieee80211CompressedBlockAckRuntime.test +++ b/tests/module/Ieee80211CompressedBlockAckRuntime.test @@ -22,9 +22,9 @@ abstract = false sim-time-limit = 1.05s **.opMode = "n(mixed-2.4Ghz)" -# Baseline capability management is absent, so both endpoint policies explicitly -# opt in to compressed Block Ack; the production default remains false. -**.assumePeerSupportsCompressedBlockAck = true +# The originator explicitly opts in based on its peer-capability assumption; +# the recipient requires no additional station-wide capability setting. +**.originatorAckPolicy.assumePeerSupportsCompressedBlockAck = true **.cmdenv-log-level = info %contains: stdout diff --git a/tests/unit/Ieee80211CompressedBlockAck_1.test b/tests/unit/Ieee80211CompressedBlockAck_1.test index 2fd6b9732b5..8d34b512d95 100644 --- a/tests/unit/Ieee80211CompressedBlockAck_1.test +++ b/tests/unit/Ieee80211CompressedBlockAck_1.test @@ -43,9 +43,9 @@ class TestOriginatorQosAckPolicy : public OriginatorQosAckPolicy class TestRecipientQosAckPolicy : public RecipientQosAckPolicy { public: - static bool isCompressedResponseNeeded(const Ptr& request, RecipientBlockAckAgreement *agreement, bool enabled) + static bool isCompressedResponseNeeded(const Ptr& request, RecipientBlockAckAgreement *agreement) { - return isCompressedBlockAckNeeded(request, agreement, enabled); + return isCompressedBlockAckNeeded(request, agreement); } }; @@ -235,18 +235,18 @@ static Packet *makeOutstandingQosFrame(MacAddress receiverAddress, Tid tid, Frag { auto request = makeCompressedBlockAckReq(); - ASSERT(!TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, nullptr, false)); - ASSERT(TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, nullptr, true)); + ASSERT(TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, nullptr)); request->setFragmentNumber(1); - ASSERT(!TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, nullptr, true)); + ASSERT(!TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, nullptr)); request->setFragmentNumber(0); RecipientBlockAckAgreement delayedAgreement(MacAddress("11:22:33:44:55:66"), 5, SequenceNumberCyclic(100), 64, SIMTIME_ZERO); + ASSERT(!TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, &delayedAgreement)); delayedAgreement.addbaResposneSent(); delayedAgreement.setIsDelayedBlockAckPolicySupported(true); - ASSERT(!TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, &delayedAgreement, true)); + ASSERT(!TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, &delayedAgreement)); delayedAgreement.setIsDelayedBlockAckPolicySupported(false); - ASSERT(TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, &delayedAgreement, true)); - EV << "Recipient capability, fragment, and agreement-policy gates passed.\n"; + ASSERT(TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, &delayedAgreement)); + EV << "Recipient fragment and agreement-policy gates passed.\n"; } { @@ -346,7 +346,7 @@ Established agreement produces the expected 64-bit bitmap. Compressed bitmap preserves leading receive-window holes. Compressed bitmap wraps from sequence 4095 to 0. Compressed bitmap preserves leading holes across sequence wrap. -Recipient capability, fragment, and agreement-policy gates passed. +Recipient fragment and agreement-policy gates passed. Originator capability, agreement, and fragmentation gates passed. Legacy Basic BAR and BA remain byte-exact. Accepted delayed agreement is retained for Basic BAR fallback. From 89030d8347400e3ea437340d9a27c83fb0c2806d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Sun, 16 Aug 2026 11:18:20 +0200 Subject: [PATCH 5/8] fix(ieee80211): correct Block Ack receive-window handling Make BlockAckRecord the authoritative owner of WinStartR and update the scoreboard for every successfully received related Data MPDU. Advance it from newer Data frames and BlockAckReq starting sequence numbers instead of coupling acknowledgment state to upward packet delivery. Fix empty Basic Block Ack records so current-window sequence and fragment entries are reported as unacknowledged while entries older than WinStartR remain acknowledged. Keep the reordering window independent from the acknowledgment window. When a future MPDU or BAR advances the window, deliver complete displaced MSDUs, discard incomplete stale entries, retain packets beyond gaps, and preserve cyclic delivery order across the 4095-to-0 boundary. Detach returned packets before deleting stale receive-buffer state. Replace the station-wide compressed Block Ack assumption with capability state stored per agreement. Derive it from explicit local support and a configured peer-address list until HT Capabilities elements are modeled. Use compressed BAR only for established immediate agreements that support it, and suppress compressed Block Ack responses when no agreement exists. Add focused unit and runtime coverage for Basic and Compressed bitmaps, Data- and BAR-driven window movement, wraparound, gaps, fragments, duplicates, ownership, Normal Ack reception, capability gating, null responses, serialization, and the BAR-to-BA exchange. Multi-TID Block Ack remains unsupported. BA Control bit 0 remains clear because it is reserved by IEEE 802.11-2024, rather than a BA Ack Policy bit. Fingerprint validation found five expected maintained-ingredient changes in QoS and Block Ack scenarios. The three previously retained tyf values were also rechecked and found stale. Fingerprint CSV updates are intentionally not included pending separate approval. --- .../ieee80211/mac/blockack/BlockAckRecord.cc | 28 +- .../ieee80211/mac/blockack/BlockAckRecord.h | 5 +- .../blockack/OriginatorBlockAckAgreement.h | 9 +- .../OriginatorBlockAckAgreementHandler.cc | 12 +- .../OriginatorBlockAckAgreementHandler.h | 5 +- .../OriginatorBlockAckAgreementPolicy.cc | 4 +- .../OriginatorBlockAckAgreementPolicy.h | 4 +- .../OriginatorBlockAckAgreementPolicy.ned | 5 +- .../blockack/RecipientBlockAckAgreement.cc | 8 +- .../mac/blockack/RecipientBlockAckAgreement.h | 5 +- .../blockackreordering/BlockAckReordering.cc | 84 ++-- .../blockackreordering/BlockAckReordering.h | 7 +- .../IOriginatorBlockAckAgreementPolicy.h | 2 +- .../mac/originator/OriginatorQosAckPolicy.cc | 10 +- .../mac/originator/OriginatorQosAckPolicy.h | 3 +- .../mac/originator/OriginatorQosAckPolicy.ned | 3 - .../mac/recipient/RecipientQosAckPolicy.cc | 7 +- .../recipient/RecipientQosMacDataService.cc | 2 +- .../Ieee80211CompressedBlockAckRuntime.test | 9 +- tests/unit/Ieee80211CompressedBlockAck_1.test | 386 +++++++++++++++++- 20 files changed, 492 insertions(+), 106 deletions(-) diff --git a/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc b/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc index 7dce744fdec..14a21493581 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc @@ -19,10 +19,14 @@ BlockAckRecord::BlockAckRecord(MacAddress originatorAddress, Tid tid, SequenceNu { } -void BlockAckRecord::blockAckPolicyFrameReceived(const Ptr& header) +void BlockAckRecord::dataFrameReceived(const Ptr& header, int windowSize) { SequenceNumberCyclic sequenceNumber = header->getSequenceNumber(); FragmentNumber fragmentNumber = header->getFragmentNumber(); + // IEEE Std 802.11-2024, 10.25.6.3(b) and 10.25.6.4(c): a related + // MPDU beyond WinEndR advances the receive window before its bit is set. + if (startingSequenceNumber + windowSize <= sequenceNumber && sequenceNumber < startingSequenceNumber + 2048) + advanceStartingSequenceNumber(sequenceNumber - windowSize + 1); acknowledgmentState[SequenceControlField(sequenceNumber.get(), fragmentNumber)] = true; } @@ -31,16 +35,7 @@ bool BlockAckRecord::getAckState(SequenceNumberCyclic sequenceNumber, FragmentNu // The status of MPDUs that are considered “old” and prior to the sequence number // range for which the receiver maintains status shall be reported as successfully // received (i.e., the corresponding bit in the bitmap shall be set to 1). - if (containsKey(acknowledgmentState, SequenceControlField(sequenceNumber.get(), fragmentNumber))) { - return true; - } - else if (acknowledgmentState.size() == 0) { - return true; // TODO old? - } - else { - auto earliest = acknowledgmentState.begin(); - return SequenceNumberCyclic(earliest->first.getSequenceNumber()) > sequenceNumber; // old = true - } + return containsKey(acknowledgmentState, SequenceControlField(sequenceNumber.get(), fragmentNumber)) || sequenceNumber < startingSequenceNumber; } bool BlockAckRecord::getCompressedAckState(SequenceNumberCyclic sequenceNumber) @@ -50,17 +45,20 @@ bool BlockAckRecord::getCompressedAckState(SequenceNumberCyclic sequenceNumber) return containsKey(acknowledgmentState, SequenceControlField(sequenceNumber.get(), 0)) || sequenceNumber < startingSequenceNumber; } -void BlockAckRecord::removeAckStates(SequenceNumberCyclic sequenceNumber) +void BlockAckRecord::advanceStartingSequenceNumber(SequenceNumberCyclic newStartingSequenceNumber) { + // IEEE Std 802.11-2024, 10.25.6.3 and 10.25.6.4: advance WinStartR + // for a newer related MPDU or BAR SSN, using the 12-bit sequence space. + if (!(startingSequenceNumber < newStartingSequenceNumber)) + return; auto it = acknowledgmentState.begin(); while (it != acknowledgmentState.end()) { - if (SequenceNumberCyclic(it->first.getSequenceNumber()) < sequenceNumber) + if (SequenceNumberCyclic(it->first.getSequenceNumber()) < newStartingSequenceNumber) it = acknowledgmentState.erase(it); else it++; } - if (startingSequenceNumber <= sequenceNumber) - startingSequenceNumber = sequenceNumber + 1; + startingSequenceNumber = newStartingSequenceNumber; } } /* namespace ieee80211 */ diff --git a/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.h b/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.h index 3eb8f9bec46..c4bb3783ceb 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.h @@ -31,13 +31,14 @@ class INET_API BlockAckRecord BlockAckRecord(MacAddress originatorAddress, Tid tid, SequenceNumberCyclic startingSequenceNumber); virtual ~BlockAckRecord() {} - void blockAckPolicyFrameReceived(const Ptr& header); + void dataFrameReceived(const Ptr& header, int windowSize); bool getAckState(SequenceNumberCyclic sequenceNumber, FragmentNumber fragmentNumber); bool getCompressedAckState(SequenceNumberCyclic sequenceNumber); - void removeAckStates(SequenceNumberCyclic sequenceNumber); + void advanceStartingSequenceNumber(SequenceNumberCyclic startingSequenceNumber); MacAddress getOriginatorAddress() { return originatorAddress; } Tid getTid() { return tid; } + SequenceNumberCyclic getStartingSequenceNumber() const { return startingSequenceNumber; } }; } /* namespace ieee80211 */ diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h index a516527bc34..21db3ce5885 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h @@ -25,19 +25,21 @@ class INET_API OriginatorBlockAckAgreement : public cObject int bufferSize = -1; bool isAMsduSupported = false; bool isDelayedBlockAckPolicySupported = false; + bool isCompressedBlockAckSupported = false; bool isAddbaResponseReceived = false; bool isAddbaRequestSent = false; 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, bool isCompressedBlockAckSupported = false) : receiverAddr(receiverAddr), tid(tid), startingSequenceNumber(startingSequenceNumber), bufferSize(bufferSize), isAMsduSupported(isAMsduSupported), - isDelayedBlockAckPolicySupported(isDelayedBlockAckPolicySupported) + isDelayedBlockAckPolicySupported(isDelayedBlockAckPolicySupported), + isCompressedBlockAckSupported(isCompressedBlockAckSupported) { } @@ -50,6 +52,7 @@ class INET_API OriginatorBlockAckAgreement : public cObject virtual bool getIsAddbaRequestSent() const { return isAddbaRequestSent; } virtual bool getIsAMsduSupported() const { return isAMsduSupported; } virtual bool getIsDelayedBlockAckPolicySupported() const { return isDelayedBlockAckPolicySupported; } + virtual bool getIsCompressedBlockAckSupported() const { return isCompressedBlockAckSupported; } virtual MacAddress getReceiverAddr() const { return receiverAddr; } virtual Tid getTid() const { return tid; } virtual const simtime_t getBlockAckTimeoutValue() const { return blockAckTimeoutValue; } @@ -60,6 +63,7 @@ class INET_API OriginatorBlockAckAgreement : public cObject 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 setIsCompressedBlockAckSupported(bool isCompressedBlockAckSupported) { this->isCompressedBlockAckSupported = isCompressedBlockAckSupported; } virtual void setBlockAckTimeoutValue(const simtime_t blockAckTimeoutValue) { this->blockAckTimeoutValue = blockAckTimeoutValue; } virtual void baPolicyFrameSent() { numSentBaPolicyFrames++; } @@ -71,4 +75,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 bab4a75ce29..4da5ffd4335 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc @@ -12,9 +12,10 @@ namespace inet { namespace ieee80211 { -void OriginatorBlockAckAgreementHandler::createAgreement(const Ptr& addbaRequest) +void OriginatorBlockAckAgreementHandler::createAgreement(const Ptr& addbaRequest, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy) { - OriginatorBlockAckAgreement *blockAckAgreement = new OriginatorBlockAckAgreement(addbaRequest->getReceiverAddress(), addbaRequest->getTid(), addbaRequest->getStartingSequenceNumber(), addbaRequest->getBufferSize(), addbaRequest->getAMsduSupported(), addbaRequest->getBlockAckPolicy() == 0); + bool isCompressedBlockAckSupported = blockAckAgreementPolicy->isPeerCompressedBlockAckSupported(addbaRequest->getReceiverAddress()); + OriginatorBlockAckAgreement *blockAckAgreement = new OriginatorBlockAckAgreement(addbaRequest->getReceiverAddress(), addbaRequest->getTid(), addbaRequest->getStartingSequenceNumber(), addbaRequest->getBufferSize(), addbaRequest->getAMsduSupported(), addbaRequest->getBlockAckPolicy() == 0, isCompressedBlockAckSupported); auto agreementId = std::make_pair(addbaRequest->getReceiverAddress(), addbaRequest->getTid()); blockAckAgreements[agreementId] = blockAckAgreement; } @@ -134,7 +135,7 @@ void OriginatorBlockAckAgreementHandler::processTransmittedDataFrame(Packet *pac 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); + createAgreement(addbaReq, blockAckAgreementPolicy); auto addbaPacket = new Packet("AddbaReq", addbaReq); callback->processMgmtFrame(addbaPacket, addbaReq); } @@ -144,7 +145,7 @@ void OriginatorBlockAckAgreementHandler::processReceivedAddbaResp(const PtrgetTransmitterAddress(), addbaResp->getTid()); if (blockAckAgreementPolicy->isAddbaReqAccepted(addbaResp, agreement)) { - updateAgreement(agreement, addbaResp); + updateAgreement(agreement, addbaResp, blockAckAgreementPolicy); scheduleInactivityTimer(callback); } else { @@ -152,12 +153,13 @@ void OriginatorBlockAckAgreementHandler::processReceivedAddbaResp(const Ptr& addbaResp) +void OriginatorBlockAckAgreementHandler::updateAgreement(OriginatorBlockAckAgreement *agreement, const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy) { agreement->setIsAddbaResponseReceived(true); agreement->setIsDelayedBlockAckPolicySupported(addbaResp->getBlockAckPolicy() == 0); agreement->setBufferSize(addbaResp->getBufferSize()); agreement->setBlockAckTimeoutValue(addbaResp->getBlockAckTimeoutValue()); + agreement->setIsCompressedBlockAckSupported(blockAckAgreementPolicy->isPeerCompressedBlockAckSupported(addbaResp->getTransmitterAddress())); agreement->calculateExpirationTime(); } diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h index 4a449d17da3..953da7b0bbb 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h @@ -24,8 +24,8 @@ class INET_API OriginatorBlockAckAgreementHandler : public IOriginatorBlockAckAg protected: virtual const Ptr buildAddbaRequest(MacAddress receiverAddr, Tid tid, SequenceNumberCyclic startingSequenceNumber, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy); - virtual void createAgreement(const Ptr& addbaRequest); - virtual void updateAgreement(OriginatorBlockAckAgreement *agreement, const Ptr& addbaResp); + virtual void createAgreement(const Ptr& addbaRequest, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy); + virtual void updateAgreement(OriginatorBlockAckAgreement *agreement, const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy); virtual void terminateAgreement(MacAddress originatorAddr, Tid tid); virtual const Ptr buildDelba(MacAddress receiverAddr, Tid tid, int reasonCode); virtual simtime_t computeEarliestExpirationTime(); @@ -48,4 +48,3 @@ class INET_API OriginatorBlockAckAgreementHandler : public IOriginatorBlockAckAg } // 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..c5e2f56c743 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc @@ -24,6 +24,9 @@ void OriginatorBlockAckAgreementPolicy::initialize(int stage) aMsduSupported = par("aMsduSupported"); maximumAllowedBufferSize = par("maximumAllowedBufferSize"); blockAckTimeoutValue = par("blockAckTimeoutValue"); + localCompressedBlockAckSupported = par("localCompressedBlockAckSupported"); + for (const auto& address : cStringTokenizer(par("compressedBlockAckPeerAddresses")).asVector()) + compressedBlockAckPeerAddresses.insert(MacAddress(address.c_str())); // TODO addbaFailureTimeout = par("addbaFailureTimeout"); WATCH(blockAckReqThreshold); } @@ -53,4 +56,3 @@ bool OriginatorBlockAckAgreementPolicy::isDelbaAccepted(const Ptr compressedBlockAckPeerAddresses; protected: virtual int numInitStages() const override { return NUM_INIT_STAGES; } @@ -41,6 +43,7 @@ class INET_API OriginatorBlockAckAgreementPolicy : public ModeSetListener, publi virtual bool isMsduSupported() const override { return aMsduSupported; } virtual simtime_t getBlockAckTimeoutValue() const override { return blockAckTimeoutValue; } virtual bool isDelayedAckPolicySupported() const override { return delayedAckPolicySupported; } + virtual bool isPeerCompressedBlockAckSupported(const MacAddress& peerAddress) const override { return localCompressedBlockAckSupported && compressedBlockAckPeerAddresses.find(peerAddress) != compressedBlockAckPeerAddresses.end(); } virtual int getMaximumAllowedBufferSize() const override { return maximumAllowedBufferSize; } }; @@ -48,4 +51,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 bbb569b4b20..6447913d774 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.ned +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.ned @@ -23,6 +23,9 @@ 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 + // Interim local HT Compressed Block Ack capability assumption until HT Capabilities IE state is modeled. + bool localCompressedBlockAckSupported = default(false); + // Explicit peers whose capability state permits HT Compressed Block Ack; empty keeps legacy Basic Block Ack behavior. + string compressedBlockAckPeerAddresses = default(""); @display("i=block/control"); } - diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.cc b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.cc index 38e4eea2944..62cc573194b 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.cc @@ -13,7 +13,6 @@ namespace inet { namespace ieee80211 { RecipientBlockAckAgreement::RecipientBlockAckAgreement(MacAddress originatorAddress, Tid tid, SequenceNumberCyclic startingSequenceNumber, int bufferSize, simtime_t lastUsedTime) : - startingSequenceNumber(startingSequenceNumber), bufferSize(bufferSize), blockAckTimeoutValue(lastUsedTime) { @@ -21,17 +20,16 @@ RecipientBlockAckAgreement::RecipientBlockAckAgreement(MacAddress originatorAddr blockAckRecord = new BlockAckRecord(originatorAddress, tid, startingSequenceNumber); } -void RecipientBlockAckAgreement::blockAckPolicyFrameReceived(const Ptr& header) +void RecipientBlockAckAgreement::dataFrameReceived(const Ptr& header) { - ASSERT(header->getAckPolicy() == BLOCK_ACK); - blockAckRecord->blockAckPolicyFrameReceived(header); + blockAckRecord->dataFrameReceived(header, bufferSize); } std::ostream& operator<<(std::ostream& os, const RecipientBlockAckAgreement& agreement) { os << "originator address = " << agreement.blockAckRecord->getOriginatorAddress() << ", " << "tid = " << agreement.blockAckRecord->getTid() << ", " - << "starting sequence number = " << agreement.startingSequenceNumber << ", " + << "starting sequence number = " << agreement.getStartingSequenceNumber() << ", " << "buffer size = " << agreement.bufferSize << ", " << "block ack timeout value = " << agreement.blockAckTimeoutValue; return os; diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h index cdb41395dbf..2bfc94b040b 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h @@ -18,7 +18,6 @@ class INET_API RecipientBlockAckAgreement : public cObject protected: BlockAckRecord *blockAckRecord = nullptr; - SequenceNumberCyclic startingSequenceNumber; int bufferSize = -1; simtime_t blockAckTimeoutValue = 0; bool isAddbaResponseSent = false; @@ -29,12 +28,12 @@ class INET_API RecipientBlockAckAgreement : public cObject RecipientBlockAckAgreement(MacAddress originatorAddress, Tid tid, SequenceNumberCyclic startingSequenceNumber, int bufferSize, simtime_t blockAckTimeoutValue); virtual ~RecipientBlockAckAgreement() { delete blockAckRecord; } - virtual void blockAckPolicyFrameReceived(const Ptr& header); + virtual void dataFrameReceived(const Ptr& header); virtual BlockAckRecord *getBlockAckRecord() const { return blockAckRecord; } virtual simtime_t getBlockAckTimeoutValue() const { return blockAckTimeoutValue; } virtual int getBufferSize() const { return bufferSize; } - virtual SequenceNumberCyclic getStartingSequenceNumber() const { return startingSequenceNumber; } + virtual SequenceNumberCyclic getStartingSequenceNumber() const { return blockAckRecord->getStartingSequenceNumber(); } virtual bool getIsAddbaResponseSent() const { return isAddbaResponseSent; } virtual bool getIsDelayedBlockAckPolicySupported() const { return isDelayedBlockAckPolicySupported; } diff --git a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc index 224c8b2b1e5..10587411f2f 100644 --- a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc +++ b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc @@ -18,34 +18,54 @@ namespace ieee80211 { BlockAckReordering::ReorderBuffer BlockAckReordering::processReceivedQoSFrame(RecipientBlockAckAgreement *agreement, Packet *dataPacket, const Ptr& dataHeader) { ReceiveBuffer *receiveBuffer = createReceiveBufferIfNecessary(agreement); + ReorderBuffer framesToPassUp; + auto sequenceNumber = dataHeader->getSequenceNumber(); + auto startingSequenceNumber = receiveBuffer->getNextExpectedSequenceNumber(); + bool advancesWindow = startingSequenceNumber + receiveBuffer->getBufferSize() <= sequenceNumber && sequenceNumber < startingSequenceNumber + 2048; + if (advancesWindow) { + // IEEE Std 802.11-2024, 10.25.6.6.2.1(b): move WinStartB so the + // future MPDU fits, preserving complete displaced MSDUs for delivery. + auto newStartingSequenceNumber = sequenceNumber - receiveBuffer->getBufferSize() + 1; + framesToPassUp = collectCompletePrecedingMpdus(receiveBuffer, newStartingSequenceNumber); + for (const auto& entry : framesToPassUp) + receiveBuffer->removeFrame(SequenceNumberCyclic(entry.first)); + // Any remaining displaced entries are incomplete and cannot be delivered. + receiveBuffer->dropFramesUntil(newStartingSequenceNumber); + receiveBuffer->setNextExpectedSequenceNumber(newStartingSequenceNumber); + } // The reception of QoS data frames using Normal Ack policy shall not be used by the // recipient to reset the timer to detect Block Ack timeout (see 10.5.4). // This allows the recipient to delete the Block Ack if the originator does not switch // back to using Block Ack. if (receiveBuffer->insertFrame(dataPacket, dataHeader)) { - if (dataHeader->getAckPolicy() == BLOCK_ACK) - agreement->blockAckPolicyFrameReceived(dataHeader); + agreement->dataFrameReceived(dataHeader); + if (advancesWindow) { + auto consecutiveCompleteMpdus = collectConsecutiveCompleteFollowingMpdus(receiveBuffer, receiveBuffer->getNextExpectedSequenceNumber()); + releaseReceiveBuffer(receiveBuffer, consecutiveCompleteMpdus); + framesToPassUp.insert(framesToPassUp.end(), consecutiveCompleteMpdus.begin(), consecutiveCompleteMpdus.end()); + return framesToPassUp; + } auto earliestCompleteMsduOrAMsdu = getEarliestCompleteMsduOrAMsduIfExists(receiveBuffer); if (earliestCompleteMsduOrAMsdu.size() > 0) { auto earliestSequenceNumber = earliestCompleteMsduOrAMsdu.at(0)->peekAtFront()->getSequenceNumber(); // If, after an MPDU is received, the receive buffer is full, the complete MSDU or A-MSDU with the earliest // sequence number shall be passed up to the next MAC process. if (receiveBuffer->isFull()) { - passedUp(agreement, receiveBuffer, earliestSequenceNumber); + passedUp(receiveBuffer, earliestSequenceNumber); return ReorderBuffer({ std::make_pair(earliestSequenceNumber.get(), Fragments(earliestCompleteMsduOrAMsdu)) }); } // If, after an MPDU is received, the receive buffer is not full, but the sequence number of the complete MSDU or // A-MSDU in the buffer with the lowest sequence number is equal to the NextExpectedSequenceNumber for // that Block Ack agreement, then the MPDU shall be passed up to the next MAC process. else if (earliestSequenceNumber == receiveBuffer->getNextExpectedSequenceNumber()) { - passedUp(agreement, receiveBuffer, earliestSequenceNumber); + passedUp(receiveBuffer, earliestSequenceNumber); return ReorderBuffer({ std::make_pair(earliestSequenceNumber.get(), Fragments(earliestCompleteMsduOrAMsdu)) }); } } } else delete dataPacket; - return ReorderBuffer({}); + return framesToPassUp; } // @@ -68,6 +88,9 @@ BlockAckReordering::ReorderBuffer BlockAckReordering::processReceivedBlockAckReq else { throw cRuntimeError("Multi-Tid BlockAckReq is currently an unimplemented feature"); } + // IEEE Std 802.11-2024, 10.25.6.3-10.25.6.5: adjust WinStartR + // from the BAR before generating the response, even without a receive buffer. + agreement->getBlockAckRecord()->advanceStartingSequenceNumber(startingSequenceNumber); auto id = std::make_pair(tid, blockAckReq->getTransmitterAddress()); auto it = receiveBuffers.find(id); if (it != receiveBuffers.end()) { @@ -81,19 +104,17 @@ BlockAckReordering::ReorderBuffer BlockAckReordering::processReceivedBlockAckReq // the starting sequence number sequentially until there is an incomplete or missing MSDU // or A-MSDU in the buffer. auto consecutiveCompleteFollowingMpdus = collectConsecutiveCompleteFollowingMpdus(receiveBuffer, startingSequenceNumber); - // If no MSDUs or A-MSDUs are passed up to the next MAC process after the receipt - // of the BlockAckReq frame and the starting sequence number of the BlockAckReq frame is newer than the - // NextExpectedSequenceNumber for that Block Ack agreement, then the NextExpectedSequenceNumber for - // that Block Ack agreement is set to the sequence number of the BlockAckReq frame. - int numOfMsdusToPassUp = completePrecedingMpdus.size() + consecutiveCompleteFollowingMpdus.size(); - if (numOfMsdusToPassUp == 0 && receiveBuffer->getNextExpectedSequenceNumber() < startingSequenceNumber) - receiveBuffer->setNextExpectedSequenceNumber(startingSequenceNumber); - // The recipient shall then release any buffers held by preceding MPDUs. - releaseReceiveBuffer(agreement, receiveBuffer, completePrecedingMpdus); - releaseReceiveBuffer(agreement, receiveBuffer, consecutiveCompleteFollowingMpdus); // The recipient shall pass MSDUs and A-MSDUs up to the next MAC process in order of increasing sequence // number. - completePrecedingMpdus.insert(consecutiveCompleteFollowingMpdus.begin(), consecutiveCompleteFollowingMpdus.end()); + completePrecedingMpdus.insert(completePrecedingMpdus.end(), consecutiveCompleteFollowingMpdus.begin(), consecutiveCompleteFollowingMpdus.end()); + // Detach all packets being returned before releasing stale buffered entries. + releaseReceiveBuffer(receiveBuffer, completePrecedingMpdus); + // Release any remaining buffers held by incomplete preceding MPDUs, then + // advance NextExpectedSequenceNumber to at least the BAR SSN without + // regressing it past consecutively released MSDUs. + receiveBuffer->dropFramesUntil(startingSequenceNumber); + if (receiveBuffer->getNextExpectedSequenceNumber() < startingSequenceNumber) + receiveBuffer->setNextExpectedSequenceNumber(startingSequenceNumber); return completePrecedingMpdus; } return ReorderBuffer(); @@ -108,12 +129,14 @@ BlockAckReordering::ReorderBuffer BlockAckReordering::collectCompletePrecedingMp { ReorderBuffer completePrecedingMpdus; const auto& buffer = receiveBuffer->getBuffer(); - for (auto it : buffer) { // collects complete preceding MPDUs - auto sequenceNumber = it.first; - auto fragments = it.second; - if (SequenceNumberCyclic(sequenceNumber) < startingSequenceNumber) - if (isComplete(fragments)) - completePrecedingMpdus[sequenceNumber] = fragments; + auto currentStartingSequenceNumber = receiveBuffer->getNextExpectedSequenceNumber(); + for (int i = 0; i < receiveBuffer->getBufferSize(); i++) { + auto sequenceNumber = currentStartingSequenceNumber + i; + if (!(sequenceNumber < startingSequenceNumber)) + break; + auto it = buffer.find(sequenceNumber.get()); + if (it != buffer.end() && isComplete(it->second)) + completePrecedingMpdus.push_back(std::make_pair(sequenceNumber.get(), it->second)); } return completePrecedingMpdus; } @@ -140,18 +163,22 @@ bool BlockAckReordering::addMsduIfComplete(ReceiveBuffer *receiveBuffer, Reorder if (it != buffer.end()) { auto fragments = it->second; if (isComplete(fragments)) { - reorderBuffer[seqNum.get()] = fragments; + reorderBuffer.push_back(std::make_pair(seqNum.get(), fragments)); return true; } } return false; } -void BlockAckReordering::releaseReceiveBuffer(RecipientBlockAckAgreement *agreement, ReceiveBuffer *receiveBuffer, const ReorderBuffer& reorderBuffer) +void BlockAckReordering::releaseReceiveBuffer(ReceiveBuffer *receiveBuffer, const ReorderBuffer& reorderBuffer) { - for (auto it : reorderBuffer) { - auto sequenceNumber = it.first; - passedUp(agreement, receiveBuffer, SequenceNumberCyclic(sequenceNumber)); + // Detach all packets whose ownership is returned before stale-buffer cleanup. + for (const auto& entry : reorderBuffer) + receiveBuffer->removeFrame(SequenceNumberCyclic(entry.first)); + for (const auto& entry : reorderBuffer) { + auto sequenceNumber = entry.first; + receiveBuffer->setNextExpectedSequenceNumber(SequenceNumberCyclic(sequenceNumber) + 1); + receiveBuffer->dropFramesUntil(SequenceNumberCyclic(sequenceNumber)); } } @@ -199,7 +226,7 @@ void BlockAckReordering::processReceivedDelba(const Ptr& d EV_DETAIL << "Receive buffer is not found" << endl; } -void BlockAckReordering::passedUp(RecipientBlockAckAgreement *agreement, ReceiveBuffer *receiveBuffer, SequenceNumberCyclic sequenceNumber) +void BlockAckReordering::passedUp(ReceiveBuffer *receiveBuffer, SequenceNumberCyclic sequenceNumber) { // Each time that the recipient passes an MSDU or A-MSDU for a Block Ack agreement up to the next MAC // process, the NextExpectedSequenceNumber for that Block Ack agreement is set to the sequence number of the @@ -207,7 +234,6 @@ void BlockAckReordering::passedUp(RecipientBlockAckAgreement *agreement, Receive receiveBuffer->setNextExpectedSequenceNumber(sequenceNumber + 1); receiveBuffer->dropFramesUntil(sequenceNumber); receiveBuffer->removeFrame(sequenceNumber); - agreement->getBlockAckRecord()->removeAckStates(sequenceNumber); } std::vector BlockAckReordering::getEarliestCompleteMsduOrAMsduIfExists(ReceiveBuffer *receiveBuffer) diff --git a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h index a50606f8a53..2d199c48cb4 100644 --- a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h +++ b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h @@ -24,7 +24,7 @@ class INET_API BlockAckReordering { public: typedef std::vector Fragments; - typedef std::map ReorderBuffer; + typedef std::vector> ReorderBuffer; protected: std::map, ReceiveBuffer *> receiveBuffers; @@ -35,8 +35,8 @@ class INET_API BlockAckReordering std::vector getEarliestCompleteMsduOrAMsduIfExists(ReceiveBuffer *receiveBuffer); bool isComplete(const Fragments& fragments); - void passedUp(RecipientBlockAckAgreement *agreement, ReceiveBuffer *receiveBuffer, SequenceNumberCyclic sequenceNumber); - void releaseReceiveBuffer(RecipientBlockAckAgreement *agreement, ReceiveBuffer *receiveBuffer, const ReorderBuffer& reorderBuffer); + void passedUp(ReceiveBuffer *receiveBuffer, SequenceNumberCyclic sequenceNumber); + void releaseReceiveBuffer(ReceiveBuffer *receiveBuffer, const ReorderBuffer& reorderBuffer); ReceiveBuffer *createReceiveBufferIfNecessary(RecipientBlockAckAgreement *agreement); bool addMsduIfComplete(ReceiveBuffer *receiveBuffer, ReorderBuffer& reorderBuffer, SequenceNumberCyclic seqNum); @@ -52,4 +52,3 @@ class INET_API BlockAckReordering } /* namespace inet */ #endif - diff --git a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementPolicy.h b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementPolicy.h index 40ea82aa95e..3ee985a3ab3 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementPolicy.h @@ -29,6 +29,7 @@ class INET_API IOriginatorBlockAckAgreementPolicy virtual simtime_t computeAddbaFailureTimeout() const = 0; virtual simtime_t getBlockAckTimeoutValue() const = 0; virtual bool isDelayedAckPolicySupported() const = 0; + virtual bool isPeerCompressedBlockAckSupported(const MacAddress& peerAddress) 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/originator/OriginatorQosAckPolicy.cc b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.cc index 92bf70024ec..3c0c29c05bc 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.cc @@ -21,7 +21,6 @@ void OriginatorQosAckPolicy::initialize(int stage) rateSelection = check_and_cast(getModuleByPath(par("rateSelectionModule"))); maxBlockAckPolicyFrameLength = par("maxBlockAckPolicyFrameLength"); blockAckReqThreshold = par("blockAckReqThreshold"); - assumePeerSupportsCompressedBlockAck = par("assumePeerSupportsCompressedBlockAck"); blockAckTimeout = par("blockAckTimeout"); ackTimeout = par("ackTimeout"); } @@ -55,16 +54,15 @@ SequenceNumberCyclic OriginatorQosAckPolicy::computeStartingSequenceNumber(const bool OriginatorQosAckPolicy::isCompressedBlockAckReq(const std::vector& outstandingFrames, OriginatorBlockAckAgreement *agreement) const { - return isCompressedBlockAckReqNeeded(outstandingFrames, agreement, assumePeerSupportsCompressedBlockAck); + return isCompressedBlockAckReqNeeded(outstandingFrames, agreement); } -bool OriginatorQosAckPolicy::isCompressedBlockAckReqNeeded(const std::vector& outstandingFrames, OriginatorBlockAckAgreement *agreement, bool assumePeerSupportsCompressedBlockAck) +bool OriginatorQosAckPolicy::isCompressedBlockAckReqNeeded(const std::vector& outstandingFrames, OriginatorBlockAckAgreement *agreement) { // IEEE Std 802.11-2024, Table 11-8 and 10.25.6.1: use the compressed // variant only for an established immediate HT Block Ack agreement. - // Peer HT capability is not represented by the baseline agreement contract; - // the parameter is an explicit assumption supplied by the configuration. - if (!assumePeerSupportsCompressedBlockAck || agreement == nullptr || !agreement->getIsAddbaResponseReceived() || agreement->getIsDelayedBlockAckPolicySupported()) + // The agreement snapshots peer capability state when it is established. + if (agreement == nullptr || !agreement->getIsCompressedBlockAckSupported() || !agreement->getIsAddbaResponseReceived() || agreement->getIsDelayedBlockAckPolicySupported()) return false; bool hasMatchingOutstandingFrame = false; for (auto frame : outstandingFrames) { diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.h b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.h index aa81ee710d6..a7a908818aa 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.h @@ -22,7 +22,6 @@ class INET_API OriginatorQosAckPolicy : public ModeSetListener, public IOriginat IQosRateSelection *rateSelection = nullptr; int maxBlockAckPolicyFrameLength = -1; int blockAckReqThreshold = -1; - bool assumePeerSupportsCompressedBlockAck = false; simtime_t blockAckTimeout = -1; simtime_t ackTimeout = -1; @@ -34,7 +33,7 @@ class INET_API OriginatorQosAckPolicy : public ModeSetListener, public IOriginat virtual bool checkAgreementPolicy(const Ptr& header, OriginatorBlockAckAgreement *agreement) const; virtual std::map> getOutstandingFramesPerReceiver(InProgressFrames *inProgressFrames) const; virtual SequenceNumberCyclic computeStartingSequenceNumber(const std::vector& outstandingFrames) const; - static bool isCompressedBlockAckReqNeeded(const std::vector& outstandingFrames, OriginatorBlockAckAgreement *agreement, bool assumePeerSupportsCompressedBlockAck); + static bool isCompressedBlockAckReqNeeded(const std::vector& outstandingFrames, OriginatorBlockAckAgreement *agreement); public: virtual bool isAckNeeded(const Ptr& header) const override; virtual AckPolicy computeAckPolicy(Packet *packet, const Ptr& header, OriginatorBlockAckAgreement *agreement) const override; diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.ned b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.ned index 6575d5290b2..ea9835be26a 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.ned +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.ned @@ -21,9 +21,6 @@ simple OriginatorQosAckPolicy extends SimpleModule like IOriginatorQosAckPolicy int blockAckReqThreshold = default(5); int maxBlockAckPolicyFrameLength @unit(B) = default(1000B); - // Set only for an HT-or-later local STA when peer capability management establishes that the peer supports Compressed Block Ack. - // This explicit assumption is needed because peer HT capabilities are not represented by the baseline agreement contract. - bool assumePeerSupportsCompressedBlockAck = default(false); double blockAckTimeout @unit(s) = default(-1s); double ackTimeout @unit(s) = default(-1s); diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc index 4c918c5995b..e0219068982 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc @@ -75,12 +75,11 @@ bool RecipientQosAckPolicy::isBlockAckNeeded(const Ptr& blockAckReq, RecipientBlockAckAgreement *agreement) { - // IEEE Std 802.11-2024, 9.3.1.7.2 and 10.25.6.5: an addressed, syntactically - // valid Compressed BlockAckReq elicits a Compressed BlockAck, including a null response. + // IEEE Std 802.11-2024, 10.25.6.4 and 10.25.6.5: a null response + // requires an established HT-immediate agreement whose partial state is absent. if (blockAckReq->getFragmentNumber() != 0) return false; - // A missing partial state still elicits the mandatory null compressed BA. - return agreement == nullptr || (agreement->getIsAddbaResponseSent() && !agreement->getIsDelayedBlockAckPolicySupported()); + return agreement != nullptr && agreement->getIsAddbaResponseSent() && !agreement->getIsDelayedBlockAckPolicySupported(); } // diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc index 3fd9f29682b..544c83cb3aa 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc @@ -67,7 +67,7 @@ std::vector RecipientQosMacDataService::dataFrameReceived(Packet *data return std::vector(); } BlockAckReordering::ReorderBuffer frames; - frames[dataHeader->getSequenceNumber().get()].push_back(dataPacket); + frames.push_back(std::make_pair(dataHeader->getSequenceNumber().get(), BlockAckReordering::Fragments({dataPacket}))); if (blockAckReordering && blockAckAgreementHandler) { Tid tid = dataHeader->getTid(); MacAddress originatorAddr = dataHeader->getTransmitterAddress(); diff --git a/tests/module/Ieee80211CompressedBlockAckRuntime.test b/tests/module/Ieee80211CompressedBlockAckRuntime.test index 8dde5158a5c..039df324eb4 100644 --- a/tests/module/Ieee80211CompressedBlockAckRuntime.test +++ b/tests/module/Ieee80211CompressedBlockAckRuntime.test @@ -22,9 +22,12 @@ abstract = false sim-time-limit = 1.05s **.opMode = "n(mixed-2.4Ghz)" -# The originator explicitly opts in based on its peer-capability assumption; -# the recipient requires no additional station-wide capability setting. -**.originatorAckPolicy.assumePeerSupportsCompressedBlockAck = true +**.originatorBlockAckAgreementPolicy.localCompressedBlockAckSupported = true +*.cliHost.wlan[*].address = "10:00:00:00:00:01" +*.srvHost.wlan[*].address = "10:00:00:00:00:02" +*.cliHost.wlan[*].mac.hcf.originatorBlockAckAgreementPolicy.compressedBlockAckPeerAddresses = "10:00:00:00:00:00" +*.ap.wlan[*].mac.hcf.originatorBlockAckAgreementPolicy.compressedBlockAckPeerAddresses = "10:00:00:00:00:01 10:00:00:00:00:02" +*.srvHost.wlan[*].mac.hcf.originatorBlockAckAgreementPolicy.compressedBlockAckPeerAddresses = "10:00:00:00:00:00" **.cmdenv-log-level = info %contains: stdout diff --git a/tests/unit/Ieee80211CompressedBlockAck_1.test b/tests/unit/Ieee80211CompressedBlockAck_1.test index 8d34b512d95..e0bbc85340e 100644 --- a/tests/unit/Ieee80211CompressedBlockAck_1.test +++ b/tests/unit/Ieee80211CompressedBlockAck_1.test @@ -5,10 +5,13 @@ IEEE Std 802.11-2024, 9.3.1.7.2, 9.3.1.8.2, 10.25.6.1, and 10.25.6.5. %includes: #include "inet/common/packet/Packet.h" #include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" +#include "inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.h" #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h" #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h" +#include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.h" #include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h" #include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckProcedure.h" +#include "inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h" #include "inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.h" #include "inet/linklayer/ieee80211/mac/originator/TxopProcedure.h" #include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" @@ -28,15 +31,50 @@ class TestRecipientBlockAckProcedure : public RecipientBlockAckProcedure class TestOriginatorBlockAckAgreementHandler : public OriginatorBlockAckAgreementHandler { public: + using OriginatorBlockAckAgreementHandler::createAgreement; using OriginatorBlockAckAgreementHandler::updateAgreement; }; +class TestOriginatorBlockAckAgreementPolicy : public OriginatorBlockAckAgreementPolicy +{ + public: + void configureCompressedBlockAckCapability(bool localSupport, std::initializer_list peerAddresses) + { + localCompressedBlockAckSupported = localSupport; + compressedBlockAckPeerAddresses = std::set(peerAddresses); + } +}; + +class TestBlockAckRecord : public BlockAckRecord +{ + public: + TestBlockAckRecord(MacAddress originatorAddress, Tid tid, SequenceNumberCyclic startingSequenceNumber) : + BlockAckRecord(originatorAddress, tid, startingSequenceNumber) + { + } + + bool hasRecordedAckState(SequenceNumberCyclic sequenceNumber, FragmentNumber fragmentNumber) const + { + return acknowledgmentState.find(SequenceControlField(sequenceNumber.get(), fragmentNumber)) != acknowledgmentState.end(); + } +}; + +class TestBlockAckReordering : public BlockAckReordering +{ + public: + ReceiveBuffer *getReceiveBuffer(Tid tid, const MacAddress& originatorAddress) const + { + auto it = receiveBuffers.find(std::make_pair(tid, originatorAddress)); + return it == receiveBuffers.end() ? nullptr : it->second; + } +}; + class TestOriginatorQosAckPolicy : public OriginatorQosAckPolicy { public: - static bool isCompressedRequestNeeded(const std::vector& outstandingFrames, OriginatorBlockAckAgreement *agreement, bool assumePeerSupport) + static bool isCompressedRequestNeeded(const std::vector& outstandingFrames, OriginatorBlockAckAgreement *agreement) { - return isCompressedBlockAckReqNeeded(outstandingFrames, agreement, assumePeerSupport); + return isCompressedBlockAckReqNeeded(outstandingFrames, agreement); } }; @@ -91,6 +129,18 @@ static Packet *makeOutstandingQosFrame(MacAddress receiverAddress, Tid tid, Frag return new Packet("outstanding", header); } +static std::pair> makeReceivedQosFrame(SequenceNumber sequenceNumber, FragmentNumber fragmentNumber = 0, bool moreFragments = false, AckPolicy ackPolicy = BLOCK_ACK) +{ + auto header = makeShared(); + header->setType(ST_DATA_WITH_QOS); + header->setAckPolicy(ackPolicy); + header->setTid(5); + header->setSequenceNumber(SequenceNumberCyclic(sequenceNumber)); + header->setFragmentNumber(fragmentNumber); + header->setMoreFragments(moreFragments); + return std::make_pair(new Packet("received", header), header); +} + %activity: { auto request = makeCompressedBlockAckReq(); @@ -145,14 +195,81 @@ static Packet *makeOutstandingQosFrame(MacAddress receiverAddress, Tid tid, Frag { TestRecipientBlockAckProcedure procedure; RecipientBlockAckAgreement emptyAgreement(MacAddress("11:22:33:44:55:66"), 5, SequenceNumberCyclic(100), 64, SIMTIME_ZERO); + emptyAgreement.addbaResposneSent(); auto request = makeCompressedBlockAckReq(); request->setStartingSequenceNumber(SequenceNumberCyclic(100)); + ASSERT(TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, &emptyAgreement)); auto response = dynamicPtrCast(procedure.buildBlockAck(request, &emptyAgreement)); for (int i = 0; i < 64; i++) ASSERT(!response->getBlockAckBitmap().getBit(i)); EV << "Empty recipient record produces a null compressed BA.\n"; } +{ + TestRecipientBlockAckProcedure procedure; + RecipientBlockAckAgreement agreement(MacAddress("11:22:33:44:55:66"), 5, SequenceNumberCyclic(100), 64, SIMTIME_ZERO); + auto request = makeShared(); + request->setTransmitterAddress(MacAddress("11:22:33:44:55:66")); + request->setTidInfo(5); + request->setStartingSequenceNumber(SequenceNumberCyclic(100)); + auto response = dynamicPtrCast(procedure.buildBlockAck(request, &agreement)); + for (int i = 0; i < 64; i++) + for (FragmentNumber fragmentNumber = 0; fragmentNumber < 16; fragmentNumber++) + ASSERT(!response->getBlockAckBitmap(i).getBit(fragmentNumber)); + agreement.getBlockAckRecord()->advanceStartingSequenceNumber(SequenceNumberCyclic(101)); + response = dynamicPtrCast(procedure.buildBlockAck(request, &agreement)); + for (FragmentNumber fragmentNumber = 0; fragmentNumber < 16; fragmentNumber++) + ASSERT(response->getBlockAckBitmap(0).getBit(fragmentNumber)); + for (int i = 1; i < 64; i++) + for (FragmentNumber fragmentNumber = 0; fragmentNumber < 16; fragmentNumber++) + ASSERT(!response->getBlockAckBitmap(i).getBit(fragmentNumber)); + EV << "Empty Basic BA distinguishes old and current-window MPDUs.\n"; +} + +{ + TestRecipientBlockAckProcedure procedure; + for (int startingSequenceNumber : {1985, 1986}) { + BlockAckReordering reordering; + RecipientBlockAckAgreement agreement(MacAddress("11:22:33:44:55:66"), 5, SequenceNumberCyclic(0), 64, SIMTIME_ZERO); + auto request = makeCompressedBlockAckReq(); + request->setStartingSequenceNumber(SequenceNumberCyclic(startingSequenceNumber)); + reordering.processReceivedBlockAckReq(&agreement, request); + ASSERT(agreement.getStartingSequenceNumber() == SequenceNumberCyclic(startingSequenceNumber)); + auto response = dynamicPtrCast(procedure.buildBlockAck(request, &agreement)); + for (int i = 0; i < 64; i++) + ASSERT(!response->getBlockAckBitmap().getBit(i)); + request->setStartingSequenceNumber(SequenceNumberCyclic(100)); + reordering.processReceivedBlockAckReq(&agreement, request); + ASSERT(agreement.getStartingSequenceNumber() == SequenceNumberCyclic(startingSequenceNumber)); + } + + BlockAckReordering reordering; + RecipientBlockAckAgreement wrappedAgreement(MacAddress("11:22:33:44:55:66"), 5, SequenceNumberCyclic(4090), 64, SIMTIME_ZERO); + auto request = makeCompressedBlockAckReq(); + request->setStartingSequenceNumber(SequenceNumberCyclic(5)); + reordering.processReceivedBlockAckReq(&wrappedAgreement, request); + ASSERT(wrappedAgreement.getStartingSequenceNumber() == SequenceNumberCyclic(5)); + ASSERT(wrappedAgreement.getBlockAckRecord()->getCompressedAckState(SequenceNumberCyclic(4095))); + ASSERT(!wrappedAgreement.getBlockAckRecord()->getCompressedAckState(SequenceNumberCyclic(5))); + EV << "BAR advances the authoritative receive window across boundaries and wrap.\n"; +} + +{ + TestBlockAckRecord record(MacAddress("11:22:33:44:55:66"), 5, SequenceNumberCyclic(100)); + auto received102 = makeReceivedQosFrame(102); + auto received105 = makeReceivedQosFrame(105); + record.dataFrameReceived(received102.second, 64); + record.dataFrameReceived(received105.second, 64); + ASSERT(record.hasRecordedAckState(SequenceNumberCyclic(102), 0)); + ASSERT(record.hasRecordedAckState(SequenceNumberCyclic(105), 0)); + record.advanceStartingSequenceNumber(SequenceNumberCyclic(104)); + ASSERT(!record.hasRecordedAckState(SequenceNumberCyclic(102), 0)); + ASSERT(record.hasRecordedAckState(SequenceNumberCyclic(105), 0)); + delete received102.first; + delete received105.first; + EV << "Receive-window advancement erases old state and retains overlap.\n"; +} + { TestRecipientBlockAckProcedure procedure; RecipientBlockAckAgreement agreement(MacAddress("11:22:33:44:55:66"), 5, SequenceNumberCyclic(101), 64, SIMTIME_ZERO); @@ -163,7 +280,7 @@ static Packet *makeOutstandingQosFrame(MacAddress receiverAddress, Tid tid, Frag data->setTid(5); data->setSequenceNumber(SequenceNumberCyclic(sequenceNumber)); data->setFragmentNumber(0); - agreement.blockAckPolicyFrameReceived(data); + agreement.dataFrameReceived(data); } auto request = makeCompressedBlockAckReq(); request->setStartingSequenceNumber(SequenceNumberCyclic(101)); @@ -183,7 +300,7 @@ static Packet *makeOutstandingQosFrame(MacAddress receiverAddress, Tid tid, Frag data->setTid(5); data->setSequenceNumber(SequenceNumberCyclic(102)); data->setFragmentNumber(0); - agreement.blockAckPolicyFrameReceived(data); + agreement.dataFrameReceived(data); auto request = makeCompressedBlockAckReq(); request->setStartingSequenceNumber(SequenceNumberCyclic(100)); auto response = dynamicPtrCast(procedure.buildBlockAck(request, &agreement)); @@ -193,6 +310,184 @@ static Packet *makeOutstandingQosFrame(MacAddress receiverAddress, Tid tid, Frag EV << "Compressed bitmap preserves leading receive-window holes.\n"; } +{ + MacAddress originatorAddress("11:22:33:44:55:66"); + TestRecipientBlockAckProcedure procedure; + TestBlockAckReordering reordering; + RecipientBlockAckAgreement agreement(originatorAddress, 5, SequenceNumberCyclic(0), 64, SIMTIME_ZERO); + auto incomplete0 = makeReceivedQosFrame(0, 0, true); + ASSERT(reordering.processReceivedQoSFrame(&agreement, incomplete0.first, incomplete0.second).empty()); + auto received64 = makeReceivedQosFrame(64); + ASSERT(reordering.processReceivedQoSFrame(&agreement, received64.first, received64.second).empty()); + auto receiveBuffer = reordering.getReceiveBuffer(5, originatorAddress); + ASSERT(receiveBuffer != nullptr); + ASSERT(receiveBuffer->getNextExpectedSequenceNumber() == SequenceNumberCyclic(1)); + ASSERT(receiveBuffer->getBuffer().find(0) == receiveBuffer->getBuffer().end()); + ASSERT(receiveBuffer->getBuffer().find(64) != receiveBuffer->getBuffer().end()); + ASSERT(agreement.getStartingSequenceNumber() == SequenceNumberCyclic(1)); + + auto request = makeCompressedBlockAckReq(); + request->setStartingSequenceNumber(SequenceNumberCyclic(1)); + auto response = dynamicPtrCast(procedure.buildBlockAck(request, &agreement)); + for (int i = 0; i < 63; i++) + ASSERT(!response->getBlockAckBitmap().getBit(i)); + ASSERT(response->getBlockAckBitmap().getBit(63)); + EV << "SN64 advances a size-64 scoreboard and remains buffered across a gap.\n"; +} + +{ + MacAddress originatorAddress("11:22:33:44:55:66"); + TestBlockAckReordering reordering; + RecipientBlockAckAgreement agreement(originatorAddress, 5, SequenceNumberCyclic(0), 64, SIMTIME_ZERO); + auto received1 = makeReceivedQosFrame(1); + ASSERT(reordering.processReceivedQoSFrame(&agreement, received1.first, received1.second).empty()); + auto received2 = makeReceivedQosFrame(2); + ASSERT(reordering.processReceivedQoSFrame(&agreement, received2.first, received2.second).empty()); + auto received65 = makeReceivedQosFrame(65); + auto framesToPassUp = reordering.processReceivedQoSFrame(&agreement, received65.first, received65.second); + ASSERT(framesToPassUp.size() == 2); + ASSERT(framesToPassUp[0].first == 1); + ASSERT(framesToPassUp[1].first == 2); + auto receiveBuffer = reordering.getReceiveBuffer(5, originatorAddress); + ASSERT(receiveBuffer->getNextExpectedSequenceNumber() == SequenceNumberCyclic(3)); + ASSERT(receiveBuffer->getBuffer().find(65) != receiveBuffer->getBuffer().end()); + for (const auto& entry : framesToPassUp) + for (auto packet : entry.second) + delete packet; + EV << "Future MPDU returns displaced and consecutive complete MSDUs without losing gaps.\n"; +} + +{ + MacAddress originatorAddress("11:22:33:44:55:66"); + TestRecipientBlockAckProcedure procedure; + TestBlockAckReordering reordering; + RecipientBlockAckAgreement agreement(originatorAddress, 5, SequenceNumberCyclic(0), 64, SIMTIME_ZERO); + auto normalAck64 = makeReceivedQosFrame(64, 0, false, NORMAL_ACK); + ASSERT(reordering.processReceivedQoSFrame(&agreement, normalAck64.first, normalAck64.second).empty()); + ASSERT(agreement.getStartingSequenceNumber() == SequenceNumberCyclic(1)); + auto request = makeCompressedBlockAckReq(); + request->setStartingSequenceNumber(SequenceNumberCyclic(1)); + auto response = dynamicPtrCast(procedure.buildBlockAck(request, &agreement)); + ASSERT(response->getBlockAckBitmap().getBit(63)); + EV << "Normal-Ack data advances and updates the Block Ack scoreboard.\n"; +} + +{ + MacAddress originatorAddress("11:22:33:44:55:66"); + TestBlockAckReordering reordering; + RecipientBlockAckAgreement agreement(originatorAddress, 5, SequenceNumberCyclic(100), 64, SIMTIME_ZERO); + auto normalAck100 = makeReceivedQosFrame(100, 0, false, NORMAL_ACK); + auto framesToPassUp = reordering.processReceivedQoSFrame(&agreement, normalAck100.first, normalAck100.second); + ASSERT(framesToPassUp.size() == 1 && framesToPassUp[0].first == 100); + ASSERT(agreement.getStartingSequenceNumber() == SequenceNumberCyclic(100)); + ASSERT(agreement.getBlockAckRecord()->getCompressedAckState(SequenceNumberCyclic(100))); + delete framesToPassUp[0].second[0]; + EV << "Upward delivery advances only the reorder cursor, not WinStartR.\n"; +} + +{ + MacAddress originatorAddress("11:22:33:44:55:66"); + TestBlockAckReordering reordering; + RecipientBlockAckAgreement agreement(originatorAddress, 5, SequenceNumberCyclic(4090), 64, SIMTIME_ZERO); + auto received4095 = makeReceivedQosFrame(4095); + ASSERT(reordering.processReceivedQoSFrame(&agreement, received4095.first, received4095.second).empty()); + auto received0 = makeReceivedQosFrame(0); + ASSERT(reordering.processReceivedQoSFrame(&agreement, received0.first, received0.second).empty()); + auto received62 = makeReceivedQosFrame(62); + auto framesToPassUp = reordering.processReceivedQoSFrame(&agreement, received62.first, received62.second); + ASSERT(framesToPassUp.size() == 2); + ASSERT(framesToPassUp[0].first == 4095); + ASSERT(framesToPassUp[1].first == 0); + auto receiveBuffer = reordering.getReceiveBuffer(5, originatorAddress); + ASSERT(receiveBuffer->getNextExpectedSequenceNumber() == SequenceNumberCyclic(1)); + ASSERT(receiveBuffer->getBuffer().find(62) != receiveBuffer->getBuffer().end()); + ASSERT(agreement.getStartingSequenceNumber() == SequenceNumberCyclic(4095)); + for (const auto& entry : framesToPassUp) + for (auto packet : entry.second) + delete packet; + EV << "Data-window slide preserves cyclic 4095-to-0 delivery order.\n"; +} + +{ + MacAddress originatorAddress("11:22:33:44:55:66"); + TestBlockAckReordering reordering; + RecipientBlockAckAgreement agreement(originatorAddress, 5, SequenceNumberCyclic(4090), 64, SIMTIME_ZERO); + auto received4095 = makeReceivedQosFrame(4095); + ASSERT(reordering.processReceivedQoSFrame(&agreement, received4095.first, received4095.second).empty()); + auto received0 = makeReceivedQosFrame(0); + ASSERT(reordering.processReceivedQoSFrame(&agreement, received0.first, received0.second).empty()); + auto request = makeCompressedBlockAckReq(); + request->setStartingSequenceNumber(SequenceNumberCyclic(1)); + auto framesToPassUp = reordering.processReceivedBlockAckReq(&agreement, request); + ASSERT(framesToPassUp.size() == 2); + ASSERT(framesToPassUp[0].first == 4095); + ASSERT(framesToPassUp[1].first == 0); + ASSERT(agreement.getStartingSequenceNumber() == SequenceNumberCyclic(1)); + for (const auto& entry : framesToPassUp) + for (auto packet : entry.second) + delete packet; + EV << "BAR release preserves cyclic order without advancing WinStartR past its SSN.\n"; +} + +{ + MacAddress originatorAddress("11:22:33:44:55:66"); + TestBlockAckReordering reordering; + RecipientBlockAckAgreement agreement(originatorAddress, 5, SequenceNumberCyclic(100), 64, SIMTIME_ZERO); + auto received101 = makeReceivedQosFrame(101); + auto firstResult = reordering.processReceivedQoSFrame(&agreement, received101.first, received101.second); + ASSERT(firstResult.empty()); + auto duplicate101 = makeReceivedQosFrame(101); + auto duplicateResult = reordering.processReceivedQoSFrame(&agreement, duplicate101.first, duplicate101.second); + ASSERT(duplicateResult.empty()); + auto receiveBuffer = reordering.getReceiveBuffer(5, originatorAddress); + ASSERT(receiveBuffer->getBuffer().at(101).size() == 1); + ASSERT(receiveBuffer->getBuffer().at(101)[0] == received101.first); + auto request = makeCompressedBlockAckReq(); + request->setStartingSequenceNumber(SequenceNumberCyclic(102)); + auto framesToPassUp = reordering.processReceivedBlockAckReq(&agreement, request); + ASSERT(framesToPassUp.size() == 1 && framesToPassUp[0].first == 101); + ASSERT(framesToPassUp[0].second[0] == received101.first); + delete framesToPassUp[0].second[0]; + EV << "Duplicate insertion preserves ownership of the original buffered MPDU.\n"; +} + +{ + MacAddress originatorAddress("11:22:33:44:55:66"); + TestBlockAckReordering reordering; + RecipientBlockAckAgreement agreement(originatorAddress, 5, SequenceNumberCyclic(100), 64, SIMTIME_ZERO); + auto incomplete101 = makeReceivedQosFrame(101, 0, true); + auto insertionResult = reordering.processReceivedQoSFrame(&agreement, incomplete101.first, incomplete101.second); + auto receiveBuffer = reordering.getReceiveBuffer(5, originatorAddress); + auto request = makeCompressedBlockAckReq(); + request->setStartingSequenceNumber(SequenceNumberCyclic(102)); + auto framesToPassUp = reordering.processReceivedBlockAckReq(&agreement, request); + if (!insertionResult.empty() || !framesToPassUp.empty() || !receiveBuffer->getBuffer().empty() || receiveBuffer->getLength() != 0 || + receiveBuffer->getNextExpectedSequenceNumber() != SequenceNumberCyclic(102) || agreement.getStartingSequenceNumber() != SequenceNumberCyclic(102)) + throw cRuntimeError("BAR did not discard an incomplete preceding MPDU or advance both receive-window starts to its SSN"); + EV << "BAR discards incomplete preceding MPDUs and advances the reorder cursor.\n"; +} + +{ + MacAddress originatorAddress("11:22:33:44:55:66"); + TestBlockAckReordering reordering; + RecipientBlockAckAgreement agreement(originatorAddress, 5, SequenceNumberCyclic(100), 64, SIMTIME_ZERO); + auto complete101 = makeReceivedQosFrame(101); + auto completeInsertionResult = reordering.processReceivedQoSFrame(&agreement, complete101.first, complete101.second); + auto incomplete102 = makeReceivedQosFrame(102, 0, true); + auto incompleteInsertionResult = reordering.processReceivedQoSFrame(&agreement, incomplete102.first, incomplete102.second); + auto receiveBuffer = reordering.getReceiveBuffer(5, originatorAddress); + auto request = makeCompressedBlockAckReq(); + request->setStartingSequenceNumber(SequenceNumberCyclic(103)); + auto framesToPassUp = reordering.processReceivedBlockAckReq(&agreement, request); + if (!completeInsertionResult.empty() || !incompleteInsertionResult.empty() || framesToPassUp.size() != 1 || framesToPassUp[0].first != 101 || + framesToPassUp[0].second.size() != 1 || framesToPassUp[0].second[0] != complete101.first || !receiveBuffer->getBuffer().empty() || + receiveBuffer->getLength() != 0 || receiveBuffer->getNextExpectedSequenceNumber() != SequenceNumberCyclic(103) || + agreement.getStartingSequenceNumber() != SequenceNumberCyclic(103)) + throw cRuntimeError("BAR did not return a complete preceding MPDU and discard the incomplete gap that followed it"); + delete framesToPassUp[0].second[0]; + EV << "BAR releases complete preceding MPDUs and discards a following incomplete gap.\n"; +} + { TestRecipientBlockAckProcedure procedure; RecipientBlockAckAgreement agreement(MacAddress("11:22:33:44:55:66"), 5, SequenceNumberCyclic(4095), 64, SIMTIME_ZERO); @@ -203,7 +498,7 @@ static Packet *makeOutstandingQosFrame(MacAddress receiverAddress, Tid tid, Frag data->setTid(5); data->setSequenceNumber(SequenceNumberCyclic(sequenceNumber)); data->setFragmentNumber(0); - agreement.blockAckPolicyFrameReceived(data); + agreement.dataFrameReceived(data); } auto request = makeCompressedBlockAckReq(); request->setStartingSequenceNumber(SequenceNumberCyclic(4095)); @@ -223,7 +518,7 @@ static Packet *makeOutstandingQosFrame(MacAddress receiverAddress, Tid tid, Frag data->setTid(5); data->setSequenceNumber(SequenceNumberCyclic(1)); data->setFragmentNumber(0); - agreement.blockAckPolicyFrameReceived(data); + agreement.dataFrameReceived(data); auto request = makeCompressedBlockAckReq(); request->setStartingSequenceNumber(SequenceNumberCyclic(4095)); auto response = dynamicPtrCast(procedure.buildBlockAck(request, &agreement)); @@ -235,7 +530,7 @@ static Packet *makeOutstandingQosFrame(MacAddress receiverAddress, Tid tid, Frag { auto request = makeCompressedBlockAckReq(); - ASSERT(TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, nullptr)); + ASSERT(!TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, nullptr)); request->setFragmentNumber(1); ASSERT(!TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, nullptr)); request->setFragmentNumber(0); @@ -249,23 +544,70 @@ static Packet *makeOutstandingQosFrame(MacAddress receiverAddress, Tid tid, Frag EV << "Recipient fragment and agreement-policy gates passed.\n"; } +{ + MacAddress listedPeer("10:20:30:40:50:60"); + MacAddress unlistedPeer("10:20:30:40:50:61"); + MacAddress positivePeer("10:20:30:40:50:62"); + TestOriginatorBlockAckAgreementPolicy localDisabledPolicy; + localDisabledPolicy.configureCompressedBlockAckCapability(false, {listedPeer}); + TestOriginatorBlockAckAgreementPolicy localEnabledPolicy; + localEnabledPolicy.configureCompressedBlockAckCapability(true, {listedPeer, positivePeer}); + ASSERT(!localDisabledPolicy.isPeerCompressedBlockAckSupported(listedPeer)); + ASSERT(!localEnabledPolicy.isPeerCompressedBlockAckSupported(unlistedPeer)); + ASSERT(localEnabledPolicy.isPeerCompressedBlockAckSupported(listedPeer)); + + TestOriginatorBlockAckAgreementHandler handler; + auto makeAddbaRequest = [](const MacAddress& receiverAddress) { + auto request = makeShared(); + request->setReceiverAddress(receiverAddress); + request->setTid(5); + request->setStartingSequenceNumber(SequenceNumberCyclic(100)); + request->setBufferSize(64); + request->setBlockAckPolicy(true); + return request; + }; + handler.createAgreement(makeAddbaRequest(listedPeer), &localDisabledPolicy); + handler.createAgreement(makeAddbaRequest(unlistedPeer), &localEnabledPolicy); + handler.createAgreement(makeAddbaRequest(positivePeer), &localEnabledPolicy); + ASSERT(!handler.getAgreement(listedPeer, 5)->getIsCompressedBlockAckSupported()); + ASSERT(!handler.getAgreement(unlistedPeer, 5)->getIsCompressedBlockAckSupported()); + ASSERT(handler.getAgreement(positivePeer, 5)->getIsCompressedBlockAckSupported()); + + auto response = makeShared(); + response->setTransmitterAddress(listedPeer); + response->setBlockAckPolicy(true); + response->setBufferSize(64); + response->setBlockAckTimeoutValue(SIMTIME_ZERO); + handler.updateAgreement(handler.getAgreement(listedPeer, 5), response, &localEnabledPolicy); + ASSERT(handler.getAgreement(listedPeer, 5)->getIsCompressedBlockAckSupported()); + EV << "Local and per-peer capability gates are snapshotted by agreements.\n"; +} + { MacAddress receiverAddress("10:20:30:40:50:60"); OriginatorBlockAckAgreement immediateAgreement(receiverAddress, 5, SequenceNumberCyclic(100), 64, false, false); immediateAgreement.setIsAddbaResponseReceived(true); std::vector outstandingFrames { makeOutstandingQosFrame(receiverAddress, 5) }; - ASSERT(!TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, &immediateAgreement, false)); - ASSERT(!TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, nullptr, true)); + ASSERT(!TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, &immediateAgreement)); + ASSERT(!TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, nullptr)); + immediateAgreement.setIsCompressedBlockAckSupported(true); OriginatorBlockAckAgreement delayedAgreement(receiverAddress, 5, SequenceNumberCyclic(100), 64, false, true); delayedAgreement.setIsAddbaResponseReceived(true); - ASSERT(!TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, &delayedAgreement, true)); - ASSERT(TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, &immediateAgreement, true)); + delayedAgreement.setIsCompressedBlockAckSupported(true); + ASSERT(!TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, &delayedAgreement)); + ASSERT(TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, &immediateAgreement)); + MacAddress legacyReceiverAddress("10:20:30:40:50:61"); + OriginatorBlockAckAgreement legacyAgreement(legacyReceiverAddress, 5, SequenceNumberCyclic(100), 64, false, false); + legacyAgreement.setIsAddbaResponseReceived(true); + std::vector legacyOutstandingFrames { makeOutstandingQosFrame(legacyReceiverAddress, 5) }; + ASSERT(!TestOriginatorQosAckPolicy::isCompressedRequestNeeded(legacyOutstandingFrames, &legacyAgreement)); + delete legacyOutstandingFrames[0]; delete outstandingFrames[0]; outstandingFrames = { makeOutstandingQosFrame(receiverAddress, 5, 1) }; - ASSERT(!TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, &immediateAgreement, true)); + ASSERT(!TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, &immediateAgreement)); delete outstandingFrames[0]; outstandingFrames = { makeOutstandingQosFrame(receiverAddress, 5, 0, true) }; - ASSERT(!TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, &immediateAgreement, true)); + ASSERT(!TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, &immediateAgreement)); delete outstandingFrames[0]; EV << "Originator capability, agreement, and fragmentation gates passed.\n"; } @@ -301,12 +643,15 @@ static Packet *makeOutstandingQosFrame(MacAddress receiverAddress, Tid tid, Frag { TestOriginatorBlockAckAgreementHandler handler; + TestOriginatorBlockAckAgreementPolicy policy; + policy.configureCompressedBlockAckCapability(false, {}); OriginatorBlockAckAgreement agreement(MacAddress("10:20:30:40:50:60"), 5, SequenceNumberCyclic(100), 64, false, false); auto delayedResponse = makeShared(); delayedResponse->setBlockAckPolicy(false); delayedResponse->setBufferSize(64); delayedResponse->setBlockAckTimeoutValue(SIMTIME_ZERO); - handler.updateAgreement(&agreement, delayedResponse); + delayedResponse->setTransmitterAddress(MacAddress("10:20:30:40:50:60")); + handler.updateAgreement(&agreement, delayedResponse, &policy); ASSERT(agreement.getIsAddbaResponseReceived()); ASSERT(agreement.getIsDelayedBlockAckPolicySupported()); EV << "Accepted delayed agreement is retained for Basic BAR fallback.\n"; @@ -342,11 +687,24 @@ Compressed BAR encoding and round-trip passed. Compressed BA encoding and round-trip passed. Missing recipient state produces a null compressed BA. Empty recipient record produces a null compressed BA. +Empty Basic BA distinguishes old and current-window MPDUs. +BAR advances the authoritative receive window across boundaries and wrap. +Receive-window advancement erases old state and retains overlap. Established agreement produces the expected 64-bit bitmap. Compressed bitmap preserves leading receive-window holes. +SN64 advances a size-64 scoreboard and remains buffered across a gap. +Future MPDU returns displaced and consecutive complete MSDUs without losing gaps. +Normal-Ack data advances and updates the Block Ack scoreboard. +Upward delivery advances only the reorder cursor, not WinStartR. +Data-window slide preserves cyclic 4095-to-0 delivery order. +BAR release preserves cyclic order without advancing WinStartR past its SSN. +Duplicate insertion preserves ownership of the original buffered MPDU. +BAR discards incomplete preceding MPDUs and advances the reorder cursor. +BAR releases complete preceding MPDUs and discards a following incomplete gap. Compressed bitmap wraps from sequence 4095 to 0. Compressed bitmap preserves leading holes across sequence wrap. Recipient fragment and agreement-policy gates passed. +Local and per-peer capability gates are snapshotted by agreements. Originator capability, agreement, and fragmentation gates passed. Legacy Basic BAR and BA remain byte-exact. Accepted delayed agreement is retained for Basic BAR fallback. From 09710613cd6cb97857fa117bc984a848503404ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Sun, 16 Aug 2026 13:03:04 +0200 Subject: [PATCH 6/8] fix(ieee80211): make Block Ack receive processing transactional Update the Block Ack scoreboard for every related received QoS Data MPDU, including frames using Normal Ack, independently of reorder-buffer admission. Handle the receive-window cases explicitly: ignore old sequence numbers, record in-window MPDUs, and advance WinStartR before recording MPDUs beyond WinEndR. This behavior deliberately applies to both Basic and Compressed Block Ack agreements, as required by IEEE 802.11-2024 sections 10.25.6.3 and 10.25.6.4. Make far-ahead reorder-window movement transactional. Validate and insert the incoming MPDU against the proposed WinStartB before releasing displaced MSDUs or changing NextExpectedSequenceNumber. If admission fails, discard only the incoming packet and preserve the reorder window and its buffered frames, while retaining the independently required scoreboard update. Calculate receive-buffer capacity after accounting for entries displaced by the proposed window. Count each fragment as one buffer slot, allow an advancing MPDU to reuse slots that will be reclaimed, and remove incomplete stale entries only after successful admission. Preserve delivery of complete displaced and consecutive MSDUs, including across the 4095-to-0 sequence number boundary. Centralize supported one-TID BlockAckReq classification and extraction of the variant, TID, and starting sequence number. Use the shared classifier in HCF, RecipientQosMacDataService, and BlockAckReordering so only Basic and Compressed requests reach the implemented paths. Multi-TID requests remain unsupported and follow the existing rejection behavior. Extend the focused Block Ack tests to cover old scoreboard inputs, Normal-Ack behavior with Basic Block Ack, fragment-full admission failure, successful slot reclamation across sequence wrap, real HCF dispatch through a procedure spy, and consistent Basic, Compressed, and Multi-TID request classification. --- .../ieee80211/mac/blockack/BlockAckRecord.cc | 8 +- .../mac/blockack/OneTidBlockAckReqVariant.h | 46 +++++++ .../blockackreordering/BlockAckReordering.cc | 78 +++++------ .../mac/blockackreordering/ReceiveBuffer.cc | 59 ++++---- .../mac/blockackreordering/ReceiveBuffer.h | 4 +- .../ieee80211/mac/coordinationfunction/Hcf.cc | 5 +- .../recipient/RecipientQosMacDataService.cc | 16 +-- tests/unit/Ieee80211CompressedBlockAck_1.test | 130 ++++++++++++++++++ 8 files changed, 267 insertions(+), 79 deletions(-) create mode 100644 src/inet/linklayer/ieee80211/mac/blockack/OneTidBlockAckReqVariant.h diff --git a/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc b/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc index 14a21493581..e212ed19537 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc @@ -23,8 +23,12 @@ void BlockAckRecord::dataFrameReceived(const Ptr& hea { SequenceNumberCyclic sequenceNumber = header->getSequenceNumber(); FragmentNumber fragmentNumber = header->getFragmentNumber(); - // IEEE Std 802.11-2024, 10.25.6.3(b) and 10.25.6.4(c): a related - // MPDU beyond WinEndR advances the receive window before its bit is set. + // IEEE Std 802.11-2024, 10.25.6.3(b), case 3: an old related MPDU + // does not change the Block Ack record. + if (!(startingSequenceNumber <= sequenceNumber && sequenceNumber < startingSequenceNumber + 2048)) + return; + // Cases 1 and 2: record an in-window MPDU, or advance WinStartR + // before recording an MPDU beyond WinEndR (also see 10.25.6.4(c)). if (startingSequenceNumber + windowSize <= sequenceNumber && sequenceNumber < startingSequenceNumber + 2048) advanceStartingSequenceNumber(sequenceNumber - windowSize + 1); acknowledgmentState[SequenceControlField(sequenceNumber.get(), fragmentNumber)] = true; diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OneTidBlockAckReqVariant.h b/src/inet/linklayer/ieee80211/mac/blockack/OneTidBlockAckReqVariant.h new file mode 100644 index 00000000000..0d158b365cf --- /dev/null +++ b/src/inet/linklayer/ieee80211/mac/blockack/OneTidBlockAckReqVariant.h @@ -0,0 +1,46 @@ +// +// Copyright (C) 2026 OpenSim Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +#ifndef __INET_ONETIDBLOCKACKREQVARIANT_H +#define __INET_ONETIDBLOCKACKREQVARIANT_H + +#include + +#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" + +namespace inet { +namespace ieee80211 { + +enum class OneTidBlockAckReqVariant +{ + BASIC, + COMPRESSED, +}; + +struct OneTidBlockAckReqDetails +{ + Ptr blockAckReq; + OneTidBlockAckReqVariant variant; + Tid tid; + SequenceNumberCyclic startingSequenceNumber; +}; + +inline std::optional getOneTidBlockAckReqDetails(const Ptr& header) +{ + if (auto basicBlockAckReq = dynamicPtrCast(header)) + return OneTidBlockAckReqDetails { basicBlockAckReq, OneTidBlockAckReqVariant::BASIC, + static_cast(basicBlockAckReq->getTidInfo()), basicBlockAckReq->getStartingSequenceNumber() }; + else if (auto compressedBlockAckReq = dynamicPtrCast(header)) + return OneTidBlockAckReqDetails { compressedBlockAckReq, OneTidBlockAckReqVariant::COMPRESSED, + static_cast(compressedBlockAckReq->getTidInfo()), compressedBlockAckReq->getStartingSequenceNumber() }; + else + return std::nullopt; +} + +} // namespace ieee80211 +} // namespace inet + +#endif diff --git a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc index 10587411f2f..d8dad589c57 100644 --- a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc +++ b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc @@ -7,6 +7,7 @@ #include "inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h" +#include "inet/linklayer/ieee80211/mac/blockack/OneTidBlockAckReqVariant.h" #include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h" namespace inet { @@ -22,10 +23,17 @@ BlockAckReordering::ReorderBuffer BlockAckReordering::processReceivedQoSFrame(Re auto sequenceNumber = dataHeader->getSequenceNumber(); auto startingSequenceNumber = receiveBuffer->getNextExpectedSequenceNumber(); bool advancesWindow = startingSequenceNumber + receiveBuffer->getBufferSize() <= sequenceNumber && sequenceNumber < startingSequenceNumber + 2048; + // IEEE Std 802.11-2024, 10.25.6.3 and 10.25.6.4: update the + // scoreboard for every related Data frame, independently of reorder storage. + agreement->dataFrameReceived(dataHeader); if (advancesWindow) { - // IEEE Std 802.11-2024, 10.25.6.6.2.1(b): move WinStartB so the - // future MPDU fits, preserving complete displaced MSDUs for delivery. + // IEEE Std 802.11-2024, 10.25.6.6.2.1(b): store the future MPDU + // before moving WinStartB and releasing complete displaced MSDUs. auto newStartingSequenceNumber = sequenceNumber - receiveBuffer->getBufferSize() + 1; + if (!receiveBuffer->insertFrame(dataPacket, dataHeader, newStartingSequenceNumber)) { + delete dataPacket; + return framesToPassUp; + } framesToPassUp = collectCompletePrecedingMpdus(receiveBuffer, newStartingSequenceNumber); for (const auto& entry : framesToPassUp) receiveBuffer->removeFrame(SequenceNumberCyclic(entry.first)); @@ -37,34 +45,33 @@ BlockAckReordering::ReorderBuffer BlockAckReordering::processReceivedQoSFrame(Re // recipient to reset the timer to detect Block Ack timeout (see 10.5.4). // This allows the recipient to delete the Block Ack if the originator does not switch // back to using Block Ack. - if (receiveBuffer->insertFrame(dataPacket, dataHeader)) { - agreement->dataFrameReceived(dataHeader); - if (advancesWindow) { - auto consecutiveCompleteMpdus = collectConsecutiveCompleteFollowingMpdus(receiveBuffer, receiveBuffer->getNextExpectedSequenceNumber()); - releaseReceiveBuffer(receiveBuffer, consecutiveCompleteMpdus); - framesToPassUp.insert(framesToPassUp.end(), consecutiveCompleteMpdus.begin(), consecutiveCompleteMpdus.end()); - return framesToPassUp; + if (!advancesWindow && !receiveBuffer->insertFrame(dataPacket, dataHeader)) { + delete dataPacket; + return framesToPassUp; + } + if (advancesWindow) { + auto consecutiveCompleteMpdus = collectConsecutiveCompleteFollowingMpdus(receiveBuffer, receiveBuffer->getNextExpectedSequenceNumber()); + releaseReceiveBuffer(receiveBuffer, consecutiveCompleteMpdus); + framesToPassUp.insert(framesToPassUp.end(), consecutiveCompleteMpdus.begin(), consecutiveCompleteMpdus.end()); + return framesToPassUp; + } + auto earliestCompleteMsduOrAMsdu = getEarliestCompleteMsduOrAMsduIfExists(receiveBuffer); + if (earliestCompleteMsduOrAMsdu.size() > 0) { + auto earliestSequenceNumber = earliestCompleteMsduOrAMsdu.at(0)->peekAtFront()->getSequenceNumber(); + // If, after an MPDU is received, the receive buffer is full, the complete MSDU or A-MSDU with the earliest + // sequence number shall be passed up to the next MAC process. + if (receiveBuffer->isFull()) { + passedUp(receiveBuffer, earliestSequenceNumber); + return ReorderBuffer({ std::make_pair(earliestSequenceNumber.get(), Fragments(earliestCompleteMsduOrAMsdu)) }); } - auto earliestCompleteMsduOrAMsdu = getEarliestCompleteMsduOrAMsduIfExists(receiveBuffer); - if (earliestCompleteMsduOrAMsdu.size() > 0) { - auto earliestSequenceNumber = earliestCompleteMsduOrAMsdu.at(0)->peekAtFront()->getSequenceNumber(); - // If, after an MPDU is received, the receive buffer is full, the complete MSDU or A-MSDU with the earliest - // sequence number shall be passed up to the next MAC process. - if (receiveBuffer->isFull()) { - passedUp(receiveBuffer, earliestSequenceNumber); - return ReorderBuffer({ std::make_pair(earliestSequenceNumber.get(), Fragments(earliestCompleteMsduOrAMsdu)) }); - } - // If, after an MPDU is received, the receive buffer is not full, but the sequence number of the complete MSDU or - // A-MSDU in the buffer with the lowest sequence number is equal to the NextExpectedSequenceNumber for - // that Block Ack agreement, then the MPDU shall be passed up to the next MAC process. - else if (earliestSequenceNumber == receiveBuffer->getNextExpectedSequenceNumber()) { - passedUp(receiveBuffer, earliestSequenceNumber); - return ReorderBuffer({ std::make_pair(earliestSequenceNumber.get(), Fragments(earliestCompleteMsduOrAMsdu)) }); - } + // If, after an MPDU is received, the receive buffer is not full, but the sequence number of the complete MSDU or + // A-MSDU in the buffer with the lowest sequence number is equal to the NextExpectedSequenceNumber for + // that Block Ack agreement, then the MPDU shall be passed up to the next MAC process. + else if (earliestSequenceNumber == receiveBuffer->getNextExpectedSequenceNumber()) { + passedUp(receiveBuffer, earliestSequenceNumber); + return ReorderBuffer({ std::make_pair(earliestSequenceNumber.get(), Fragments(earliestCompleteMsduOrAMsdu)) }); } } - else - delete dataPacket; return framesToPassUp; } @@ -75,23 +82,14 @@ BlockAckReordering::ReorderBuffer BlockAckReordering::processReceivedBlockAckReq { // The originator shall use the Block Ack starting sequence control to signal the first MPDU in the block for // which an acknowledgment is expected. - SequenceNumberCyclic startingSequenceNumber; - Tid tid = -1; - if (auto basicReq = dynamicPtrCast(blockAckReq)) { - tid = basicReq->getTidInfo(); - startingSequenceNumber = basicReq->getStartingSequenceNumber(); - } - else if (auto compressedReq = dynamicPtrCast(blockAckReq)) { - tid = compressedReq->getTidInfo(); - startingSequenceNumber = compressedReq->getStartingSequenceNumber(); - } - else { + auto blockAckReqDetails = getOneTidBlockAckReqDetails(blockAckReq); + if (!blockAckReqDetails) throw cRuntimeError("Multi-Tid BlockAckReq is currently an unimplemented feature"); - } + auto startingSequenceNumber = blockAckReqDetails->startingSequenceNumber; // IEEE Std 802.11-2024, 10.25.6.3-10.25.6.5: adjust WinStartR // from the BAR before generating the response, even without a receive buffer. agreement->getBlockAckRecord()->advanceStartingSequenceNumber(startingSequenceNumber); - auto id = std::make_pair(tid, blockAckReq->getTransmitterAddress()); + auto id = std::make_pair(blockAckReqDetails->tid, blockAckReq->getTransmitterAddress()); auto it = receiveBuffers.find(id); if (it != receiveBuffers.end()) { ReceiveBuffer *receiveBuffer = it->second; diff --git a/src/inet/linklayer/ieee80211/mac/blockackreordering/ReceiveBuffer.cc b/src/inet/linklayer/ieee80211/mac/blockackreordering/ReceiveBuffer.cc index 1dd9c959c8f..3c3097ee4ce 100644 --- a/src/inet/linklayer/ieee80211/mac/blockackreordering/ReceiveBuffer.cc +++ b/src/inet/linklayer/ieee80211/mac/blockackreordering/ReceiveBuffer.cc @@ -22,33 +22,47 @@ ReceiveBuffer::ReceiveBuffer(int bufferSize, SequenceNumberCyclic nextExpectedSe // data frame, unless the sequence number of the frame is older than the NextExpectedSequenceNumber for that // Block Ack agreement, in which case the frame is discarded because it is either old or a duplicate. // -bool ReceiveBuffer::insertFrame(Packet *dataPacket, const Ptr& dataHeader) +bool ReceiveBuffer::canInsertFrame(const Ptr& dataHeader, SequenceNumberCyclic nextExpectedSequenceNumber) const { auto sequenceNumber = dataHeader->getSequenceNumber(); auto fragmentNumber = dataHeader->getFragmentNumber(); - // The total number of MPDUs in these MSDUs may not - // exceed the reorder buffer size in the receiver. - if (length < bufferSize && nextExpectedSequenceNumber <= sequenceNumber && sequenceNumber < nextExpectedSequenceNumber + bufferSize) { - auto it = buffer.find(sequenceNumber.get()); - if (it != buffer.end()) { - auto& fragments = it->second; - // TODO efficiency - for (auto fragment : fragments) { - const auto& fragmentHeader = fragment->peekAtFront(); - if (fragmentHeader->getSequenceNumber() == sequenceNumber && fragmentHeader->getFragmentNumber() == fragmentNumber) - return false; - } - fragments.push_back(dataPacket); - } - else { - buffer[sequenceNumber.get()].push_back(dataPacket); + if (!(nextExpectedSequenceNumber <= sequenceNumber && sequenceNumber < nextExpectedSequenceNumber + bufferSize)) + return false; + int retainedLength = length; + for (const auto& entry : buffer) { + if (SequenceNumberCyclic(entry.first) < nextExpectedSequenceNumber) + retainedLength -= entry.second.size(); + } + // IEEE Std 802.11-2024, 9.4.1.13, footnote 26: each fragment + // occupies one receive-buffer slot. + if (retainedLength >= bufferSize) + return false; + auto it = buffer.find(sequenceNumber.get()); + if (it != buffer.end()) { + for (auto fragment : it->second) { + const auto& fragmentHeader = fragment->peekAtFront(); + if (fragmentHeader->getSequenceNumber() == sequenceNumber && fragmentHeader->getFragmentNumber() == fragmentNumber) + return false; } - // The total number of frames that can be sent depends on the total - // number of MPDUs in all the outstanding MSDUs. - length++; - return true; } - return false; + return true; +} + +bool ReceiveBuffer::insertFrame(Packet *dataPacket, const Ptr& dataHeader) +{ + return insertFrame(dataPacket, dataHeader, nextExpectedSequenceNumber); +} + +bool ReceiveBuffer::insertFrame(Packet *dataPacket, const Ptr& dataHeader, SequenceNumberCyclic nextExpectedSequenceNumber) +{ + if (!canInsertFrame(dataHeader, nextExpectedSequenceNumber)) + return false; + auto sequenceNumber = dataHeader->getSequenceNumber(); + buffer[sequenceNumber.get()].push_back(dataPacket); + // The total number of frames that can be sent depends on the total + // number of MPDUs in all the outstanding MSDUs. + length++; + return true; } void ReceiveBuffer::dropFramesUntil(SequenceNumberCyclic sequenceNumber) @@ -87,4 +101,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..77a711a8d36 100644 --- a/src/inet/linklayer/ieee80211/mac/blockackreordering/ReceiveBuffer.h +++ b/src/inet/linklayer/ieee80211/mac/blockackreordering/ReceiveBuffer.h @@ -30,11 +30,14 @@ class INET_API ReceiveBuffer int length = 0; SequenceNumberCyclic nextExpectedSequenceNumber; + bool canInsertFrame(const Ptr& dataHeader, SequenceNumberCyclic nextExpectedSequenceNumber) const; + public: ReceiveBuffer(int bufferSize, SequenceNumberCyclic nextExpectedSequenceNumber); virtual ~ReceiveBuffer(); bool insertFrame(Packet *dataPacket, const Ptr& dataHeader); + bool insertFrame(Packet *dataPacket, const Ptr& dataHeader, SequenceNumberCyclic nextExpectedSequenceNumber); void dropFramesUntil(SequenceNumberCyclic sequenceNumber); void removeFrame(SequenceNumberCyclic sequenceNumber); @@ -50,4 +53,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 ea44357fac3..e57c7b20ac1 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/OneTidBlockAckReqVariant.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" @@ -322,9 +323,9 @@ void Hcf::recipientProcessReceivedControlFrame(Packet *packet, const Ptr(header)) ctsProcedure->processReceivedRts(packet, rtsFrame, ctsPolicy, this); - else if (auto blockAckRequest = dynamicPtrCast(header)) { + else if (auto blockAckReqDetails = getOneTidBlockAckReqDetails(header)) { if (recipientBlockAckProcedure) - recipientBlockAckProcedure->processReceivedBlockAckReq(packet, blockAckRequest, recipientAckPolicy, recipientBlockAckAgreementHandler, this); + recipientBlockAckProcedure->processReceivedBlockAckReq(packet, blockAckReqDetails->blockAckReq, recipientAckPolicy, recipientBlockAckAgreementHandler, this); } else if (dynamicPtrCast(header)) EV_WARN << "ACK frame received after timeout, ignoring it.\n"; // drop it, it is an ACK frame that is received after the ACKTimeout diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc index 544c83cb3aa..7c7efb2aeeb 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc @@ -10,6 +10,7 @@ #include "inet/common/Simsignals.h" #include "inet/linklayer/ieee80211/mac/aggregation/MpduDeaggregation.h" #include "inet/linklayer/ieee80211/mac/aggregation/MsduDeaggregation.h" +#include "inet/linklayer/ieee80211/mac/blockack/OneTidBlockAckReqVariant.h" #include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h" #include "inet/linklayer/ieee80211/mac/duplicateremoval/QosDuplicateRemoval.h" #include "inet/linklayer/ieee80211/mac/fragmentation/BasicReassembly.h" @@ -136,20 +137,13 @@ std::vector RecipientQosMacDataService::managementFrameReceived(Packet std::vector RecipientQosMacDataService::controlFrameReceived(Packet *controlPacket, const Ptr& controlHeader, IRecipientBlockAckAgreementHandler *blockAckAgreementHandler) { Enter_Method("controlFrameReceived"); - if (auto blockAckReq = dynamicPtrCast(controlHeader)) { + if (auto blockAckReqDetails = getOneTidBlockAckReqDetails(controlHeader)) { BlockAckReordering::ReorderBuffer frames; if (blockAckReordering) { - Tid tid = -1; - if (auto basicBlockAckReq = dynamicPtrCast(blockAckReq)) - tid = basicBlockAckReq->getTidInfo(); - else if (auto compressedBlockAckReq = dynamicPtrCast(blockAckReq)) - tid = compressedBlockAckReq->getTidInfo(); - else - return std::vector(); - MacAddress originatorAddr = blockAckReq->getTransmitterAddress(); - RecipientBlockAckAgreement *agreement = blockAckAgreementHandler->getAgreement(tid, originatorAddr); + MacAddress originatorAddr = blockAckReqDetails->blockAckReq->getTransmitterAddress(); + RecipientBlockAckAgreement *agreement = blockAckAgreementHandler->getAgreement(blockAckReqDetails->tid, originatorAddr); if (agreement) - frames = blockAckReordering->processReceivedBlockAckReq(agreement, blockAckReq); + frames = blockAckReordering->processReceivedBlockAckReq(agreement, blockAckReqDetails->blockAckReq); else return std::vector(); } diff --git a/tests/unit/Ieee80211CompressedBlockAck_1.test b/tests/unit/Ieee80211CompressedBlockAck_1.test index e0bbc85340e..4a2a7b708d8 100644 --- a/tests/unit/Ieee80211CompressedBlockAck_1.test +++ b/tests/unit/Ieee80211CompressedBlockAck_1.test @@ -6,12 +6,14 @@ IEEE Std 802.11-2024, 9.3.1.7.2, 9.3.1.8.2, 10.25.6.1, and 10.25.6.5. #include "inet/common/packet/Packet.h" #include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" #include "inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.h" +#include "inet/linklayer/ieee80211/mac/blockack/OneTidBlockAckReqVariant.h" #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h" #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h" #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.h" #include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h" #include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckProcedure.h" #include "inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h" +#include "inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h" #include "inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.h" #include "inet/linklayer/ieee80211/mac/originator/TxopProcedure.h" #include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" @@ -28,6 +30,37 @@ class TestRecipientBlockAckProcedure : public RecipientBlockAckProcedure using RecipientBlockAckProcedure::buildBlockAck; }; +class SpyRecipientBlockAckProcedure : public IRecipientBlockAckProcedure +{ + public: + std::vector variants; + std::vector tids; + + virtual void processReceivedBlockAckReq(Packet *packet, const Ptr& blockAckReq, IRecipientQosAckPolicy *ackPolicy, IRecipientBlockAckAgreementHandler *blockAckAgreementHandler, IProcedureCallback *callback) override + { + auto blockAckReqDetails = getOneTidBlockAckReqDetails(blockAckReq); + ASSERT(blockAckReqDetails.has_value()); + variants.push_back(blockAckReqDetails->variant); + tids.push_back(blockAckReqDetails->tid); + } + + virtual void processTransmittedBlockAck(const Ptr& blockAck) override {} +}; + +class TestHcf : public Hcf +{ + public: + void setRecipientBlockAckProcedure(IRecipientBlockAckProcedure *procedure) + { + recipientBlockAckProcedure = procedure; + } + + void processControlHeader(const Ptr& header) + { + recipientProcessReceivedControlFrame(nullptr, header); + } +}; + class TestOriginatorBlockAckAgreementHandler : public OriginatorBlockAckAgreementHandler { public: @@ -181,6 +214,39 @@ static std::pair> makeReceivedQosFrame(Sequen EV << "Compressed BA encoding and round-trip passed.\n"; } +{ + TestHcf hcf; + auto spy = new SpyRecipientBlockAckProcedure(); + hcf.setRecipientBlockAckProcedure(spy); + auto basicRequest = makeShared(); + basicRequest->setTidInfo(3); + basicRequest->setStartingSequenceNumber(SequenceNumberCyclic(4095)); + auto compressedRequest = makeShared(); + compressedRequest->setTidInfo(5); + compressedRequest->setStartingSequenceNumber(SequenceNumberCyclic(7)); + auto basicDetails = getOneTidBlockAckReqDetails(basicRequest); + auto compressedDetails = getOneTidBlockAckReqDetails(compressedRequest); + ASSERT(basicDetails && basicDetails->variant == OneTidBlockAckReqVariant::BASIC && basicDetails->tid == 3 && basicDetails->startingSequenceNumber == SequenceNumberCyclic(4095)); + ASSERT(compressedDetails && compressedDetails->variant == OneTidBlockAckReqVariant::COMPRESSED && compressedDetails->tid == 5 && compressedDetails->startingSequenceNumber == SequenceNumberCyclic(7)); + auto multiTidRequest = makeShared(); + ASSERT(!getOneTidBlockAckReqDetails(multiTidRequest)); + ASSERT(!getOneTidBlockAckReqDetails(makeShared())); + hcf.processControlHeader(basicRequest); + hcf.processControlHeader(compressedRequest); + ASSERT(spy->variants == std::vector({ OneTidBlockAckReqVariant::BASIC, OneTidBlockAckReqVariant::COMPRESSED })); + ASSERT(spy->tids == std::vector({ 3, 5 })); + bool multiTidRejectedAsUnknown = false; + try { + hcf.processControlHeader(multiTidRequest); + } + catch (const cRuntimeError& error) { + multiTidRejectedAsUnknown = error.getFormattedMessage() == "Unknown control frame"; + } + ASSERT(multiTidRejectedAsUnknown); + ASSERT(spy->variants.size() == 2); + EV << "HCF accepts one-TID BAR variants and rejects Multi-TID as unknown.\n"; +} + { TestRecipientBlockAckProcedure procedure; auto request = makeCompressedBlockAckReq(); @@ -265,6 +331,8 @@ static std::pair> makeReceivedQosFrame(Sequen record.advanceStartingSequenceNumber(SequenceNumberCyclic(104)); ASSERT(!record.hasRecordedAckState(SequenceNumberCyclic(102), 0)); ASSERT(record.hasRecordedAckState(SequenceNumberCyclic(105), 0)); + record.dataFrameReceived(received102.second, 64); + ASSERT(!record.hasRecordedAckState(SequenceNumberCyclic(102), 0)); delete received102.first; delete received105.first; EV << "Receive-window advancement erases old state and retains overlap.\n"; @@ -357,6 +425,56 @@ static std::pair> makeReceivedQosFrame(Sequen EV << "Future MPDU returns displaced and consecutive complete MSDUs without losing gaps.\n"; } +{ + // IEEE Std 802.11-2024, 9.4.1.13 footnote 26 and 10.25.6.6.2.1(b): + // fragments consume individual slots, and a rejected future MPDU must not move WinStartB. + MacAddress originatorAddress("11:22:33:44:55:66"); + TestBlockAckReordering reordering; + RecipientBlockAckAgreement agreement(originatorAddress, 5, SequenceNumberCyclic(1), 2, SIMTIME_ZERO); + auto incomplete2Fragment0 = makeReceivedQosFrame(2, 0, true); + auto incomplete2Fragment1 = makeReceivedQosFrame(2, 1, true); + ASSERT(reordering.processReceivedQoSFrame(&agreement, incomplete2Fragment0.first, incomplete2Fragment0.second).empty()); + ASSERT(reordering.processReceivedQoSFrame(&agreement, incomplete2Fragment1.first, incomplete2Fragment1.second).empty()); + auto receiveBuffer = reordering.getReceiveBuffer(5, originatorAddress); + ASSERT(receiveBuffer->getLength() == 2); + ASSERT(receiveBuffer->getBuffer().at(2)[0] == incomplete2Fragment0.first); + ASSERT(receiveBuffer->getBuffer().at(2)[1] == incomplete2Fragment1.first); + + auto received3 = makeReceivedQosFrame(3); + ASSERT(reordering.processReceivedQoSFrame(&agreement, received3.first, received3.second).empty()); + ASSERT(receiveBuffer->getNextExpectedSequenceNumber() == SequenceNumberCyclic(1)); + ASSERT(receiveBuffer->getLength() == 2); + ASSERT(receiveBuffer->getBuffer().size() == 1); + ASSERT(receiveBuffer->getBuffer().at(2)[0] == incomplete2Fragment0.first); + ASSERT(receiveBuffer->getBuffer().at(2)[1] == incomplete2Fragment1.first); + ASSERT(receiveBuffer->getBuffer().find(3) == receiveBuffer->getBuffer().end()); + ASSERT(agreement.getStartingSequenceNumber() == SequenceNumberCyclic(2)); + ASSERT(agreement.getBlockAckRecord()->getCompressedAckState(SequenceNumberCyclic(3))); + EV << "Rejected future MPDU preserves a fragment-full reorder window but updates the scoreboard.\n"; +} + +{ + MacAddress originatorAddress("11:22:33:44:55:66"); + TestBlockAckReordering reordering; + RecipientBlockAckAgreement agreement(originatorAddress, 5, SequenceNumberCyclic(4095), 2, SIMTIME_ZERO); + auto incomplete4095 = makeReceivedQosFrame(4095, 0, true); + auto incomplete0 = makeReceivedQosFrame(0, 0, true); + ASSERT(reordering.processReceivedQoSFrame(&agreement, incomplete4095.first, incomplete4095.second).empty()); + ASSERT(reordering.processReceivedQoSFrame(&agreement, incomplete0.first, incomplete0.second).empty()); + auto receiveBuffer = reordering.getReceiveBuffer(5, originatorAddress); + ASSERT(receiveBuffer->getLength() == 2); + + auto received1 = makeReceivedQosFrame(1); + ASSERT(reordering.processReceivedQoSFrame(&agreement, received1.first, received1.second).empty()); + ASSERT(receiveBuffer->getNextExpectedSequenceNumber() == SequenceNumberCyclic(0)); + ASSERT(receiveBuffer->getLength() == 2); + ASSERT(receiveBuffer->getBuffer().find(4095) == receiveBuffer->getBuffer().end()); + ASSERT(receiveBuffer->getBuffer().at(0).size() == 1 && receiveBuffer->getBuffer().at(0)[0] == incomplete0.first); + ASSERT(receiveBuffer->getBuffer().at(1).size() == 1 && receiveBuffer->getBuffer().at(1)[0] == received1.first); + ASSERT(agreement.getStartingSequenceNumber() == SequenceNumberCyclic(0)); + EV << "Future MPDU reclaims a displaced slot across sequence wrap before advancing WinStartB.\n"; +} + { MacAddress originatorAddress("11:22:33:44:55:66"); TestRecipientBlockAckProcedure procedure; @@ -369,6 +487,15 @@ static std::pair> makeReceivedQosFrame(Sequen request->setStartingSequenceNumber(SequenceNumberCyclic(1)); auto response = dynamicPtrCast(procedure.buildBlockAck(request, &agreement)); ASSERT(response->getBlockAckBitmap().getBit(63)); + auto basicRequest = makeShared(); + basicRequest->setTidInfo(5); + basicRequest->setStartingSequenceNumber(SequenceNumberCyclic(0)); + auto basicResponse = dynamicPtrCast(procedure.buildBlockAck(basicRequest, &agreement)); + for (FragmentNumber fragmentNumber = 0; fragmentNumber < 16; fragmentNumber++) + ASSERT(basicResponse->getBlockAckBitmap(0).getBit(fragmentNumber)); + for (int sequenceIndex = 1; sequenceIndex < 64; sequenceIndex++) + for (FragmentNumber fragmentNumber = 0; fragmentNumber < 16; fragmentNumber++) + ASSERT(!basicResponse->getBlockAckBitmap(sequenceIndex).getBit(fragmentNumber)); EV << "Normal-Ack data advances and updates the Block Ack scoreboard.\n"; } @@ -685,6 +812,7 @@ EV << ".\n"; %contains: stdout Compressed BAR encoding and round-trip passed. Compressed BA encoding and round-trip passed. +HCF accepts one-TID BAR variants and rejects Multi-TID as unknown. Missing recipient state produces a null compressed BA. Empty recipient record produces a null compressed BA. Empty Basic BA distinguishes old and current-window MPDUs. @@ -694,6 +822,8 @@ Established agreement produces the expected 64-bit bitmap. Compressed bitmap preserves leading receive-window holes. SN64 advances a size-64 scoreboard and remains buffered across a gap. Future MPDU returns displaced and consecutive complete MSDUs without losing gaps. +Rejected future MPDU preserves a fragment-full reorder window but updates the scoreboard. +Future MPDU reclaims a displaced slot across sequence wrap before advancing WinStartB. Normal-Ack data advances and updates the Block Ack scoreboard. Upward delivery advances only the reorder cursor, not WinStartR. Data-window slide preserves cyclic 4095-to-0 delivery order. From 59ceed5d6346864cedf47b344c19535d1e29f365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Sun, 16 Aug 2026 13:37:51 +0200 Subject: [PATCH 7/8] fix(ieee80211): record frames at Block Ack window start Accept WinStartR in the receiver Block Ack bookkeeping and add focused Basic and Compressed Block Ack coverage for the boundary. --- .../ieee80211/mac/blockack/BlockAckRecord.cc | 5 ++- tests/unit/Ieee80211BlockAckRecord_1.test | 40 +++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 tests/unit/Ieee80211BlockAckRecord_1.test diff --git a/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc b/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc index e212ed19537..54d478446b1 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc @@ -24,8 +24,9 @@ void BlockAckRecord::dataFrameReceived(const Ptr& hea SequenceNumberCyclic sequenceNumber = header->getSequenceNumber(); FragmentNumber fragmentNumber = header->getFragmentNumber(); // IEEE Std 802.11-2024, 10.25.6.3(b), case 3: an old related MPDU - // does not change the Block Ack record. - if (!(startingSequenceNumber <= sequenceNumber && sequenceNumber < startingSequenceNumber + 2048)) + // does not change the Block Ack record. The cyclic comparison below already + // restricts the accepted range to [WinStartR, WinStartR + 2047]. + if (!(startingSequenceNumber <= sequenceNumber)) return; // Cases 1 and 2: record an in-window MPDU, or advance WinStartR // before recording an MPDU beyond WinEndR (also see 10.25.6.4(c)). diff --git a/tests/unit/Ieee80211BlockAckRecord_1.test b/tests/unit/Ieee80211BlockAckRecord_1.test new file mode 100644 index 00000000000..99b9e16a7c4 --- /dev/null +++ b/tests/unit/Ieee80211BlockAckRecord_1.test @@ -0,0 +1,40 @@ +%description: +Validate that a Block Ack record accepts the MPDU at WinStartR and reports Basic fragment and Compressed state. +IEEE Std 802.11-2024, 10.25.6.3(b), case 1. + +%includes: +#include "inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.h" + +%global: +using namespace inet; +using namespace inet::ieee80211; + +static Ptr makeReceivedQosHeader(SequenceNumber sequenceNumber) +{ + auto header = makeShared(); + header->setType(ST_DATA_WITH_QOS); + header->setTid(5); + header->setSequenceNumber(SequenceNumberCyclic(sequenceNumber)); + header->setFragmentNumber(0); + return header; +} + +%activity: +{ + BlockAckRecord record(MacAddress("11:22:33:44:55:66"), 5, SequenceNumberCyclic(101)); + record.dataFrameReceived(makeReceivedQosHeader(101), 64); + record.dataFrameReceived(makeReceivedQosHeader(103), 64); + ASSERT(record.getAckState(SequenceNumberCyclic(101), 0)); + ASSERT(!record.getAckState(SequenceNumberCyclic(102), 0)); + ASSERT(record.getAckState(SequenceNumberCyclic(103), 0)); + ASSERT(record.getCompressedAckState(SequenceNumberCyclic(101))); + ASSERT(!record.getCompressedAckState(SequenceNumberCyclic(102))); + ASSERT(record.getCompressedAckState(SequenceNumberCyclic(103))); + EV << "IEEE Std 802.11-2024 10.25.6.3(b) WinStartR boundary is recorded in Basic and Compressed Block Ack state.\n"; +} + +EV << ".\n"; + +%contains: stdout +IEEE Std 802.11-2024 10.25.6.3(b) WinStartR boundary is recorded in Basic and Compressed Block Ack state. +. From ca929104d5f1f1dec4f83a87660cf38678504b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Sun, 16 Aug 2026 15:07:14 +0200 Subject: [PATCH 8/8] test(ieee80211): update Block Ack fingerprints Refresh five maintained non-tyf fingerprint expectations for the Block Ack and QoS scenarios affected by the current IEEE 802.11 MAC behavior changes. Updated cases include BlockAck showcase NoFragmentation and MixedTraffic, fragmentation HCFfragblockack, adhoc QoS MacQos run 1, and wireless QoS MacQosWithBlockAck. Focused debug validation passed all 17 selected tests with tplx, ~tNl, and ~tND. The full suite was intentionally skipped after the focused validation. --- tests/fingerprint/examples.csv | 4 ++-- tests/fingerprint/showcases.csv | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/fingerprint/examples.csv b/tests/fingerprint/examples.csv index 8d31686982e..bc3af66b2f9 100644 --- a/tests/fingerprint/examples.csv +++ b/tests/fingerprint/examples.csv @@ -7,7 +7,7 @@ # /examples/adhoc/ieee80211/, -f omnetpp.ini -c Ping2 -r 0, 100s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND, ERROR, wireless adhoc # [Config Ping2] # interactive config, needed a *.numHosts parameter /examples/adhoc/qos/, -f omnetpp.ini -c MacNonQos -r 0, 10s, 5749-0281/tplx;f38a-cb93/~tNl;fd9b-683f/~tND;0eb8-3e3b/tyf, PASS, wireless adhoc Ipv4 /examples/adhoc/qos/, -f omnetpp.ini -c MacQos -r 0, 10s, 8d50-a4b1/tplx;d392-4369/~tNl;8da4-91dc/~tND;2c1e-66e5/tyf, PASS, wireless adhoc Ipv4 -/examples/adhoc/qos/, -f omnetpp.ini -c MacQos -r 1, 10s, 5a48-dd94/tplx;45f0-2ffe/~tNl;8d6d-ad67/~tND;a587-3a4d/tyf, PASS, wireless adhoc Ipv4 +/examples/adhoc/qos/, -f omnetpp.ini -c MacQos -r 1, 10s, 5a48-dd94/tplx;45f0-2ffe/~tNl;fc7a-58e1/~tND;a587-3a4d/tyf, PASS, wireless adhoc Ipv4 /examples/adhoc/qos/, -f omnetpp.ini -c Fragmentation, 10s, 9c66-e6f0/tplx;cc9e-d1a6/~tNl;53c8-e1b0/~tND;33b7-00f9/tyf, PASS, wireless adhoc Ipv4 /examples/adhoc/qos/, -f omnetpp.ini -c MsduAggregation, 10s, 6139-16b9/tplx;cfcf-1e52/~tNl;31df-c237/~tND;df04-18f7/tyf, PASS, wireless adhoc Ipv4 @@ -658,7 +658,7 @@ /examples/wireless/qos/, -f omnetpp.ini -c MacQos -r 0, 10s, 5e57-04bd/tplx;68a7-f409/~tNl;53ea-a840/~tND;9f3c-4512/tyf, PASS, wireless Ipv4 /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, cf83-e60c/tplx;51b0-197f/~tNl;9291-3bf1/~tND;431e-d96c/tyf, PASS, wireless Ipv4 +/examples/wireless/qos/, -f omnetpp.ini -c MacQosWithBlockAck -r 0, 10s, e286-f28c/tplx;c4b0-6dfb/~tNl;03a5-3c33/~tND;431e-d96c/tyf, 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/fingerprint/showcases.csv b/tests/fingerprint/showcases.csv index efa9fe7bf5f..fa515480748 100644 --- a/tests/fingerprint/showcases.csv +++ b/tests/fingerprint/showcases.csv @@ -199,9 +199,9 @@ /showcases/wireless/analogmodel/, -f omnetpp.ini -c Distance -r 0, 2.5s, 1e75-270e/tplx;6d7a-d84c/~tNl;b380-6cd5/~tND;5575-fd8f/tyf, PASS, wireless Ipv4 /showcases/wireless/analogmodel/, -f omnetpp.ini -c Noise -r 0, 0.1s, dd08-a63c/tplx;e167-9c84/~tNl;643d-41a6/~tND;0d1f-df73/tyf, PASS, wireless Ipv4 -/showcases/wireless/blockack/, -f omnetpp.ini -c NoFragmentation -r 0, 1s, 5b0b-f055/tplx;8511-cc76/~tNl;5847-83de/~tND, PASS, wireless Ipv4 +/showcases/wireless/blockack/, -f omnetpp.ini -c NoFragmentation -r 0, 1s, a3c4-e51b/tplx;2b40-8d27/~tNl;72e2-3ef7/~tND, PASS, wireless Ipv4 /showcases/wireless/blockack/, -f omnetpp.ini -c Fragmentation -r 0, 1s, 7f81-62c8/tplx;2638-6c00/~tNl;82ef-52a1/~tND, PASS, wireless Ipv4 -/showcases/wireless/blockack/, -f omnetpp.ini -c MixedTraffic -r 0, 1s, 26b2-73e4/tplx;d7bc-6c9e/~tNl;cfda-783c/~tND, PASS, wireless Ipv4 +/showcases/wireless/blockack/, -f omnetpp.ini -c MixedTraffic -r 0, 1s, 7146-df3a/tplx;2a12-37dc/~tNl;7ffb-9931/~tND, PASS, wireless Ipv4 /showcases/wireless/crosstalk/, -f omnetpp.ini -c CompletelyOverlappingFrequencyBands -r 0, 1s, d0d7-43e0/tplx;867a-07a4/~tNl;df78-8445/~tND;3ea3-43da/tyf, PASS, wireless Ipv4 /showcases/wireless/crosstalk/, -f omnetpp.ini -c IndependentFrequencyBandsOneRadioMediumModule -r 0, 1s, 70c6-72b6/tplx;cf96-5e4d/~tNl;3cba-ae59/~tND;d1b2-9fd4/tyf, PASS, wireless Ipv4 @@ -267,7 +267,7 @@ /showcases/wireless/fragmentation/, -f omnetpp.ini -c DCFnofrag -r 0, 1s, 52b9-628f/tplx;3fec-74a2/~tNl;6073-4582/~tND;8871-1dd1/tyf, PASS, wireless Ipv4 /showcases/wireless/fragmentation/, -f omnetpp.ini -c DCFfrag -r 0, 1s, 57ee-7ddf/tplx;dab9-5e8d/~tNl;7f9b-00fc/~tND;f985-34fb/tyf, PASS, wireless Ipv4 /showcases/wireless/fragmentation/, -f omnetpp.ini -c HCFfrag -r 0, 1s, 73ec-f869/tplx;2366-9a07/~tNl;dcb8-5554/~tND;335d-6687/tyf, PASS, wireless Ipv4 -/showcases/wireless/fragmentation/, -f omnetpp.ini -c HCFfragblockack -r 0, 1s, 598f-9c58/tplx;0010-a266/~tNl;0533-8742/~tND;6f6e-b101/tyf, PASS, wireless Ipv4 +/showcases/wireless/fragmentation/, -f omnetpp.ini -c HCFfragblockack -r 0, 1s, 598f-9c58/tplx;0010-a266/~tNl;7be2-50fc/~tND;6f6e-b101/tyf, PASS, wireless Ipv4 /showcases/wireless/handover/, -f omnetpp.ini -c General -r 0, 250s, 47b6-4dfd/tplx;a78b-61da/~tNl;b639-081a/~tND;02e9-9ad3/tyf, PASS, wireless