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..54d478446b1 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc @@ -12,16 +12,26 @@ 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) { } -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), case 3: an old related MPDU + // 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)). + if (startingSequenceNumber + windowSize <= sequenceNumber && sequenceNumber < startingSequenceNumber + 2048) + advanceStartingSequenceNumber(sequenceNumber - windowSize + 1); acknowledgmentState[SequenceControlField(sequenceNumber.get(), fragmentNumber)] = true; } @@ -30,29 +40,31 @@ 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; } -void BlockAckRecord::removeAckStates(SequenceNumberCyclic sequenceNumber) +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::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++; } + startingSequenceNumber = newStartingSequenceNumber; } } /* 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..c4bb3783ceb 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.h @@ -24,22 +24,24 @@ 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); + void dataFrameReceived(const Ptr& header, int windowSize); bool getAckState(SequenceNumberCyclic sequenceNumber, FragmentNumber fragmentNumber); - void removeAckStates(SequenceNumberCyclic sequenceNumber); + bool getCompressedAckState(SequenceNumberCyclic sequenceNumber); + void advanceStartingSequenceNumber(SequenceNumberCyclic startingSequenceNumber); MacAddress getOriginatorAddress() { return originatorAddress; } Tid getTid() { return tid; } + SequenceNumberCyclic getStartingSequenceNumber() const { return startingSequenceNumber; } }; } /* namespace ieee80211 */ } /* namespace inet */ #endif - 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/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 2f11dc365db..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; } @@ -81,6 +82,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"); } @@ -126,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); } @@ -136,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 { @@ -144,11 +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(); } @@ -180,4 +191,3 @@ OriginatorBlockAckAgreementHandler::~OriginatorBlockAckAgreementHandler() } // namespace ieee80211 } // namespace inet - diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h index 4a449d17da3..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/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..62cc573194b 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.cc @@ -13,25 +13,23 @@ namespace inet { namespace ieee80211 { RecipientBlockAckAgreement::RecipientBlockAckAgreement(MacAddress originatorAddress, Tid tid, SequenceNumberCyclic startingSequenceNumber, int bufferSize, simtime_t lastUsedTime) : - startingSequenceNumber(startingSequenceNumber), bufferSize(bufferSize), blockAckTimeoutValue(lastUsedTime) { calculateExpirationTime(); - blockAckRecord = new BlockAckRecord(originatorAddress, tid); + 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; @@ -39,4 +37,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..2bfc94b040b 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h @@ -18,24 +18,27 @@ class INET_API RecipientBlockAckAgreement : public cObject protected: BlockAckRecord *blockAckRecord = nullptr; - SequenceNumberCyclic startingSequenceNumber; int bufferSize = -1; simtime_t blockAckTimeoutValue = 0; bool isAddbaResponseSent = false; + bool isDelayedBlockAckPolicySupported = false; simtime_t expirationTime = -1; public: 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; } 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 +48,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..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 { @@ -18,34 +19,60 @@ 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; + // 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): 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)); + // 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); - 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); - 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); - return ReorderBuffer({ std::make_pair(earliestSequenceNumber.get(), Fragments(earliestCompleteMsduOrAMsdu)) }); - } + 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)) }); + } + // 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 ReorderBuffer({}); + return framesToPassUp; } // @@ -55,20 +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 id = std::make_pair(tid, blockAckReq->getTransmitterAddress()); + 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(blockAckReqDetails->tid, blockAckReq->getTransmitterAddress()); auto it = receiveBuffers.find(id); if (it != receiveBuffers.end()) { ReceiveBuffer *receiveBuffer = it->second; @@ -81,19 +102,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 +127,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 +161,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 +224,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 +232,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) @@ -244,4 +268,3 @@ BlockAckReordering::~BlockAckReordering() } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h index a50606f8a53..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/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/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/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..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 @@ -597,8 +598,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 +616,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 +697,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 +785,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..3c0c29c05bc 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.cc @@ -52,15 +52,28 @@ 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); +} + +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. + // 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) { + 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 +153,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); 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 +50,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..ea9835be26a 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.ned +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.ned @@ -26,4 +26,3 @@ simple OriginatorQosAckPolicy extends SimpleModule like IOriginatorQosAckPolicy @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..9448f7b0d5c 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"); @@ -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; @@ -248,4 +249,3 @@ void QosRateSelection::frameTransmitted(Packet *packet, const Ptr(getModuleByPath(par("rateSelectionModule"))); - } } -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 +67,21 @@ bool RecipientQosAckPolicy::isBlockAckNeeded(const Ptr(blockAckReq)) + return isCompressedBlockAckNeeded(compressedBlockAckReq, agreement); else throw cRuntimeError("Unsupported BlockAckReq"); } +bool RecipientQosAckPolicy::isCompressedBlockAckNeeded(const Ptr& blockAckReq, RecipientBlockAckAgreement *agreement) +{ + // 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; + 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 +101,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..d74e0ecc962 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h @@ -25,7 +25,8 @@ class INET_API RecipientQosAckPolicy : public ModeSetListener, public IRecipient 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); + simtime_t computeBlockAckDuration(Packet *packet, const Ptr& blockAckReq) const; simtime_t computeAckDuration(Packet *packet, const Ptr& dataOrMgmtHeader) const; public: @@ -33,11 +34,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..6fb9639b69e 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.ned +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.ned @@ -20,4 +20,3 @@ simple RecipientQosAckPolicy extends SimpleModule like IRecipientQosAckPolicy string rateSelectionModule; @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..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" @@ -67,7 +68,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(); @@ -136,14 +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 = blockAckReq->getTidInfo(); - 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(); } @@ -196,4 +196,3 @@ RecipientQosMacDataService::~RecipientQosMacDataService() } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/tests/fingerprint/examples.csv b/tests/fingerprint/examples.csv index 283915ab865..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, 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;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, 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, 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 c483f40c8a3..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, 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, 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, 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, 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;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 @@ -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 diff --git a/tests/module/Ieee80211CompressedBlockAckRuntime.test b/tests/module/Ieee80211CompressedBlockAckRuntime.test new file mode 100644 index 00000000000..039df324eb4 --- /dev/null +++ b/tests/module/Ieee80211CompressedBlockAckRuntime.test @@ -0,0 +1,40 @@ +%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)" +**.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 +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/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. +. diff --git a/tests/unit/Ieee80211CompressedBlockAck_1.test b/tests/unit/Ieee80211CompressedBlockAck_1.test new file mode 100644 index 00000000000..4a2a7b708d8 --- /dev/null +++ b/tests/unit/Ieee80211CompressedBlockAck_1.test @@ -0,0 +1,842 @@ +%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/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" +#include "inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211OfdmMode.h" + +%global: +using namespace inet; +using namespace inet::ieee80211; + +class TestRecipientBlockAckProcedure : public RecipientBlockAckProcedure +{ + public: + 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: + 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) + { + return isCompressedBlockAckReqNeeded(outstandingFrames, agreement); + } +}; + +class TestRecipientQosAckPolicy : public RecipientQosAckPolicy +{ + public: + static bool isCompressedResponseNeeded(const Ptr& request, RecipientBlockAckAgreement *agreement) + { + return isCompressedBlockAckNeeded(request, agreement); + } +}; + +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) +{ + 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); +} + +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(); + 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"; +} + +{ + 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(); + 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); + 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)); + 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"; +} + +{ + 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.dataFrameReceived(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.dataFrameReceived(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"; +} + +{ + 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"; +} + +{ + // 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; + 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)); + 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"; +} + +{ + 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); + 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.dataFrameReceived(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.dataFrameReceived(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)); + request->setFragmentNumber(1); + 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)); + delayedAgreement.setIsDelayedBlockAckPolicySupported(false); + ASSERT(TestRecipientQosAckPolicy::isCompressedResponseNeeded(request, &delayedAgreement)); + 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)); + ASSERT(!TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, nullptr)); + immediateAgreement.setIsCompressedBlockAckSupported(true); + OriginatorBlockAckAgreement delayedAgreement(receiverAddress, 5, SequenceNumberCyclic(100), 64, false, true); + delayedAgreement.setIsAddbaResponseReceived(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)); + delete outstandingFrames[0]; + outstandingFrames = { makeOutstandingQosFrame(receiverAddress, 5, 0, true) }; + ASSERT(!TestOriginatorQosAckPolicy::isCompressedRequestNeeded(outstandingFrames, &immediateAgreement)); + 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; + 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); + 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"; +} + +{ + // 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 +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. +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. +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. +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. +Basic and Compressed BAR rate selection is variant-agnostic. +.