From f2f013090cd39849831c74205eba8b7801ff9f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Wed, 16 Sep 2026 00:23:05 +0200 Subject: [PATCH 1/3] ieee80211: fix: clamp negative Block Ack response durations An undersized BAR reservation must yield zero response Duration rather than a negative model value or unsigned wire wrap. Test insufficient, exact, and positive reservations through the production policy. Change: src.ieee80211.duration | behavior.change.fix | test | wifi-audit --- .../mac/recipient/RecipientQosAckPolicy.cc | 5 ++- tests/unit/Ieee80211BlockAckDuration_1.test | 45 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 tests/unit/Ieee80211BlockAckDuration_1.test diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc index 29f793f0110..1e4370b416f 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc @@ -91,9 +91,10 @@ simtime_t RecipientQosAckPolicy::computeAckDurationField(Packet *packet, const P // simtime_t RecipientQosAckPolicy::computeBasicBlockAckDurationField(Packet *packet, const Ptr& basicBlockAckReq) const { - return basicBlockAckReq->getDurationField() - modeSet->getSifsTime() - computeBasicBlockAckDuration(packet, basicBlockAckReq); + simtime_t duration = basicBlockAckReq->getDurationField() - modeSet->getSifsTime() - computeBasicBlockAckDuration(packet, basicBlockAckReq); + // IEEE Std 802.11-2024, 9.2.5.1: a negative calculated Duration/ID is inserted as zero. + return duration < 0 ? 0 : duration; } } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/tests/unit/Ieee80211BlockAckDuration_1.test b/tests/unit/Ieee80211BlockAckDuration_1.test new file mode 100644 index 00000000000..1df6bb96c5f --- /dev/null +++ b/tests/unit/Ieee80211BlockAckDuration_1.test @@ -0,0 +1,45 @@ +%description: +A Basic BlockAck response must clamp a negative calculated Duration to zero +before it reaches the model header or serializer (IEEE 802.11-2024, 9.2.5.1). + +%includes: +#include "inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h" +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +%global: +class FixedBlockAckRate : public IQosRateSelection +{ + public: + const IIeee80211Mode *mode = Ieee80211ModeSet::getModeSet("a")->getMode(Mbps(24)); + virtual const IIeee80211Mode *computeResponseBlockAckFrameMode(Packet *, const Ptr&) override { return mode; } + virtual const IIeee80211Mode *computeResponseCtsFrameMode(Packet *, const Ptr&) override { throw cRuntimeError("unexpected CTS lookup"); } + virtual const IIeee80211Mode *computeResponseAckFrameMode(Packet *, const Ptr&) override { throw cRuntimeError("unexpected ACK lookup"); } + virtual const IIeee80211Mode *computeMode(Packet *, const Ptr&, TxopProcedure *) override { throw cRuntimeError("unexpected data lookup"); } +}; +class BlockAckDurationPolicy : public RecipientQosAckPolicy +{ + public: + void configure(IQosRateSelection *selection) { + rateSelection = selection; + modeSet = const_cast(Ieee80211ModeSet::getModeSet("a")); + } +}; + +%activity: +FixedBlockAckRate rate; +BlockAckDurationPolicy policy; +policy.configure(&rate); +Packet packet("BAR"); +const auto elapsed = Ieee80211ModeSet::getModeSet("a")->getSifsTime() + rate.mode->getDuration(LENGTH_BASIC_BLOCKACK); +for (auto duration : {SIMTIME_ZERO, elapsed - SimTime(1, SIMTIME_US), elapsed, elapsed + SimTime(37, SIMTIME_US)}) { + auto bar = makeShared(); + bar->setDurationField(duration); + auto actual = policy.computeBasicBlockAckDurationField(&packet, bar); + ASSERT(actual == (duration <= elapsed ? SIMTIME_ZERO : SimTime(37, SIMTIME_US))); +} +EV << "BlockAck duration is zero for insufficient reservations and preserves positive residuals.\n"; + +%contains: stdout +BlockAck duration is zero for insufficient reservations and preserves positive residuals. From d7ce9abc1ae583004b4f4ecb6ee3441e97961614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Wed, 16 Sep 2026 00:23:05 +0200 Subject: [PATCH 2/3] ieee80211: fix: round ordinary Duration fields up to microseconds Fractional Duration calculations must reserve the next whole microsecond in typed transmitted headers as well as serialized bytes. Normalize at DCF, HCF and response construction boundaries, reject out-of-range ordinary durations, and keep special Duration/ID encodings out of received NAV calculations. Tests cover all seven serialized header families, fractional and range boundaries, and received special-ID filtering. Fixtures that serialize a pre-transmission header now explicitly assign zero Duration. PHY airtime remains unchanged. Change: src.ieee80211.duration | behavior.change.fix | test | wifi-audit --- .../ieee80211/mac/Ieee80211Duration.h | 37 ++++++++ .../mac/Ieee80211MacHeaderSerializer.cc | 15 ++-- src/inet/linklayer/ieee80211/mac/Rx.cc | 7 +- .../mac/aggregation/MsduDeaggregation.cc | 1 + .../blockack/RecipientBlockAckProcedure.cc | 2 + .../ieee80211/mac/coordinationfunction/Dcf.cc | 2 + .../ieee80211/mac/coordinationfunction/Hcf.cc | 2 + .../ieee80211/mac/recipient/CtsProcedure.cc | 2 + .../mac/recipient/RecipientAckProcedure.cc | 2 + tests/unit/Ieee80211DurationRounding_1.test | 86 +++++++++++++++++++ tests/unit/Ieee80211MsduAggregation_1.test | 2 + .../unit/Ieee80211OnWireBitCompliance_1.test | 1 + 12 files changed, 150 insertions(+), 9 deletions(-) create mode 100644 src/inet/linklayer/ieee80211/mac/Ieee80211Duration.h create mode 100644 tests/unit/Ieee80211DurationRounding_1.test diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211Duration.h b/src/inet/linklayer/ieee80211/mac/Ieee80211Duration.h new file mode 100644 index 00000000000..33879c59eaa --- /dev/null +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Duration.h @@ -0,0 +1,37 @@ +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +#ifndef __INET_IEEE80211DURATION_H +#define __INET_IEEE80211DURATION_H + +#include "inet/common/INETDefs.h" + +namespace inet { +namespace ieee80211 { + +inline simtime_t normalizeIeee80211Duration(simtime_t duration) +{ + // IEEE Std 802.11-2024, 9.2.5.1: negative calculations become zero; fractions round upward. + if (duration <= SIMTIME_ZERO) + return SIMTIME_ZERO; + // 9.2.4.2, Table 9-9: ordinary Duration is 15 bits. AID is a separate representation. + if (duration > SimTime(32767, SIMTIME_US)) + throw cRuntimeError("Calculated IEEE 802.11 Duration exceeds 32767 microseconds"); + auto microseconds = duration.inUnit(SIMTIME_US); + if (SimTime(microseconds, SIMTIME_US) < duration) + microseconds++; + return SimTime(microseconds, SIMTIME_US); +} + +inline uint16_t encodeIeee80211Duration(simtime_t duration) +{ + if (duration < SIMTIME_ZERO) + throw cRuntimeError("Cannot serialize an unset or negative IEEE 802.11 Duration"); + return static_cast(normalizeIeee80211Duration(duration).inUnit(SIMTIME_US)); +} + +} // namespace ieee80211 +} // namespace inet + +#endif diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc b/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc index 2a482cded2f..c33160c40bd 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc @@ -6,6 +6,7 @@ #include "inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Duration.h" #include "inet/common/checksum/Checksum.h" #include "inet/common/packet/serializer/ChunkSerializerRegistry.h" @@ -185,7 +186,7 @@ void Ieee80211MacHeaderSerializer::serializeFields(MemoryOutputStream& stream, c // variants -- not just plain Data (ST_DATA) and QoS-Data (ST_DATA_WITH_QOS). if (macHeader->getFrameType() == 2) { auto dataHeader = dynamicPtrCast(chunk); - stream.writeUint16Le(dataHeader->getDurationField().inUnit(SIMTIME_US)); + stream.writeUint16Le(ieee80211::encodeIeee80211Duration(dataHeader->getDurationField())); stream.writeMacAddress(dataHeader->getReceiverAddress()); stream.writeMacAddress(dataHeader->getTransmitterAddress()); stream.writeMacAddress(dataHeader->getAddress3()); @@ -222,7 +223,7 @@ void Ieee80211MacHeaderSerializer::serializeFields(MemoryOutputStream& stream, c case ST_ACTION: case ST_NOACKACTION: { auto mgmtHeader = dynamicPtrCast(chunk); - stream.writeUint16Le(mgmtHeader->getDurationField().inUnit(SIMTIME_US)); + stream.writeUint16Le(ieee80211::encodeIeee80211Duration(mgmtHeader->getDurationField())); stream.writeMacAddress(mgmtHeader->getReceiverAddress()); stream.writeMacAddress(mgmtHeader->getTransmitterAddress()); stream.writeMacAddress(mgmtHeader->getAddress3()); @@ -298,7 +299,7 @@ void Ieee80211MacHeaderSerializer::serializeFields(MemoryOutputStream& stream, c } case ST_RTS: { auto rtsFrame = dynamicPtrCast(chunk); - stream.writeUint16Le(rtsFrame->getDurationField().inUnit(SIMTIME_US)); + stream.writeUint16Le(ieee80211::encodeIeee80211Duration(rtsFrame->getDurationField())); stream.writeMacAddress(rtsFrame->getReceiverAddress()); stream.writeMacAddress(rtsFrame->getTransmitterAddress()); if (stream.getLength() - startPos != rtsFrame->getChunkLength()) @@ -308,7 +309,7 @@ void Ieee80211MacHeaderSerializer::serializeFields(MemoryOutputStream& stream, c } case ST_CTS: { auto ctsFrame = dynamicPtrCast(chunk); - stream.writeUint16Le(ctsFrame->getDurationField().inUnit(SIMTIME_US)); + stream.writeUint16Le(ieee80211::encodeIeee80211Duration(ctsFrame->getDurationField())); stream.writeMacAddress(ctsFrame->getReceiverAddress()); if (stream.getLength() - startPos != ctsFrame->getChunkLength()) throw cRuntimeError("Cannot serialize the Ieee80211CtsFrame: chunkLength is %d B, does not match the %d B of serialized fields", @@ -317,7 +318,7 @@ void Ieee80211MacHeaderSerializer::serializeFields(MemoryOutputStream& stream, c } case ST_ACK: { auto ackFrame = dynamicPtrCast(chunk); - stream.writeUint16Le(ackFrame->getDurationField().inUnit(SIMTIME_US)); + stream.writeUint16Le(ieee80211::encodeIeee80211Duration(ackFrame->getDurationField())); stream.writeMacAddress(ackFrame->getReceiverAddress()); if (stream.getLength() - startPos != ackFrame->getChunkLength()) throw cRuntimeError("Cannot serialize the Ieee80211AckFrame: chunkLength is %d B, does not match the %d B of serialized fields", @@ -326,7 +327,7 @@ void Ieee80211MacHeaderSerializer::serializeFields(MemoryOutputStream& stream, c } case ST_BLOCKACK_REQ: { auto blockAckReq = dynamicPtrCast(chunk); - stream.writeUint16Le(blockAckReq->getDurationField().inUnit(SIMTIME_US)); + stream.writeUint16Le(ieee80211::encodeIeee80211Duration(blockAckReq->getDurationField())); stream.writeMacAddress(blockAckReq->getReceiverAddress()); stream.writeMacAddress(blockAckReq->getTransmitterAddress()); bool multiTid = blockAckReq->getMultiTid(); @@ -362,7 +363,7 @@ void Ieee80211MacHeaderSerializer::serializeFields(MemoryOutputStream& stream, c } case ST_BLOCKACK: { auto blockAck = dynamicPtrCast(chunk); - stream.writeUint16Le(blockAck->getDurationField().inUnit(SIMTIME_US)); + stream.writeUint16Le(ieee80211::encodeIeee80211Duration(blockAck->getDurationField())); stream.writeMacAddress(blockAck->getReceiverAddress()); stream.writeMacAddress(blockAck->getTransmitterAddress()); bool multiTid = blockAck->getMultiTid(); diff --git a/src/inet/linklayer/ieee80211/mac/Rx.cc b/src/inet/linklayer/ieee80211/mac/Rx.cc index 19b9da0889c..0627ec68f5d 100644 --- a/src/inet/linklayer/ieee80211/mac/Rx.cc +++ b/src/inet/linklayer/ieee80211/mac/Rx.cc @@ -99,8 +99,11 @@ bool Rx::lowerFrameReceived(Packet *packet) if (isFrameOk) { EV_INFO << "Received frame from PHY: " << packet << endl; const auto& header = packet->peekAtFront(); - if (header->getReceiverAddress() != address) - setOrExtendNav(header->getDurationField()); + // IEEE Std 802.11-2024, 9.2.4.2: bit 15 distinguishes special + // Duration/ID encodings; they are not ordinary NAV intervals. + auto duration = header->getDurationField(); + if (header->getReceiverAddress() != address && duration >= SIMTIME_ZERO && duration <= SimTime(32767, SIMTIME_US)) + setOrExtendNav(duration); return true; } else { diff --git a/src/inet/linklayer/ieee80211/mac/aggregation/MsduDeaggregation.cc b/src/inet/linklayer/ieee80211/mac/aggregation/MsduDeaggregation.cc index bd4b3e82321..6681e7ae443 100644 --- a/src/inet/linklayer/ieee80211/mac/aggregation/MsduDeaggregation.cc +++ b/src/inet/linklayer/ieee80211/mac/aggregation/MsduDeaggregation.cc @@ -69,6 +69,7 @@ std::vector *MsduDeaggregation::deaggregateFrame(Packet *aggregatedFra if (header->getToDS() && header->getFromDS()) header->addChunkLength(B(6)); header->setTid(tid); + header->setDurationField(amsduHeader->getDurationField()); header->setSequenceNumber(SequenceNumberCyclic(0)); setExplodedFrameAddress(header, msduSubframeHeader, amsduHeader); frame->insertAtFront(header); diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckProcedure.cc b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckProcedure.cc index ab7cb050e5f..74d63f2c446 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckProcedure.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckProcedure.cc @@ -5,6 +5,7 @@ // +#include "inet/linklayer/ieee80211/mac/Ieee80211Duration.h" #include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckProcedure.h" #include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h" @@ -25,6 +26,7 @@ void RecipientBlockAckProcedure::processReceivedBlockAckReq(Packet *blockAckPack if (ackPolicy->isBlockAckNeeded(basicBlockAckReq, agreement)) { auto blockAck = buildBlockAck(basicBlockAckReq, agreement); auto duration = ackPolicy->computeBasicBlockAckDurationField(blockAckPacketReq, basicBlockAckReq); + duration = normalizeIeee80211Duration(duration); blockAck->setDurationField(duration); auto blockAckPacket = new Packet("BasicBlockAck", blockAck); EV_DEBUG << "Duration for " << blockAckPacket->getName() << " is set to " << duration << " s.\n"; diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc index ddb1cf9aa65..1452ed3ca71 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc @@ -5,6 +5,7 @@ // +#include "inet/linklayer/ieee80211/mac/Ieee80211Duration.h" #include "inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.h" #include "inet/common/ModuleAccess.h" @@ -194,6 +195,7 @@ void Dcf::transmitFrame(Packet *packet, simtime_t ifs) auto pendingPacket = channelAccess->getInProgressFrames()->getPendingFrameFor(packet); auto duration = originatorProtectionMechanism->computeDurationField(packet, header, pendingPacket, pendingPacket == nullptr ? nullptr : pendingPacket->peekAtFront()); const auto& updatedHeader = packet->removeAtFront(); + duration = normalizeIeee80211Duration(duration); updatedHeader->setDurationField(duration); EV_DEBUG << "Duration for " << packet->getName() << " is set to " << duration << " s.\n"; packet->insertAtFront(updatedHeader); diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc index e64ce4db852..9292a1f17ac 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc @@ -5,6 +5,7 @@ // +#include "inet/linklayer/ieee80211/mac/Ieee80211Duration.h" #include "inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h" #include "inet/common/ModuleAccess.h" @@ -719,6 +720,7 @@ void Hcf::transmitFrame(Packet *packet, simtime_t ifs) const auto& pendingHeader = pendingPacket == nullptr ? nullptr : pendingPacket->peekAtFront(); auto duration = singleProtectionMechanism->computeDurationField(packet, header, pendingPacket, pendingHeader, txop, recipientAckPolicy); auto header = packet->removeAtFront(); + duration = normalizeIeee80211Duration(duration); header->setDurationField(duration); EV_DEBUG << "Duration for " << packet->getName() << " is set to " << duration << " s.\n"; packet->insertAtFront(header); diff --git a/src/inet/linklayer/ieee80211/mac/recipient/CtsProcedure.cc b/src/inet/linklayer/ieee80211/mac/recipient/CtsProcedure.cc index dd0dad9936f..0cb1c24fdee 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/CtsProcedure.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/CtsProcedure.cc @@ -5,6 +5,7 @@ // +#include "inet/linklayer/ieee80211/mac/Ieee80211Duration.h" #include "inet/linklayer/ieee80211/mac/recipient/CtsProcedure.h" namespace inet { @@ -18,6 +19,7 @@ void CtsProcedure::processReceivedRts(Packet *rtsPacket, const PtrisCtsNeeded(rtsFrame)) { auto ctsFrame = buildCts(rtsFrame); auto duration = ctsPolicy->computeCtsDurationField(rtsPacket, rtsFrame); + duration = normalizeIeee80211Duration(duration); ctsFrame->setDurationField(duration); auto ctsPacket = new Packet("CTS", ctsFrame); EV_DEBUG << "Duration for " << ctsPacket->getName() << " is set to " << duration << " s.\n"; diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckProcedure.cc b/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckProcedure.cc index 064934a2c1c..fc4975eb49e 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckProcedure.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckProcedure.cc @@ -5,6 +5,7 @@ // +#include "inet/linklayer/ieee80211/mac/Ieee80211Duration.h" #include "inet/linklayer/ieee80211/mac/recipient/RecipientAckProcedure.h" namespace inet { @@ -18,6 +19,7 @@ void RecipientAckProcedure::processReceivedFrame(Packet *packet, const PtrisAckNeeded(dataOrMgmtHeader)) { auto ackFrame = buildAck(dataOrMgmtHeader); auto duration = ackPolicy->computeAckDurationField(packet, dataOrMgmtHeader); + duration = normalizeIeee80211Duration(duration); ackFrame->setDurationField(duration); auto ackPacket = new Packet("WlanAck", ackFrame); EV_DEBUG << "Duration for " << ackFrame->getName() << " is set to " << duration << " s.\n"; diff --git a/tests/unit/Ieee80211DurationRounding_1.test b/tests/unit/Ieee80211DurationRounding_1.test new file mode 100644 index 00000000000..2761a19b91b --- /dev/null +++ b/tests/unit/Ieee80211DurationRounding_1.test @@ -0,0 +1,86 @@ +%description: +Ordinary Duration/ID fields round fractional microseconds upward in every +supported MAC header family (IEEE 802.11-2024, 9.2.5.1). + +%includes: +#include "inet/common/packet/Packet.h" +#include "inet/common/packet/chunk/BytesChunk.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Duration.h" +#include "inet/linklayer/ieee80211/mac/Rx.h" +using namespace inet; +using namespace inet::ieee80211; + +%global: +class TestRx : public Rx { + public: + int updates = 0; + virtual bool isFcsOk(Packet *) const override { return true; } + protected: + virtual void setOrExtendNav(simtime_t) override { updates++; } +}; +static Ptr frame(int family) +{ + switch (family) { + case 0: return makeShared(); + case 1: { + auto header = makeShared(); + header->setType(ST_AUTHENTICATION); + return header; + } + case 2: return makeShared(); + case 3: return makeShared(); + case 4: return makeShared(); + case 5: return makeShared(); + default: { + auto header = makeShared(); + for (int i = 0; i < 64; i++) + for (int bit = 0; bit < 16; bit++) + header->getBlockAckBitmapForUpdate(i).setBit(bit, false); + return header; + } + } +} + +%activity: +ASSERT(normalizeIeee80211Duration(SimTime(-1, SIMTIME_US)) == SIMTIME_ZERO); +ASSERT(normalizeIeee80211Duration(SimTime(44800, SIMTIME_NS)) == SimTime(45, SIMTIME_US)); +ASSERT(normalizeIeee80211Duration(SimTime(32766800, SIMTIME_NS)) == SimTime(32767, SIMTIME_US)); +for (auto invalid : {SimTime(-1, SIMTIME_US), SimTime(32767100, SIMTIME_NS)}) { + bool rejected = false; + try { encodeIeee80211Duration(invalid); } + catch (const cRuntimeError&) { rejected = true; } + ASSERT(rejected); +} +for (int family = 0; family < 7; family++) { + for (int nanoseconds : {0, 1, 44000, 44800, 32766800}) { + auto header = frame(family); + if (auto data = dynamicPtrCast(header)) + data->setSequenceNumber(SequenceNumberCyclic(0)); + if (auto bar = dynamicPtrCast(header)) + bar->setStartingSequenceNumber(SequenceNumberCyclic(0)); + if (auto ba = dynamicPtrCast(header)) + ba->setStartingSequenceNumber(SequenceNumberCyclic(0)); + header->setDurationField(SimTime(nanoseconds, SIMTIME_NS)); + Packet packet("duration", header); + const auto bytes = packet.peekAllAsBytes()->getBytes(); + ASSERT(bytes.size() >= 4); + int encoded = bytes[2] | (bytes[3] << 8); + ASSERT(encoded == (nanoseconds + 999) / 1000); + } +} +TestRx rx; +for (int duration : {-1, 0, 32767, 32768, 65535}) { + auto header = makeShared(); + header->setReceiverAddress(MacAddress("02:00:00:00:00:12")); + header->setDurationField(SimTime(duration, SIMTIME_US)); + auto packet = new Packet("nav", header); + int before = rx.updates; + ASSERT(rx.lowerFrameReceived(packet)); + ASSERT(rx.updates - before == (duration >= 0 && duration <= 32767 ? 1 : 0)); + delete packet; +} +EV << "All seven ordinary MAC Duration fields round upward.\n"; + +%contains: stdout +All seven ordinary MAC Duration fields round upward. diff --git a/tests/unit/Ieee80211MsduAggregation_1.test b/tests/unit/Ieee80211MsduAggregation_1.test index c6b142c9e29..7c78c00971b 100644 --- a/tests/unit/Ieee80211MsduAggregation_1.test +++ b/tests/unit/Ieee80211MsduAggregation_1.test @@ -67,6 +67,8 @@ for (bool toDS : {false, true}) { // OriginatorQosMacDataService assigns the sequence after aggregation, before transmission. auto sequencedHeader = aggregate->removeAtFront(); sequenceNumbers.assignSequenceNumber(sequencedHeader); + // The MAC assigns Duration before a frame is serialized for transmission. + sequencedHeader->setDurationField(SIMTIME_ZERO); aggregate->insertAtFront(sequencedHeader); auto outer = aggregate->peekAtFront(); ASSERT(outer->getReceiverAddress() == ra); diff --git a/tests/unit/Ieee80211OnWireBitCompliance_1.test b/tests/unit/Ieee80211OnWireBitCompliance_1.test index afebe6f9f27..33742bd38ec 100644 --- a/tests/unit/Ieee80211OnWireBitCompliance_1.test +++ b/tests/unit/Ieee80211OnWireBitCompliance_1.test @@ -21,6 +21,7 @@ for (int ds = 0; ds < 4; ds++) { for (int policy = 0; policy < 4; policy++) { auto header = makeShared(); header->setType(ST_DATA_WITH_QOS); + header->setDurationField(SIMTIME_ZERO); header->setSequenceNumber(SequenceNumberCyclic(0)); header->setToDS(ds & 1); header->setFromDS(ds & 2); From 2cd66c39bc6a1812a3ddea364cfb069aadb71546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Wed, 16 Sep 2026 00:23:06 +0200 Subject: [PATCH 3/3] ieee80211: fix: exclude special Duration IDs from response reservations Reserved or special incoming Duration/ID encodings are not airtime. Exclude them before ACK, CTS and Block Ack subtraction so malformed peer input cannot become a large reservation or trigger the ordinary-duration serializer range check. Preserve residual airtime for valid values. Focused tests cover both DCF and QoS ACK/CTS policies, Basic Block Ack, negative input and special encodings. Debug and release builds pass. Change: src.ieee80211.duration | behavior.change.fix | test | wifi-audit --- .../ieee80211/mac/Ieee80211Duration.h | 7 +++ .../ieee80211/mac/recipient/CtsPolicy.cc | 3 +- .../ieee80211/mac/recipient/QosCtsPolicy.cc | 3 +- .../mac/recipient/RecipientAckPolicy.cc | 3 +- .../mac/recipient/RecipientQosAckPolicy.cc | 5 +- tests/unit/Ieee80211BlockAckDuration_1.test | 5 ++ tests/unit/Ieee80211ResponseDuration_1.test | 60 +++++++++++++++++++ 7 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 tests/unit/Ieee80211ResponseDuration_1.test diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211Duration.h b/src/inet/linklayer/ieee80211/mac/Ieee80211Duration.h index 33879c59eaa..7496df17bfa 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211Duration.h +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Duration.h @@ -10,6 +10,13 @@ namespace inet { namespace ieee80211 { +// Only the ordinary 15-bit encoding denotes a reservation. Reserved and +// special Duration/ID values must not enter response-duration arithmetic. +inline simtime_t getIeee80211OrdinaryDuration(simtime_t duration) +{ + return duration >= SIMTIME_ZERO && duration <= SimTime(32767, SIMTIME_US) ? duration : SIMTIME_ZERO; +} + inline simtime_t normalizeIeee80211Duration(simtime_t duration) { // IEEE Std 802.11-2024, 9.2.5.1: negative calculations become zero; fractions round upward. diff --git a/src/inet/linklayer/ieee80211/mac/recipient/CtsPolicy.cc b/src/inet/linklayer/ieee80211/mac/recipient/CtsPolicy.cc index 17edca5ffea..d90db6f5d1e 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/CtsPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/CtsPolicy.cc @@ -6,6 +6,7 @@ #include "inet/linklayer/ieee80211/mac/recipient/CtsPolicy.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Duration.h" #include "inet/common/ModuleAccess.h" @@ -35,7 +36,7 @@ simtime_t CtsPolicy::computeCtsDuration(Packet *rtsPacket, const Ptr& rtsFrame) const { - simtime_t duration = rtsFrame->getDurationField() - modeSet->getSifsTime() - computeCtsDuration(rtsPacket, rtsFrame); + simtime_t duration = getIeee80211OrdinaryDuration(rtsFrame->getDurationField()) - modeSet->getSifsTime() - computeCtsDuration(rtsPacket, rtsFrame); return duration < 0 ? 0 : duration; } diff --git a/src/inet/linklayer/ieee80211/mac/recipient/QosCtsPolicy.cc b/src/inet/linklayer/ieee80211/mac/recipient/QosCtsPolicy.cc index afd8af57f9d..8fe29cce3ea 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/QosCtsPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/QosCtsPolicy.cc @@ -6,6 +6,7 @@ #include "inet/linklayer/ieee80211/mac/recipient/QosCtsPolicy.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Duration.h" #include "inet/common/ModuleAccess.h" @@ -36,7 +37,7 @@ simtime_t QosCtsPolicy::computeCtsDuration(Packet *rtsPacket, const Ptr& rtsFrame) const { - simtime_t duration = rtsFrame->getDurationField() - modeSet->getSifsTime() - computeCtsDuration(rtsPacket, rtsFrame); + simtime_t duration = getIeee80211OrdinaryDuration(rtsFrame->getDurationField()) - modeSet->getSifsTime() - computeCtsDuration(rtsPacket, rtsFrame); return duration < 0 ? 0 : duration; } diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckPolicy.cc b/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckPolicy.cc index af8077331bb..59e0e201c7f 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckPolicy.cc @@ -6,6 +6,7 @@ #include "inet/linklayer/ieee80211/mac/recipient/RecipientAckPolicy.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Duration.h" #include "inet/common/ModuleAccess.h" @@ -53,7 +54,7 @@ bool RecipientAckPolicy::isAckNeeded(const Ptr& simtime_t RecipientAckPolicy::computeAckDurationField(Packet *packet, const Ptr& header) const { if (header->getMoreFragments()) { - auto duration = header->getDurationField() - modeSet->getSifsTime() - computeAckDuration(packet, header); + auto duration = getIeee80211OrdinaryDuration(header->getDurationField()) - modeSet->getSifsTime() - computeAckDuration(packet, header); duration = ceil(duration, SimTime(1, SIMTIME_US)); if (duration < 0) EV_WARN << "ACK duration field would be negative, returning 0 instead.\n"; diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc index 1e4370b416f..3a80fb3fa42 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc @@ -6,6 +6,7 @@ #include "inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Duration.h" #include "inet/common/ModuleAccess.h" @@ -78,7 +79,7 @@ bool RecipientQosAckPolicy::isBlockAckNeeded(const Ptr& header) const { - simtime_t duration = header->getDurationField() - modeSet->getSifsTime() - computeAckDuration(packet, header); + simtime_t duration = getIeee80211OrdinaryDuration(header->getDurationField()) - modeSet->getSifsTime() - computeAckDuration(packet, header); return duration < 0 ? 0 : duration; } @@ -91,7 +92,7 @@ simtime_t RecipientQosAckPolicy::computeAckDurationField(Packet *packet, const P // simtime_t RecipientQosAckPolicy::computeBasicBlockAckDurationField(Packet *packet, const Ptr& basicBlockAckReq) const { - simtime_t duration = basicBlockAckReq->getDurationField() - modeSet->getSifsTime() - computeBasicBlockAckDuration(packet, basicBlockAckReq); + simtime_t duration = getIeee80211OrdinaryDuration(basicBlockAckReq->getDurationField()) - modeSet->getSifsTime() - computeBasicBlockAckDuration(packet, basicBlockAckReq); // IEEE Std 802.11-2024, 9.2.5.1: a negative calculated Duration/ID is inserted as zero. return duration < 0 ? 0 : duration; } diff --git a/tests/unit/Ieee80211BlockAckDuration_1.test b/tests/unit/Ieee80211BlockAckDuration_1.test index 1df6bb96c5f..cfddc72de71 100644 --- a/tests/unit/Ieee80211BlockAckDuration_1.test +++ b/tests/unit/Ieee80211BlockAckDuration_1.test @@ -39,6 +39,11 @@ for (auto duration : {SIMTIME_ZERO, elapsed - SimTime(1, SIMTIME_US), elapsed, e auto actual = policy.computeBasicBlockAckDurationField(&packet, bar); ASSERT(actual == (duration <= elapsed ? SIMTIME_ZERO : SimTime(37, SIMTIME_US))); } +for (int special : {32768, 65535}) { + auto bar = makeShared(); + bar->setDurationField(SimTime(special, SIMTIME_US)); + ASSERT(policy.computeBasicBlockAckDurationField(&packet, bar) == SIMTIME_ZERO); +} EV << "BlockAck duration is zero for insufficient reservations and preserves positive residuals.\n"; %contains: stdout diff --git a/tests/unit/Ieee80211ResponseDuration_1.test b/tests/unit/Ieee80211ResponseDuration_1.test new file mode 100644 index 00000000000..3d1225908e8 --- /dev/null +++ b/tests/unit/Ieee80211ResponseDuration_1.test @@ -0,0 +1,60 @@ +%description: +Reserved/special Duration/ID encodings do not become ordinary ACK or CTS +reservations; valid ordinary durations preserve their residual airtime. + +%includes: +#include "inet/linklayer/ieee80211/mac/recipient/CtsPolicy.h" +#include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h" +#include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" +#include "inet/linklayer/ieee80211/mac/recipient/QosCtsPolicy.h" +#include "inet/linklayer/ieee80211/mac/recipient/RecipientAckPolicy.h" +#include "inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h" +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +%global: +template class TestCts : public T { + public: + TestCts() { this->modeSet = const_cast(Ieee80211ModeSet::getModeSet("a")); } + protected: + virtual simtime_t computeCtsDuration(Packet *, const Ptr&) const override { return SimTime(10, SIMTIME_US); } +}; +template class FixedAckRate : public T { + public: + virtual const IIeee80211Mode *computeResponseAckFrameMode(Packet *, const Ptr&) override { + return Ieee80211ModeSet::getModeSet("a")->getMode(Mbps(24)); + } +}; +template class TestAck : public T { + protected: + FixedAckRate rate; + public: + TestAck() { + this->modeSet = const_cast(Ieee80211ModeSet::getModeSet("a")); + this->rateSelection = &rate; + } +}; + +%activity: +TestCts dcfCts; +TestCts hcfCts; +TestAck dcfAck; +TestAck hcfAck; +Packet packet("request"); +for (int duration : {-1, 0, 200, 32768, 65535}) { + auto rts = makeShared(); + rts->setDurationField(SimTime(duration, SIMTIME_US)); + auto data = makeShared(); + data->setMoreFragments(true); + data->setDurationField(SimTime(duration, SIMTIME_US)); + auto expected = duration == 200 ? SimTime(174, SIMTIME_US) : SIMTIME_ZERO; + ASSERT(dcfCts.computeCtsDurationField(&packet, rts) == expected); + ASSERT(hcfCts.computeCtsDurationField(&packet, rts) == expected); + ASSERT(dcfAck.computeAckDurationField(&packet, data) == (duration == 200 ? SimTime(156, SIMTIME_US) : SIMTIME_ZERO)); + ASSERT(hcfAck.computeAckDurationField(&packet, data) == (duration == 200 ? SimTime(156, SIMTIME_US) : SIMTIME_ZERO)); +} +EV << "ACK and CTS policies ignore non-duration encodings.\n"; + +%contains: stdout +ACK and CTS policies ignore non-duration encodings.