From b5f3e57ee510aebb1f9e2dff50ff27d02defb9cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Wed, 16 Sep 2026 00:21:19 +0200 Subject: [PATCH] ieee80211: fix: use response rate for fragmented-frame ACK duration Reserve the ACK following the next DCF fragment using its response mode. A production exchange at 54 Mbps with 24 Mbps ACKs now reserves the missing four microseconds. The focused module test and all four fragmentation fingerprints pass. Change: src.ieee80211.duration | behavior.change.fix | test | wifi-audit --- .../OriginatorProtectionMechanism.cc | 4 +- .../Ieee80211FragmentAckDuration_1.test | 199 ++++++++++++++++++ 2 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 tests/module/Ieee80211FragmentAckDuration_1.test diff --git a/src/inet/linklayer/ieee80211/mac/protectionmechanism/OriginatorProtectionMechanism.cc b/src/inet/linklayer/ieee80211/mac/protectionmechanism/OriginatorProtectionMechanism.cc index a0ffcea12ed..ea32fb22ee4 100644 --- a/src/inet/linklayer/ieee80211/mac/protectionmechanism/OriginatorProtectionMechanism.cc +++ b/src/inet/linklayer/ieee80211/mac/protectionmechanism/OriginatorProtectionMechanism.cc @@ -49,6 +49,7 @@ simtime_t OriginatorProtectionMechanism::computeRtsDurationField(Packet *rtsPack // — If the More Fragments bit is 1 in the Frame Control field of a frame and the Address 1 field contains // an individual address, the duration value is set to the time, in microseconds, required to transmit the // next fragment of this data frame, plus two ACK frames, plus three SIFS intervals. +// IEEE Std 802.11-2024, 9.3.2.1.5 and 10.6.6.5.2: each response ACK uses the applicable response rate. // simtime_t OriginatorProtectionMechanism::computeDataFrameDurationField(Packet *dataPacket, const Ptr& dataHeader, Packet *pendingPacket, const Ptr& pendingHeader) { @@ -61,7 +62,7 @@ simtime_t OriginatorProtectionMechanism::computeDataFrameDurationField(Packet *d simtime_t pendingFrameDuration = rateSelection->computeMode(pendingPacket, pendingHeader)->getDuration(pendingPacket->getDataLength()); auto pendingFrameMode = rateSelection->computeMode(pendingPacket, pendingHeader); RateSelection::setFrameMode(pendingPacket, pendingHeader, pendingFrameMode); - simtime_t ackToPendingFrame = pendingFrameMode->getDuration(LENGTH_ACK); + simtime_t ackToPendingFrame = rateSelection->computeResponseAckFrameMode(pendingPacket, pendingHeader)->getDuration(LENGTH_ACK); return pendingFrameDuration + ackToDataFrameDuration + ackToPendingFrame + 3 * modeSet->getSifsTime(); } } @@ -104,4 +105,3 @@ simtime_t OriginatorProtectionMechanism::computeDurationField(Packet *packet, co } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/tests/module/Ieee80211FragmentAckDuration_1.test b/tests/module/Ieee80211FragmentAckDuration_1.test new file mode 100644 index 00000000000..1fa624623f1 --- /dev/null +++ b/tests/module/Ieee80211FragmentAckDuration_1.test @@ -0,0 +1,199 @@ +%description: +Verify that a fragmented non-QoS data frame reserves the next fragment and both +ACK exchanges using the response ACK PHY mode. The assertion observes the +typed Duration/ID field on the production DCF packetSentToPeer signal after +each frame has completed transmission. + +%file: Ieee80211FragmentAckDuration.cc + +#include + +#include "inet/common/Simsignals.h" +#include "inet/common/SimpleModule.h" +#include "inet/common/packet/Packet.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" + +namespace inet { +namespace ieee80211 { + +class Ieee80211FragmentAckDurationChecker : public cSimpleModule, public cListener +{ + protected: + struct FrameObservation { + simtime_t duration; + b dataLength; + int fragmentNumber; + int sequenceNumber; + bool moreFragments; + const physicallayer::IIeee80211Mode *mode; + }; + + cComponent *senderDcf = nullptr; + cComponent *receiverDcf = nullptr; + std::vector dataFrames; + std::vector ackModes; + + virtual void initialize() override + { + senderDcf = getModuleByPath("^.source.wlan[0].mac.dcf"); + receiverDcf = getModuleByPath("^.receiver.wlan[0].mac.dcf"); + senderDcf->subscribe(packetSentToPeerSignal, this); + receiverDcf->subscribe(packetSentToPeerSignal, this); + } + + virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *value, cObject *details) override + { + if (signalID != packetSentToPeerSignal) + return; + auto packet = check_and_cast(value); + const auto& header = packet->peekAtFront(); + if (source == receiverDcf) { + if (header->getType() != ST_ACK) + return; + const auto& modeReq = packet->findTag(); + ASSERT(modeReq != nullptr); + ASSERT(modeReq->getMode() != nullptr); + ackModes.push_back(modeReq->getMode()); + return; + } + if (source != senderDcf) + return; + auto dataHeader = dynamicPtrCast(header); + if (dataHeader == nullptr) + return; + const auto& modeReq = packet->findTag(); + ASSERT(modeReq != nullptr); + auto mode = modeReq->getMode(); + ASSERT(mode != nullptr); + dataFrames.push_back({header->getDurationField(), packet->getDataLength(), + dataHeader->getFragmentNumber(), dataHeader->getSequenceNumber().get(), + dataHeader->getMoreFragments(), mode}); + } + + virtual void finish() override + { + ASSERT(dataFrames.size() == 2); + const FrameObservation *more = nullptr; + const FrameObservation *last = nullptr; + for (const auto& frame : dataFrames) { + if (frame.moreFragments) { + ASSERT(more == nullptr); + more = &frame; + } + else { + ASSERT(last == nullptr); + last = &frame; + } + } + ASSERT(more != nullptr); + ASSERT(last != nullptr); + ASSERT(more->fragmentNumber == 0); + ASSERT(last->fragmentNumber == 1); + ASSERT(more->sequenceNumber == last->sequenceNumber); + ASSERT(more->mode->getDataMode()->getNetBitrate() == Mbps(54)); + ASSERT(last->mode->getDataMode()->getNetBitrate() == Mbps(54)); + ASSERT(ackModes.size() == 2); + for (auto mode : ackModes) + ASSERT(mode->getDataMode()->getNetBitrate() == Mbps(24)); + + const auto *modeSet = physicallayer::Ieee80211ModeSet::getModeSet("g(erp)"); + const auto *responseAckMode = modeSet->getMode(Mbps(24)); + const auto *dataMode = more->mode; + const auto currentAckDuration = responseAckMode->getDuration(LENGTH_ACK); + const auto expectedCorrect = dataMode->getDuration(last->dataLength) + + currentAckDuration + currentAckDuration + 3 * modeSet->getSifsTime(); + const auto expectedWithDataRateAck = dataMode->getDuration(last->dataLength) + + currentAckDuration + dataMode->getDuration(LENGTH_ACK) + 3 * modeSet->getSifsTime(); + ASSERT(expectedCorrect != expectedWithDataRateAck); + // The data-rate ACK is 4 us shorter than the configured 24 Mbps + // response ACK in this ERP timing model. + ASSERT(expectedCorrect - expectedWithDataRateAck == SimTime(4, SIMTIME_US)); + ASSERT(more->duration == expectedCorrect); + + // A final fragment reserves only its immediate response ACK and SIFS. + ASSERT(last->duration == currentAckDuration + modeSet->getSifsTime()); + std::cout << "Fragmented DCF Duration/ID uses the 24 Mbps response ACK mode for both fragments.\n"; + } +}; + +Define_Module(Ieee80211FragmentAckDurationChecker); + +} // namespace ieee80211 +} // namespace inet + +%file: test.ned + +import inet.common.SimpleModule; +import inet.networklayer.configurator.ipv4.Ipv4NetworkConfigurator; +import inet.node.inet.AdhocHost; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; + +simple Ieee80211FragmentAckDurationChecker extends SimpleModule +{ + parameters: + @class(::inet::ieee80211::Ieee80211FragmentAckDurationChecker); +} + +network Ieee80211FragmentAckDurationTest +{ + submodules: + configurator: Ipv4NetworkConfigurator; + radioMedium: Ieee80211ScalarRadioMedium; + source: AdhocHost; + receiver: AdhocHost; + test: Ieee80211FragmentAckDurationChecker; +} + +%inifile: omnetpp.ini + +[General] +network = Ieee80211FragmentAckDurationTest +ned-path = .;../../../../src;../../lib +seed-set = 0 +sim-time-limit = 10ms +cmdenv-express-mode = false +record-vector-results = false +record-scalar-results = false + +*.configurator.addStaticRoutes = true +**.arp.typename = "GlobalArp" + +*.source.mobility.typename = "StationaryMobility" +*.receiver.mobility.typename = "StationaryMobility" +**.mobility.initFromDisplayString = false +*.source.mobility.initialX = 0m +*.source.mobility.initialY = 0m +*.source.mobility.initialZ = 0m +*.receiver.mobility.initialX = 10m +*.receiver.mobility.initialY = 0m +*.receiver.mobility.initialZ = 0m + +**.wlan[*].opMode = "g(erp)" +**.wlan[*].bitrate = -1bps +**.wlan[*].radio.transmitter.power = 50mW +**.wlan[*].radio.receiver.sensitivity = -85dBm +**.wlan[*].radio.receiver.snirThreshold = 4dB +*.source.wlan[0].mac.mtu = 2304B +*.receiver.wlan[0].mac.mtu = 2304B + +*.source.wlan[0].mac.dcf.rateSelection.dataFrameBitrate = 54Mbps +*.source.wlan[0].mac.dcf.rateSelection.responseAckFrameBitrate = 24Mbps +*.receiver.wlan[0].mac.dcf.rateSelection.responseAckFrameBitrate = 24Mbps +*.source.wlan[0].mac.dcf.originatorMacDataService.fragmentationPolicy.fragmentationThreshold = 1000B + +*.source.numApps = 1 +*.source.app[0].typename = "UdpBasicApp" +*.source.app[0].destAddresses = "receiver" +*.source.app[0].destPort = 5000 +*.source.app[0].messageLength = 1500B +*.source.app[0].startTime = 1ms +*.source.app[0].stopTime = 2ms +*.source.app[0].sendInterval = 1s +*.receiver.numApps = 1 +*.receiver.app[0].typename = "UdpSink" +*.receiver.app[0].localPort = 5000 + +%contains: stdout +Fragmented DCF Duration/ID uses the 24 Mbps response ACK mode for both fragments.