From 688c765453c923a0083cfdd19c428dd4f65dd6f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Sat, 15 Aug 2026 14:09:41 +0200 Subject: [PATCH 01/10] feat(802.11n): add configurable HT Greenfield mode --- .../lan80211/omnetpp-ht-greenfield.ini | 38 +++++ .../ieee80211/ExtUpperIeee80211Interface.ned | 3 +- .../ieee80211/Ieee80211Interface.ned | 3 +- .../linklayer/ieee80211/mac/Ieee80211Mac.ned | 3 +- .../ieee80211/mode/Ieee80211HtMode.cc | 5 +- .../wireless/ieee80211/mode/Ieee80211HtMode.h | 3 +- .../ieee80211/mode/Ieee80211ModeSet.cc | 151 ++++++++++-------- .../ieee80211/packetlevel/Ieee80211Radio.ned | 3 +- .../packetlevel/Ieee80211Receiver.ned | 3 +- .../packetlevel/Ieee80211Transmitter.ned | 2 +- .../module/Ieee80211HtGreenfieldRuntime.test | 114 +++++++++++++ tests/unit/Ieee80211HtGreenfield_1.test | 46 ++++++ 12 files changed, 291 insertions(+), 83 deletions(-) create mode 100644 examples/wireless/lan80211/omnetpp-ht-greenfield.ini create mode 100644 tests/module/Ieee80211HtGreenfieldRuntime.test create mode 100644 tests/unit/Ieee80211HtGreenfield_1.test diff --git a/examples/wireless/lan80211/omnetpp-ht-greenfield.ini b/examples/wireless/lan80211/omnetpp-ht-greenfield.ini new file mode 100644 index 00000000000..9cc212679f4 --- /dev/null +++ b/examples/wireless/lan80211/omnetpp-ht-greenfield.ini @@ -0,0 +1,38 @@ +[General] +network = Lan80211 +abstract = true +cmdenv-express-mode = true + +**.constraintAreaMinX = 0m +**.constraintAreaMinY = 0m +**.constraintAreaMinZ = 0m +**.constraintAreaMaxX = 600m +**.constraintAreaMaxY = 400m +**.constraintAreaMaxZ = 0m + +**.arp.typename = "GlobalArp" +**.opMode = "n(greenfield-2.4Ghz)" +**.wlan[*].bitrate = 13Mbps +**.wlan[*].radio.transmitter.centerFrequency = 2.4GHz + +*.ap.wlan[*].address = "10:00:00:00:00:00" +*.host[*].**.mgmt.accessPointAddress = "10:00:00:00:00:00" + +*.ap.mobility.typename = "StationaryMobility" +*.ap.mobility.initialX = 250m +*.ap.mobility.initialY = 200m +*.host[*].mobility.typename = "StationaryMobility" +*.host[*].mobility.initialY = 200m +*.host[0].mobility.initialX = 260m +*.host[1].mobility.initialX = 270m + +[Config HtGreenfield] +description = "HT Greenfield mode exchanging ping traffic through an access point" +sim-time-limit = 2s +*.numHosts = 2 +*.host[0].numApps = 0 +*.host[1].numApps = 1 +*.host[1].app[0].typename = "PingApp" +*.host[1].app[0].destAddr = "host[0]" +*.host[1].app[0].sendInterval = 100ms +*.host[1].app[0].printPing = true diff --git a/src/inet/emulation/linklayer/ieee80211/ExtUpperIeee80211Interface.ned b/src/inet/emulation/linklayer/ieee80211/ExtUpperIeee80211Interface.ned index 83b958c0f9f..57161c6354f 100644 --- a/src/inet/emulation/linklayer/ieee80211/ExtUpperIeee80211Interface.ned +++ b/src/inet/emulation/linklayer/ieee80211/ExtUpperIeee80211Interface.ned @@ -27,7 +27,7 @@ module ExtUpperIeee80211Interface extends ExtInterface like IWirelessInterface { parameters: string energySourceModule = default(""); - string opMode @enum("a","b","g(erp)","g(mixed)","n(mixed-2.4Ghz)","p","ac") = default("g(mixed)"); + string opMode @enum("a","b","g(erp)","g(mixed)","n(mixed-2.4Ghz)","n(greenfield-2.4Ghz)","p","ac") = default("g(mixed)"); double bitrate @unit(bps) = default(-1bps); **.opMode = this.opMode; **.bitrate = this.bitrate; @@ -92,4 +92,3 @@ module ExtUpperIeee80211Interface extends ExtInterface like IWirelessInterface classifier.in <-- { @display("m=n"); } <-- tap.lowerLayerOut; } - diff --git a/src/inet/linklayer/ieee80211/Ieee80211Interface.ned b/src/inet/linklayer/ieee80211/Ieee80211Interface.ned index e32eaba59fc..3534c8d7c18 100644 --- a/src/inet/linklayer/ieee80211/Ieee80211Interface.ned +++ b/src/inet/linklayer/ieee80211/Ieee80211Interface.ned @@ -63,7 +63,7 @@ module Ieee80211Interface extends NetworkInterface like IWirelessInterface parameters: string interfaceTableModule; string energySourceModule = default(""); - string opMode @enum("a","b","g(erp)","g(mixed)","n(mixed-2.4Ghz)","p","ac") = default("g(mixed)"); + string opMode @enum("a","b","g(erp)","g(mixed)","n(mixed-2.4Ghz)","n(greenfield-2.4Ghz)","p","ac") = default("g(mixed)"); string address @mutable = default("auto"); // MAC address as hex string (12 hex digits), or // "auto". "auto" values will be replaced by // a generated MAC address in init stage 0. @@ -129,4 +129,3 @@ module Ieee80211Interface extends NetworkInterface like IWirelessInterface classifier.in <-- { @display("m=n"); } <-- upperLayerIn; } - diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.ned b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.ned index bcd161d3889..84c96742c33 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.ned +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.ned @@ -78,7 +78,7 @@ module Ieee80211Mac extends MacProtocolBase like IIeee80211Mac { parameters: string mibModule; - string modeSet @enum("a","b","g(erp)","g(mixed)","n(mixed-2.4Ghz)","p","ac") = default("g(mixed)"); + string modeSet @enum("a","b","g(erp)","g(mixed)","n(mixed-2.4Ghz)","n(greenfield-2.4Ghz)","p","ac") = default("g(mixed)"); string fcsMode @enum("declared","computed") = default("declared"); string initialRadioMode @enum("off","sleep","receiver","transmitter","transceiver") = default("receiver"); @@ -130,4 +130,3 @@ module Ieee80211Mac extends MacProtocolBase like IIeee80211Mac @display("p=250,200"); } } - diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.cc b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.cc index 319128cce20..628be02a656 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.cc +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.cc @@ -326,7 +326,7 @@ Ieee80211HtCompliantModes::~Ieee80211HtCompliantModes() const Ieee80211HtMode *Ieee80211HtCompliantModes::getCompliantMode(const Ieee80211Htmcs *mcsMode, Ieee80211HtMode::BandMode centerFrequencyMode, Ieee80211HtPreambleMode::HighTroughputPreambleFormat preambleFormat, Ieee80211HtModeBase::GuardIntervalType guardIntervalType) { const char *name = ""; // TODO - auto htModeId = std::make_tuple(mcsMode->getBandwidth(), mcsMode->getMcsIndex(), guardIntervalType); + auto htModeId = std::make_tuple(mcsMode->getBandwidth(), mcsMode->getMcsIndex(), centerFrequencyMode, preambleFormat, guardIntervalType); auto mode = singleton.modeCache.find(htModeId); if (mode == singleton.modeCache.end()) { const Ieee80211OfdmModulation *modulation = nullptr; @@ -348,7 +348,7 @@ const Ieee80211HtMode *Ieee80211HtCompliantModes::getCompliantMode(const Ieee802 const Ieee80211HtDataMode *dataMode = new Ieee80211HtDataMode(mcsMode, mcsMode->getBandwidth(), guardIntervalType); const Ieee80211HtPreambleMode *preambleMode = new Ieee80211HtPreambleMode(htSignal, legacySignal, preambleFormat, dataMode->getNumberOfSpatialStreams()); const Ieee80211HtMode *htMode = new Ieee80211HtMode(name, preambleMode, dataMode, centerFrequencyMode); - singleton.modeCache.insert(std::pair, const Ieee80211HtMode *>(htModeId, htMode)); + singleton.modeCache.emplace(htModeId, htMode); return htMode; } return mode->second; @@ -544,4 +544,3 @@ const DI Ieee80211HtmcsTable::htMcs76BW40MHz([](){ return new Ie } /* namespace physicallayer */ } /* namespace inet */ - diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h index e263107568e..a7295d35d36 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h @@ -460,7 +460,7 @@ class INET_API Ieee80211HtCompliantModes protected: static OPP_THREAD_LOCAL const Ieee80211HtCompliantModes singleton; - mutable std::map, const Ieee80211HtMode *> modeCache; + mutable std::map, const Ieee80211HtMode *> modeCache; public: Ieee80211HtCompliantModes(); @@ -473,4 +473,3 @@ class INET_API Ieee80211HtCompliantModes } /* namespace inet */ #endif - diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc index 929d3e8cecc..85b2fb0d2ef 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc @@ -24,6 +24,87 @@ namespace physicallayer { Register_Abstract_Class(Ieee80211ModeSet); +#define HT_MODE_ENTRY(WIDTH, MCS, MANDATORY, FORMAT, GUARD_INTERVAL) \ + { MANDATORY, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs##MCS##BW##WIDTH##MHz, Ieee80211HtMode::BAND_2_4GHZ, FORMAT, GUARD_INTERVAL) }, +#define HT_MODE_ENTRIES_20(FORMAT) \ + HT_MODE_ENTRY(20, 0, true, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) \ + HT_MODE_ENTRY(20, 1, true, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) \ + HT_MODE_ENTRY(20, 2, true, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) \ + HT_MODE_ENTRY(20, 3, true, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) \ + HT_MODE_ENTRY(20, 4, true, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) \ + HT_MODE_ENTRY(20, 5, true, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) \ + HT_MODE_ENTRY(20, 6, true, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) \ + HT_MODE_ENTRY(20, 7, true, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) \ + HT_MODE_ENTRY(20, 8, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 9, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 10, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 11, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 12, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 13, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 14, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 15, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 16, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 17, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 18, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 19, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 20, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 21, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 22, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 23, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 24, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 25, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 26, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 27, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 28, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 29, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 30, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(20, 31, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) +#define HT_MODE_ENTRIES_40(FORMAT) \ + HT_MODE_ENTRY(40, 0, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 1, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 2, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 3, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 4, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 5, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 6, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 7, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 8, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 9, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 10, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 11, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 12, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 13, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 14, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 15, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 16, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 17, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 18, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 19, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 20, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 21, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 22, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 23, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 24, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 25, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 26, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 27, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 28, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 29, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 30, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ + HT_MODE_ENTRY(40, 31, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) + +static Ieee80211ModeSet createHtModeSet(const char *name, Ieee80211HtPreambleMode::HighTroughputPreambleFormat preambleFormat) +{ + return Ieee80211ModeSet(name, { // This table is not complete; it only contains 2.4GHz homogeneous spatial streams, all mandatory and optional modes + HT_MODE_ENTRIES_20(preambleFormat) + HT_MODE_ENTRIES_40(preambleFormat) + }); +} + +#undef HT_MODE_ENTRIES_40 +#undef HT_MODE_ENTRIES_20 +#undef HT_MODE_ENTRY + const DelayedInitializer> Ieee80211ModeSet::modeSets([]() { return new std::vector { Ieee80211ModeSet("a", { { true, &Ieee80211OfdmCompliantModes::ofdmMode6MbpsCS20MHz }, @@ -76,72 +157,9 @@ const DelayedInitializer> Ieee80211ModeSet::modeSe { false, &Ieee80211OfdmCompliantModes::ofdmMode24MbpsCS10MHz }, { false, &Ieee80211OfdmCompliantModes::ofdmMode27Mbps }, }), - Ieee80211ModeSet("n(mixed-2.4Ghz)", { // This table is not complete; it only contains 2.4GHz homogeneous spatial streams, all mandatory and optional modes - { true, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs0BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) }, - { true, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs1BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) }, - { true, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs2BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) }, - { true, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs3BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) }, - { true, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs4BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) }, - { true, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs5BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) }, - { true, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs6BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) }, - { true, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs7BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs8BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs9BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs10BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs11BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs12BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs13BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs14BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs15BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs16BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs17BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs18BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs19BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs20BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs21BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs22BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs23BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs24BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs25BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs26BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs27BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs28BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs29BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs30BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs31BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs0BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs1BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs2BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs3BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs4BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs5BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs6BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs7BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs8BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs9BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs10BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs11BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs12BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs13BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs14BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs15BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs16BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs17BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs18BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs19BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs20BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs21BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs22BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs23BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs24BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs25BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs26BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs27BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs28BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs29BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs30BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, - { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs31BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) } - }), + createHtModeSet("n(mixed-2.4Ghz)", Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED), + // IEEE Std 802.11-2024, 19.1.4, 19.3.9.5, 19.4.3: HT-greenfield is a distinct optional PPDU format with separate timing. + createHtModeSet("n(greenfield-2.4Ghz)", Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD), Ieee80211ModeSet("ac", { { true, Ieee80211VhtCompliantModes::getCompliantMode(&Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, Ieee80211VhtMode::BAND_5GHZ, Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG) }, { true, Ieee80211VhtCompliantModes::getCompliantMode(&Ieee80211VhtmcsTable::vhtMcs1BW20MHzNss1, Ieee80211VhtMode::BAND_5GHZ, Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG) }, @@ -626,4 +644,3 @@ const Ieee80211ModeSet *Ieee80211ModeSet::getModeSet(const char *mode) } // namespace physicallayer } // namespace inet - diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.ned b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.ned index c32061114d3..2258e4c82bc 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.ned +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.ned @@ -25,7 +25,7 @@ import inet.physicallayer.wireless.common.base.packetlevel.FlatRadioBase; module Ieee80211Radio extends FlatRadioBase { parameters: - string opMode @enum("a", "b", "g(erp)", "g(mixed)", "n(mixed-2.4Ghz)", "p", "ac") = default("g(mixed)"); // Operation mode + string opMode @enum("a", "b", "g(erp)", "g(mixed)", "n(mixed-2.4Ghz)", "n(greenfield-2.4Ghz)", "p", "ac") = default("g(mixed)"); // Operation mode string bandName @enum("2.4 GHz", "5 GHz", "5 GHz (20 MHz)", "5 GHz (40 MHz)", "5 GHz (80 MHz)", "5 GHz (160 MHz)", "5.9 GHz") = default("2.4 GHz"); // Band name int channelNumber = default(0); // Initial channel number within the band (TODO this is offset by 1) string fcsMode @enum("declared","computed") = default("declared"); @@ -49,4 +49,3 @@ module Ieee80211Radio extends FlatRadioBase @signal[radioChannelChanged](type=long); @statistic[radioChannel](title="Radio channel"; source=radioChannelChanged; record=histogram,vector; interpolationmode=sample-hold); } - diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.ned b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.ned index c0c19e2afc2..29e9fc5cd80 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.ned +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.ned @@ -23,7 +23,7 @@ import inet.physicallayer.wireless.common.base.packetlevel.NarrowbandReceiverBas module Ieee80211Receiver extends NarrowbandReceiverBase { parameters: - string opMode @enum("a","b","g(erp)","g(mixed)","n(mixed-2.4Ghz)","p","ac"); + string opMode @enum("a","b","g(erp)","g(mixed)","n(mixed-2.4Ghz)","n(greenfield-2.4Ghz)","p","ac"); string bandName @enum("2.4 GHz","5 GHz","5 GHz (20 MHz)","5 GHz (40 MHz)","5 GHz (80 MHz)","5 GHz (160 MHz)","5.9 GHz"); int channelNumber; modulation = default("BPSK"); // TODO this is simply wrong @@ -32,4 +32,3 @@ module Ieee80211Receiver extends NarrowbandReceiverBase *.opMode = this.opMode; @class(Ieee80211Receiver); } - diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.ned b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.ned index 3d50ab670e5..9c8b409b751 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.ned +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.ned @@ -17,7 +17,7 @@ import inet.physicallayer.wireless.common.base.packetlevel.NarrowbandTransmitter module Ieee80211Transmitter extends NarrowbandTransmitterBase { parameters: - string opMode @enum("a","b","g(erp)","g(mixed)","n(mixed-2.4Ghz)","p","ac"); + string opMode @enum("a","b","g(erp)","g(mixed)","n(mixed-2.4Ghz)","n(greenfield-2.4Ghz)","p","ac"); string bandName @enum("2.4 GHz","5 GHz","5 GHz (20 MHz)","5 GHz (40 MHz)","5 GHz (80 MHz)","5 GHz (160 MHz)","5.9 GHz"); int channelNumber; modulation = default("BPSK"); // TODO: This is simply wrong diff --git a/tests/module/Ieee80211HtGreenfieldRuntime.test b/tests/module/Ieee80211HtGreenfieldRuntime.test new file mode 100644 index 00000000000..0e2db395db4 --- /dev/null +++ b/tests/module/Ieee80211HtGreenfieldRuntime.test @@ -0,0 +1,114 @@ +%description: + +Checks during one real packet exchange that Greenfield and mixed HT +operation select their corresponding preamble formats. + +%file: Test.cc +#include +#include + +#include "inet/common/Simsignals.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmission.h" + +using namespace inet; +using namespace inet::physicallayer; + +namespace Ieee80211HtGreenfieldRuntime { + +class HtPreambleCheckingRadio : public Ieee80211Radio, public cListener +{ + protected: + int htTransmissionCount = 0; + + virtual void initialize(int stage) override + { + Ieee80211Radio::initialize(stage); + if (stage == INITSTAGE_LOCAL) + subscribe(transmissionStartedSignal, this); + } + + virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) override + { + if (signalID != transmissionStartedSignal) + return; + auto transmission = check_and_cast(obj); + auto mode = dynamic_cast(transmission->getMode()); + if (mode == nullptr) + return; + htTransmissionCount++; + const char *opMode = par("opMode"); + auto expectedFormat = strcmp(opMode, "n(greenfield-2.4Ghz)") == 0 ? Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD : Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED; + const char *expectedPreamble = expectedFormat == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD ? "greenfield" : "mixed"; + if (mode->getPreambleMode()->getPreambleFormat() != expectedFormat) + throw cRuntimeError("HT transmission used an unexpected preamble format (actual %d, expected %s for %s)", (int)mode->getPreambleMode()->getPreambleFormat(), expectedPreamble, opMode); + } + + virtual void finish() override + { + Ieee80211Radio::finish(); + if (htTransmissionCount == 0) + return; + const char *opMode = par("opMode"); + const char *preamble = strcmp(opMode, "n(greenfield-2.4Ghz)") == 0 ? "greenfield" : "mixed"; + std::cout << "Observed " << htTransmissionCount << " runtime HT " << preamble << " transmission(s).\n"; + } +}; + +Define_Module(HtPreambleCheckingRadio); + +} // namespace Ieee80211HtGreenfieldRuntime + +%file: test.ned + +import inet.examples.wireless.lan80211.Lan80211; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211Radio; + +module HtPreambleCheckingRadio extends Ieee80211Radio +{ + parameters: + @class(HtPreambleCheckingRadio); +} + +network TestHtGreenfield extends Lan80211 +{ + parameters: + numHosts = default(2); +} + +%inifile: omnetpp.ini + +[General] +include ../../../../examples/wireless/lan80211/omnetpp-ht-greenfield.ini +network = TestHtGreenfield +abstract = false +ned-path = .;../../../../src;../../../../examples;../../lib +cmdenv-express-mode = true +record-vector-results = false +record-eventlog = false +seed-set = 0 + +**.wlan[*].radio.typename = "HtPreambleCheckingRadio" + +[Config HtPreambleRuntime] +abstract = false +network = TestHtGreenfield +sim-time-limit = 2s +*.numHosts = 2 +*.host[0].numApps = 1 +*.host[0].app[0].typename = "PingApp" +*.host[0].app[0].destAddr = "host[1]" +*.host[0].app[0].sendInterval = 100ms +*.host[1].numApps = 1 +*.host[1].app[0].typename = "PingApp" +*.host[1].app[0].destAddr = "host[0]" +*.host[1].app[0].sendInterval = 100ms +*.ap.wlan[*].opMode = "n(mixed-2.4Ghz)" +*.host[1].wlan[*].opMode = "n(mixed-2.4Ghz)" + +%extraargs: -c HtPreambleRuntime + +%contains-regex: stdout +Observed [1-9][0-9]* runtime HT greenfield transmission\(s\). +Observed [1-9][0-9]* runtime HT mixed transmission\(s\). diff --git a/tests/unit/Ieee80211HtGreenfield_1.test b/tests/unit/Ieee80211HtGreenfield_1.test new file mode 100644 index 00000000000..44b790b4a3f --- /dev/null +++ b/tests/unit/Ieee80211HtGreenfield_1.test @@ -0,0 +1,46 @@ +%description: +Checks that the 802.11n mixed and Greenfield mode profiles preserve distinct +preamble formats and timing when HT modes are cached. + +%includes: +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" + +%global: +using namespace inet; +using namespace inet::physicallayer; + +static const Ieee80211HtMode *findMcs0LongGiMode(const Ieee80211ModeSet *modeSet) +{ + auto mode = dynamic_cast(modeSet->getMode(Mbps(6.5), MHz(20), 1)); + return mode != nullptr && mode->getDataMode()->getModulationAndCodingScheme()->getMcsIndex() == 0 && + mode->getDataMode()->getGuardIntervalType() == Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG ? mode : nullptr; +} + +%activity: +auto mixedProfile = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"); +auto greenfieldProfile = Ieee80211ModeSet::getModeSet("n(greenfield-2.4Ghz)"); +ASSERT(mixedProfile != nullptr); +ASSERT(greenfieldProfile != nullptr); +ASSERT(mixedProfile == Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)")); +ASSERT(greenfieldProfile == Ieee80211ModeSet::getModeSet("n(greenfield-2.4Ghz)")); + +auto mixedMode = findMcs0LongGiMode(mixedProfile); +auto greenfieldMode = findMcs0LongGiMode(greenfieldProfile); +ASSERT(mixedMode != nullptr); +ASSERT(greenfieldMode != nullptr); +ASSERT(mixedMode != greenfieldMode); +ASSERT(mixedMode->getCenterFrequencyMode() == Ieee80211HtMode::BAND_2_4GHZ); +ASSERT(greenfieldMode->getCenterFrequencyMode() == Ieee80211HtMode::BAND_2_4GHZ); +ASSERT(mixedMode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED); +ASSERT(greenfieldMode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD); +ASSERT(mixedMode->getPreambleMode()->getDuration() == SimTime(36, SIMTIME_US)); +ASSERT(greenfieldMode->getPreambleMode()->getDuration() == SimTime(24, SIMTIME_US)); +ASSERT(mixedMode->getDuration(B(0)) == SimTime(40, SIMTIME_US)); +ASSERT(greenfieldMode->getDuration(B(0)) == SimTime(28, SIMTIME_US)); +ASSERT(mixedMode->getDuration(B(0)) - greenfieldMode->getDuration(B(0)) == SimTime(12, SIMTIME_US)); + +EV << "HT mixed and Greenfield modes have distinct cached identities and timing.\n"; + +%contains: stdout +HT mixed and Greenfield modes have distinct cached identities and timing. From f09a54a688e0fa7db802c1e2dd12a6b65374af96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Sat, 15 Aug 2026 15:07:21 +0200 Subject: [PATCH 02/10] Fix heterogeneous HT Greenfield response-rate selection HT mixed and Greenfield modes are cached as distinct objects because their PPDU preambles and timing differ. A received mode can therefore be semantically equivalent to a local mode-set entry without being pointer-identical, which previously caused response-rate selection to reject the mode as unknown. Resolve mode-set membership and lookup by PHY mode properties while preserving distinct preamble-specific mode objects. Select the HT-mixed profile for HT ACK, CTS, and BlockAck responses, as required for HT control responses, and apply the mapping to both legacy and QoS rate selection. Extend unit coverage for cross-profile lookup and mixed control-response selection, and update the heterogeneous runtime test to observe both HT preamble formats without assuming every HT transmission uses the local preferred format. Validation: make MODE=release -j12; inet_run_unit_tests -m release -f 'Ieee80211HtGreenfield_1\.test'; inet_run_module_tests -f 'Ieee80211HtGreenfieldRuntime\.test'. --- .../lan80211/omnetpp-ht-greenfield.ini | 1 - .../mac/rateselection/QosRateSelection.cc | 25 +++++---- .../mac/rateselection/RateSelection.cc | 13 +++-- .../ieee80211/mode/Ieee80211ModeSet.cc | 53 +++++++++++++++++++ .../ieee80211/mode/Ieee80211ModeSet.h | 6 ++- .../module/Ieee80211HtGreenfieldRuntime.test | 23 ++++---- tests/unit/Ieee80211HtGreenfield_1.test | 7 +++ 7 files changed, 97 insertions(+), 31 deletions(-) diff --git a/examples/wireless/lan80211/omnetpp-ht-greenfield.ini b/examples/wireless/lan80211/omnetpp-ht-greenfield.ini index 9cc212679f4..9d59b48fcc6 100644 --- a/examples/wireless/lan80211/omnetpp-ht-greenfield.ini +++ b/examples/wireless/lan80211/omnetpp-ht-greenfield.ini @@ -13,7 +13,6 @@ cmdenv-express-mode = true **.arp.typename = "GlobalArp" **.opMode = "n(greenfield-2.4Ghz)" **.wlan[*].bitrate = 13Mbps -**.wlan[*].radio.transmitter.centerFrequency = 2.4GHz *.ap.wlan[*].address = "10:00:00:00:00:00" *.host[*].**.mgmt.accessPointAddress = "10:00:00:00:00:00" diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc index c8579ac4424..4602c71a02b 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc @@ -32,11 +32,11 @@ void QosRateSelection::initialize(int stage) double controlFrameBitrate = par("controlFrameBitrate"); controlFrameMode = (controlFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(controlFrameBitrate)); double responseAckFrameBitrate = par("responseAckFrameBitrate"); - responseAckFrameMode = (responseAckFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseAckFrameBitrate)); + responseAckFrameMode = (responseAckFrameBitrate == -1) ? nullptr : modeSet->getControlResponseMode(modeSet->getMode(bps(responseAckFrameBitrate))); double responseBlockAckFrameBitrate = par("responseBlockAckFrameBitrate"); - responseBlockAckFrameMode = (responseBlockAckFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseBlockAckFrameBitrate)); + responseBlockAckFrameMode = (responseBlockAckFrameBitrate == -1) ? nullptr : modeSet->getControlResponseMode(modeSet->getMode(bps(responseBlockAckFrameBitrate))); double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); - responseCtsFrameMode = (responseCtsFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseCtsFrameBitrate)); + responseCtsFrameMode = (responseCtsFrameBitrate == -1) ? nullptr : modeSet->getControlResponseMode(modeSet->getMode(bps(responseCtsFrameBitrate))); } } @@ -75,9 +75,11 @@ const IIeee80211Mode *QosRateSelection::computeResponseAckFrameMode(Packet *pack auto mode = getMode(packet, dataOrMgmtHeader); ASSERT(modeSet->containsMode(mode)); if (!responseAckFrameMode) { - if (modeSet->getIsMandatory(mode)) - return mode; - else if (auto slowerMode = modeSet->getSlowerMandatoryMode(mode)) + auto responseModeSet = modeSet->getControlResponseModeSet(mode); + auto responseMode = responseModeSet->getMode(mode); + if (responseModeSet->getIsMandatory(responseMode)) + return responseMode; + else if (auto slowerMode = responseModeSet->getSlowerMandatoryMode(responseMode)) return slowerMode; else throw cRuntimeError("Mandatory mode not found"); @@ -92,9 +94,11 @@ const IIeee80211Mode *QosRateSelection::computeResponseCtsFrameMode(Packet *pack auto mode = getMode(packet, rtsFrame); ASSERT(modeSet->containsMode(mode)); if (!responseCtsFrameMode) { - if (modeSet->getIsMandatory(mode)) - return mode; - else if (auto slowerMode = modeSet->getSlowerMandatoryMode(mode)) + auto responseModeSet = modeSet->getControlResponseModeSet(mode); + auto responseMode = responseModeSet->getMode(mode); + if (responseModeSet->getIsMandatory(responseMode)) + return responseMode; + else if (auto slowerMode = responseModeSet->getSlowerMandatoryMode(responseMode)) return slowerMode; else throw cRuntimeError("Mandatory mode not found"); @@ -112,7 +116,7 @@ const IIeee80211Mode *QosRateSelection::computeResponseCtsFrameMode(Packet *pack const IIeee80211Mode *QosRateSelection::computeResponseBlockAckFrameMode(Packet *packet, const Ptr& blockAckReq) { if (dynamicPtrCast(blockAckReq)) - return responseBlockAckFrameMode ? responseBlockAckFrameMode : getMode(packet, blockAckReq); + return responseBlockAckFrameMode ? responseBlockAckFrameMode : modeSet->getControlResponseMode(getMode(packet, blockAckReq)); else throw cRuntimeError("Unknown BlockAckReq frame type"); } @@ -248,4 +252,3 @@ void QosRateSelection::frameTransmitted(Packet *packet, const PtrgetMode(bps(controlFrameBitrate)); double responseAckFrameBitrate = par("responseAckFrameBitrate"); - responseAckFrameMode = (responseAckFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseAckFrameBitrate)); + responseAckFrameMode = (responseAckFrameBitrate == -1) ? nullptr : modeSet->getControlResponseMode(modeSet->getMode(bps(responseAckFrameBitrate))); double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); - responseCtsFrameMode = (responseCtsFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseCtsFrameBitrate)); + responseCtsFrameMode = (responseCtsFrameBitrate == -1) ? nullptr : modeSet->getControlResponseMode(modeSet->getMode(bps(responseCtsFrameBitrate))); fastestMandatoryMode = modeSet->getFastestMandatoryMode(); // WATCH(dataOrMgmtRateControl); @@ -84,7 +84,9 @@ const IIeee80211Mode *RateSelection::computeResponseAckFrameMode(Packet *packet, else { auto mode = getMode(packet, dataOrMgmtHeader); ASSERT(modeSet->containsMode(mode)); - return modeSet->getIsMandatory(mode) ? mode : modeSet->getSlowerMandatoryMode(mode); // TODO BSSBasicRateSet + auto responseModeSet = modeSet->getControlResponseModeSet(mode); + auto responseMode = responseModeSet->getMode(mode); + return responseModeSet->getIsMandatory(responseMode) ? responseMode : responseModeSet->getSlowerMandatoryMode(responseMode); // TODO BSSBasicRateSet } } @@ -95,7 +97,9 @@ const IIeee80211Mode *RateSelection::computeResponseCtsFrameMode(Packet *packet, else { auto mode = getMode(packet, rtsFrame); ASSERT(modeSet->containsMode(mode)); - return modeSet->getIsMandatory(mode) ? mode : modeSet->getSlowerMandatoryMode(mode); // TODO BSSBasicRateSet + auto responseModeSet = modeSet->getControlResponseModeSet(mode); + auto responseMode = responseModeSet->getMode(mode); + return responseModeSet->getIsMandatory(responseMode) ? responseMode : responseModeSet->getSlowerMandatoryMode(responseMode); // TODO BSSBasicRateSet } } @@ -170,4 +174,3 @@ void RateSelection::setFrameMode(Packet *packet, const Ptr +#include #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ErpOfdmMode.h" @@ -496,6 +497,30 @@ int Ieee80211ModeSet::findModeIndex(const IIeee80211Mode *mode) const for (size_t index = 0; index < entries.size(); index++) if (entries[index].mode == mode) return index; + // HT mixed and Greenfield modes have distinct cached identities because their + // preambles and timing differ, but they represent the same data-rate mode for + // mode-set membership and rate selection purposes. + if (mode != nullptr) { + for (size_t index = 0; index < entries.size(); index++) { + auto entryMode = entries[index].mode; + auto entryHeaderMode = entryMode->getHeaderMode(); + auto modeHeaderMode = mode->getHeaderMode(); + auto entryDataMode = entryMode->getDataMode(); + auto modeDataMode = mode->getDataMode(); + if (typeid(*entryMode) == typeid(*mode) && + entryHeaderMode->getNetBitrate() == modeHeaderMode->getNetBitrate() && + entryHeaderMode->getGrossBitrate() == modeHeaderMode->getGrossBitrate() && + entryHeaderMode->getSymbolInterval() == modeHeaderMode->getSymbolInterval() && + entryDataMode->getBandwidth() == modeDataMode->getBandwidth() && + entryDataMode->getNetBitrate() == modeDataMode->getNetBitrate() && + entryDataMode->getGrossBitrate() == modeDataMode->getGrossBitrate() && + entryDataMode->getSymbolInterval() == modeDataMode->getSymbolInterval() && + entryDataMode->getNumberOfSpatialStreams() == modeDataMode->getNumberOfSpatialStreams()) + { + return index; + } + } + } return -1; } @@ -513,6 +538,20 @@ bool Ieee80211ModeSet::getIsMandatory(const IIeee80211Mode *mode) const return entries[getModeIndex(mode)].isMandatory; } +const IIeee80211Mode *Ieee80211ModeSet::findMode(const IIeee80211Mode *mode) const +{ + int index = findModeIndex(mode); + return index >= 0 ? entries[index].mode : nullptr; +} + +const IIeee80211Mode *Ieee80211ModeSet::getMode(const IIeee80211Mode *mode) const +{ + auto result = findMode(mode); + if (result == nullptr) + throw cRuntimeError("Unknown mode in operation mode: '%s'", getName()); + return result; +} + const IIeee80211Mode *Ieee80211ModeSet::findMode(bps bitrate, Hz bandwidth, int numSpatialStreams) const { return findMode(bitrate - Mbps(0.05), bitrate + Mbps(0.05), bandwidth, numSpatialStreams); @@ -616,6 +655,20 @@ const IIeee80211Mode *Ieee80211ModeSet::getFasterMandatoryMode(const IIeee80211M return nullptr; } +const Ieee80211ModeSet *Ieee80211ModeSet::getControlResponseModeSet(const IIeee80211Mode *mode) const +{ + // IEEE 802.11 prohibits HT-GF format for control response frames; use the + // HT-mixed profile while retaining the received mode's rate parameters. + if (dynamic_cast(mode) != nullptr) + return getModeSet("n(mixed-2.4Ghz)"); + return this; +} + +const IIeee80211Mode *Ieee80211ModeSet::getControlResponseMode(const IIeee80211Mode *mode) const +{ + return getControlResponseModeSet(mode)->getMode(mode); +} + const Ieee80211ModeSet *Ieee80211ModeSet::findModeSet(const char *mode) { for (size_t index = 0; index < (&modeSets)->size(); index++) { diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h index 98155bf77f6..19e861d833c 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h @@ -52,6 +52,8 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject bool containsMode(const IIeee80211Mode *mode) const { return findModeIndex(mode) != -1; } bool getIsMandatory(const IIeee80211Mode *mode) const; + const IIeee80211Mode *findMode(const IIeee80211Mode *mode) const; + const IIeee80211Mode *getMode(const IIeee80211Mode *mode) const; const IIeee80211Mode *findMode(bps bitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1) const; const IIeee80211Mode *findMode(bps minBitrate, bps maxBitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1) const; const IIeee80211Mode *getMode(bps bitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1) const; @@ -65,6 +67,9 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject const IIeee80211Mode *getSlowerMandatoryMode(const IIeee80211Mode *mode) const; const IIeee80211Mode *getFasterMandatoryMode(const IIeee80211Mode *mode) const; + const Ieee80211ModeSet *getControlResponseModeSet(const IIeee80211Mode *mode) const; + const IIeee80211Mode *getControlResponseMode(const IIeee80211Mode *mode) const; + static const Ieee80211ModeSet *findModeSet(const char *mode); static const Ieee80211ModeSet *getModeSet(const char *mode); @@ -84,4 +89,3 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject } // namespace inet #endif - diff --git a/tests/module/Ieee80211HtGreenfieldRuntime.test b/tests/module/Ieee80211HtGreenfieldRuntime.test index 0e2db395db4..1699371f3e0 100644 --- a/tests/module/Ieee80211HtGreenfieldRuntime.test +++ b/tests/module/Ieee80211HtGreenfieldRuntime.test @@ -4,7 +4,6 @@ Checks during one real packet exchange that Greenfield and mixed HT operation select their corresponding preamble formats. %file: Test.cc -#include #include #include "inet/common/Simsignals.h" @@ -20,7 +19,8 @@ namespace Ieee80211HtGreenfieldRuntime { class HtPreambleCheckingRadio : public Ieee80211Radio, public cListener { protected: - int htTransmissionCount = 0; + int htGreenfieldTransmissionCount = 0; + int htMixedTransmissionCount = 0; virtual void initialize(int stage) override { @@ -37,22 +37,19 @@ class HtPreambleCheckingRadio : public Ieee80211Radio, public cListener auto mode = dynamic_cast(transmission->getMode()); if (mode == nullptr) return; - htTransmissionCount++; - const char *opMode = par("opMode"); - auto expectedFormat = strcmp(opMode, "n(greenfield-2.4Ghz)") == 0 ? Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD : Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED; - const char *expectedPreamble = expectedFormat == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD ? "greenfield" : "mixed"; - if (mode->getPreambleMode()->getPreambleFormat() != expectedFormat) - throw cRuntimeError("HT transmission used an unexpected preamble format (actual %d, expected %s for %s)", (int)mode->getPreambleMode()->getPreambleFormat(), expectedPreamble, opMode); + if (mode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD) + htGreenfieldTransmissionCount++; + else if (mode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED) + htMixedTransmissionCount++; } virtual void finish() override { Ieee80211Radio::finish(); - if (htTransmissionCount == 0) - return; - const char *opMode = par("opMode"); - const char *preamble = strcmp(opMode, "n(greenfield-2.4Ghz)") == 0 ? "greenfield" : "mixed"; - std::cout << "Observed " << htTransmissionCount << " runtime HT " << preamble << " transmission(s).\n"; + if (htGreenfieldTransmissionCount > 0) + std::cout << "Observed " << htGreenfieldTransmissionCount << " runtime HT greenfield transmission(s).\n"; + if (htMixedTransmissionCount > 0) + std::cout << "Observed " << htMixedTransmissionCount << " runtime HT mixed transmission(s).\n"; } }; diff --git a/tests/unit/Ieee80211HtGreenfield_1.test b/tests/unit/Ieee80211HtGreenfield_1.test index 44b790b4a3f..2a686ac0dc1 100644 --- a/tests/unit/Ieee80211HtGreenfield_1.test +++ b/tests/unit/Ieee80211HtGreenfield_1.test @@ -39,6 +39,13 @@ ASSERT(greenfieldMode->getPreambleMode()->getDuration() == SimTime(24, SIMTIME_U ASSERT(mixedMode->getDuration(B(0)) == SimTime(40, SIMTIME_US)); ASSERT(greenfieldMode->getDuration(B(0)) == SimTime(28, SIMTIME_US)); ASSERT(mixedMode->getDuration(B(0)) - greenfieldMode->getDuration(B(0)) == SimTime(12, SIMTIME_US)); +ASSERT(mixedProfile->containsMode(greenfieldMode)); +ASSERT(greenfieldProfile->containsMode(mixedMode)); +ASSERT(mixedProfile->getMode(greenfieldMode) == mixedMode); +ASSERT(greenfieldProfile->getMode(mixedMode) == greenfieldMode); +ASSERT(mixedProfile->getControlResponseMode(mixedMode) == mixedMode); +ASSERT(greenfieldProfile->getControlResponseModeSet(greenfieldMode) == mixedProfile); +ASSERT(greenfieldProfile->getControlResponseMode(greenfieldMode) == mixedMode); EV << "HT mixed and Greenfield modes have distinct cached identities and timing.\n"; From 971e4ffc9a304e09862910e97cba977820215337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Sat, 15 Aug 2026 17:36:15 +0200 Subject: [PATCH 03/10] Fix HT control-response mode matching Restrict cross-profile mode equivalence to HT modes and compare the MCS index, bandwidth, guard interval, frequency band, and spatial stream count while intentionally ignoring the mixed/Greenfield preamble format. Discover the control-response mode set from the registered same-band HT-mixed profiles instead of hardcoding the 2.4 GHz profile, and fail explicitly when the counterpart is missing or ambiguous. Keep configured ACK, CTS, and BlockAck modes owned by each rate selector's active mode set, translating them to the control-response profile only when the response is computed. Add focused coverage for HT mixed/Greenfield mapping, 5 GHz rejection without a registered counterpart, strict ERP mode membership, and configured DCF/QoS response-mode caching and conversion. --- .../mac/rateselection/QosRateSelection.cc | 18 ++- .../mac/rateselection/RateSelection.cc | 8 +- .../ieee80211/mode/Ieee80211ModeSet.cc | 55 ++++---- ...e80211ConfiguredResponseRateSelection.test | 122 ++++++++++++++++++ tests/unit/Ieee80211HtGreenfield_1.test | 55 +++++++- 5 files changed, 224 insertions(+), 34 deletions(-) create mode 100644 tests/module/Ieee80211ConfiguredResponseRateSelection.test diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc index 4602c71a02b..b191ad9904d 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc @@ -32,11 +32,11 @@ void QosRateSelection::initialize(int stage) double controlFrameBitrate = par("controlFrameBitrate"); controlFrameMode = (controlFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(controlFrameBitrate)); double responseAckFrameBitrate = par("responseAckFrameBitrate"); - responseAckFrameMode = (responseAckFrameBitrate == -1) ? nullptr : modeSet->getControlResponseMode(modeSet->getMode(bps(responseAckFrameBitrate))); + responseAckFrameMode = (responseAckFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseAckFrameBitrate)); double responseBlockAckFrameBitrate = par("responseBlockAckFrameBitrate"); - responseBlockAckFrameMode = (responseBlockAckFrameBitrate == -1) ? nullptr : modeSet->getControlResponseMode(modeSet->getMode(bps(responseBlockAckFrameBitrate))); + responseBlockAckFrameMode = (responseBlockAckFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseBlockAckFrameBitrate)); double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); - responseCtsFrameMode = (responseCtsFrameBitrate == -1) ? nullptr : modeSet->getControlResponseMode(modeSet->getMode(bps(responseCtsFrameBitrate))); + responseCtsFrameMode = (responseCtsFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseCtsFrameBitrate)); } } @@ -85,7 +85,7 @@ const IIeee80211Mode *QosRateSelection::computeResponseAckFrameMode(Packet *pack throw cRuntimeError("Mandatory mode not found"); } else - return responseAckFrameMode; + return modeSet->getControlResponseMode(responseAckFrameMode); } const IIeee80211Mode *QosRateSelection::computeResponseCtsFrameMode(Packet *packet, const Ptr& rtsFrame) @@ -104,7 +104,7 @@ const IIeee80211Mode *QosRateSelection::computeResponseCtsFrameMode(Packet *pack throw cRuntimeError("Mandatory mode not found"); } else - return responseCtsFrameMode; + return modeSet->getControlResponseMode(responseCtsFrameMode); } // @@ -115,8 +115,12 @@ const IIeee80211Mode *QosRateSelection::computeResponseCtsFrameMode(Packet *pack // const IIeee80211Mode *QosRateSelection::computeResponseBlockAckFrameMode(Packet *packet, const Ptr& blockAckReq) { - if (dynamicPtrCast(blockAckReq)) - return responseBlockAckFrameMode ? responseBlockAckFrameMode : modeSet->getControlResponseMode(getMode(packet, blockAckReq)); + if (dynamicPtrCast(blockAckReq)) { + if (responseBlockAckFrameMode) + return modeSet->getControlResponseMode(responseBlockAckFrameMode); + else + return modeSet->getControlResponseMode(getMode(packet, blockAckReq)); + } else throw cRuntimeError("Unknown BlockAckReq frame type"); } diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc index d3671871d57..2d9a1482401 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc @@ -38,9 +38,9 @@ void RateSelection::initialize(int stage) double controlFrameBitrate = par("controlFrameBitrate"); controlFrameMode = (controlFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(controlFrameBitrate)); double responseAckFrameBitrate = par("responseAckFrameBitrate"); - responseAckFrameMode = (responseAckFrameBitrate == -1) ? nullptr : modeSet->getControlResponseMode(modeSet->getMode(bps(responseAckFrameBitrate))); + responseAckFrameMode = (responseAckFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseAckFrameBitrate)); double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); - responseCtsFrameMode = (responseCtsFrameBitrate == -1) ? nullptr : modeSet->getControlResponseMode(modeSet->getMode(bps(responseCtsFrameBitrate))); + responseCtsFrameMode = (responseCtsFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseCtsFrameBitrate)); fastestMandatoryMode = modeSet->getFastestMandatoryMode(); // WATCH(dataOrMgmtRateControl); @@ -80,7 +80,7 @@ const IIeee80211Mode *RateSelection::getMode(Packet *packet, const Ptr& dataOrMgmtHeader) { if (responseAckFrameMode) - return responseAckFrameMode; + return modeSet->getControlResponseMode(responseAckFrameMode); else { auto mode = getMode(packet, dataOrMgmtHeader); ASSERT(modeSet->containsMode(mode)); @@ -93,7 +93,7 @@ const IIeee80211Mode *RateSelection::computeResponseAckFrameMode(Packet *packet, const IIeee80211Mode *RateSelection::computeResponseCtsFrameMode(Packet *packet, const Ptr& rtsFrame) { if (responseCtsFrameMode) - return responseCtsFrameMode; + return modeSet->getControlResponseMode(responseCtsFrameMode); else { auto mode = getMode(packet, rtsFrame); ASSERT(modeSet->containsMode(mode)); diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc index b0d4c95c1f2..7ea1164aa78 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc @@ -8,7 +8,6 @@ #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" #include -#include #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ErpOfdmMode.h" @@ -500,24 +499,20 @@ int Ieee80211ModeSet::findModeIndex(const IIeee80211Mode *mode) const // HT mixed and Greenfield modes have distinct cached identities because their // preambles and timing differ, but they represent the same data-rate mode for // mode-set membership and rate selection purposes. - if (mode != nullptr) { + if (auto htMode = dynamic_cast(mode)) { + auto htDataMode = htMode->getDataMode(); for (size_t index = 0; index < entries.size(); index++) { - auto entryMode = entries[index].mode; - auto entryHeaderMode = entryMode->getHeaderMode(); - auto modeHeaderMode = mode->getHeaderMode(); - auto entryDataMode = entryMode->getDataMode(); - auto modeDataMode = mode->getDataMode(); - if (typeid(*entryMode) == typeid(*mode) && - entryHeaderMode->getNetBitrate() == modeHeaderMode->getNetBitrate() && - entryHeaderMode->getGrossBitrate() == modeHeaderMode->getGrossBitrate() && - entryHeaderMode->getSymbolInterval() == modeHeaderMode->getSymbolInterval() && - entryDataMode->getBandwidth() == modeDataMode->getBandwidth() && - entryDataMode->getNetBitrate() == modeDataMode->getNetBitrate() && - entryDataMode->getGrossBitrate() == modeDataMode->getGrossBitrate() && - entryDataMode->getSymbolInterval() == modeDataMode->getSymbolInterval() && - entryDataMode->getNumberOfSpatialStreams() == modeDataMode->getNumberOfSpatialStreams()) - { - return index; + auto entryHtMode = dynamic_cast(entries[index].mode); + if (entryHtMode != nullptr) { + auto entryHtDataMode = entryHtMode->getDataMode(); + if (entryHtDataMode->getMcsIndex() == htDataMode->getMcsIndex() && + entryHtDataMode->getBandwidth() == htDataMode->getBandwidth() && + entryHtDataMode->getGuardIntervalType() == htDataMode->getGuardIntervalType() && + entryHtMode->getCenterFrequencyMode() == htMode->getCenterFrequencyMode() && + entryHtDataMode->getNumberOfSpatialStreams() == htDataMode->getNumberOfSpatialStreams()) + { + return index; + } } } } @@ -657,10 +652,26 @@ const IIeee80211Mode *Ieee80211ModeSet::getFasterMandatoryMode(const IIeee80211M const Ieee80211ModeSet *Ieee80211ModeSet::getControlResponseModeSet(const IIeee80211Mode *mode) const { - // IEEE 802.11 prohibits HT-GF format for control response frames; use the - // HT-mixed profile while retaining the received mode's rate parameters. - if (dynamic_cast(mode) != nullptr) - return getModeSet("n(mixed-2.4Ghz)"); + // IEEE 802.11 prohibits HT-GF format for control response frames; use a + // same-band HT-mixed profile while retaining the received mode's rate parameters. + if (auto htMode = dynamic_cast(mode)) { + const Ieee80211ModeSet *controlResponseModeSet = nullptr; + for (size_t index = 0; index < (&modeSets)->size(); index++) { + auto candidateModeSet = &(&modeSets)->at(index); + auto candidateMode = dynamic_cast(candidateModeSet->findMode(mode)); + if (candidateMode != nullptr && + candidateMode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED && + candidateMode->getCenterFrequencyMode() == htMode->getCenterFrequencyMode()) + { + if (controlResponseModeSet != nullptr) + throw cRuntimeError("Multiple same-band HT-mixed control response mode sets for mode: '%s'", mode->getName()); + controlResponseModeSet = candidateModeSet; + } + } + if (controlResponseModeSet == nullptr) + throw cRuntimeError("No same-band HT-mixed control response mode set for mode: '%s'", mode->getName()); + return controlResponseModeSet; + } return this; } diff --git a/tests/module/Ieee80211ConfiguredResponseRateSelection.test b/tests/module/Ieee80211ConfiguredResponseRateSelection.test new file mode 100644 index 00000000000..02a1ae0c711 --- /dev/null +++ b/tests/module/Ieee80211ConfiguredResponseRateSelection.test @@ -0,0 +1,122 @@ +%description: +Checks that configured response rates are cached as local modes and converted +to HT-mixed control-response modes at compute time for DCF and QoS rate +selection, including a Basic BlockAck response. + +%file: Test.cc +#include + +#include "inet/common/InitStages.h" +#include "inet/common/packet/Packet.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" +#define protected public // test-only access to verify initialized response-mode ownership +#include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" +#include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h" +#undef protected +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" + +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +namespace Ieee80211ConfiguredResponseRateSelection { + +static void assertConfiguredResponse(const char *name, const IIeee80211Mode *mode) +{ + auto htMode = dynamic_cast(mode); + if (htMode == nullptr) + throw cRuntimeError("%s did not return an HT mode", name); + if (htMode->getPreambleMode()->getPreambleFormat() != Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED) + throw cRuntimeError("%s did not return an HT-mixed mode", name); + if (htMode->getDataMode()->getNetBitrate() != Mbps(6.5)) + throw cRuntimeError("%s did not return 6.5 Mbps", name); +} + +class ConfiguredResponseProbe : public cSimpleModule +{ + protected: + virtual int numInitStages() const override { return NUM_INIT_STAGES; } + + virtual void initialize(int stage) override + { + if (stage != INITSTAGE_LAST) + return; + + auto dcfRateSelection = check_and_cast(getSimulation()->getModuleByPath("TestConfiguredResponses.host[0].wlan[0].mac.dcf.rateSelection")); + auto qosRateSelection = check_and_cast(getSimulation()->getModuleByPath("TestConfiguredResponses.host[0].wlan[0].mac.hcf.rateSelection")); + + if (double(dcfRateSelection->par("responseAckFrameBitrate")) != 6.5e6 + || double(qosRateSelection->par("responseAckFrameBitrate")) != 6.5e6) + throw cRuntimeError("Configured response bitrate was not applied to both rate selections"); + + auto sourceMode = Ieee80211ModeSet::getModeSet("n(greenfield-2.4Ghz)")->getMode(Mbps(6.5)); + if (dcfRateSelection->responseAckFrameMode != sourceMode || dcfRateSelection->responseCtsFrameMode != sourceMode + || qosRateSelection->responseAckFrameMode != sourceMode || qosRateSelection->responseCtsFrameMode != sourceMode + || qosRateSelection->responseBlockAckFrameMode != sourceMode) + throw cRuntimeError("Configured response modes were not cached as local Greenfield modes"); + Packet packet("receivedFrame"); + packet.addTag()->setMode(sourceMode); + + assertConfiguredResponse("DCF ACK", dcfRateSelection->computeResponseAckFrameMode(nullptr, nullptr)); + assertConfiguredResponse("DCF CTS", dcfRateSelection->computeResponseCtsFrameMode(nullptr, nullptr)); + assertConfiguredResponse("QoS ACK", qosRateSelection->computeResponseAckFrameMode(&packet, nullptr)); + assertConfiguredResponse("QoS CTS", qosRateSelection->computeResponseCtsFrameMode(&packet, nullptr)); + + auto basicBlockAckReq = makeShared(); + assertConfiguredResponse("QoS Basic BlockAck", qosRateSelection->computeResponseBlockAckFrameMode(&packet, basicBlockAckReq)); + + std::cout << "Configured DCF and QoS ACK/CTS/Basic BlockAck responses use HT-mixed 6.5 Mbps modes.\n"; + } +}; + +Define_Module(ConfiguredResponseProbe); + +} // namespace Ieee80211ConfiguredResponseRateSelection + +%file: test.ned + +import inet.examples.wireless.lan80211.Lan80211; + +simple ConfiguredResponseProbe +{ + @class(ConfiguredResponseProbe); +} + +network TestConfiguredResponses extends Lan80211 +{ + parameters: + numHosts = default(2); + submodules: + configuredResponseProbe: ConfiguredResponseProbe; +} + +%inifile: omnetpp.ini + +[General] +include ../../../../examples/wireless/lan80211/omnetpp-ht-greenfield.ini +network = TestConfiguredResponses +abstract = false +ned-path = .;../../../../src;../../../../examples;../../lib +cmdenv-express-mode = true +record-vector-results = false +record-eventlog = false +seed-set = 0 + +[Config ConfiguredResponses] +abstract = false +network = TestConfiguredResponses +sim-time-limit = 1s +**.opMode = "n(greenfield-2.4Ghz)" +**.qosStation = true +**.wlan[*].mac.dcf.rateSelection.responseAckFrameBitrate = 6.5Mbps +**.wlan[*].mac.dcf.rateSelection.responseCtsFrameBitrate = 6.5Mbps +**.wlan[*].mac.hcf.rateSelection.responseAckFrameBitrate = 6.5Mbps +**.wlan[*].mac.hcf.rateSelection.responseCtsFrameBitrate = 6.5Mbps +**.wlan[*].mac.hcf.rateSelection.responseBlockAckFrameBitrate = 6.5Mbps + +%extraargs: -c ConfiguredResponses + +%contains: stdout +Configured DCF and QoS ACK/CTS/Basic BlockAck responses use HT-mixed 6.5 Mbps modes. diff --git a/tests/unit/Ieee80211HtGreenfield_1.test b/tests/unit/Ieee80211HtGreenfield_1.test index 2a686ac0dc1..c73d94e0502 100644 --- a/tests/unit/Ieee80211HtGreenfield_1.test +++ b/tests/unit/Ieee80211HtGreenfield_1.test @@ -1,10 +1,14 @@ %description: Checks that the 802.11n mixed and Greenfield mode profiles preserve distinct -preamble formats and timing when HT modes are cached. +preamble formats and timing while supporting cross-profile lookup, and that +legacy ERP mode profiles retain strict mode membership. %includes: +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssOfdmMode.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ErpOfdmMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211OfdmMode.h" %global: using namespace inet; @@ -17,6 +21,28 @@ static const Ieee80211HtMode *findMcs0LongGiMode(const Ieee80211ModeSet *modeSet mode->getDataMode()->getGuardIntervalType() == Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG ? mode : nullptr; } +static bool rejectsUnknownMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) +{ + try { + modeSet->getMode(mode); + return false; + } + catch (const cRuntimeError&) { + return true; + } +} + +static bool rejectsControlResponseModeSet(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) +{ + try { + modeSet->getControlResponseModeSet(mode); + return false; + } + catch (const cRuntimeError&) { + return true; + } +} + %activity: auto mixedProfile = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"); auto greenfieldProfile = Ieee80211ModeSet::getModeSet("n(greenfield-2.4Ghz)"); @@ -47,6 +73,33 @@ ASSERT(mixedProfile->getControlResponseMode(mixedMode) == mixedMode); ASSERT(greenfieldProfile->getControlResponseModeSet(greenfieldMode) == mixedProfile); ASSERT(greenfieldProfile->getControlResponseMode(greenfieldMode) == mixedMode); +auto greenfield5GhzMode = Ieee80211HtCompliantModes::getCompliantMode( + &Ieee80211HtmcsTable::htMcs0BW20MHz, + Ieee80211HtMode::BAND_5GHZ, + Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, + Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG); +ASSERT(greenfield5GhzMode->getCenterFrequencyMode() == Ieee80211HtMode::BAND_5GHZ); +ASSERT(greenfield5GhzMode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD); +ASSERT(rejectsControlResponseModeSet(greenfieldProfile, greenfield5GhzMode)); + +auto erpMixedProfile = Ieee80211ModeSet::getModeSet("g(mixed)"); +auto erpOnlyProfile = Ieee80211ModeSet::getModeSet("g(erp)"); +auto erpMixedMode = &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps; +auto erpOnlyMode = &Ieee80211ErpOfdmCompliantModes::erpOnlyOfdmMode6Mbps; +Ieee80211DsssOfdmMode dsssOfdmMode("dsss-ofdm", nullptr, nullptr, nullptr, nullptr, &Ieee80211OfdmCompliantModes::ofdmDataMode6MbpsCS20MHz); +ASSERT(erpMixedProfile != nullptr); +ASSERT(erpOnlyProfile != nullptr); +ASSERT(erpMixedMode != erpOnlyMode); +ASSERT(erpMixedMode->getSlotTime() == SimTime(20, SIMTIME_US)); +ASSERT(erpOnlyMode->getSlotTime() == SimTime(9, SIMTIME_US)); +ASSERT(!erpMixedProfile->containsMode(erpOnlyMode)); +ASSERT(!erpOnlyProfile->containsMode(erpMixedMode)); +ASSERT(erpMixedProfile->findMode(erpOnlyMode) == nullptr); +ASSERT(erpOnlyProfile->findMode(erpMixedMode) == nullptr); +ASSERT(erpMixedProfile->findMode(&dsssOfdmMode) == nullptr); +ASSERT(rejectsUnknownMode(erpMixedProfile, erpOnlyMode)); +ASSERT(rejectsUnknownMode(erpOnlyProfile, erpMixedMode)); + EV << "HT mixed and Greenfield modes have distinct cached identities and timing.\n"; %contains: stdout From ec396c45c6afb0f3b242db0fcd472ea29ccc9ee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Sun, 16 Aug 2026 02:46:41 +0200 Subject: [PATCH 04/10] 802.11: keep HT mode membership strict Restore exact pointer identity for mode-set membership so receiver feasibility checks and transmitter validation do not treat HT mixed and Greenfield modes as interchangeable. Add a separate HT-equivalence lookup for control-response format translation and cache successful control-response resolutions. Preserve same-band mixed-format selection while rejecting modes outside the configured operation mode, including unsupported 5 GHz HT inputs. Include band and preamble format in the VHT compliant-mode cache key. Update the HT unit and runtime coverage, and remove the protected-to-public preprocessor workaround from the configured response-rate test. --- .../ieee80211/mode/Ieee80211ModeSet.cc | 40 ++++++++++---- .../ieee80211/mode/Ieee80211ModeSet.h | 8 +++ .../ieee80211/mode/Ieee80211VhtMode.cc | 7 ++- .../ieee80211/mode/Ieee80211VhtMode.h | 2 +- ...e80211ConfiguredResponseRateSelection.test | 13 ++--- .../module/Ieee80211HtGreenfieldRuntime.test | 43 ++++++++------- tests/unit/Ieee80211HtGreenfield_1.test | 54 ++++++++++++++++--- 7 files changed, 113 insertions(+), 54 deletions(-) diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc index 7ea1164aa78..9bf49a06b61 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc @@ -496,9 +496,14 @@ int Ieee80211ModeSet::findModeIndex(const IIeee80211Mode *mode) const for (size_t index = 0; index < entries.size(); index++) if (entries[index].mode == mode) return index; - // HT mixed and Greenfield modes have distinct cached identities because their - // preambles and timing differ, but they represent the same data-rate mode for - // mode-set membership and rate selection purposes. + return -1; +} + +int Ieee80211ModeSet::findEquivalentModeIndex(const IIeee80211Mode *mode) const +{ + int modeIndex = findModeIndex(mode); + if (modeIndex != -1) + return modeIndex; if (auto htMode = dynamic_cast(mode)) { auto htDataMode = htMode->getDataMode(); for (size_t index = 0; index < entries.size(); index++) { @@ -535,7 +540,7 @@ bool Ieee80211ModeSet::getIsMandatory(const IIeee80211Mode *mode) const const IIeee80211Mode *Ieee80211ModeSet::findMode(const IIeee80211Mode *mode) const { - int index = findModeIndex(mode); + int index = findEquivalentModeIndex(mode); return index >= 0 ? entries[index].mode : nullptr; } @@ -650,12 +655,20 @@ const IIeee80211Mode *Ieee80211ModeSet::getFasterMandatoryMode(const IIeee80211M return nullptr; } -const Ieee80211ModeSet *Ieee80211ModeSet::getControlResponseModeSet(const IIeee80211Mode *mode) const +const Ieee80211ModeSet::ControlResponseMode& Ieee80211ModeSet::resolveControlResponseMode(const IIeee80211Mode *mode) const { - // IEEE 802.11 prohibits HT-GF format for control response frames; use a - // same-band HT-mixed profile while retaining the received mode's rate parameters. + auto cachedMode = controlResponseModeCache.find(mode); + if (cachedMode != controlResponseModeCache.end()) + return cachedMode->second; + if (!containsMode(mode)) + throw cRuntimeError("Control response mode is not in operation mode '%s': '%s'", getName(), mode->getName()); + + const Ieee80211ModeSet *controlResponseModeSet = this; + const IIeee80211Mode *controlResponseMode = mode; + // IEEE 802.11-2024, 10.6.6.5.7 requires HT control responses to use the + // HT-mixed format; 19.3.9.5.1 defines HT-mixed and HT-Greenfield preambles. if (auto htMode = dynamic_cast(mode)) { - const Ieee80211ModeSet *controlResponseModeSet = nullptr; + controlResponseModeSet = nullptr; for (size_t index = 0; index < (&modeSets)->size(); index++) { auto candidateModeSet = &(&modeSets)->at(index); auto candidateMode = dynamic_cast(candidateModeSet->findMode(mode)); @@ -670,14 +683,19 @@ const Ieee80211ModeSet *Ieee80211ModeSet::getControlResponseModeSet(const IIeee8 } if (controlResponseModeSet == nullptr) throw cRuntimeError("No same-band HT-mixed control response mode set for mode: '%s'", mode->getName()); - return controlResponseModeSet; + controlResponseMode = controlResponseModeSet->getMode(mode); } - return this; + return controlResponseModeCache.emplace(mode, ControlResponseMode { controlResponseModeSet, controlResponseMode }).first->second; +} + +const Ieee80211ModeSet *Ieee80211ModeSet::getControlResponseModeSet(const IIeee80211Mode *mode) const +{ + return resolveControlResponseMode(mode).modeSet; } const IIeee80211Mode *Ieee80211ModeSet::getControlResponseMode(const IIeee80211Mode *mode) const { - return getControlResponseModeSet(mode)->getMode(mode); + return resolveControlResponseMode(mode).mode; } const Ieee80211ModeSet *Ieee80211ModeSet::findModeSet(const char *mode) diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h index 19e861d833c..ee23d827671 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h @@ -27,16 +27,24 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject bool operator()(const Entry& left, const Entry& right) { return left.mode->getDataMode()->getNetBitrate() < right.mode->getDataMode()->getNetBitrate(); } }; + struct ControlResponseMode { + const Ieee80211ModeSet *modeSet; + const IIeee80211Mode *mode; + }; + protected: std::string name; const std::vector entries; + mutable std::map controlResponseModeCache; public: static const DelayedInitializer> modeSets; protected: int findModeIndex(const IIeee80211Mode *mode) const; + int findEquivalentModeIndex(const IIeee80211Mode *mode) const; int getModeIndex(const IIeee80211Mode *mode) const; + const ControlResponseMode& resolveControlResponseMode(const IIeee80211Mode *mode) const; public: Ieee80211ModeSet(const char *name, const std::vector entries); diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.cc b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.cc index 89b142e37cb..07a6f6c0db4 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.cc +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.cc @@ -674,8 +674,8 @@ const Ieee80211VhtMode *Ieee80211VhtCompliantModes::getCompliantMode(const Ieee8 { const char *name = ""; // TODO unsigned int nss = mcsMode->getNumNss(); - auto htModeId = std::make_tuple(mcsMode->getBandwidth(), mcsMode->getMcsIndex(), guardIntervalType, nss); - auto mode = singleton.modeCache.find(htModeId); + auto vhtModeId = std::make_tuple(mcsMode->getBandwidth(), mcsMode->getMcsIndex(), centerFrequencyMode, preambleFormat, guardIntervalType, nss); + auto mode = singleton.modeCache.find(vhtModeId); if (mode == singleton.modeCache.end()) { const Ieee80211OfdmSignalMode *legacySignal = nullptr; const Ieee80211VhtSignalMode *htSignal = nullptr; @@ -693,7 +693,7 @@ const Ieee80211VhtMode *Ieee80211VhtCompliantModes::getCompliantMode(const Ieee8 const Ieee80211VhtDataMode *dataMode = new Ieee80211VhtDataMode(mcsMode, mcsMode->getBandwidth(), guardIntervalType); const Ieee80211VhtPreambleMode *preambleMode = new Ieee80211VhtPreambleMode(htSignal, legacySignal, preambleFormat, dataMode->getNumberOfSpatialStreams()); const Ieee80211VhtMode *htMode = new Ieee80211VhtMode(name, preambleMode, dataMode, centerFrequencyMode); - singleton.modeCache.insert(std::pair, const Ieee80211VhtMode *>(htModeId, htMode)); + singleton.modeCache.emplace(vhtModeId, htMode); return htMode; } return mode->second; @@ -1072,4 +1072,3 @@ const DI Ieee80211VhtmcsTable::vhtMcs9BW160MHzNss8([](){ return } /* namespace physicallayer */ } /* namespace inet */ - diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.h index 531c6e1d48d..c804237e55e 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.h @@ -679,7 +679,7 @@ class INET_API Ieee80211VhtCompliantModes protected: static OPP_THREAD_LOCAL const Ieee80211VhtCompliantModes singleton; - mutable std::map, const Ieee80211VhtMode *> modeCache; + mutable std::map, const Ieee80211VhtMode *> modeCache; public: Ieee80211VhtCompliantModes(); diff --git a/tests/module/Ieee80211ConfiguredResponseRateSelection.test b/tests/module/Ieee80211ConfiguredResponseRateSelection.test index 02a1ae0c711..7f0d0436e19 100644 --- a/tests/module/Ieee80211ConfiguredResponseRateSelection.test +++ b/tests/module/Ieee80211ConfiguredResponseRateSelection.test @@ -1,7 +1,6 @@ %description: -Checks that configured response rates are cached as local modes and converted -to HT-mixed control-response modes at compute time for DCF and QoS rate -selection, including a Basic BlockAck response. +Checks through the public rate-selection API that configured Greenfield response +rates are converted to HT-mixed modes for DCF and QoS ACK, CTS, and Basic BlockAck responses. %file: Test.cc #include @@ -9,10 +8,8 @@ selection, including a Basic BlockAck response. #include "inet/common/InitStages.h" #include "inet/common/packet/Packet.h" #include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" -#define protected public // test-only access to verify initialized response-mode ownership #include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" #include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h" -#undef protected #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" @@ -52,12 +49,8 @@ class ConfiguredResponseProbe : public cSimpleModule throw cRuntimeError("Configured response bitrate was not applied to both rate selections"); auto sourceMode = Ieee80211ModeSet::getModeSet("n(greenfield-2.4Ghz)")->getMode(Mbps(6.5)); - if (dcfRateSelection->responseAckFrameMode != sourceMode || dcfRateSelection->responseCtsFrameMode != sourceMode - || qosRateSelection->responseAckFrameMode != sourceMode || qosRateSelection->responseCtsFrameMode != sourceMode - || qosRateSelection->responseBlockAckFrameMode != sourceMode) - throw cRuntimeError("Configured response modes were not cached as local Greenfield modes"); Packet packet("receivedFrame"); - packet.addTag()->setMode(sourceMode); + packet.addTag()->setMode(sourceMode); assertConfiguredResponse("DCF ACK", dcfRateSelection->computeResponseAckFrameMode(nullptr, nullptr)); assertConfiguredResponse("DCF CTS", dcfRateSelection->computeResponseCtsFrameMode(nullptr, nullptr)); diff --git a/tests/module/Ieee80211HtGreenfieldRuntime.test b/tests/module/Ieee80211HtGreenfieldRuntime.test index 1699371f3e0..b1ef7410d76 100644 --- a/tests/module/Ieee80211HtGreenfieldRuntime.test +++ b/tests/module/Ieee80211HtGreenfieldRuntime.test @@ -1,7 +1,7 @@ %description: -Checks during one real packet exchange that Greenfield and mixed HT -operation select their corresponding preamble formats. +Checks real HT-Greenfield transmissions using response-free, group-addressed +traffic from an ad hoc station. %file: Test.cc #include @@ -20,7 +20,6 @@ class HtPreambleCheckingRadio : public Ieee80211Radio, public cListener { protected: int htGreenfieldTransmissionCount = 0; - int htMixedTransmissionCount = 0; virtual void initialize(int stage) override { @@ -39,17 +38,13 @@ class HtPreambleCheckingRadio : public Ieee80211Radio, public cListener return; if (mode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD) htGreenfieldTransmissionCount++; - else if (mode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED) - htMixedTransmissionCount++; } virtual void finish() override { Ieee80211Radio::finish(); if (htGreenfieldTransmissionCount > 0) - std::cout << "Observed " << htGreenfieldTransmissionCount << " runtime HT greenfield transmission(s).\n"; - if (htMixedTransmissionCount > 0) - std::cout << "Observed " << htMixedTransmissionCount << " runtime HT mixed transmission(s).\n"; + std::cout << "Observed " << htGreenfieldTransmissionCount << " runtime HT-Greenfield broadcast transmission(s).\n"; } }; @@ -59,7 +54,9 @@ Define_Module(HtPreambleCheckingRadio); %file: test.ned -import inet.examples.wireless.lan80211.Lan80211; +import inet.networklayer.configurator.ipv4.Ipv4NetworkConfigurator; +import inet.node.inet.AdhocHost; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211Radio; module HtPreambleCheckingRadio extends Ieee80211Radio @@ -68,10 +65,14 @@ module HtPreambleCheckingRadio extends Ieee80211Radio @class(HtPreambleCheckingRadio); } -network TestHtGreenfield extends Lan80211 +network TestHtGreenfield { parameters: - numHosts = default(2); + int numHosts = default(2); + submodules: + configurator: Ipv4NetworkConfigurator; + radioMedium: Ieee80211ScalarRadioMedium; + host[numHosts]: AdhocHost; } %inifile: omnetpp.ini @@ -92,20 +93,18 @@ seed-set = 0 abstract = false network = TestHtGreenfield sim-time-limit = 2s -*.numHosts = 2 +*.numHosts = 1 *.host[0].numApps = 1 -*.host[0].app[0].typename = "PingApp" -*.host[0].app[0].destAddr = "host[1]" +*.host[0].app[0].typename = "UdpBasicApp" +*.host[0].app[0].destAddresses = "255.255.255.255" +*.host[0].app[0].destPort = 1000 +*.host[0].app[0].messageLength = 100B +*.host[0].app[0].startTime = 100ms *.host[0].app[0].sendInterval = 100ms -*.host[1].numApps = 1 -*.host[1].app[0].typename = "PingApp" -*.host[1].app[0].destAddr = "host[0]" -*.host[1].app[0].sendInterval = 100ms -*.ap.wlan[*].opMode = "n(mixed-2.4Ghz)" -*.host[1].wlan[*].opMode = "n(mixed-2.4Ghz)" +**.limitedBroadcast = true +**.wlan[*].opMode = "n(greenfield-2.4Ghz)" %extraargs: -c HtPreambleRuntime %contains-regex: stdout -Observed [1-9][0-9]* runtime HT greenfield transmission\(s\). -Observed [1-9][0-9]* runtime HT mixed transmission\(s\). +Observed [1-9][0-9]* runtime HT-Greenfield broadcast transmission\(s\). diff --git a/tests/unit/Ieee80211HtGreenfield_1.test b/tests/unit/Ieee80211HtGreenfield_1.test index c73d94e0502..416dd931431 100644 --- a/tests/unit/Ieee80211HtGreenfield_1.test +++ b/tests/unit/Ieee80211HtGreenfield_1.test @@ -1,14 +1,17 @@ %description: Checks that the 802.11n mixed and Greenfield mode profiles preserve distinct -preamble formats and timing while supporting cross-profile lookup, and that -legacy ERP mode profiles retain strict mode membership. +preamble formats and timing, retain strict membership, and support explicit +cross-profile lookup for control-response translation. Also checks VHT cache identity. %includes: +#include + #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssOfdmMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ErpOfdmMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211OfdmMode.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.h" %global: using namespace inet; @@ -32,12 +35,23 @@ static bool rejectsUnknownMode(const Ieee80211ModeSet *modeSet, const IIeee80211 } } -static bool rejectsControlResponseModeSet(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) +static bool rejectsNonLocalControlResponseMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) { try { modeSet->getControlResponseModeSet(mode); return false; } + catch (const cRuntimeError& error) { + return std::strstr(error.what(), "not in operation mode") != nullptr; + } +} + +static bool rejectsMandatoryLookup(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) +{ + try { + modeSet->getIsMandatory(mode); + return false; + } catch (const cRuntimeError&) { return true; } @@ -65,13 +79,18 @@ ASSERT(greenfieldMode->getPreambleMode()->getDuration() == SimTime(24, SIMTIME_U ASSERT(mixedMode->getDuration(B(0)) == SimTime(40, SIMTIME_US)); ASSERT(greenfieldMode->getDuration(B(0)) == SimTime(28, SIMTIME_US)); ASSERT(mixedMode->getDuration(B(0)) - greenfieldMode->getDuration(B(0)) == SimTime(12, SIMTIME_US)); -ASSERT(mixedProfile->containsMode(greenfieldMode)); -ASSERT(greenfieldProfile->containsMode(mixedMode)); +ASSERT(!mixedProfile->containsMode(greenfieldMode)); +ASSERT(!greenfieldProfile->containsMode(mixedMode)); +ASSERT(mixedProfile->getSlowerMode(greenfieldMode) == nullptr); +ASSERT(greenfieldProfile->getFasterMode(mixedMode) == nullptr); +ASSERT(rejectsMandatoryLookup(mixedProfile, greenfieldMode)); +ASSERT(rejectsMandatoryLookup(greenfieldProfile, mixedMode)); ASSERT(mixedProfile->getMode(greenfieldMode) == mixedMode); ASSERT(greenfieldProfile->getMode(mixedMode) == greenfieldMode); ASSERT(mixedProfile->getControlResponseMode(mixedMode) == mixedMode); ASSERT(greenfieldProfile->getControlResponseModeSet(greenfieldMode) == mixedProfile); ASSERT(greenfieldProfile->getControlResponseMode(greenfieldMode) == mixedMode); +ASSERT(greenfieldProfile->getControlResponseMode(greenfieldMode) == mixedMode); auto greenfield5GhzMode = Ieee80211HtCompliantModes::getCompliantMode( &Ieee80211HtmcsTable::htMcs0BW20MHz, @@ -80,7 +99,30 @@ auto greenfield5GhzMode = Ieee80211HtCompliantModes::getCompliantMode( Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG); ASSERT(greenfield5GhzMode->getCenterFrequencyMode() == Ieee80211HtMode::BAND_5GHZ); ASSERT(greenfield5GhzMode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD); -ASSERT(rejectsControlResponseModeSet(greenfieldProfile, greenfield5GhzMode)); +ASSERT(!greenfieldProfile->containsMode(greenfield5GhzMode)); +ASSERT(rejectsNonLocalControlResponseMode(greenfieldProfile, greenfield5GhzMode)); + +auto vhtMixed5GhzMode = Ieee80211VhtCompliantModes::getCompliantMode( + &Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, + Ieee80211VhtMode::BAND_5GHZ, + Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, + Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG); +auto vhtGreenfield5GhzMode = Ieee80211VhtCompliantModes::getCompliantMode( + &Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, + Ieee80211VhtMode::BAND_5GHZ, + Ieee80211VhtPreambleMode::HT_PREAMBLE_GREENFIELD, + Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG); +auto vhtMixed2GhzMode = Ieee80211VhtCompliantModes::getCompliantMode( + &Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, + Ieee80211VhtMode::BAND_2_4GHZ, + Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, + Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG); +ASSERT(vhtMixed5GhzMode != vhtGreenfield5GhzMode); +ASSERT(vhtMixed5GhzMode != vhtMixed2GhzMode); +ASSERT(vhtGreenfield5GhzMode != vhtMixed2GhzMode); +ASSERT(vhtMixed5GhzMode->getCenterFrequencyMode() == Ieee80211VhtMode::BAND_5GHZ); +ASSERT(vhtGreenfield5GhzMode->getPreambleMode()->getPreambleFormat() == Ieee80211VhtPreambleMode::HT_PREAMBLE_GREENFIELD); +ASSERT(vhtMixed2GhzMode->getCenterFrequencyMode() == Ieee80211VhtMode::BAND_2_4GHZ); auto erpMixedProfile = Ieee80211ModeSet::getModeSet("g(mixed)"); auto erpOnlyProfile = Ieee80211ModeSet::getModeSet("g(erp)"); From 46cd4ab9b4ffe06f5a34b2b5297d59f5526b3768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Wed, 19 Aug 2026 12:17:25 +0200 Subject: [PATCH 05/10] 802.11: fix Greenfield control response handling Keep operational mode membership pointer-strict while modeling mandatory PHY capabilities separately. Greenfield HT profiles now explicitly support non-HT and HT-mixed response modes without making those modes selectable for normal data transmission. Apply IEEE 802.11-2024 control-response rules in DCF and QoS rate selection: ordinary HT ACK and Basic BlockAck responses use mandatory non-HT rates, CTS responses to HT-carried RTS frames use HT-mixed format, and configured response rates still derive their format from the eliciting PPDU. Preserve legacy and VHT override behavior and mandatory-rate fallback semantics. Remove fuzzy public mode lookup, the mutable process-global response cache, and repeated global mode-table scans. Precompute immutable response mappings, make the mode-set registry thread-local to match HT mode ownership, and re-resolve configured response modes after mode-set changes. Use explicit supported-mode validation for per-packet TX requests and RX feasibility while keeping default transmitter modes selectable and mode-set transitions atomic. Add strict membership and transmitter transition unit coverage, configured and dynamic ACK/CTS/BlockAck checks, and a two-station forced RTS/CTS runtime regression that observes HT-GF RTS, HT-MF CTS, HT-GF data, and non-HT ACK frames. Validated in debug mode with a clean build, 90/90 unit tests, 2/2 focused module tests, the HT-Greenfield example at 20/20 ping replies, and an unchanged focused fingerprint. --- .../mac/rateselection/QosRateSelection.cc | 96 +++++----- .../mac/rateselection/QosRateSelection.h | 1 + .../mac/rateselection/RateSelection.cc | 67 ++++--- .../mac/rateselection/RateSelection.h | 1 + .../ieee80211/mode/Ieee80211ModeSet.cc | 166 +++++++++++------- .../ieee80211/mode/Ieee80211ModeSet.h | 24 +-- .../packetlevel/Ieee80211Receiver.cc | 4 +- .../packetlevel/Ieee80211Transmitter.cc | 17 +- ...e80211ConfiguredResponseRateSelection.test | 51 ++++-- .../module/Ieee80211HtGreenfieldRuntime.test | 44 +++-- tests/unit/Ieee80211HtGreenfield_1.test | 58 ++++-- 11 files changed, 339 insertions(+), 190 deletions(-) diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc index b191ad9904d..f444662577a 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc @@ -7,6 +7,8 @@ #include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" + #include "inet/common/ModuleAccess.h" #include "inet/common/Simsignals.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" @@ -16,6 +18,24 @@ namespace ieee80211 { using namespace inet::physicallayer; +static const IIeee80211Mode *getConfiguredControlResponseMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) +{ + // Explicit legacy/VHT overrides retain their requested mode; HT rates need the + // bounded non-HT control-response conversion for ACK/BlockAck. + return dynamic_cast(mode) != nullptr ? modeSet->getNonHtControlResponseMode(mode) : mode; +} + +static const IIeee80211Mode *getDynamicControlResponseMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) +{ + if (dynamic_cast(mode) != nullptr || !modeSet->containsMode(mode)) + return modeSet->getNonHtControlResponseMode(mode); + if (modeSet->getIsMandatory(mode)) + return mode; + if (auto slowerMode = modeSet->getSlowerMandatoryMode(mode)) + return slowerMode; + return modeSet->getNonHtControlResponseMode(mode); +} + Define_Module(QosRateSelection); void QosRateSelection::initialize(int stage) @@ -31,15 +51,22 @@ void QosRateSelection::initialize(int stage) mgmtFrameMode = (mgmtFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(mgmtFrameBitrate)); double controlFrameBitrate = par("controlFrameBitrate"); controlFrameMode = (controlFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(controlFrameBitrate)); - double responseAckFrameBitrate = par("responseAckFrameBitrate"); - responseAckFrameMode = (responseAckFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseAckFrameBitrate)); - double responseBlockAckFrameBitrate = par("responseBlockAckFrameBitrate"); - responseBlockAckFrameMode = (responseBlockAckFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseBlockAckFrameBitrate)); - double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); - responseCtsFrameMode = (responseCtsFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseCtsFrameBitrate)); + resolveConfiguredResponseModes(); } } +void QosRateSelection::resolveConfiguredResponseModes() +{ + if (modeSet == nullptr) + return; + double responseAckFrameBitrate = par("responseAckFrameBitrate"); + responseAckFrameMode = responseAckFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(responseAckFrameBitrate)); + double responseBlockAckFrameBitrate = par("responseBlockAckFrameBitrate"); + responseBlockAckFrameMode = responseBlockAckFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(responseBlockAckFrameBitrate)); + double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); + responseCtsFrameMode = responseCtsFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(responseCtsFrameBitrate)); +} + const IIeee80211Mode *QosRateSelection::getMode(Packet *packet, const Ptr& header) { const auto& modeReqTag = packet->findTag(); @@ -71,40 +98,25 @@ bool QosRateSelection::isControlResponseFrame(const Ptr& dataOrMgmtHeader) { - // TODO BSSBasicRateSet, alternate rate + if (responseAckFrameMode) + return getConfiguredControlResponseMode(modeSet, responseAckFrameMode); auto mode = getMode(packet, dataOrMgmtHeader); - ASSERT(modeSet->containsMode(mode)); - if (!responseAckFrameMode) { - auto responseModeSet = modeSet->getControlResponseModeSet(mode); - auto responseMode = responseModeSet->getMode(mode); - if (responseModeSet->getIsMandatory(responseMode)) - return responseMode; - else if (auto slowerMode = responseModeSet->getSlowerMandatoryMode(responseMode)) - return slowerMode; - else - throw cRuntimeError("Mandatory mode not found"); - } - else - return modeSet->getControlResponseMode(responseAckFrameMode); + ASSERT(modeSet->supportsMode(mode)); + // IEEE 802.11-2024 10.6.6.1/10.6.6.5.2: this bounded model uses a + // mandatory non-HT rate for ordinary ACK responses; BSSBasicRateSet is not modelled. + return getDynamicControlResponseMode(modeSet, mode); } const IIeee80211Mode *QosRateSelection::computeResponseCtsFrameMode(Packet *packet, const Ptr& rtsFrame) { - // TODO BSSBasicRateSet, alternate rate auto mode = getMode(packet, rtsFrame); - ASSERT(modeSet->containsMode(mode)); - if (!responseCtsFrameMode) { - auto responseModeSet = modeSet->getControlResponseModeSet(mode); - auto responseMode = responseModeSet->getMode(mode); - if (responseModeSet->getIsMandatory(responseMode)) - return responseMode; - else if (auto slowerMode = responseModeSet->getSlowerMandatoryMode(responseMode)) - return slowerMode; - else - throw cRuntimeError("Mandatory mode not found"); + ASSERT(modeSet->supportsMode(mode)); + auto responseMode = responseCtsFrameMode ? responseCtsFrameMode : mode; + if (dynamic_cast(mode) != nullptr) { + // IEEE 802.11-2024 10.6.6.1 and 10.6.6.5.7 require an HT response to HT RTS; HT-GF is never a response. + return responseCtsFrameMode && dynamic_cast(responseMode) == nullptr ? responseMode : modeSet->getControlResponseMode(responseMode); } - else - return modeSet->getControlResponseMode(responseCtsFrameMode); + return responseCtsFrameMode ? getConfiguredControlResponseMode(modeSet, responseMode) : getDynamicControlResponseMode(modeSet, responseMode); } // @@ -115,14 +127,17 @@ const IIeee80211Mode *QosRateSelection::computeResponseCtsFrameMode(Packet *pack // const IIeee80211Mode *QosRateSelection::computeResponseBlockAckFrameMode(Packet *packet, const Ptr& blockAckReq) { - if (dynamicPtrCast(blockAckReq)) { - if (responseBlockAckFrameMode) - return modeSet->getControlResponseMode(responseBlockAckFrameMode); - else - return modeSet->getControlResponseMode(getMode(packet, blockAckReq)); - } - else + if (!dynamicPtrCast(blockAckReq)) throw cRuntimeError("Unknown BlockAckReq frame type"); + if (responseBlockAckFrameMode) + return getConfiguredControlResponseMode(modeSet, responseBlockAckFrameMode); + auto mode = getMode(packet, blockAckReq); + ASSERT(modeSet->supportsMode(mode)); + // IEEE 802.11-2024 10.6.6.5.2 permits non-HT Basic BlockAck responses; + // this model has no BSSBasicRateSet/HT Control context to select another PPDU. + if (dynamic_cast(mode) != nullptr) + return modeSet->getNonHtControlResponseMode(mode); + return mode; } const IIeee80211Mode *QosRateSelection::computeDataOrMgmtFrameMode(const Ptr& dataOrMgmtHeader) @@ -245,6 +260,7 @@ void QosRateSelection::receiveSignal(cComponent *source, simsignal_t signalID, c if (signalID == modesetChangedSignal) { modeSet = check_and_cast(obj); fastestMandatoryMode = modeSet->getFastestMandatoryMode(); + resolveConfiguredResponseModes(); } } diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h index 167acc72b63..580f88a7c05 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h @@ -52,6 +52,7 @@ class INET_API QosRateSelection : public IQosRateSelection, public ModeSetListen virtual int numInitStages() const override { return NUM_INIT_STAGES; } virtual void initialize(int stage) override; virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) override; + void resolveConfiguredResponseModes(); virtual const physicallayer::IIeee80211Mode *getMode(Packet *packet, const Ptr& header); virtual const physicallayer::IIeee80211Mode *computeControlFrameMode(const Ptr& header, TxopProcedure *txopProcedure); diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc index 2d9a1482401..2e13f3f4a4b 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc @@ -7,6 +7,8 @@ #include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" + #include "inet/common/ModuleAccess.h" #include "inet/common/Simsignals.h" #include "inet/linklayer/ieee80211/mac/contract/IRateControl.h" @@ -20,6 +22,24 @@ namespace ieee80211 { using namespace inet::physicallayer; +static const IIeee80211Mode *getConfiguredControlResponseMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) +{ + // Explicit legacy/VHT overrides retain their requested mode; HT rates need the + // bounded non-HT control-response conversion for ACK/BlockAck. + return dynamic_cast(mode) != nullptr ? modeSet->getNonHtControlResponseMode(mode) : mode; +} + +static const IIeee80211Mode *getDynamicControlResponseMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) +{ + if (dynamic_cast(mode) != nullptr || !modeSet->containsMode(mode)) + return modeSet->getNonHtControlResponseMode(mode); + if (modeSet->getIsMandatory(mode)) + return mode; + if (auto slowerMode = modeSet->getSlowerMandatoryMode(mode)) + return slowerMode; + return modeSet->getNonHtControlResponseMode(mode); +} + Define_Module(RateSelection); void RateSelection::initialize(int stage) @@ -37,10 +57,7 @@ void RateSelection::initialize(int stage) mgmtFrameMode = (mgmtFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(mgmtFrameBitrate)); double controlFrameBitrate = par("controlFrameBitrate"); controlFrameMode = (controlFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(controlFrameBitrate)); - double responseAckFrameBitrate = par("responseAckFrameBitrate"); - responseAckFrameMode = (responseAckFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseAckFrameBitrate)); - double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); - responseCtsFrameMode = (responseCtsFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseCtsFrameBitrate)); + resolveConfiguredResponseModes(); fastestMandatoryMode = modeSet->getFastestMandatoryMode(); // WATCH(dataOrMgmtRateControl); @@ -60,6 +77,16 @@ void RateSelection::initialize(int stage) } } +void RateSelection::resolveConfiguredResponseModes() +{ + if (modeSet == nullptr) + return; + double responseAckFrameBitrate = par("responseAckFrameBitrate"); + responseAckFrameMode = responseAckFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(responseAckFrameBitrate)); + double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); + responseCtsFrameMode = responseCtsFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(responseCtsFrameBitrate)); +} + const IIeee80211Mode *RateSelection::getMode(Packet *packet, const Ptr& header) { const auto& modeReqTag = packet->findTag(); @@ -79,28 +106,27 @@ const IIeee80211Mode *RateSelection::getMode(Packet *packet, const Ptr& dataOrMgmtHeader) { + // Keep configured responses independent of the eliciting packet; callers may + // resolve a configured response while constructing a test packet. if (responseAckFrameMode) - return modeSet->getControlResponseMode(responseAckFrameMode); - else { - auto mode = getMode(packet, dataOrMgmtHeader); - ASSERT(modeSet->containsMode(mode)); - auto responseModeSet = modeSet->getControlResponseModeSet(mode); - auto responseMode = responseModeSet->getMode(mode); - return responseModeSet->getIsMandatory(responseMode) ? responseMode : responseModeSet->getSlowerMandatoryMode(responseMode); // TODO BSSBasicRateSet - } + return getConfiguredControlResponseMode(modeSet, responseAckFrameMode); + auto mode = getMode(packet, dataOrMgmtHeader); + ASSERT(modeSet->supportsMode(mode)); + // IEEE 802.11-2024 10.6.6.1/10.6.6.5.2: this bounded model uses a + // mandatory non-HT rate for ordinary ACK responses; BSSBasicRateSet is not modelled. + return getDynamicControlResponseMode(modeSet, mode); } const IIeee80211Mode *RateSelection::computeResponseCtsFrameMode(Packet *packet, const Ptr& rtsFrame) { - if (responseCtsFrameMode) - return modeSet->getControlResponseMode(responseCtsFrameMode); - else { - auto mode = getMode(packet, rtsFrame); - ASSERT(modeSet->containsMode(mode)); - auto responseModeSet = modeSet->getControlResponseModeSet(mode); - auto responseMode = responseModeSet->getMode(mode); - return responseModeSet->getIsMandatory(responseMode) ? responseMode : responseModeSet->getSlowerMandatoryMode(responseMode); // TODO BSSBasicRateSet + auto mode = getMode(packet, rtsFrame); + ASSERT(modeSet->supportsMode(mode)); + auto responseMode = responseCtsFrameMode ? responseCtsFrameMode : mode; + if (dynamic_cast(mode) != nullptr) { + // IEEE 802.11-2024 10.6.6.1 and 10.6.6.5.7 require an HT response to HT RTS; HT-GF is never a response. + return responseCtsFrameMode && dynamic_cast(responseMode) == nullptr ? responseMode : modeSet->getControlResponseMode(responseMode); } + return responseCtsFrameMode ? getConfiguredControlResponseMode(modeSet, responseMode) : getDynamicControlResponseMode(modeSet, responseMode); } // 802.11-1999 Std. @@ -157,6 +183,7 @@ void RateSelection::receiveSignal(cComponent *source, simsignal_t signalID, cObj if (signalID == modesetChangedSignal) { modeSet = check_and_cast(obj); fastestMandatoryMode = modeSet->getFastestMandatoryMode(); + resolveConfiguredResponseModes(); } } diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h index 98d525812f6..0382ec2f703 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h @@ -52,6 +52,7 @@ class INET_API RateSelection : public IRateSelection, public SimpleModule, publi virtual int numInitStages() const override { return NUM_INIT_STAGES; } virtual void initialize(int stage) override; virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) override; + void resolveConfiguredResponseModes(); virtual const physicallayer::IIeee80211Mode *getMode(Packet *packet, const Ptr& header); virtual const physicallayer::IIeee80211Mode *computeControlFrameMode(const Ptr& header); diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc index 9bf49a06b61..c2f6615c39b 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc @@ -93,19 +93,47 @@ Register_Abstract_Class(Ieee80211ModeSet); HT_MODE_ENTRY(40, 30, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) \ HT_MODE_ENTRY(40, 31, false, FORMAT, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) -static Ieee80211ModeSet createHtModeSet(const char *name, Ieee80211HtPreambleMode::HighTroughputPreambleFormat preambleFormat) +static std::vector createHtEntries(Ieee80211HtPreambleMode::HighTroughputPreambleFormat preambleFormat) { - return Ieee80211ModeSet(name, { // This table is not complete; it only contains 2.4GHz homogeneous spatial streams, all mandatory and optional modes + return { HT_MODE_ENTRIES_20(preambleFormat) HT_MODE_ENTRIES_40(preambleFormat) + }; +} + +static std::vector createHtSupportedEntries(Ieee80211HtPreambleMode::HighTroughputPreambleFormat preambleFormat) +{ + auto result = createHtEntries(preambleFormat); + auto mixedEntries = createHtEntries(Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED); + if (preambleFormat == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD) + result.insert(result.end(), mixedEntries.begin(), mixedEntries.end()); + result.insert(result.end(), { + { true, &Ieee80211DsssCompliantModes::dsssMode1Mbps }, + { true, &Ieee80211DsssCompliantModes::dsssMode2Mbps }, + { true, &Ieee80211HrDsssCompliantModes::hrDsssMode5_5MbpsCckLongPreamble }, + { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode9Mbps }, + { true, &Ieee80211HrDsssCompliantModes::hrDsssMode11MbpsCckLongPreamble }, + { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode12Mbps }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode18Mbps }, + { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode24Mbps }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode36Mbps }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode48Mbps }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode54Mbps }, }); + return result; +} + +static Ieee80211ModeSet createHtModeSet(const char *name, Ieee80211HtPreambleMode::HighTroughputPreambleFormat preambleFormat) +{ + return Ieee80211ModeSet(name, createHtEntries(preambleFormat), createHtSupportedEntries(preambleFormat)); } #undef HT_MODE_ENTRIES_40 #undef HT_MODE_ENTRIES_20 #undef HT_MODE_ENTRY -const DelayedInitializer> Ieee80211ModeSet::modeSets([]() { return new std::vector { +OPP_THREAD_LOCAL const DelayedInitializer> Ieee80211ModeSet::modeSets([]() { return new std::vector { Ieee80211ModeSet("a", { { true, &Ieee80211OfdmCompliantModes::ofdmMode6MbpsCS20MHz }, { false, &Ieee80211OfdmCompliantModes::ofdmMode9MbpsCS20MHz }, @@ -473,9 +501,12 @@ const DelayedInitializer> Ieee80211ModeSet::modeSe { false, Ieee80211VhtCompliantModes::getCompliantMode(&Ieee80211VhtmcsTable::vhtMcs9BW160MHzNss8, Ieee80211VhtMode::BAND_5GHZ, Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211VhtModeBase::HT_GUARD_INTERVAL_SHORT) }, }),}; }); -Ieee80211ModeSet::Ieee80211ModeSet(const char *name, const std::vector entries) : +Ieee80211ModeSet::Ieee80211ModeSet(const char *name, const std::vector entries, const std::vector supportedEntries) : name(name), - entries(entries) + entries(entries), + supportedEntries(supportedEntries.empty() ? entries : supportedEntries), + controlResponseModes(createControlResponseModes(supportedEntries.empty() ? entries : supportedEntries)), + nonHtControlResponseEntries(createNonHtControlResponseEntries(supportedEntries.empty() ? entries : supportedEntries)) { std::vector *nonConstEntries = const_cast *>(&this->entries); std::stable_sort(nonConstEntries->begin(), nonConstEntries->end(), EntryNetBitrateComparator()); @@ -499,29 +530,45 @@ int Ieee80211ModeSet::findModeIndex(const IIeee80211Mode *mode) const return -1; } -int Ieee80211ModeSet::findEquivalentModeIndex(const IIeee80211Mode *mode) const +std::map Ieee80211ModeSet::createControlResponseModes(const std::vector& supportedEntries) { - int modeIndex = findModeIndex(mode); - if (modeIndex != -1) - return modeIndex; - if (auto htMode = dynamic_cast(mode)) { - auto htDataMode = htMode->getDataMode(); - for (size_t index = 0; index < entries.size(); index++) { - auto entryHtMode = dynamic_cast(entries[index].mode); - if (entryHtMode != nullptr) { - auto entryHtDataMode = entryHtMode->getDataMode(); - if (entryHtDataMode->getMcsIndex() == htDataMode->getMcsIndex() && - entryHtDataMode->getBandwidth() == htDataMode->getBandwidth() && - entryHtDataMode->getGuardIntervalType() == htDataMode->getGuardIntervalType() && - entryHtMode->getCenterFrequencyMode() == htMode->getCenterFrequencyMode() && - entryHtDataMode->getNumberOfSpatialStreams() == htDataMode->getNumberOfSpatialStreams()) - { - return index; - } + std::map result; + for (const auto& sourceEntry : supportedEntries) { + auto source = dynamic_cast(sourceEntry.mode); + if (source == nullptr || source->getPreambleMode()->getPreambleFormat() != Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD) + continue; + for (const auto& candidateEntry : supportedEntries) { + auto candidate = dynamic_cast(candidateEntry.mode); + if (candidate != nullptr && candidate->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED && + candidate->getCenterFrequencyMode() == source->getCenterFrequencyMode() && + candidate->getDataMode()->getMcsIndex() == source->getDataMode()->getMcsIndex() && + candidate->getDataMode()->getBandwidth() == source->getDataMode()->getBandwidth() && + candidate->getDataMode()->getGuardIntervalType() == source->getDataMode()->getGuardIntervalType() && + candidate->getDataMode()->getNumberOfSpatialStreams() == source->getDataMode()->getNumberOfSpatialStreams()) { + result.emplace(sourceEntry.mode, candidateEntry.mode); + break; } } } - return -1; + return result; +} + +std::vector Ieee80211ModeSet::createNonHtControlResponseEntries(const std::vector& supportedEntries) +{ + std::vector result; + for (const auto& entry : supportedEntries) + if (entry.isMandatory && dynamic_cast(entry.mode) == nullptr && dynamic_cast(entry.mode) == nullptr) + result.push_back(entry); + std::stable_sort(result.begin(), result.end(), EntryNetBitrateComparator()); + return result; +} + +bool Ieee80211ModeSet::supportsMode(const IIeee80211Mode *mode) const +{ + for (const auto& entry : supportedEntries) + if (entry.mode == mode) + return true; + return false; } int Ieee80211ModeSet::getModeIndex(const IIeee80211Mode *mode) const @@ -540,7 +587,7 @@ bool Ieee80211ModeSet::getIsMandatory(const IIeee80211Mode *mode) const const IIeee80211Mode *Ieee80211ModeSet::findMode(const IIeee80211Mode *mode) const { - int index = findEquivalentModeIndex(mode); + int index = findModeIndex(mode); return index >= 0 ? entries[index].mode : nullptr; } @@ -655,47 +702,42 @@ const IIeee80211Mode *Ieee80211ModeSet::getFasterMandatoryMode(const IIeee80211M return nullptr; } -const Ieee80211ModeSet::ControlResponseMode& Ieee80211ModeSet::resolveControlResponseMode(const IIeee80211Mode *mode) const -{ - auto cachedMode = controlResponseModeCache.find(mode); - if (cachedMode != controlResponseModeCache.end()) - return cachedMode->second; - if (!containsMode(mode)) - throw cRuntimeError("Control response mode is not in operation mode '%s': '%s'", getName(), mode->getName()); - - const Ieee80211ModeSet *controlResponseModeSet = this; - const IIeee80211Mode *controlResponseMode = mode; - // IEEE 802.11-2024, 10.6.6.5.7 requires HT control responses to use the - // HT-mixed format; 19.3.9.5.1 defines HT-mixed and HT-Greenfield preambles. - if (auto htMode = dynamic_cast(mode)) { - controlResponseModeSet = nullptr; - for (size_t index = 0; index < (&modeSets)->size(); index++) { - auto candidateModeSet = &(&modeSets)->at(index); - auto candidateMode = dynamic_cast(candidateModeSet->findMode(mode)); - if (candidateMode != nullptr && - candidateMode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED && - candidateMode->getCenterFrequencyMode() == htMode->getCenterFrequencyMode()) - { - if (controlResponseModeSet != nullptr) - throw cRuntimeError("Multiple same-band HT-mixed control response mode sets for mode: '%s'", mode->getName()); - controlResponseModeSet = candidateModeSet; - } - } - if (controlResponseModeSet == nullptr) - throw cRuntimeError("No same-band HT-mixed control response mode set for mode: '%s'", mode->getName()); - controlResponseMode = controlResponseModeSet->getMode(mode); - } - return controlResponseModeCache.emplace(mode, ControlResponseMode { controlResponseModeSet, controlResponseMode }).first->second; -} - -const Ieee80211ModeSet *Ieee80211ModeSet::getControlResponseModeSet(const IIeee80211Mode *mode) const +const IIeee80211Mode *Ieee80211ModeSet::getControlResponseMode(const IIeee80211Mode *mode) const { - return resolveControlResponseMode(mode).modeSet; + if (!supportsMode(mode)) + throw cRuntimeError("Control response mode is not supported by operation mode %s: %s", getName(), mode->getName()); + auto it = controlResponseModes.find(mode); + return it != controlResponseModes.end() ? it->second : mode; } -const IIeee80211Mode *Ieee80211ModeSet::getControlResponseMode(const IIeee80211Mode *mode) const +const IIeee80211Mode *Ieee80211ModeSet::getNonHtControlResponseMode(const IIeee80211Mode *mode) const { - return resolveControlResponseMode(mode).mode; + if (!supportsMode(mode)) + throw cRuntimeError("Control response mode is not supported by operation mode %s: %s", getName(), mode->getName()); + auto htMode = dynamic_cast(mode); + if (htMode == nullptr) { + // VHT response-format selection remains unchanged; this fallback is HT-scoped. + if (dynamic_cast(mode) != nullptr) + return mode; + const IIeee80211Mode *result = nullptr; + for (const auto& entry : nonHtControlResponseEntries) { + auto candidate = entry.mode; + if (candidate->getDataMode()->getNetBitrate() <= mode->getDataMode()->getNetBitrate() && + (result == nullptr || candidate->getDataMode()->getNetBitrate() > result->getDataMode()->getNetBitrate())) + result = candidate; + } + return result != nullptr ? result : mode; + } + const IIeee80211Mode *result = nullptr; + for (const auto& entry : nonHtControlResponseEntries) { + auto candidate = entry.mode; + if (candidate->getDataMode()->getNetBitrate() <= mode->getDataMode()->getNetBitrate() && + (result == nullptr || candidate->getDataMode()->getNetBitrate() > result->getDataMode()->getNetBitrate())) + result = candidate; + } + if (result == nullptr) + throw cRuntimeError("No mandatory non-HT control response mode for %s", mode->getName()); + return result; } const Ieee80211ModeSet *Ieee80211ModeSet::findModeSet(const char *mode) diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h index ee23d827671..c147d0e4333 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h @@ -16,7 +16,7 @@ namespace physicallayer { class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject { - protected: + public: class INET_API Entry { public: bool isMandatory; @@ -27,27 +27,25 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject bool operator()(const Entry& left, const Entry& right) { return left.mode->getDataMode()->getNetBitrate() < right.mode->getDataMode()->getNetBitrate(); } }; - struct ControlResponseMode { - const Ieee80211ModeSet *modeSet; - const IIeee80211Mode *mode; - }; - protected: std::string name; const std::vector entries; - mutable std::map controlResponseModeCache; + // Entries are selectable modes; supportedEntries also contains immutable PHY capabilities needed for mandatory control responses. + const std::vector supportedEntries; + const std::map controlResponseModes; + const std::vector nonHtControlResponseEntries; public: - static const DelayedInitializer> modeSets; + static OPP_THREAD_LOCAL const DelayedInitializer> modeSets; protected: int findModeIndex(const IIeee80211Mode *mode) const; - int findEquivalentModeIndex(const IIeee80211Mode *mode) const; int getModeIndex(const IIeee80211Mode *mode) const; - const ControlResponseMode& resolveControlResponseMode(const IIeee80211Mode *mode) const; + static std::map createControlResponseModes(const std::vector& supportedEntries); + static std::vector createNonHtControlResponseEntries(const std::vector& supportedEntries); public: - Ieee80211ModeSet(const char *name, const std::vector entries); + Ieee80211ModeSet(const char *name, const std::vector entries, const std::vector supportedEntries = {}); virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override { return stream << "Ieee80211ModeSet, name = " << name; } @@ -58,8 +56,10 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject bool isMandatory(int index) { return entries[index].isMandatory; } bool containsMode(const IIeee80211Mode *mode) const { return findModeIndex(mode) != -1; } + bool supportsMode(const IIeee80211Mode *mode) const; bool getIsMandatory(const IIeee80211Mode *mode) const; + // Pointer lookup is intentionally strict. Use getControlResponseMode() for an explicitly requested response that needs HT-mixed translation. const IIeee80211Mode *findMode(const IIeee80211Mode *mode) const; const IIeee80211Mode *getMode(const IIeee80211Mode *mode) const; const IIeee80211Mode *findMode(bps bitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1) const; @@ -75,8 +75,8 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject const IIeee80211Mode *getSlowerMandatoryMode(const IIeee80211Mode *mode) const; const IIeee80211Mode *getFasterMandatoryMode(const IIeee80211Mode *mode) const; - const Ieee80211ModeSet *getControlResponseModeSet(const IIeee80211Mode *mode) const; const IIeee80211Mode *getControlResponseMode(const IIeee80211Mode *mode) const; + const IIeee80211Mode *getNonHtControlResponseMode(const IIeee80211Mode *mode) const; static const Ieee80211ModeSet *findModeSet(const char *mode); static const Ieee80211ModeSet *getModeSet(const char *mode); diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.cc b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.cc index 708a876eb8f..dfe4b51ae10 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.cc +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.cc @@ -50,13 +50,13 @@ std::ostream& Ieee80211Receiver::printToStream(std::ostream& stream, int level, bool Ieee80211Receiver::computeIsReceptionPossible(const IListening *listening, const ITransmission *transmission) const { auto ieee80211Transmission = dynamic_cast(transmission); - return ieee80211Transmission && modeSet->containsMode(ieee80211Transmission->getMode()) && NarrowbandReceiverBase::computeIsReceptionPossible(listening, transmission); + return ieee80211Transmission && modeSet->supportsMode(ieee80211Transmission->getMode()) && NarrowbandReceiverBase::computeIsReceptionPossible(listening, transmission); } bool Ieee80211Receiver::computeIsReceptionPossible(const IListening *listening, const IReception *reception, IRadioSignal::SignalPart part) const { auto ieee80211Transmission = dynamic_cast(reception->getTransmission()); - return ieee80211Transmission && modeSet->containsMode(ieee80211Transmission->getMode()) && getAnalogModel()->computeIsReceptionPossible(listening, reception, sensitivity); + return ieee80211Transmission && modeSet->supportsMode(ieee80211Transmission->getMode()) && getAnalogModel()->computeIsReceptionPossible(listening, reception, sensitivity); } const IReceptionResult *Ieee80211Receiver::computeReceptionResult(const IListening *listening, const IReception *reception, const IInterference *interference, const ISnir *snir, const std::vector *decisions) const diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc index c70d545570f..0b9ceab24ce 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc @@ -49,7 +49,7 @@ const IIeee80211Mode *Ieee80211Transmitter::computeTransmissionMode(const Packet const auto& modeReq = const_cast(packet)->findTag(); const auto& bitrateReq = const_cast(packet)->findTag(); if (modeReq != nullptr) { - if (modeSet != nullptr && !modeSet->containsMode(modeReq->getMode())) + if (modeSet != nullptr && !modeSet->supportsMode(modeReq->getMode())) throw cRuntimeError("Unsupported mode requested"); transmissionMode = modeReq->getMode(); } @@ -75,17 +75,24 @@ const Ieee80211Channel *Ieee80211Transmitter::computeTransmissionChannel(const P void Ieee80211Transmitter::setModeSet(const Ieee80211ModeSet *modeSet) { if (this->modeSet != modeSet) { + const IIeee80211Mode *newMode = nullptr; + if (modeSet != nullptr && mode != nullptr) { + newMode = modeSet->containsMode(mode) ? mode : modeSet->getMode(mode->getDataMode()->getNetBitrate(), mode->getDataMode()->getBandwidth(), mode->getDataMode()->getNumberOfSpatialStreams()); + } this->modeSet = modeSet; - if (mode != nullptr) - mode = modeSet != nullptr ? modeSet->getMode(mode->getDataMode()->getNetBitrate()) : nullptr; + this->mode = newMode; } } void Ieee80211Transmitter::setMode(const IIeee80211Mode *mode) { if (this->mode != mode) { - if (modeSet->findMode(mode->getDataMode()->getNetBitrate(), mode->getDataMode()->getBandwidth()) == nullptr) - throw cRuntimeError("Invalid mode"); + if (mode == nullptr) { + this->mode = nullptr; + return; + } + if (modeSet == nullptr || !modeSet->containsMode(mode)) + throw cRuntimeError("Invalid or unsupported mode"); this->mode = mode; } } diff --git a/tests/module/Ieee80211ConfiguredResponseRateSelection.test b/tests/module/Ieee80211ConfiguredResponseRateSelection.test index 7f0d0436e19..1b6af9e32a7 100644 --- a/tests/module/Ieee80211ConfiguredResponseRateSelection.test +++ b/tests/module/Ieee80211ConfiguredResponseRateSelection.test @@ -1,6 +1,6 @@ %description: -Checks through the public rate-selection API that configured Greenfield response -rates are converted to HT-mixed modes for DCF and QoS ACK, CTS, and Basic BlockAck responses. +Checks through the public rate-selection API that configured Greenfield response rates use +non-HT ACK/BlockAck and HT-mixed CTS formats. %file: Test.cc #include @@ -10,6 +10,7 @@ rates are converted to HT-mixed modes for DCF and QoS ACK, CTS, and Basic BlockA #include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" #include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" #include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ErpOfdmMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" @@ -20,15 +21,17 @@ using namespace inet::physicallayer; namespace Ieee80211ConfiguredResponseRateSelection { -static void assertConfiguredResponse(const char *name, const IIeee80211Mode *mode) +static void assertHtMixedResponse(const char *name, const IIeee80211Mode *mode) { auto htMode = dynamic_cast(mode); - if (htMode == nullptr) - throw cRuntimeError("%s did not return an HT mode", name); - if (htMode->getPreambleMode()->getPreambleFormat() != Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED) - throw cRuntimeError("%s did not return an HT-mixed mode", name); - if (htMode->getDataMode()->getNetBitrate() != Mbps(6.5)) - throw cRuntimeError("%s did not return 6.5 Mbps", name); + if (htMode == nullptr || htMode->getPreambleMode()->getPreambleFormat() != Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED || htMode->getDataMode()->getNetBitrate() != Mbps(6.5)) + throw cRuntimeError("%s did not return HT-mixed 6.5 Mbps", name); +} + +static void assertNonHtResponse(const char *name, const IIeee80211Mode *mode) +{ + if (dynamic_cast(mode) != nullptr || mode->getDataMode()->getNetBitrate() != Mbps(6)) + throw cRuntimeError("%s did not return non-HT 6 Mbps", name); } class ConfiguredResponseProbe : public cSimpleModule @@ -43,6 +46,7 @@ class ConfiguredResponseProbe : public cSimpleModule auto dcfRateSelection = check_and_cast(getSimulation()->getModuleByPath("TestConfiguredResponses.host[0].wlan[0].mac.dcf.rateSelection")); auto qosRateSelection = check_and_cast(getSimulation()->getModuleByPath("TestConfiguredResponses.host[0].wlan[0].mac.hcf.rateSelection")); + auto dynamicQosRateSelection = check_and_cast(getSimulation()->getModuleByPath("TestConfiguredResponses.host[1].wlan[0].mac.hcf.rateSelection")); if (double(dcfRateSelection->par("responseAckFrameBitrate")) != 6.5e6 || double(qosRateSelection->par("responseAckFrameBitrate")) != 6.5e6) @@ -51,16 +55,26 @@ class ConfiguredResponseProbe : public cSimpleModule auto sourceMode = Ieee80211ModeSet::getModeSet("n(greenfield-2.4Ghz)")->getMode(Mbps(6.5)); Packet packet("receivedFrame"); packet.addTag()->setMode(sourceMode); + Packet nonHtPacket("receivedNonHtFrame"); + nonHtPacket.addTag()->setMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); - assertConfiguredResponse("DCF ACK", dcfRateSelection->computeResponseAckFrameMode(nullptr, nullptr)); - assertConfiguredResponse("DCF CTS", dcfRateSelection->computeResponseCtsFrameMode(nullptr, nullptr)); - assertConfiguredResponse("QoS ACK", qosRateSelection->computeResponseAckFrameMode(&packet, nullptr)); - assertConfiguredResponse("QoS CTS", qosRateSelection->computeResponseCtsFrameMode(&packet, nullptr)); + assertNonHtResponse("DCF ACK", dcfRateSelection->computeResponseAckFrameMode(nullptr, nullptr)); + assertHtMixedResponse("DCF CTS HT", dcfRateSelection->computeResponseCtsFrameMode(&packet, nullptr)); + assertNonHtResponse("DCF CTS non-HT", dcfRateSelection->computeResponseCtsFrameMode(&nonHtPacket, nullptr)); + assertNonHtResponse("QoS ACK", qosRateSelection->computeResponseAckFrameMode(&packet, nullptr)); + assertHtMixedResponse("QoS CTS HT", qosRateSelection->computeResponseCtsFrameMode(&packet, nullptr)); + assertNonHtResponse("QoS CTS non-HT", qosRateSelection->computeResponseCtsFrameMode(&nonHtPacket, nullptr)); auto basicBlockAckReq = makeShared(); - assertConfiguredResponse("QoS Basic BlockAck", qosRateSelection->computeResponseBlockAckFrameMode(&packet, basicBlockAckReq)); - - std::cout << "Configured DCF and QoS ACK/CTS/Basic BlockAck responses use HT-mixed 6.5 Mbps modes.\n"; + assertNonHtResponse("QoS Basic BlockAck", qosRateSelection->computeResponseBlockAckFrameMode(&packet, basicBlockAckReq)); + Packet optionalNonHtPacket("receivedOptionalNonHtFrame"); + optionalNonHtPacket.addTag()->setMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode9Mbps); + assertNonHtResponse("Dynamic QoS ACK", dynamicQosRateSelection->computeResponseAckFrameMode(&optionalNonHtPacket, nullptr)); + assertNonHtResponse("Dynamic QoS CTS", dynamicQosRateSelection->computeResponseCtsFrameMode(&optionalNonHtPacket, nullptr)); + ASSERT(dynamicQosRateSelection->computeResponseBlockAckFrameMode(&optionalNonHtPacket, basicBlockAckReq) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode9Mbps); + ASSERT(dynamicQosRateSelection->computeResponseBlockAckFrameMode(&packet, basicBlockAckReq) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); + + std::cout << "Configured DCF and QoS ACK/Basic BlockAck use non-HT 6 Mbps; HT CTS uses HT-mixed 6.5 Mbps and non-HT CTS uses non-HT 6 Mbps; dynamic non-HT Basic BlockAck retains ERP 9 Mbps.\n"; } }; @@ -103,6 +117,9 @@ network = TestConfiguredResponses sim-time-limit = 1s **.opMode = "n(greenfield-2.4Ghz)" **.qosStation = true +*.host[1].wlan[0].mac.hcf.rateSelection.responseAckFrameBitrate = -1bps +*.host[1].wlan[0].mac.hcf.rateSelection.responseCtsFrameBitrate = -1bps +*.host[1].wlan[0].mac.hcf.rateSelection.responseBlockAckFrameBitrate = -1bps **.wlan[*].mac.dcf.rateSelection.responseAckFrameBitrate = 6.5Mbps **.wlan[*].mac.dcf.rateSelection.responseCtsFrameBitrate = 6.5Mbps **.wlan[*].mac.hcf.rateSelection.responseAckFrameBitrate = 6.5Mbps @@ -112,4 +129,4 @@ sim-time-limit = 1s %extraargs: -c ConfiguredResponses %contains: stdout -Configured DCF and QoS ACK/CTS/Basic BlockAck responses use HT-mixed 6.5 Mbps modes. +Configured DCF and QoS ACK/Basic BlockAck use non-HT 6 Mbps; HT CTS uses HT-mixed 6.5 Mbps and non-HT CTS uses non-HT 6 Mbps; dynamic non-HT Basic BlockAck retains ERP 9 Mbps. diff --git a/tests/module/Ieee80211HtGreenfieldRuntime.test b/tests/module/Ieee80211HtGreenfieldRuntime.test index b1ef7410d76..4295764193d 100644 --- a/tests/module/Ieee80211HtGreenfieldRuntime.test +++ b/tests/module/Ieee80211HtGreenfieldRuntime.test @@ -1,7 +1,6 @@ %description: -Checks real HT-Greenfield transmissions using response-free, group-addressed -traffic from an ad hoc station. +Checks forced HT-Greenfield RTS/HT-mixed CTS plus unicast HT-Greenfield data and non-HT ACK responses between two ad hoc stations. %file: Test.cc #include @@ -10,17 +9,22 @@ traffic from an ad hoc station. #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmission.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" using namespace inet; using namespace inet::physicallayer; +using namespace inet::ieee80211; namespace Ieee80211HtGreenfieldRuntime { +static OPP_THREAD_LOCAL int htGreenfieldRtsCount = 0; +static OPP_THREAD_LOCAL int htMixedCtsCount = 0; +static OPP_THREAD_LOCAL int htGreenfieldDataCount = 0; +static OPP_THREAD_LOCAL int nonHtAckCount = 0; + class HtPreambleCheckingRadio : public Ieee80211Radio, public cListener { protected: - int htGreenfieldTransmissionCount = 0; - virtual void initialize(int stage) override { Ieee80211Radio::initialize(stage); @@ -34,17 +38,26 @@ class HtPreambleCheckingRadio : public Ieee80211Radio, public cListener return; auto transmission = check_and_cast(obj); auto mode = dynamic_cast(transmission->getMode()); - if (mode == nullptr) - return; - if (mode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD) - htGreenfieldTransmissionCount++; + auto phyHeader = Ieee80211Radio::peekIeee80211PhyHeaderAtFront(transmission->getPacket()); + auto macHeader = transmission->getPacket()->peekDataAt(phyHeader->getChunkLength()); + auto isHtGreenfield = mode != nullptr && mode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD; + auto isHtMixed = mode != nullptr && mode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED; + if (dynamicPtrCast(macHeader) && isHtGreenfield) + htGreenfieldRtsCount++; + if (dynamicPtrCast(macHeader) && isHtMixed) + htMixedCtsCount++; + if (dynamicPtrCast(macHeader) && isHtGreenfield) + htGreenfieldDataCount++; + if (dynamicPtrCast(macHeader) && mode == nullptr) + nonHtAckCount++; } virtual void finish() override { Ieee80211Radio::finish(); - if (htGreenfieldTransmissionCount > 0) - std::cout << "Observed " << htGreenfieldTransmissionCount << " runtime HT-Greenfield broadcast transmission(s).\n"; + if (htGreenfieldRtsCount == 0 || htMixedCtsCount == 0 || htGreenfieldDataCount == 0 || nonHtAckCount == 0) + throw cRuntimeError("Expected HT-GF RTS, HT-MF CTS, HT-GF data, and non-HT ACK transmissions"); + std::cout << "Observed " << htGreenfieldRtsCount << " HT-Greenfield RTS, " << htMixedCtsCount << " HT-mixed CTS, " << htGreenfieldDataCount << " HT-Greenfield data, and " << nonHtAckCount << " non-HT ACK response(s).\n"; } }; @@ -93,18 +106,21 @@ seed-set = 0 abstract = false network = TestHtGreenfield sim-time-limit = 2s -*.numHosts = 1 +*.numHosts = 2 *.host[0].numApps = 1 *.host[0].app[0].typename = "UdpBasicApp" -*.host[0].app[0].destAddresses = "255.255.255.255" +*.host[0].app[0].destAddresses = "host[1]" *.host[0].app[0].destPort = 1000 *.host[0].app[0].messageLength = 100B *.host[0].app[0].startTime = 100ms +*.host[1].numApps = 1 +*.host[1].app[0].typename = "UdpSink" +*.host[1].app[0].localPort = 1000 *.host[0].app[0].sendInterval = 100ms -**.limitedBroadcast = true **.wlan[*].opMode = "n(greenfield-2.4Ghz)" +**.wlan[*].mac.dcf.rtsPolicy.rtsThreshold = 1B %extraargs: -c HtPreambleRuntime %contains-regex: stdout -Observed [1-9][0-9]* runtime HT-Greenfield broadcast transmission\(s\). +Observed [1-9][0-9]* HT-Greenfield RTS, [1-9][0-9]* HT-mixed CTS, [1-9][0-9]* HT-Greenfield data, and [1-9][0-9]* non-HT ACK response\(s\). diff --git a/tests/unit/Ieee80211HtGreenfield_1.test b/tests/unit/Ieee80211HtGreenfield_1.test index 416dd931431..32baf2e412f 100644 --- a/tests/unit/Ieee80211HtGreenfield_1.test +++ b/tests/unit/Ieee80211HtGreenfield_1.test @@ -1,7 +1,6 @@ %description: Checks that the 802.11n mixed and Greenfield mode profiles preserve distinct -preamble formats and timing, retain strict membership, and support explicit -cross-profile lookup for control-response translation. Also checks VHT cache identity. +preamble formats and timing, retain strict membership and supplementary Greenfield control-response support, including legacy fallback rates. Also checks VHT cache identity. %includes: #include @@ -10,6 +9,7 @@ cross-profile lookup for control-response translation. Also checks VHT cache ide #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ErpOfdmMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211OfdmMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.h" @@ -17,6 +17,13 @@ cross-profile lookup for control-response translation. Also checks VHT cache ide using namespace inet; using namespace inet::physicallayer; +class TestIeee80211Transmitter : public Ieee80211Transmitter +{ + public: + const Ieee80211ModeSet *getTestModeSet() const { return modeSet; } + const IIeee80211Mode *getTestMode() const { return mode; } +}; + static const Ieee80211HtMode *findMcs0LongGiMode(const Ieee80211ModeSet *modeSet) { auto mode = dynamic_cast(modeSet->getMode(Mbps(6.5), MHz(20), 1)); @@ -35,17 +42,6 @@ static bool rejectsUnknownMode(const Ieee80211ModeSet *modeSet, const IIeee80211 } } -static bool rejectsNonLocalControlResponseMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) -{ - try { - modeSet->getControlResponseModeSet(mode); - return false; - } - catch (const cRuntimeError& error) { - return std::strstr(error.what(), "not in operation mode") != nullptr; - } -} - static bool rejectsMandatoryLookup(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) { try { @@ -85,13 +81,39 @@ ASSERT(mixedProfile->getSlowerMode(greenfieldMode) == nullptr); ASSERT(greenfieldProfile->getFasterMode(mixedMode) == nullptr); ASSERT(rejectsMandatoryLookup(mixedProfile, greenfieldMode)); ASSERT(rejectsMandatoryLookup(greenfieldProfile, mixedMode)); -ASSERT(mixedProfile->getMode(greenfieldMode) == mixedMode); -ASSERT(greenfieldProfile->getMode(mixedMode) == greenfieldMode); +ASSERT(rejectsUnknownMode(mixedProfile, greenfieldMode)); +ASSERT(rejectsUnknownMode(greenfieldProfile, mixedMode)); +ASSERT(mixedProfile->supportsMode(mixedMode)); +ASSERT(greenfieldProfile->supportsMode(greenfieldMode)); +ASSERT(greenfieldProfile->supportsMode(mixedMode)); +ASSERT(!mixedProfile->supportsMode(greenfieldMode)); +ASSERT(greenfieldProfile->supportsMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps)); +ASSERT(greenfieldProfile->getNonHtControlResponseMode(greenfieldMode) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); +ASSERT(greenfieldProfile->getNonHtControlResponseMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode9Mbps) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); ASSERT(mixedProfile->getControlResponseMode(mixedMode) == mixedMode); -ASSERT(greenfieldProfile->getControlResponseModeSet(greenfieldMode) == mixedProfile); -ASSERT(greenfieldProfile->getControlResponseMode(greenfieldMode) == mixedMode); ASSERT(greenfieldProfile->getControlResponseMode(greenfieldMode) == mixedMode); +auto transmitter = new TestIeee80211Transmitter; +transmitter->setModeSet(greenfieldProfile); +transmitter->setMode(greenfieldMode); +transmitter->setModeSet(mixedProfile); +ASSERT(transmitter->getTestModeSet() == mixedProfile); +ASSERT(transmitter->getTestMode() == mixedMode); +transmitter->setModeSet(greenfieldProfile); +ASSERT(transmitter->getTestModeSet() == greenfieldProfile); +ASSERT(transmitter->getTestMode() == greenfieldMode); +try { + transmitter->setMode(mixedMode); + ASSERT(false); +} +catch (const cRuntimeError&) { + ASSERT(transmitter->getTestMode() == greenfieldMode); +} +transmitter->setModeSet(nullptr); +ASSERT(transmitter->getTestModeSet() == nullptr); +ASSERT(transmitter->getTestMode() == nullptr); +delete transmitter; + auto greenfield5GhzMode = Ieee80211HtCompliantModes::getCompliantMode( &Ieee80211HtmcsTable::htMcs0BW20MHz, Ieee80211HtMode::BAND_5GHZ, @@ -100,7 +122,7 @@ auto greenfield5GhzMode = Ieee80211HtCompliantModes::getCompliantMode( ASSERT(greenfield5GhzMode->getCenterFrequencyMode() == Ieee80211HtMode::BAND_5GHZ); ASSERT(greenfield5GhzMode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD); ASSERT(!greenfieldProfile->containsMode(greenfield5GhzMode)); -ASSERT(rejectsNonLocalControlResponseMode(greenfieldProfile, greenfield5GhzMode)); +ASSERT(!greenfieldProfile->supportsMode(greenfield5GhzMode)); auto vhtMixed5GhzMode = Ieee80211VhtCompliantModes::getCompliantMode( &Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, From 660dce8fd555a010c00e0f9c9cd64e948acff406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Wed, 19 Aug 2026 14:13:57 +0200 Subject: [PATCH 06/10] 802.11: fix HT response rate handling --- .../mac/rateselection/QosRateSelection.cc | 83 ++++++-------- .../mac/rateselection/QosRateSelection.h | 3 +- .../mac/rateselection/RateSelection.cc | 78 ++++++------- .../mac/rateselection/RateSelection.h | 3 +- .../ieee80211/mode/Ieee80211ModeSet.cc | 103 ++++++++++++++---- .../ieee80211/mode/Ieee80211ModeSet.h | 12 +- .../packetlevel/Ieee80211Transmitter.cc | 25 ++++- ...e80211ConfiguredResponseRateSelection.test | 73 ++++++++++++- .../module/Ieee80211HtGreenfieldRuntime.test | 6 + tests/unit/Ieee80211HtGreenfield_1.test | 78 +++++++++++++ 10 files changed, 334 insertions(+), 130 deletions(-) diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc index f444662577a..621b8145ccf 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc @@ -7,8 +7,6 @@ #include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" -#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" - #include "inet/common/ModuleAccess.h" #include "inet/common/Simsignals.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" @@ -18,24 +16,6 @@ namespace ieee80211 { using namespace inet::physicallayer; -static const IIeee80211Mode *getConfiguredControlResponseMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) -{ - // Explicit legacy/VHT overrides retain their requested mode; HT rates need the - // bounded non-HT control-response conversion for ACK/BlockAck. - return dynamic_cast(mode) != nullptr ? modeSet->getNonHtControlResponseMode(mode) : mode; -} - -static const IIeee80211Mode *getDynamicControlResponseMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) -{ - if (dynamic_cast(mode) != nullptr || !modeSet->containsMode(mode)) - return modeSet->getNonHtControlResponseMode(mode); - if (modeSet->getIsMandatory(mode)) - return mode; - if (auto slowerMode = modeSet->getSlowerMandatoryMode(mode)) - return slowerMode; - return modeSet->getNonHtControlResponseMode(mode); -} - Define_Module(QosRateSelection); void QosRateSelection::initialize(int stage) @@ -43,28 +23,40 @@ void QosRateSelection::initialize(int stage) ModeSetListener::initialize(stage); if (stage == INITSTAGE_LINK_LAYER) { dataOrMgmtRateControl = dynamic_cast(findModuleByPath(par("rateControlModule"))); - double multicastFrameBitrate = par("multicastFrameBitrate"); - multicastFrameMode = (multicastFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(multicastFrameBitrate)); - double dataFrameBitrate = par("dataFrameBitrate"); - dataFrameMode = (dataFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(dataFrameBitrate), Hz(par("dataFrameBandwidth")), par("dataFrameNumSpatialStreams")); - double mgmtFrameBitrate = par("mgmtFrameBitrate"); - mgmtFrameMode = (mgmtFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(mgmtFrameBitrate)); - double controlFrameBitrate = par("controlFrameBitrate"); - controlFrameMode = (controlFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(controlFrameBitrate)); - resolveConfiguredResponseModes(); + resolveConfiguredModes(modeSet); } } -void QosRateSelection::resolveConfiguredResponseModes() +void QosRateSelection::resolveConfiguredModes(const Ieee80211ModeSet *newModeSet) { - if (modeSet == nullptr) + if (newModeSet == nullptr) return; + double multicastFrameBitrate = par("multicastFrameBitrate"); + auto newMulticastFrameMode = multicastFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(multicastFrameBitrate)); + double dataFrameBitrate = par("dataFrameBitrate"); + auto newDataFrameMode = dataFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(dataFrameBitrate), Hz(par("dataFrameBandwidth")), par("dataFrameNumSpatialStreams")); + double mgmtFrameBitrate = par("mgmtFrameBitrate"); + auto newMgmtFrameMode = mgmtFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(mgmtFrameBitrate)); + double controlFrameBitrate = par("controlFrameBitrate"); + auto newControlFrameMode = controlFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(controlFrameBitrate)); double responseAckFrameBitrate = par("responseAckFrameBitrate"); - responseAckFrameMode = responseAckFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(responseAckFrameBitrate)); + auto newResponseAckFrameMode = responseAckFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(responseAckFrameBitrate)); double responseBlockAckFrameBitrate = par("responseBlockAckFrameBitrate"); - responseBlockAckFrameMode = responseBlockAckFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(responseBlockAckFrameBitrate)); + auto newResponseBlockAckFrameMode = responseBlockAckFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(responseBlockAckFrameBitrate)); double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); - responseCtsFrameMode = responseCtsFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(responseCtsFrameBitrate)); + auto newResponseCtsFrameMode = responseCtsFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(responseCtsFrameBitrate)); + auto newFastestMandatoryMode = newModeSet->getFastestMandatoryMode(); + + modeSet = newModeSet; + multicastFrameMode = newMulticastFrameMode; + dataFrameMode = newDataFrameMode; + mgmtFrameMode = newMgmtFrameMode; + controlFrameMode = newControlFrameMode; + responseAckFrameMode = newResponseAckFrameMode; + responseBlockAckFrameMode = newResponseBlockAckFrameMode; + responseCtsFrameMode = newResponseCtsFrameMode; + fastestMandatoryMode = newFastestMandatoryMode; + lastTransmittedFrameMode.clear(); } const IIeee80211Mode *QosRateSelection::getMode(Packet *packet, const Ptr& header) @@ -99,24 +91,21 @@ bool QosRateSelection::isControlResponseFrame(const Ptr& dataOrMgmtHeader) { if (responseAckFrameMode) - return getConfiguredControlResponseMode(modeSet, responseAckFrameMode); + return modeSet->getNonHtControlResponseMode(responseAckFrameMode, false); auto mode = getMode(packet, dataOrMgmtHeader); ASSERT(modeSet->supportsMode(mode)); // IEEE 802.11-2024 10.6.6.1/10.6.6.5.2: this bounded model uses a // mandatory non-HT rate for ordinary ACK responses; BSSBasicRateSet is not modelled. - return getDynamicControlResponseMode(modeSet, mode); + return modeSet->getMandatoryControlResponseMode(mode); } const IIeee80211Mode *QosRateSelection::computeResponseCtsFrameMode(Packet *packet, const Ptr& rtsFrame) { auto mode = getMode(packet, rtsFrame); ASSERT(modeSet->supportsMode(mode)); - auto responseMode = responseCtsFrameMode ? responseCtsFrameMode : mode; - if (dynamic_cast(mode) != nullptr) { - // IEEE 802.11-2024 10.6.6.1 and 10.6.6.5.7 require an HT response to HT RTS; HT-GF is never a response. - return responseCtsFrameMode && dynamic_cast(responseMode) == nullptr ? responseMode : modeSet->getControlResponseMode(responseMode); - } - return responseCtsFrameMode ? getConfiguredControlResponseMode(modeSet, responseMode) : getDynamicControlResponseMode(modeSet, responseMode); + // The eliciting mode is required even when a CTS rate is configured because + // the response format and CandidateMCSSet depend on the received PPDU. + return modeSet->getControlResponseMode(mode, responseCtsFrameMode); } // @@ -130,14 +119,12 @@ const IIeee80211Mode *QosRateSelection::computeResponseBlockAckFrameMode(Packet if (!dynamicPtrCast(blockAckReq)) throw cRuntimeError("Unknown BlockAckReq frame type"); if (responseBlockAckFrameMode) - return getConfiguredControlResponseMode(modeSet, responseBlockAckFrameMode); + return modeSet->getNonHtControlResponseMode(responseBlockAckFrameMode, false); auto mode = getMode(packet, blockAckReq); ASSERT(modeSet->supportsMode(mode)); // IEEE 802.11-2024 10.6.6.5.2 permits non-HT Basic BlockAck responses; // this model has no BSSBasicRateSet/HT Control context to select another PPDU. - if (dynamic_cast(mode) != nullptr) - return modeSet->getNonHtControlResponseMode(mode); - return mode; + return modeSet->getNonHtControlResponseMode(mode, false); } const IIeee80211Mode *QosRateSelection::computeDataOrMgmtFrameMode(const Ptr& dataOrMgmtHeader) @@ -258,9 +245,7 @@ void QosRateSelection::receiveSignal(cComponent *source, simsignal_t signalID, c Enter_Method("%s", cComponent::getSignalName(signalID)); if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - fastestMandatoryMode = modeSet->getFastestMandatoryMode(); - resolveConfiguredResponseModes(); + resolveConfiguredModes(check_and_cast(obj)); } } diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h index 580f88a7c05..9942347e79f 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h @@ -52,7 +52,7 @@ class INET_API QosRateSelection : public IQosRateSelection, public ModeSetListen virtual int numInitStages() const override { return NUM_INIT_STAGES; } virtual void initialize(int stage) override; virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) override; - void resolveConfiguredResponseModes(); + void resolveConfiguredModes(const physicallayer::Ieee80211ModeSet *newModeSet); virtual const physicallayer::IIeee80211Mode *getMode(Packet *packet, const Ptr& header); virtual const physicallayer::IIeee80211Mode *computeControlFrameMode(const Ptr& header, TxopProcedure *txopProcedure); @@ -79,4 +79,3 @@ class INET_API QosRateSelection : public IQosRateSelection, public ModeSetListen } /* namespace inet */ #endif - diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc index 2e13f3f4a4b..dfad89e2be1 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc @@ -7,8 +7,6 @@ #include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h" -#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" - #include "inet/common/ModuleAccess.h" #include "inet/common/Simsignals.h" #include "inet/linklayer/ieee80211/mac/contract/IRateControl.h" @@ -22,24 +20,6 @@ namespace ieee80211 { using namespace inet::physicallayer; -static const IIeee80211Mode *getConfiguredControlResponseMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) -{ - // Explicit legacy/VHT overrides retain their requested mode; HT rates need the - // bounded non-HT control-response conversion for ACK/BlockAck. - return dynamic_cast(mode) != nullptr ? modeSet->getNonHtControlResponseMode(mode) : mode; -} - -static const IIeee80211Mode *getDynamicControlResponseMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) -{ - if (dynamic_cast(mode) != nullptr || !modeSet->containsMode(mode)) - return modeSet->getNonHtControlResponseMode(mode); - if (modeSet->getIsMandatory(mode)) - return mode; - if (auto slowerMode = modeSet->getSlowerMandatoryMode(mode)) - return slowerMode; - return modeSet->getNonHtControlResponseMode(mode); -} - Define_Module(RateSelection); void RateSelection::initialize(int stage) @@ -49,16 +29,7 @@ void RateSelection::initialize(int stage) } else if (stage == INITSTAGE_LINK_LAYER) { dataOrMgmtRateControl = dynamic_cast(findModuleByPath(par("rateControlModule"))); - double multicastFrameBitrate = par("multicastFrameBitrate"); - multicastFrameMode = (multicastFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(multicastFrameBitrate)); - double dataFrameBitrate = par("dataFrameBitrate"); - dataFrameMode = (dataFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(dataFrameBitrate), Hz(par("dataFrameBandwidth")), par("dataFrameNumSpatialStreams")); - double mgmtFrameBitrate = par("mgmtFrameBitrate"); - mgmtFrameMode = (mgmtFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(mgmtFrameBitrate)); - double controlFrameBitrate = par("controlFrameBitrate"); - controlFrameMode = (controlFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(controlFrameBitrate)); - resolveConfiguredResponseModes(); - fastestMandatoryMode = modeSet->getFastestMandatoryMode(); + resolveConfiguredModes(modeSet); // WATCH(dataOrMgmtRateControl); // WATCH(*((cObject**)&fastestMandatoryMode)); @@ -77,14 +48,35 @@ void RateSelection::initialize(int stage) } } -void RateSelection::resolveConfiguredResponseModes() +void RateSelection::resolveConfiguredModes(const Ieee80211ModeSet *newModeSet) { - if (modeSet == nullptr) + if (newModeSet == nullptr) return; + double multicastFrameBitrate = par("multicastFrameBitrate"); + auto newMulticastFrameMode = multicastFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(multicastFrameBitrate)); + double dataFrameBitrate = par("dataFrameBitrate"); + auto newDataFrameMode = dataFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(dataFrameBitrate), Hz(par("dataFrameBandwidth")), par("dataFrameNumSpatialStreams")); + double mgmtFrameBitrate = par("mgmtFrameBitrate"); + auto newMgmtFrameMode = mgmtFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(mgmtFrameBitrate)); + double controlFrameBitrate = par("controlFrameBitrate"); + auto newControlFrameMode = controlFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(controlFrameBitrate)); double responseAckFrameBitrate = par("responseAckFrameBitrate"); - responseAckFrameMode = responseAckFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(responseAckFrameBitrate)); + auto newResponseAckFrameMode = responseAckFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(responseAckFrameBitrate)); double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); - responseCtsFrameMode = responseCtsFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(responseCtsFrameBitrate)); + auto newResponseCtsFrameMode = responseCtsFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(responseCtsFrameBitrate)); + auto newFastestMandatoryMode = newModeSet->getFastestMandatoryMode(); + + // Commit only after every configured mode has been resolved, so a failed + // runtime operation-mode change leaves the previous state intact. + modeSet = newModeSet; + multicastFrameMode = newMulticastFrameMode; + dataFrameMode = newDataFrameMode; + mgmtFrameMode = newMgmtFrameMode; + controlFrameMode = newControlFrameMode; + responseAckFrameMode = newResponseAckFrameMode; + responseCtsFrameMode = newResponseCtsFrameMode; + fastestMandatoryMode = newFastestMandatoryMode; + lastTransmittedFrameMode.clear(); } const IIeee80211Mode *RateSelection::getMode(Packet *packet, const Ptr& header) @@ -109,24 +101,22 @@ const IIeee80211Mode *RateSelection::computeResponseAckFrameMode(Packet *packet, // Keep configured responses independent of the eliciting packet; callers may // resolve a configured response while constructing a test packet. if (responseAckFrameMode) - return getConfiguredControlResponseMode(modeSet, responseAckFrameMode); + return modeSet->getNonHtControlResponseMode(responseAckFrameMode, false); auto mode = getMode(packet, dataOrMgmtHeader); ASSERT(modeSet->supportsMode(mode)); // IEEE 802.11-2024 10.6.6.1/10.6.6.5.2: this bounded model uses a // mandatory non-HT rate for ordinary ACK responses; BSSBasicRateSet is not modelled. - return getDynamicControlResponseMode(modeSet, mode); + return modeSet->getMandatoryControlResponseMode(mode); } const IIeee80211Mode *RateSelection::computeResponseCtsFrameMode(Packet *packet, const Ptr& rtsFrame) { auto mode = getMode(packet, rtsFrame); ASSERT(modeSet->supportsMode(mode)); - auto responseMode = responseCtsFrameMode ? responseCtsFrameMode : mode; - if (dynamic_cast(mode) != nullptr) { - // IEEE 802.11-2024 10.6.6.1 and 10.6.6.5.7 require an HT response to HT RTS; HT-GF is never a response. - return responseCtsFrameMode && dynamic_cast(responseMode) == nullptr ? responseMode : modeSet->getControlResponseMode(responseMode); - } - return responseCtsFrameMode ? getConfiguredControlResponseMode(modeSet, responseMode) : getDynamicControlResponseMode(modeSet, responseMode); + // IEEE 802.11-2024 10.6.6.1 and 10.6.6.5.7 require an HT response to an + // HT RTS and forbid HT-GF for the response. Consequently, even a configured + // CTS rate needs the eliciting PPDU's mode tag to select a legal format/MCS. + return modeSet->getControlResponseMode(mode, responseCtsFrameMode); } // 802.11-1999 Std. @@ -181,9 +171,7 @@ void RateSelection::receiveSignal(cComponent *source, simsignal_t signalID, cObj Enter_Method("%s", cComponent::getSignalName(signalID)); if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - fastestMandatoryMode = modeSet->getFastestMandatoryMode(); - resolveConfiguredResponseModes(); + resolveConfiguredModes(check_and_cast(obj)); } } diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h index 0382ec2f703..32913339d2a 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h @@ -52,7 +52,7 @@ class INET_API RateSelection : public IRateSelection, public SimpleModule, publi virtual int numInitStages() const override { return NUM_INIT_STAGES; } virtual void initialize(int stage) override; virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) override; - void resolveConfiguredResponseModes(); + void resolveConfiguredModes(const physicallayer::Ieee80211ModeSet *newModeSet); virtual const physicallayer::IIeee80211Mode *getMode(Packet *packet, const Ptr& header); virtual const physicallayer::IIeee80211Mode *computeControlFrameMode(const Ptr& header); @@ -78,4 +78,3 @@ class INET_API RateSelection : public IRateSelection, public SimpleModule, publi } // namespace inet #endif - diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc index c2f6615c39b..93b51433c5b 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc @@ -8,6 +8,7 @@ #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" #include +#include #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ErpOfdmMode.h" @@ -104,9 +105,12 @@ static std::vector createHtEntries(Ieee80211HtPreambleM static std::vector createHtSupportedEntries(Ieee80211HtPreambleMode::HighTroughputPreambleFormat preambleFormat) { auto result = createHtEntries(preambleFormat); - auto mixedEntries = createHtEntries(Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED); - if (preambleFormat == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD) + if (preambleFormat == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD) { + auto mixedEntries = createHtEntries(Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED); result.insert(result.end(), mixedEntries.begin(), mixedEntries.end()); + } + // IEEE 802.11-2024 19.1.1 and 19.1.4 require a 2.4 GHz HT STA to support + // the mandatory Clause 16/18 rates and the non-HT and HT-mixed formats. result.insert(result.end(), { { true, &Ieee80211DsssCompliantModes::dsssMode1Mbps }, { true, &Ieee80211DsssCompliantModes::dsssMode2Mbps }, @@ -501,6 +505,11 @@ OPP_THREAD_LOCAL const DelayedInitializer> Ieee802 { false, Ieee80211VhtCompliantModes::getCompliantMode(&Ieee80211VhtmcsTable::vhtMcs9BW160MHzNss8, Ieee80211VhtMode::BAND_5GHZ, Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211VhtModeBase::HT_GUARD_INTERVAL_SHORT) }, }),}; }); +Ieee80211ModeSet::Ieee80211ModeSet(const char *name, const std::vector entries) : + Ieee80211ModeSet(name, entries, entries) +{ +} + Ieee80211ModeSet::Ieee80211ModeSet(const char *name, const std::vector entries, const std::vector supportedEntries) : name(name), entries(entries), @@ -533,22 +542,51 @@ int Ieee80211ModeSet::findModeIndex(const IIeee80211Mode *mode) const std::map Ieee80211ModeSet::createControlResponseModes(const std::vector& supportedEntries) { std::map result; + std::set mandatoryHtMcsIndexes; + for (const auto& entry : supportedEntries) { + auto mode = dynamic_cast(entry.mode); + if (entry.isMandatory && mode != nullptr) + mandatoryHtMcsIndexes.insert(mode->getDataMode()->getMcsIndex()); + } for (const auto& sourceEntry : supportedEntries) { auto source = dynamic_cast(sourceEntry.mode); - if (source == nullptr || source->getPreambleMode()->getPreambleFormat() != Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD) + if (source == nullptr) continue; + std::vector candidates; for (const auto& candidateEntry : supportedEntries) { auto candidate = dynamic_cast(candidateEntry.mode); if (candidate != nullptr && candidate->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED && candidate->getCenterFrequencyMode() == source->getCenterFrequencyMode() && - candidate->getDataMode()->getMcsIndex() == source->getDataMode()->getMcsIndex() && candidate->getDataMode()->getBandwidth() == source->getDataMode()->getBandwidth() && - candidate->getDataMode()->getGuardIntervalType() == source->getDataMode()->getGuardIntervalType() && - candidate->getDataMode()->getNumberOfSpatialStreams() == source->getDataMode()->getNumberOfSpatialStreams()) { - result.emplace(sourceEntry.mode, candidateEntry.mode); - break; - } + mandatoryHtMcsIndexes.find(candidate->getDataMode()->getMcsIndex()) != mandatoryHtMcsIndexes.end() && + candidate->getDataMode()->getMcsIndex() <= source->getDataMode()->getMcsIndex() && + candidate->getDataMode()->getNumberOfSpatialStreams() <= source->getDataMode()->getNumberOfSpatialStreams()) + candidates.push_back(candidate); + } + // IEEE 802.11-2024 10.6.6.5.3: with no Basic HT-MCS Set modelled, + // CandidateMCSSet is the mandatory HT MCSs. After the MCS-index bound, + // retain the highest NSS not exceeding the received NSS, then select the + // highest indexed MCS whose per-stream modulation and coding rate do not + // exceed those of the received MCS. The modelled MCS 0..31 are EQM. + int highestNss = -1; + for (auto candidate : candidates) + highestNss = std::max(highestNss, candidate->getDataMode()->getNumberOfSpatialStreams()); + const Ieee80211HtMode *response = nullptr; + auto sourceMcs = source->getDataMode()->getModulationAndCodingScheme(); + int sourceModulation = sourceMcs->getModulation()->getSubcarrierModulation()->getCodeWordSize(); + double sourceCodeRate = sourceMcs->getCode()->getForwardErrorCorrection()->getCodeRate(); + for (auto candidate : candidates) { + auto candidateDataMode = candidate->getDataMode(); + auto candidateMcs = candidateDataMode->getModulationAndCodingScheme(); + if (candidateDataMode->getNumberOfSpatialStreams() == highestNss && + candidateMcs->getModulation()->getSubcarrierModulation()->getCodeWordSize() <= sourceModulation && + candidateMcs->getCode()->getForwardErrorCorrection()->getCodeRate() <= sourceCodeRate && + (response == nullptr || candidateDataMode->getMcsIndex() > response->getDataMode()->getMcsIndex())) + response = candidate; } + if (response == nullptr) + throw cRuntimeError("No mandatory HT control response mode for %s", source->getName()); + result.emplace(sourceEntry.mode, response); } return result; } @@ -702,31 +740,52 @@ const IIeee80211Mode *Ieee80211ModeSet::getFasterMandatoryMode(const IIeee80211M return nullptr; } -const IIeee80211Mode *Ieee80211ModeSet::getControlResponseMode(const IIeee80211Mode *mode) const +const IIeee80211Mode *Ieee80211ModeSet::getControlResponseMode(const IIeee80211Mode *mode, const IIeee80211Mode *configuredMode) const { if (!supportsMode(mode)) throw cRuntimeError("Control response mode is not supported by operation mode %s: %s", getName(), mode->getName()); auto it = controlResponseModes.find(mode); - return it != controlResponseModes.end() ? it->second : mode; + if (it == controlResponseModes.end()) { + if (configuredMode != nullptr) + return getNonHtControlResponseMode(configuredMode, false); + return getMandatoryControlResponseMode(mode); + } + auto primaryMode = it->second; + if (configuredMode == nullptr) + return primaryMode; + if (!supportsMode(configuredMode)) + throw cRuntimeError("Configured control response mode is not supported by operation mode %s: %s", getName(), configuredMode->getName()); + auto configuredIt = controlResponseModes.find(configuredMode); + if (configuredIt == controlResponseModes.end()) + throw cRuntimeError("An HT RTS requires an HT-mixed CTS response, configured mode is non-HT: %s", configuredMode->getName()); + if (configuredIt->second != primaryMode) + throw cRuntimeError("Configured CTS mode differs from the primary HT control response MCS for %s; alternate MCS duration selection is not modeled", mode->getName()); + return configuredIt->second; } -const IIeee80211Mode *Ieee80211ModeSet::getNonHtControlResponseMode(const IIeee80211Mode *mode) const +const IIeee80211Mode *Ieee80211ModeSet::getMandatoryControlResponseMode(const IIeee80211Mode *mode) const { if (!supportsMode(mode)) throw cRuntimeError("Control response mode is not supported by operation mode %s: %s", getName(), mode->getName()); - auto htMode = dynamic_cast(mode); - if (htMode == nullptr) { + if (controlResponseModes.find(mode) != controlResponseModes.end() || !containsMode(mode)) + return getNonHtControlResponseMode(mode); + if (getIsMandatory(mode)) + return mode; + if (auto slowerMode = getSlowerMandatoryMode(mode)) + return slowerMode; + return getNonHtControlResponseMode(mode); +} + +const IIeee80211Mode *Ieee80211ModeSet::getNonHtControlResponseMode(const IIeee80211Mode *mode, bool mandatory) const +{ + if (!supportsMode(mode)) + throw cRuntimeError("Control response mode is not supported by operation mode %s: %s", getName(), mode->getName()); + if (controlResponseModes.find(mode) == controlResponseModes.end()) { // VHT response-format selection remains unchanged; this fallback is HT-scoped. if (dynamic_cast(mode) != nullptr) return mode; - const IIeee80211Mode *result = nullptr; - for (const auto& entry : nonHtControlResponseEntries) { - auto candidate = entry.mode; - if (candidate->getDataMode()->getNetBitrate() <= mode->getDataMode()->getNetBitrate() && - (result == nullptr || candidate->getDataMode()->getNetBitrate() > result->getDataMode()->getNetBitrate())) - result = candidate; - } - return result != nullptr ? result : mode; + if (!mandatory) + return mode; } const IIeee80211Mode *result = nullptr; for (const auto& entry : nonHtControlResponseEntries) { diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h index c147d0e4333..b379caac0ea 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h @@ -45,7 +45,8 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject static std::vector createNonHtControlResponseEntries(const std::vector& supportedEntries); public: - Ieee80211ModeSet(const char *name, const std::vector entries, const std::vector supportedEntries = {}); + Ieee80211ModeSet(const char *name, const std::vector entries); + Ieee80211ModeSet(const char *name, const std::vector entries, const std::vector supportedEntries); virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override { return stream << "Ieee80211ModeSet, name = " << name; } @@ -75,8 +76,13 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject const IIeee80211Mode *getSlowerMandatoryMode(const IIeee80211Mode *mode) const; const IIeee80211Mode *getFasterMandatoryMode(const IIeee80211Mode *mode) const; - const IIeee80211Mode *getControlResponseMode(const IIeee80211Mode *mode) const; - const IIeee80211Mode *getNonHtControlResponseMode(const IIeee80211Mode *mode) const; + // Selects the primary response mode for an eliciting mode. configuredMode, + // when present, is constrained by the eliciting PPDU's response format/MCS. + const IIeee80211Mode *getControlResponseMode(const IIeee80211Mode *mode, const IIeee80211Mode *configuredMode = nullptr) const; + const IIeee80211Mode *getMandatoryControlResponseMode(const IIeee80211Mode *mode) const; + // HT modes are always converted to a mandatory non-HT response. For a + // non-HT mode, mandatory=false preserves an explicitly selected rate. + const IIeee80211Mode *getNonHtControlResponseMode(const IIeee80211Mode *mode, bool mandatory = true) const; static const Ieee80211ModeSet *findModeSet(const char *mode); static const Ieee80211ModeSet *getModeSet(const char *mode); diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc index 0b9ceab24ce..c0e32ae25aa 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc @@ -77,7 +77,17 @@ void Ieee80211Transmitter::setModeSet(const Ieee80211ModeSet *modeSet) if (this->modeSet != modeSet) { const IIeee80211Mode *newMode = nullptr; if (modeSet != nullptr && mode != nullptr) { - newMode = modeSet->containsMode(mode) ? mode : modeSet->getMode(mode->getDataMode()->getNetBitrate(), mode->getDataMode()->getBandwidth(), mode->getDataMode()->getNumberOfSpatialStreams()); + if (modeSet->containsMode(mode)) + newMode = mode; + else { + auto oldDataMode = mode->getDataMode(); + newMode = modeSet->getMode(oldDataMode->getNetBitrate(), oldDataMode->getBandwidth(), oldDataMode->getNumberOfSpatialStreams()); + // OFDM modes with different channel spacing may have the same + // bitrate, occupied bandwidth, and NSS. Their symbol intervals + // distinguish those non-equivalent PHY modes (e.g. a vs p). + if (newMode->getDataMode()->getSymbolInterval() != oldDataMode->getSymbolInterval()) + throw cRuntimeError("No equivalent current mode in operation mode %s", modeSet->getName()); + } } this->modeSet = modeSet; this->mode = newMode; @@ -150,8 +160,17 @@ const ITransmission *Ieee80211Transmitter::createTransmission(const IRadio *tran const Quaternion& startOrientation = mobility->getCurrentAngularPosition(); const Quaternion& endOrientation = mobility->getCurrentAngularPosition(); const simtime_t preambleDuration = transmissionMode->getPreambleMode()->getDuration(); - const simtime_t headerDuration = transmissionMode->getHeaderMode()->getDuration(); - const simtime_t dataDuration = duration - headerDuration - preambleDuration; + const simtime_t modeledDataDuration = transmissionMode->getDataMode()->getDuration(B(phyHeader->getLengthField())); + // HT/VHT include their SIG fields in the PHY preamble duration, so their + // mode duration is exactly preamble + modeled data. Other PHYs expose a + // separate header; their residual data interval may also include a trailing + // signal extension (ERP). Keep that extension in chronological data time. + const bool headerIncludedInPreamble = duration == preambleDuration + modeledDataDuration; + const simtime_t headerDuration = headerIncludedInPreamble ? SIMTIME_ZERO : transmissionMode->getHeaderMode()->getDuration(); + const simtime_t dataDuration = headerIncludedInPreamble ? modeledDataDuration : duration - headerDuration - preambleDuration; + if (preambleDuration < SIMTIME_ZERO || headerDuration < SIMTIME_ZERO || dataDuration < SIMTIME_ZERO || + preambleDuration + headerDuration + dataDuration != duration) + throw cRuntimeError("Invalid transmission duration decomposition for mode %s", transmissionMode->getName()); auto analogModel = getAnalogModel()->createAnalogModel(preambleDuration, headerDuration, dataDuration, centerFrequency, transmissionBandwidth, transmissionPower); return new Ieee80211Transmission(transmitter, packet, startTime, endTime, preambleDuration, headerDuration, dataDuration, startPosition, endPosition, startOrientation, endOrientation, nullptr, nullptr, nullptr, nullptr, analogModel, transmissionMode, transmissionChannel); } diff --git a/tests/module/Ieee80211ConfiguredResponseRateSelection.test b/tests/module/Ieee80211ConfiguredResponseRateSelection.test index 1b6af9e32a7..1d5bd785cfd 100644 --- a/tests/module/Ieee80211ConfiguredResponseRateSelection.test +++ b/tests/module/Ieee80211ConfiguredResponseRateSelection.test @@ -6,6 +6,7 @@ non-HT ACK/BlockAck and HT-mixed CTS formats. #include #include "inet/common/InitStages.h" +#include "inet/common/Simsignals.h" #include "inet/common/packet/Packet.h" #include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" #include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" @@ -53,12 +54,38 @@ class ConfiguredResponseProbe : public cSimpleModule throw cRuntimeError("Configured response bitrate was not applied to both rate selections"); auto sourceMode = Ieee80211ModeSet::getModeSet("n(greenfield-2.4Ghz)")->getMode(Mbps(6.5)); + auto mixedSourceMode = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)")->getMode(Mbps(6.5)); Packet packet("receivedFrame"); packet.addTag()->setMode(sourceMode); + Packet mixedPacket("receivedMixedFrame"); + mixedPacket.addTag()->setMode(mixedSourceMode); Packet nonHtPacket("receivedNonHtFrame"); nonHtPacket.addTag()->setMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); + auto dataHeader = makeShared(); + dataHeader->setReceiverAddress(MacAddress("02:00:00:00:00:01")); + Packet dataPacket("configuredDataFrame"); + + ASSERT(dcfRateSelection->computeMode(&dataPacket, dataHeader) == sourceMode); + ASSERT(qosRateSelection->computeMode(&dataPacket, dataHeader, nullptr) == sourceMode); assertNonHtResponse("DCF ACK", dcfRateSelection->computeResponseAckFrameMode(nullptr, nullptr)); + Packet untaggedPacket("untaggedRts"); + try { + dcfRateSelection->computeResponseCtsFrameMode(&untaggedPacket, nullptr); + throw cRuntimeError("Configured DCF CTS unexpectedly accepted an untagged eliciting packet"); + } + catch (const cRuntimeError& error) { + if (error.getFormattedMessage().find("Missing mode") == std::string::npos) + throw; + } + try { + qosRateSelection->computeResponseCtsFrameMode(&untaggedPacket, nullptr); + throw cRuntimeError("Configured QoS CTS unexpectedly accepted an untagged eliciting packet"); + } + catch (const cRuntimeError& error) { + if (error.getFormattedMessage().find("Missing mode") == std::string::npos) + throw; + } assertHtMixedResponse("DCF CTS HT", dcfRateSelection->computeResponseCtsFrameMode(&packet, nullptr)); assertNonHtResponse("DCF CTS non-HT", dcfRateSelection->computeResponseCtsFrameMode(&nonHtPacket, nullptr)); assertNonHtResponse("QoS ACK", qosRateSelection->computeResponseAckFrameMode(&packet, nullptr)); @@ -67,14 +94,45 @@ class ConfiguredResponseProbe : public cSimpleModule auto basicBlockAckReq = makeShared(); assertNonHtResponse("QoS Basic BlockAck", qosRateSelection->computeResponseBlockAckFrameMode(&packet, basicBlockAckReq)); + assertNonHtResponse("Dynamic mixed QoS ACK", dynamicQosRateSelection->computeResponseAckFrameMode(&mixedPacket, nullptr)); + assertHtMixedResponse("Dynamic mixed QoS CTS", dynamicQosRateSelection->computeResponseCtsFrameMode(&mixedPacket, nullptr)); + assertNonHtResponse("Dynamic mixed QoS Basic BlockAck", dynamicQosRateSelection->computeResponseBlockAckFrameMode(&mixedPacket, basicBlockAckReq)); Packet optionalNonHtPacket("receivedOptionalNonHtFrame"); optionalNonHtPacket.addTag()->setMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode9Mbps); assertNonHtResponse("Dynamic QoS ACK", dynamicQosRateSelection->computeResponseAckFrameMode(&optionalNonHtPacket, nullptr)); assertNonHtResponse("Dynamic QoS CTS", dynamicQosRateSelection->computeResponseCtsFrameMode(&optionalNonHtPacket, nullptr)); ASSERT(dynamicQosRateSelection->computeResponseBlockAckFrameMode(&optionalNonHtPacket, basicBlockAckReq) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode9Mbps); - ASSERT(dynamicQosRateSelection->computeResponseBlockAckFrameMode(&packet, basicBlockAckReq) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); - - std::cout << "Configured DCF and QoS ACK/Basic BlockAck use non-HT 6 Mbps; HT CTS uses HT-mixed 6.5 Mbps and non-HT CTS uses non-HT 6 Mbps; dynamic non-HT Basic BlockAck retains ERP 9 Mbps.\n"; + auto optionalHtMode = Ieee80211HtCompliantModes::getCompliantMode( + &Ieee80211HtmcsTable::htMcs8BW20MHz, + Ieee80211HtMode::BAND_2_4GHZ, + Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, + Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT); + Packet optionalHtPacket("receivedOptionalHtFrame"); + optionalHtPacket.addTag()->setMode(optionalHtMode); + assertHtMixedResponse("Configured CTS optional HT MCS", qosRateSelection->computeResponseCtsFrameMode(&optionalHtPacket, nullptr)); + + // Exercise the actual signal path with a compatible mode-set transition. + // The configured data cache must bind to the exact profile object while + // the configured response operations remain usable after each refresh. + auto wlan = check_and_cast(getSimulation()->getModuleByPath("TestConfiguredResponses.host[0].wlan[0]")); + wlan->emit(modesetChangedSignal, const_cast(Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"))); + ASSERT(dcfRateSelection->computeMode(&dataPacket, dataHeader) == mixedSourceMode); + ASSERT(qosRateSelection->computeMode(&dataPacket, dataHeader, nullptr) == mixedSourceMode); + assertHtMixedResponse("Rebound DCF CTS mixed", dcfRateSelection->computeResponseCtsFrameMode(&mixedPacket, nullptr)); + assertHtMixedResponse("Rebound QoS CTS mixed", qosRateSelection->computeResponseCtsFrameMode(&mixedPacket, nullptr)); + assertNonHtResponse("Rebound DCF ACK mixed", dcfRateSelection->computeResponseAckFrameMode(nullptr, nullptr)); + assertNonHtResponse("Rebound QoS ACK mixed", qosRateSelection->computeResponseAckFrameMode(nullptr, nullptr)); + assertNonHtResponse("Rebound QoS BlockAck mixed", qosRateSelection->computeResponseBlockAckFrameMode(nullptr, basicBlockAckReq)); + wlan->emit(modesetChangedSignal, const_cast(Ieee80211ModeSet::getModeSet("n(greenfield-2.4Ghz)"))); + ASSERT(dcfRateSelection->computeMode(&dataPacket, dataHeader) == sourceMode); + ASSERT(qosRateSelection->computeMode(&dataPacket, dataHeader, nullptr) == sourceMode); + assertHtMixedResponse("Rebound DCF CTS Greenfield", dcfRateSelection->computeResponseCtsFrameMode(&packet, nullptr)); + assertHtMixedResponse("Rebound QoS CTS Greenfield", qosRateSelection->computeResponseCtsFrameMode(&packet, nullptr)); + assertNonHtResponse("Rebound DCF ACK Greenfield", dcfRateSelection->computeResponseAckFrameMode(nullptr, nullptr)); + assertNonHtResponse("Rebound QoS ACK Greenfield", qosRateSelection->computeResponseAckFrameMode(nullptr, nullptr)); + assertNonHtResponse("Rebound QoS BlockAck Greenfield", qosRateSelection->computeResponseBlockAckFrameMode(nullptr, basicBlockAckReq)); + + std::cout << "Configured and dynamic mixed/GF ACK/Basic BlockAck use non-HT 6 Mbps; HT CTS uses bounded HT-mixed MCS 0 and requires an eliciting mode tag; dynamic non-HT Basic BlockAck retains ERP 9 Mbps.\n"; } }; @@ -116,6 +174,7 @@ abstract = false network = TestConfiguredResponses sim-time-limit = 1s **.opMode = "n(greenfield-2.4Ghz)" +*.host[1].wlan[*].opMode = "n(mixed-2.4Ghz)" **.qosStation = true *.host[1].wlan[0].mac.hcf.rateSelection.responseAckFrameBitrate = -1bps *.host[1].wlan[0].mac.hcf.rateSelection.responseCtsFrameBitrate = -1bps @@ -125,8 +184,14 @@ sim-time-limit = 1s **.wlan[*].mac.hcf.rateSelection.responseAckFrameBitrate = 6.5Mbps **.wlan[*].mac.hcf.rateSelection.responseCtsFrameBitrate = 6.5Mbps **.wlan[*].mac.hcf.rateSelection.responseBlockAckFrameBitrate = 6.5Mbps +*.host[0].wlan[0].mac.dcf.rateSelection.dataFrameBitrate = 6.5Mbps +*.host[0].wlan[0].mac.dcf.rateSelection.dataFrameBandwidth = 20MHz +*.host[0].wlan[0].mac.dcf.rateSelection.dataFrameNumSpatialStreams = 1 +*.host[0].wlan[0].mac.hcf.rateSelection.dataFrameBitrate = 6.5Mbps +*.host[0].wlan[0].mac.hcf.rateSelection.dataFrameBandwidth = 20MHz +*.host[0].wlan[0].mac.hcf.rateSelection.dataFrameNumSpatialStreams = 1 %extraargs: -c ConfiguredResponses %contains: stdout -Configured DCF and QoS ACK/Basic BlockAck use non-HT 6 Mbps; HT CTS uses HT-mixed 6.5 Mbps and non-HT CTS uses non-HT 6 Mbps; dynamic non-HT Basic BlockAck retains ERP 9 Mbps. +Configured and dynamic mixed/GF ACK/Basic BlockAck use non-HT 6 Mbps; HT CTS uses bounded HT-mixed MCS 0 and requires an eliciting mode tag; dynamic non-HT Basic BlockAck retains ERP 9 Mbps. diff --git a/tests/module/Ieee80211HtGreenfieldRuntime.test b/tests/module/Ieee80211HtGreenfieldRuntime.test index 4295764193d..60d013e74f3 100644 --- a/tests/module/Ieee80211HtGreenfieldRuntime.test +++ b/tests/module/Ieee80211HtGreenfieldRuntime.test @@ -39,6 +39,12 @@ class HtPreambleCheckingRadio : public Ieee80211Radio, public cListener auto transmission = check_and_cast(obj); auto mode = dynamic_cast(transmission->getMode()); auto phyHeader = Ieee80211Radio::peekIeee80211PhyHeaderAtFront(transmission->getPacket()); + if (transmission->getPreambleDuration() < SIMTIME_ZERO || transmission->getHeaderDuration() < SIMTIME_ZERO || transmission->getDataDuration() < SIMTIME_ZERO || + transmission->getPreambleDuration() + transmission->getHeaderDuration() + transmission->getDataDuration() != transmission->getDuration()) + throw cRuntimeError("Invalid preamble/header/data duration decomposition"); + if (mode != nullptr && (transmission->getHeaderDuration() != SIMTIME_ZERO || + transmission->getDataDuration() != mode->getDataMode()->getDuration(B(phyHeader->getLengthField())))) + throw cRuntimeError("HT transmission did not keep SIG in the preamble and data in the data interval"); auto macHeader = transmission->getPacket()->peekDataAt(phyHeader->getChunkLength()); auto isHtGreenfield = mode != nullptr && mode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD; auto isHtMixed = mode != nullptr && mode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED; diff --git a/tests/unit/Ieee80211HtGreenfield_1.test b/tests/unit/Ieee80211HtGreenfield_1.test index 32baf2e412f..06c11b640ce 100644 --- a/tests/unit/Ieee80211HtGreenfield_1.test +++ b/tests/unit/Ieee80211HtGreenfield_1.test @@ -5,11 +5,13 @@ preamble formats and timing, retain strict membership and supplementary Greenfie %includes: #include +#include "inet/common/packet/Packet.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssOfdmMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ErpOfdmMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211OfdmMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.h" @@ -22,6 +24,7 @@ class TestIeee80211Transmitter : public Ieee80211Transmitter public: const Ieee80211ModeSet *getTestModeSet() const { return modeSet; } const IIeee80211Mode *getTestMode() const { return mode; } + const IIeee80211Mode *getTestTransmissionMode(const Packet *packet) const { return computeTransmissionMode(packet); } }; static const Ieee80211HtMode *findMcs0LongGiMode(const Ieee80211ModeSet *modeSet) @@ -75,6 +78,10 @@ ASSERT(greenfieldMode->getPreambleMode()->getDuration() == SimTime(24, SIMTIME_U ASSERT(mixedMode->getDuration(B(0)) == SimTime(40, SIMTIME_US)); ASSERT(greenfieldMode->getDuration(B(0)) == SimTime(28, SIMTIME_US)); ASSERT(mixedMode->getDuration(B(0)) - greenfieldMode->getDuration(B(0)) == SimTime(12, SIMTIME_US)); +ASSERT(mixedMode->getDataMode()->getDuration(B(0)) == SimTime(4, SIMTIME_US)); +ASSERT(greenfieldMode->getDataMode()->getDuration(B(0)) == SimTime(4, SIMTIME_US)); +ASSERT(mixedMode->getDuration(B(0)) == mixedMode->getPreambleMode()->getDuration() + mixedMode->getDataMode()->getDuration(B(0))); +ASSERT(greenfieldMode->getDuration(B(0)) == greenfieldMode->getPreambleMode()->getDuration() + greenfieldMode->getDataMode()->getDuration(B(0))); ASSERT(!mixedProfile->containsMode(greenfieldMode)); ASSERT(!greenfieldProfile->containsMode(mixedMode)); ASSERT(mixedProfile->getSlowerMode(greenfieldMode) == nullptr); @@ -87,12 +94,62 @@ ASSERT(mixedProfile->supportsMode(mixedMode)); ASSERT(greenfieldProfile->supportsMode(greenfieldMode)); ASSERT(greenfieldProfile->supportsMode(mixedMode)); ASSERT(!mixedProfile->supportsMode(greenfieldMode)); +ASSERT(mixedProfile->supportsMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps)); +ASSERT(!mixedProfile->containsMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps)); ASSERT(greenfieldProfile->supportsMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps)); +ASSERT(mixedProfile->getNonHtControlResponseMode(mixedMode) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); ASSERT(greenfieldProfile->getNonHtControlResponseMode(greenfieldMode) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); ASSERT(greenfieldProfile->getNonHtControlResponseMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode9Mbps) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); ASSERT(mixedProfile->getControlResponseMode(mixedMode) == mixedMode); ASSERT(greenfieldProfile->getControlResponseMode(greenfieldMode) == mixedMode); +auto greenfieldMcs8 = Ieee80211HtCompliantModes::getCompliantMode( + &Ieee80211HtmcsTable::htMcs8BW20MHz, + Ieee80211HtMode::BAND_2_4GHZ, + Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, + Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT); +ASSERT(greenfieldProfile->supportsMode(greenfieldMcs8)); +// MCS 8 is optional and uses two BPSK 1/2 streams. With no Basic HT-MCS Set +// modelled, 10.6.6.5.3 selects mandatory one-stream MCS 0 for the CTS. +ASSERT(greenfieldProfile->getControlResponseMode(greenfieldMcs8) == mixedMode); +ASSERT(greenfieldProfile->getControlResponseMode(greenfieldMcs8, greenfieldMode) == mixedMode); + +auto greenfieldMcs7 = Ieee80211HtCompliantModes::getCompliantMode( + &Ieee80211HtmcsTable::htMcs7BW20MHz, + Ieee80211HtMode::BAND_2_4GHZ, + Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, + Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG); +auto mixedMcs7 = Ieee80211HtCompliantModes::getCompliantMode( + &Ieee80211HtmcsTable::htMcs7BW20MHz, + Ieee80211HtMode::BAND_2_4GHZ, + Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, + Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG); +auto greenfieldMcs15 = Ieee80211HtCompliantModes::getCompliantMode( + &Ieee80211HtmcsTable::htMcs15BW20MHz, + Ieee80211HtMode::BAND_2_4GHZ, + Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, + Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT); +ASSERT(greenfieldProfile->getControlResponseMode(greenfieldMcs7) == mixedMcs7); +ASSERT(greenfieldProfile->getControlResponseMode(greenfieldMcs15) == mixedMcs7); +try { + greenfieldProfile->getControlResponseMode(greenfieldMcs7, greenfieldMode); + ASSERT(false); +} +catch (const cRuntimeError&) { +} + +auto greenfieldMcs8Bw40 = Ieee80211HtCompliantModes::getCompliantMode( + &Ieee80211HtmcsTable::htMcs8BW40MHz, + Ieee80211HtMode::BAND_2_4GHZ, + Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, + Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT); +auto mixedMcs0Bw40 = Ieee80211HtCompliantModes::getCompliantMode( + &Ieee80211HtmcsTable::htMcs0BW40MHz, + Ieee80211HtMode::BAND_2_4GHZ, + Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, + Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT); +ASSERT(greenfieldProfile->getControlResponseMode(greenfieldMcs8Bw40) == mixedMcs0Bw40); + auto transmitter = new TestIeee80211Transmitter; transmitter->setModeSet(greenfieldProfile); transmitter->setMode(greenfieldMode); @@ -102,6 +159,9 @@ ASSERT(transmitter->getTestMode() == mixedMode); transmitter->setModeSet(greenfieldProfile); ASSERT(transmitter->getTestModeSet() == greenfieldProfile); ASSERT(transmitter->getTestMode() == greenfieldMode); +Packet supportedLegacyPacket("supportedLegacy"); +supportedLegacyPacket.addTag()->setMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); +ASSERT(transmitter->getTestTransmissionMode(&supportedLegacyPacket) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); try { transmitter->setMode(mixedMode); ASSERT(false); @@ -114,6 +174,22 @@ ASSERT(transmitter->getTestModeSet() == nullptr); ASSERT(transmitter->getTestMode() == nullptr); delete transmitter; +auto ofdmProfile = Ieee80211ModeSet::getModeSet("a"); +auto halfRateProfile = Ieee80211ModeSet::getModeSet("p"); +auto ofdm6 = ofdmProfile->getMode(Mbps(6), MHz(20), 1); +transmitter = new TestIeee80211Transmitter; +transmitter->setModeSet(ofdmProfile); +transmitter->setMode(ofdm6); +try { + transmitter->setModeSet(halfRateProfile); + ASSERT(false); +} +catch (const cRuntimeError&) { + ASSERT(transmitter->getTestModeSet() == ofdmProfile); + ASSERT(transmitter->getTestMode() == ofdm6); +} +delete transmitter; + auto greenfield5GhzMode = Ieee80211HtCompliantModes::getCompliantMode( &Ieee80211HtmcsTable::htMcs0BW20MHz, Ieee80211HtMode::BAND_5GHZ, @@ -145,6 +221,8 @@ ASSERT(vhtGreenfield5GhzMode != vhtMixed2GhzMode); ASSERT(vhtMixed5GhzMode->getCenterFrequencyMode() == Ieee80211VhtMode::BAND_5GHZ); ASSERT(vhtGreenfield5GhzMode->getPreambleMode()->getPreambleFormat() == Ieee80211VhtPreambleMode::HT_PREAMBLE_GREENFIELD); ASSERT(vhtMixed2GhzMode->getCenterFrequencyMode() == Ieee80211VhtMode::BAND_2_4GHZ); +ASSERT(vhtMixed5GhzMode->getDuration(B(0)) == vhtMixed5GhzMode->getPreambleMode()->getDuration() + vhtMixed5GhzMode->getDataMode()->getDuration(B(0))); +ASSERT(vhtMixed5GhzMode->getDataMode()->getDuration(B(0)) >= SIMTIME_ZERO); auto erpMixedProfile = Ieee80211ModeSet::getModeSet("g(mixed)"); auto erpOnlyProfile = Ieee80211ModeSet::getModeSet("g(erp)"); From f452a340501382854c7fbd79565591c513f22e88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Wed, 19 Aug 2026 18:08:14 +0200 Subject: [PATCH 07/10] 802.11: preserve HT response compatibility Honor responseCtsFrameBitrate for HT exchanges by translating the configured mode to its exact HT-mixed counterpart while preserving MCS, bandwidth, spatial streams, guard interval, and band. Keep standards-derived primary selection for the automatic path, and document the configured value as an explicit model override. Restrict supplementary HT-mixed and legacy DSSS/HR-DSSS/ERP capabilities to the Greenfield profile. This restores the established n(mixed-2.4Ghz) membership contract, HT ACK and Basic BlockAck selection, and strict transmitter/receiver acceptance behavior. Update unit and module coverage for mismatched configured and derived CTS MCS values, Greenfield-only supplementary support, mixed-profile response behavior, mode-set rebinding, and legacy-mode rejection. Validated with the debug build, all 90 unit tests, the focused configured-response and Greenfield runtime module tests, and the mixed-HT TXOP fingerprint. --- .../mac/rateselection/QosRateSelection.ned | 4 + .../mac/rateselection/RateSelection.ned | 4 + .../ieee80211/mode/Ieee80211ModeSet.cc | 82 ++++++++++++++----- .../ieee80211/mode/Ieee80211ModeSet.h | 10 ++- ...e80211ConfiguredResponseRateSelection.test | 42 ++++++---- tests/unit/Ieee80211HtGreenfield_1.test | 36 ++++++-- 6 files changed, 129 insertions(+), 49 deletions(-) diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned index b3b96441781..72719d78999 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned @@ -28,6 +28,10 @@ simple QosRateSelection extends SimpleModule double responseAckFrameBitrate @unit(bps) = default(-1bps); double responseBlockAckFrameBitrate @unit(bps) = default(-1bps); + // -1 selects the standard-derived automatic response. A configured HT value is a deliberate + // model override translated only to HT-mixed while preserving the resolved configured mode's + // MCS, bandwidth, NSS, and GI; it can therefore bypass IEEE 802.11-2024 + // 10.6.6.5.3/10.6.6.5.7 response constraints. double responseCtsFrameBitrate @unit(bps) = default(-1bps); double dataFrameBitrate @unit(bps) = default(-1bps); // Fastest diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned index b60bba95a51..ae26102f441 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned @@ -23,6 +23,10 @@ simple RateSelection extends SimpleModule like IRateSelection double multicastFrameBitrate @unit(bps) = default(-1bps); double responseAckFrameBitrate @unit(bps) = default(-1bps); + // -1 selects the standard-derived automatic response. A configured HT value is a deliberate + // model override translated only to HT-mixed while preserving the resolved configured mode's + // MCS, bandwidth, NSS, and GI; it can therefore bypass IEEE 802.11-2024 + // 10.6.6.5.3/10.6.6.5.7 response constraints. double responseCtsFrameBitrate @unit(bps) = default(-1bps); double dataFrameBitrate @unit(bps) = default(-1bps); // Fastest diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc index 93b51433c5b..46efbd37177 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc @@ -108,23 +108,25 @@ static std::vector createHtSupportedEntries(Ieee80211Ht if (preambleFormat == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD) { auto mixedEntries = createHtEntries(Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED); result.insert(result.end(), mixedEntries.begin(), mixedEntries.end()); + // Supplement the Greenfield profile with the HT-mixed and mandatory + // Clause 16/18 capabilities required by IEEE 802.11-2024 19.1.1 and + // 19.1.4. The legacy mixed profile intentionally remains selectable-only + // to preserve its established rate-selection and mode-membership contract. + result.insert(result.end(), { + { true, &Ieee80211DsssCompliantModes::dsssMode1Mbps }, + { true, &Ieee80211DsssCompliantModes::dsssMode2Mbps }, + { true, &Ieee80211HrDsssCompliantModes::hrDsssMode5_5MbpsCckLongPreamble }, + { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode9Mbps }, + { true, &Ieee80211HrDsssCompliantModes::hrDsssMode11MbpsCckLongPreamble }, + { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode12Mbps }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode18Mbps }, + { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode24Mbps }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode36Mbps }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode48Mbps }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode54Mbps }, + }); } - // IEEE 802.11-2024 19.1.1 and 19.1.4 require a 2.4 GHz HT STA to support - // the mandatory Clause 16/18 rates and the non-HT and HT-mixed formats. - result.insert(result.end(), { - { true, &Ieee80211DsssCompliantModes::dsssMode1Mbps }, - { true, &Ieee80211DsssCompliantModes::dsssMode2Mbps }, - { true, &Ieee80211HrDsssCompliantModes::hrDsssMode5_5MbpsCckLongPreamble }, - { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps }, - { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode9Mbps }, - { true, &Ieee80211HrDsssCompliantModes::hrDsssMode11MbpsCckLongPreamble }, - { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode12Mbps }, - { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode18Mbps }, - { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode24Mbps }, - { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode36Mbps }, - { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode48Mbps }, - { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode54Mbps }, - }); return result; } @@ -515,6 +517,7 @@ Ieee80211ModeSet::Ieee80211ModeSet(const char *name, const std::vector en entries(entries), supportedEntries(supportedEntries.empty() ? entries : supportedEntries), controlResponseModes(createControlResponseModes(supportedEntries.empty() ? entries : supportedEntries)), + htMixedControlResponseModes(createHtMixedControlResponseModes(supportedEntries.empty() ? entries : supportedEntries)), nonHtControlResponseEntries(createNonHtControlResponseEntries(supportedEntries.empty() ? entries : supportedEntries)) { std::vector *nonConstEntries = const_cast *>(&this->entries); @@ -591,6 +594,30 @@ std::map Ieee80211ModeSet::creat return result; } +std::map Ieee80211ModeSet::createHtMixedControlResponseModes(const std::vector& supportedEntries) +{ + std::map result; + for (const auto& sourceEntry : supportedEntries) { + auto source = dynamic_cast(sourceEntry.mode); + if (source == nullptr) + continue; + for (const auto& candidateEntry : supportedEntries) { + auto candidate = dynamic_cast(candidateEntry.mode); + if (candidate != nullptr && candidate->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED && + candidate->getCenterFrequencyMode() == source->getCenterFrequencyMode() && + candidate->getDataMode()->getMcsIndex() == source->getDataMode()->getMcsIndex() && + candidate->getDataMode()->getBandwidth() == source->getDataMode()->getBandwidth() && + candidate->getDataMode()->getNumberOfSpatialStreams() == source->getDataMode()->getNumberOfSpatialStreams() && + candidate->getDataMode()->getGuardIntervalType() == source->getDataMode()->getGuardIntervalType()) + { + result.emplace(sourceEntry.mode, candidateEntry.mode); + break; + } + } + } + return result; +} + std::vector Ieee80211ModeSet::createNonHtControlResponseEntries(const std::vector& supportedEntries) { std::vector result; @@ -753,13 +780,15 @@ const IIeee80211Mode *Ieee80211ModeSet::getControlResponseMode(const IIeee80211M auto primaryMode = it->second; if (configuredMode == nullptr) return primaryMode; + // A configured HT response is a deliberate model extension beyond the + // automatic response constraints in IEEE 802.11-2024 10.6.6.5.3 and + // 10.6.6.5.7. Translate only its preamble to HT-mixed, preserving the + // resolved configured mode's MCS, bandwidth, NSS, GI, and band. if (!supportsMode(configuredMode)) throw cRuntimeError("Configured control response mode is not supported by operation mode %s: %s", getName(), configuredMode->getName()); - auto configuredIt = controlResponseModes.find(configuredMode); - if (configuredIt == controlResponseModes.end()) + auto configuredIt = htMixedControlResponseModes.find(configuredMode); + if (configuredIt == htMixedControlResponseModes.end()) throw cRuntimeError("An HT RTS requires an HT-mixed CTS response, configured mode is non-HT: %s", configuredMode->getName()); - if (configuredIt->second != primaryMode) - throw cRuntimeError("Configured CTS mode differs from the primary HT control response MCS for %s; alternate MCS duration selection is not modeled", mode->getName()); return configuredIt->second; } @@ -767,7 +796,7 @@ const IIeee80211Mode *Ieee80211ModeSet::getMandatoryControlResponseMode(const II { if (!supportsMode(mode)) throw cRuntimeError("Control response mode is not supported by operation mode %s: %s", getName(), mode->getName()); - if (controlResponseModes.find(mode) != controlResponseModes.end() || !containsMode(mode)) + if (!nonHtControlResponseEntries.empty() && (controlResponseModes.find(mode) != controlResponseModes.end() || !containsMode(mode))) return getNonHtControlResponseMode(mode); if (getIsMandatory(mode)) return mode; @@ -780,6 +809,17 @@ const IIeee80211Mode *Ieee80211ModeSet::getNonHtControlResponseMode(const IIeee8 { if (!supportsMode(mode)) throw cRuntimeError("Control response mode is not supported by operation mode %s: %s", getName(), mode->getName()); + if (nonHtControlResponseEntries.empty()) { + if (!containsMode(mode)) + throw cRuntimeError("No non-HT control response mode for %s", mode->getName()); + if (!mandatory) + return mode; + if (getIsMandatory(mode)) + return mode; + if (auto slowerMode = getSlowerMandatoryMode(mode)) + return slowerMode; + throw cRuntimeError("No mandatory control response mode for %s", mode->getName()); + } if (controlResponseModes.find(mode) == controlResponseModes.end()) { // VHT response-format selection remains unchanged; this fallback is HT-scoped. if (dynamic_cast(mode) != nullptr) diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h index b379caac0ea..88eefed78f5 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h @@ -33,6 +33,7 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject // Entries are selectable modes; supportedEntries also contains immutable PHY capabilities needed for mandatory control responses. const std::vector supportedEntries; const std::map controlResponseModes; + const std::map htMixedControlResponseModes; const std::vector nonHtControlResponseEntries; public: @@ -42,6 +43,7 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject int findModeIndex(const IIeee80211Mode *mode) const; int getModeIndex(const IIeee80211Mode *mode) const; static std::map createControlResponseModes(const std::vector& supportedEntries); + static std::map createHtMixedControlResponseModes(const std::vector& supportedEntries); static std::vector createNonHtControlResponseEntries(const std::vector& supportedEntries); public: @@ -76,12 +78,12 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject const IIeee80211Mode *getSlowerMandatoryMode(const IIeee80211Mode *mode) const; const IIeee80211Mode *getFasterMandatoryMode(const IIeee80211Mode *mode) const; - // Selects the primary response mode for an eliciting mode. configuredMode, - // when present, is constrained by the eliciting PPDU's response format/MCS. + // Automatic responses follow IEEE 802.11-2024 10.6.6.5.3/10.6.6.5.7. A + // configured HT mode is a deliberate override translated only to HT-mixed. const IIeee80211Mode *getControlResponseMode(const IIeee80211Mode *mode, const IIeee80211Mode *configuredMode = nullptr) const; const IIeee80211Mode *getMandatoryControlResponseMode(const IIeee80211Mode *mode) const; - // HT modes are always converted to a mandatory non-HT response. For a - // non-HT mode, mandatory=false preserves an explicitly selected rate. + // Greenfield HT modes are converted to non-HT responses; mixed-profile HT + // modes retain the selected HT format. mandatory=false preserves the rate. const IIeee80211Mode *getNonHtControlResponseMode(const IIeee80211Mode *mode, bool mandatory = true) const; static const Ieee80211ModeSet *findModeSet(const char *mode); diff --git a/tests/module/Ieee80211ConfiguredResponseRateSelection.test b/tests/module/Ieee80211ConfiguredResponseRateSelection.test index 1d5bd785cfd..8fd83776c35 100644 --- a/tests/module/Ieee80211ConfiguredResponseRateSelection.test +++ b/tests/module/Ieee80211ConfiguredResponseRateSelection.test @@ -1,8 +1,10 @@ %description: -Checks through the public rate-selection API that configured Greenfield response rates use -non-HT ACK/BlockAck and HT-mixed CTS formats. +Checks through the public rate-selection API that Greenfield ACK/BlockAck responses use +non-HT rates, mixed responses retain HT rates, and configured HT CTS rates use exact +HT-mixed counterparts. %file: Test.cc +#include #include #include "inet/common/InitStages.h" @@ -26,7 +28,7 @@ static void assertHtMixedResponse(const char *name, const IIeee80211Mode *mode) { auto htMode = dynamic_cast(mode); if (htMode == nullptr || htMode->getPreambleMode()->getPreambleFormat() != Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED || htMode->getDataMode()->getNetBitrate() != Mbps(6.5)) - throw cRuntimeError("%s did not return HT-mixed 6.5 Mbps", name); + throw cRuntimeError("%s did not return HT-mixed 6.5 Mbps (actual mode: %s, bitrate: %g)", name, mode->getName(), mode->getDataMode()->getNetBitrate().get()); } static void assertNonHtResponse(const char *name, const IIeee80211Mode *mode) @@ -48,6 +50,10 @@ class ConfiguredResponseProbe : public cSimpleModule auto dcfRateSelection = check_and_cast(getSimulation()->getModuleByPath("TestConfiguredResponses.host[0].wlan[0].mac.dcf.rateSelection")); auto qosRateSelection = check_and_cast(getSimulation()->getModuleByPath("TestConfiguredResponses.host[0].wlan[0].mac.hcf.rateSelection")); auto dynamicQosRateSelection = check_and_cast(getSimulation()->getModuleByPath("TestConfiguredResponses.host[1].wlan[0].mac.hcf.rateSelection")); + auto dynamicWlan = check_and_cast(getSimulation()->getModuleByPath("TestConfiguredResponses.host[1].wlan[0]")); + auto dynamicMac = check_and_cast(getSimulation()->getModuleByPath("TestConfiguredResponses.host[1].wlan[0].mac")); + if (strcmp(dynamicWlan->par("opMode"), "n(mixed-2.4Ghz)") != 0 || strcmp(dynamicMac->par("modeSet"), "n(mixed-2.4Ghz)") != 0) + throw cRuntimeError("Dynamic response probe is not using the mixed profile (wlan: %s, mac: %s)", dynamicWlan->par("opMode").stringValue(), dynamicMac->par("modeSet").stringValue()); if (double(dcfRateSelection->par("responseAckFrameBitrate")) != 6.5e6 || double(qosRateSelection->par("responseAckFrameBitrate")) != 6.5e6) @@ -94,14 +100,9 @@ class ConfiguredResponseProbe : public cSimpleModule auto basicBlockAckReq = makeShared(); assertNonHtResponse("QoS Basic BlockAck", qosRateSelection->computeResponseBlockAckFrameMode(&packet, basicBlockAckReq)); - assertNonHtResponse("Dynamic mixed QoS ACK", dynamicQosRateSelection->computeResponseAckFrameMode(&mixedPacket, nullptr)); + assertHtMixedResponse("Dynamic mixed QoS ACK", dynamicQosRateSelection->computeResponseAckFrameMode(&mixedPacket, nullptr)); assertHtMixedResponse("Dynamic mixed QoS CTS", dynamicQosRateSelection->computeResponseCtsFrameMode(&mixedPacket, nullptr)); - assertNonHtResponse("Dynamic mixed QoS Basic BlockAck", dynamicQosRateSelection->computeResponseBlockAckFrameMode(&mixedPacket, basicBlockAckReq)); - Packet optionalNonHtPacket("receivedOptionalNonHtFrame"); - optionalNonHtPacket.addTag()->setMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode9Mbps); - assertNonHtResponse("Dynamic QoS ACK", dynamicQosRateSelection->computeResponseAckFrameMode(&optionalNonHtPacket, nullptr)); - assertNonHtResponse("Dynamic QoS CTS", dynamicQosRateSelection->computeResponseCtsFrameMode(&optionalNonHtPacket, nullptr)); - ASSERT(dynamicQosRateSelection->computeResponseBlockAckFrameMode(&optionalNonHtPacket, basicBlockAckReq) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode9Mbps); + assertHtMixedResponse("Dynamic mixed QoS Basic BlockAck", dynamicQosRateSelection->computeResponseBlockAckFrameMode(&mixedPacket, basicBlockAckReq)); auto optionalHtMode = Ieee80211HtCompliantModes::getCompliantMode( &Ieee80211HtmcsTable::htMcs8BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, @@ -110,6 +111,15 @@ class ConfiguredResponseProbe : public cSimpleModule Packet optionalHtPacket("receivedOptionalHtFrame"); optionalHtPacket.addTag()->setMode(optionalHtMode); assertHtMixedResponse("Configured CTS optional HT MCS", qosRateSelection->computeResponseCtsFrameMode(&optionalHtPacket, nullptr)); + auto greenfieldMcs7 = Ieee80211HtCompliantModes::getCompliantMode( + &Ieee80211HtmcsTable::htMcs7BW20MHz, + Ieee80211HtMode::BAND_2_4GHZ, + Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, + Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG); + Packet greenfieldMcs7Packet("receivedGreenfieldMcs7Frame"); + greenfieldMcs7Packet.addTag()->setMode(greenfieldMcs7); + assertHtMixedResponse("Configured DCF CTS MCS 0 override for MCS 7", dcfRateSelection->computeResponseCtsFrameMode(&greenfieldMcs7Packet, nullptr)); + assertHtMixedResponse("Configured CTS MCS 0 override for MCS 7", qosRateSelection->computeResponseCtsFrameMode(&greenfieldMcs7Packet, nullptr)); // Exercise the actual signal path with a compatible mode-set transition. // The configured data cache must bind to the exact profile object while @@ -120,9 +130,9 @@ class ConfiguredResponseProbe : public cSimpleModule ASSERT(qosRateSelection->computeMode(&dataPacket, dataHeader, nullptr) == mixedSourceMode); assertHtMixedResponse("Rebound DCF CTS mixed", dcfRateSelection->computeResponseCtsFrameMode(&mixedPacket, nullptr)); assertHtMixedResponse("Rebound QoS CTS mixed", qosRateSelection->computeResponseCtsFrameMode(&mixedPacket, nullptr)); - assertNonHtResponse("Rebound DCF ACK mixed", dcfRateSelection->computeResponseAckFrameMode(nullptr, nullptr)); - assertNonHtResponse("Rebound QoS ACK mixed", qosRateSelection->computeResponseAckFrameMode(nullptr, nullptr)); - assertNonHtResponse("Rebound QoS BlockAck mixed", qosRateSelection->computeResponseBlockAckFrameMode(nullptr, basicBlockAckReq)); + assertHtMixedResponse("Rebound DCF ACK mixed", dcfRateSelection->computeResponseAckFrameMode(nullptr, nullptr)); + assertHtMixedResponse("Rebound QoS ACK mixed", qosRateSelection->computeResponseAckFrameMode(nullptr, nullptr)); + assertHtMixedResponse("Rebound QoS BlockAck mixed", qosRateSelection->computeResponseBlockAckFrameMode(nullptr, basicBlockAckReq)); wlan->emit(modesetChangedSignal, const_cast(Ieee80211ModeSet::getModeSet("n(greenfield-2.4Ghz)"))); ASSERT(dcfRateSelection->computeMode(&dataPacket, dataHeader) == sourceMode); ASSERT(qosRateSelection->computeMode(&dataPacket, dataHeader, nullptr) == sourceMode); @@ -132,7 +142,7 @@ class ConfiguredResponseProbe : public cSimpleModule assertNonHtResponse("Rebound QoS ACK Greenfield", qosRateSelection->computeResponseAckFrameMode(nullptr, nullptr)); assertNonHtResponse("Rebound QoS BlockAck Greenfield", qosRateSelection->computeResponseBlockAckFrameMode(nullptr, basicBlockAckReq)); - std::cout << "Configured and dynamic mixed/GF ACK/Basic BlockAck use non-HT 6 Mbps; HT CTS uses bounded HT-mixed MCS 0 and requires an eliciting mode tag; dynamic non-HT Basic BlockAck retains ERP 9 Mbps.\n"; + std::cout << "Mixed ACK/Basic BlockAck remain HT while Greenfield responses use non-HT 6 Mbps; configured HT CTS uses the exact HT-mixed MCS override and requires an eliciting mode tag.\n"; } }; @@ -173,8 +183,8 @@ seed-set = 0 abstract = false network = TestConfiguredResponses sim-time-limit = 1s -**.opMode = "n(greenfield-2.4Ghz)" *.host[1].wlan[*].opMode = "n(mixed-2.4Ghz)" +**.opMode = "n(greenfield-2.4Ghz)" **.qosStation = true *.host[1].wlan[0].mac.hcf.rateSelection.responseAckFrameBitrate = -1bps *.host[1].wlan[0].mac.hcf.rateSelection.responseCtsFrameBitrate = -1bps @@ -194,4 +204,4 @@ sim-time-limit = 1s %extraargs: -c ConfiguredResponses %contains: stdout -Configured and dynamic mixed/GF ACK/Basic BlockAck use non-HT 6 Mbps; HT CTS uses bounded HT-mixed MCS 0 and requires an eliciting mode tag; dynamic non-HT Basic BlockAck retains ERP 9 Mbps. +Mixed ACK/Basic BlockAck remain HT while Greenfield responses use non-HT 6 Mbps; configured HT CTS uses the exact HT-mixed MCS override and requires an eliciting mode tag. diff --git a/tests/unit/Ieee80211HtGreenfield_1.test b/tests/unit/Ieee80211HtGreenfield_1.test index 06c11b640ce..c5db20daafb 100644 --- a/tests/unit/Ieee80211HtGreenfield_1.test +++ b/tests/unit/Ieee80211HtGreenfield_1.test @@ -94,10 +94,10 @@ ASSERT(mixedProfile->supportsMode(mixedMode)); ASSERT(greenfieldProfile->supportsMode(greenfieldMode)); ASSERT(greenfieldProfile->supportsMode(mixedMode)); ASSERT(!mixedProfile->supportsMode(greenfieldMode)); -ASSERT(mixedProfile->supportsMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps)); +ASSERT(!mixedProfile->supportsMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps)); ASSERT(!mixedProfile->containsMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps)); ASSERT(greenfieldProfile->supportsMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps)); -ASSERT(mixedProfile->getNonHtControlResponseMode(mixedMode) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); +ASSERT(mixedProfile->getNonHtControlResponseMode(mixedMode) == mixedMode); ASSERT(greenfieldProfile->getNonHtControlResponseMode(greenfieldMode) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); ASSERT(greenfieldProfile->getNonHtControlResponseMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode9Mbps) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); ASSERT(mixedProfile->getControlResponseMode(mixedMode) == mixedMode); @@ -131,12 +131,24 @@ auto greenfieldMcs15 = Ieee80211HtCompliantModes::getCompliantMode( Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT); ASSERT(greenfieldProfile->getControlResponseMode(greenfieldMcs7) == mixedMcs7); ASSERT(greenfieldProfile->getControlResponseMode(greenfieldMcs15) == mixedMcs7); -try { - greenfieldProfile->getControlResponseMode(greenfieldMcs7, greenfieldMode); - ASSERT(false); -} -catch (const cRuntimeError&) { -} +ASSERT(greenfieldProfile->getControlResponseMode(greenfieldMcs7, greenfieldMode) == mixedMode); + +auto mixedMcs15 = Ieee80211HtCompliantModes::getCompliantMode( + &Ieee80211HtmcsTable::htMcs15BW20MHz, + Ieee80211HtMode::BAND_2_4GHZ, + Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, + Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT); +auto configuredMixedResponse = dynamic_cast(greenfieldProfile->getControlResponseMode(greenfieldMcs7, greenfieldMcs15)); +ASSERT(configuredMixedResponse == mixedMcs15); +ASSERT(configuredMixedResponse->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED); +ASSERT(configuredMixedResponse->getCenterFrequencyMode() == greenfieldMcs15->getCenterFrequencyMode()); +ASSERT(configuredMixedResponse->getDataMode()->getMcsIndex() == greenfieldMcs15->getDataMode()->getMcsIndex()); +ASSERT(configuredMixedResponse->getDataMode()->getBandwidth() == greenfieldMcs15->getDataMode()->getBandwidth()); +ASSERT(configuredMixedResponse->getDataMode()->getNumberOfSpatialStreams() == greenfieldMcs15->getDataMode()->getNumberOfSpatialStreams()); +ASSERT(configuredMixedResponse->getDataMode()->getGuardIntervalType() == greenfieldMcs15->getDataMode()->getGuardIntervalType()); +ASSERT(mixedProfile->getMandatoryControlResponseMode(mixedMcs7) == mixedMcs7); +ASSERT(mixedProfile->getMandatoryControlResponseMode(mixedMcs15) == mixedMcs7); +ASSERT(mixedProfile->getNonHtControlResponseMode(mixedMcs15, false) == mixedMcs15); auto greenfieldMcs8Bw40 = Ieee80211HtCompliantModes::getCompliantMode( &Ieee80211HtmcsTable::htMcs8BW40MHz, @@ -162,6 +174,14 @@ ASSERT(transmitter->getTestMode() == greenfieldMode); Packet supportedLegacyPacket("supportedLegacy"); supportedLegacyPacket.addTag()->setMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); ASSERT(transmitter->getTestTransmissionMode(&supportedLegacyPacket) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); +transmitter->setModeSet(mixedProfile); +try { + transmitter->getTestTransmissionMode(&supportedLegacyPacket); + ASSERT(false); +} +catch (const cRuntimeError&) { +} +transmitter->setModeSet(greenfieldProfile); try { transmitter->setMode(mixedMode); ASSERT(false); From 5209ca38c913d3acc1ff6fc0330f451ba70584c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Wed, 19 Aug 2026 19:17:47 +0200 Subject: [PATCH 08/10] Fix HT response mode initialization invariants Fail immediately during link-layer initialization when RateSelection has not received a mode set, while preserving the null-safe runtime refresh path. Document the bounded mandatory-index fallback used when no Basic HT-MCS Set is modeled, including the separate channel-width filtering and resulting 40 MHz candidate behavior. Add an expected-failure module test for the missing mode-set invariant and clarify the existing 40 MHz MCS 8 to MCS 0 unit-test expectation. Validated with the debug build, focused HT Greenfield and configured-response tests, the new negative initialization test, and related 802.11 fingerprints. --- .../mac/rateselection/RateSelection.cc | 2 + .../ieee80211/mode/Ieee80211ModeSet.cc | 12 ++++- ...ateSelectionNullModeSetInitialization.test | 53 +++++++++++++++++++ tests/unit/Ieee80211HtGreenfield_1.test | 3 ++ 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 tests/module/RateSelectionNullModeSetInitialization.test diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc index dfad89e2be1..88efbd66b08 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc @@ -29,6 +29,8 @@ void RateSelection::initialize(int stage) } else if (stage == INITSTAGE_LINK_LAYER) { dataOrMgmtRateControl = dynamic_cast(findModuleByPath(par("rateControlModule"))); + if (modeSet == nullptr) + throw cRuntimeError("RateSelection module %s has no mode set at link-layer initialization", getFullPath().c_str()); resolveConfiguredModes(modeSet); // WATCH(dataOrMgmtRateControl); diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc index 46efbd37177..6ea5a27e188 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc @@ -545,6 +545,15 @@ int Ieee80211ModeSet::findModeIndex(const IIeee80211Mode *mode) const std::map Ieee80211ModeSet::createControlResponseModes(const std::vector& supportedEntries) { std::map result; + // IEEE 802.11-2024 Table 9-230 defines the Basic HT-MCS Set as a BSS- + // configured bitmap of MCS indexes. It is not modelled here, so use the + // mandatory entries (currently the 20 MHz entries) as a bounded fallback + // for the candidate indexes. Clause 10.6.6.5.3 selects CH_BANDWIDTH + // separately, and the candidate filter below then keeps only modes at the + // source bandwidth. Consequently, optional 40 MHz MCS 0..7 are treated as + // candidate MCSs. A modelled Basic HT-MCS Set would replace this mandatory- + // index fallback with the BSS-configured indexes; bandwidth filtering would + // remain a separate step. std::set mandatoryHtMcsIndexes; for (const auto& entry : supportedEntries) { auto mode = dynamic_cast(entry.mode); @@ -567,7 +576,8 @@ std::map Ieee80211ModeSet::creat candidates.push_back(candidate); } // IEEE 802.11-2024 10.6.6.5.3: with no Basic HT-MCS Set modelled, - // CandidateMCSSet is the mandatory HT MCSs. After the MCS-index bound, + // CandidateMCSSet uses the mandatory-index fallback. After the + // bandwidth and MCS-index bounds, // retain the highest NSS not exceeding the received NSS, then select the // highest indexed MCS whose per-stream modulation and coding rate do not // exceed those of the received MCS. The modelled MCS 0..31 are EQM. diff --git a/tests/module/RateSelectionNullModeSetInitialization.test b/tests/module/RateSelectionNullModeSetInitialization.test new file mode 100644 index 00000000000..24ede0cf611 --- /dev/null +++ b/tests/module/RateSelectionNullModeSetInitialization.test @@ -0,0 +1,53 @@ +%description: +RateSelection must reject reaching link-layer initialization without a mode set. + +%file: Test.cc +#include "inet/common/InitStages.h" +#include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h" + +using namespace inet; +using namespace inet::ieee80211; + +namespace RateSelectionNullModeSetInitialization { + +class NullModeSetRateSelection : public RateSelection +{ + protected: + virtual void initialize(int stage) override + { + if (stage == INITSTAGE_LINK_LAYER) + RateSelection::initialize(stage); + } +}; + +Define_Module(NullModeSetRateSelection); + +} // namespace RateSelectionNullModeSetInitialization + +%file: test.ned +import inet.linklayer.ieee80211.mac.rateselection.RateSelection; + +simple NullModeSetRateSelection extends RateSelection +{ + parameters: + @class(NullModeSetRateSelection); + rateControlModule = ""; +} + +network TestRateSelectionNullModeSetInitialization +{ + submodules: + rateSelection: NullModeSetRateSelection; +} + +%inifile: omnetpp.ini +[General] +network = TestRateSelectionNullModeSetInitialization +ned-path = .;../../../../src;../../lib +cmdenv-express-mode = true +record-vector-results = false + +%exitcode: 1 + +%contains-regex: stderr +RateSelection module TestRateSelectionNullModeSetInitialization\.rateSelection has no mode set at link-layer initialization diff --git a/tests/unit/Ieee80211HtGreenfield_1.test b/tests/unit/Ieee80211HtGreenfield_1.test index c5db20daafb..722665149cf 100644 --- a/tests/unit/Ieee80211HtGreenfield_1.test +++ b/tests/unit/Ieee80211HtGreenfield_1.test @@ -160,6 +160,9 @@ auto mixedMcs0Bw40 = Ieee80211HtCompliantModes::getCompliantMode( Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT); +// The bounded no-Basic-HT-MCS-Set approximation reuses mandatory 20 MHz MCS +// indexes for candidates at the source bandwidth, so optional 40 MHz MCS 8 +// maps to 40 MHz MCS 0 (without changing the established response behavior). ASSERT(greenfieldProfile->getControlResponseMode(greenfieldMcs8Bw40) == mixedMcs0Bw40); auto transmitter = new TestIeee80211Transmitter; From b6a3e0726624a0073d72cdc417b5af8ea7bd1220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Thu, 20 Aug 2026 14:02:27 +0200 Subject: [PATCH 09/10] 802.11: align HT legacy response capabilities --- .../mac/rateselection/QosRateSelection.cc | 2 + .../mac/rateselection/QosRateSelection.ned | 7 ++- .../mac/rateselection/RateSelection.ned | 4 +- .../ieee80211/mode/Ieee80211ModeSet.cc | 39 +++++++------ .../ieee80211/mode/Ieee80211ModeSet.h | 4 +- ...e80211ConfiguredResponseRateSelection.test | 19 +++--- ...ateSelectionNullModeSetInitialization.test | 53 +++++++++++++++++ tests/unit/Ieee80211HtGreenfield_1.test | 58 ++++++++++++++++--- 8 files changed, 145 insertions(+), 41 deletions(-) create mode 100644 tests/module/QosRateSelectionNullModeSetInitialization.test diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc index 621b8145ccf..b82333b8e42 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc @@ -23,6 +23,8 @@ void QosRateSelection::initialize(int stage) ModeSetListener::initialize(stage); if (stage == INITSTAGE_LINK_LAYER) { dataOrMgmtRateControl = dynamic_cast(findModuleByPath(par("rateControlModule"))); + if (modeSet == nullptr) + throw cRuntimeError("QosRateSelection module %s has no mode set at link-layer initialization", getFullPath().c_str()); resolveConfiguredModes(modeSet); } } diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned index 72719d78999..39ccdbca8eb 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned @@ -26,7 +26,13 @@ simple QosRateSelection extends SimpleModule double multicastFrameBitrate @unit(bps) = default(-1bps); + // -1 selects the standard-derived automatic response. In a 2.4 GHz HT profile, + // a configured HT bitrate is an upper bound mapped to the highest mandatory + // non-HT rate at or below it; it does not select an exact HT ACK PPDU. double responseAckFrameBitrate @unit(bps) = default(-1bps); + // -1 selects the standard-derived automatic response. In a 2.4 GHz HT profile, + // a configured HT bitrate is an upper bound mapped to the highest mandatory + // non-HT rate at or below it; it does not select an exact HT BlockAck PPDU. double responseBlockAckFrameBitrate @unit(bps) = default(-1bps); // -1 selects the standard-derived automatic response. A configured HT value is a deliberate // model override translated only to HT-mixed while preserving the resolved configured mode's @@ -42,4 +48,3 @@ simple QosRateSelection extends SimpleModule double controlFrameBitrate @unit(bps) = default(-1bps); @display("i=block/cogwheel"); } - diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned index ae26102f441..bec41fd696b 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned @@ -22,6 +22,9 @@ simple RateSelection extends SimpleModule like IRateSelection double multicastFrameBitrate @unit(bps) = default(-1bps); + // -1 selects the standard-derived automatic response. In a 2.4 GHz HT profile, + // a configured HT bitrate is an upper bound mapped to the highest mandatory + // non-HT rate at or below it; it does not select an exact HT ACK PPDU. double responseAckFrameBitrate @unit(bps) = default(-1bps); // -1 selects the standard-derived automatic response. A configured HT value is a deliberate // model override translated only to HT-mixed while preserving the resolved configured mode's @@ -37,4 +40,3 @@ simple RateSelection extends SimpleModule like IRateSelection double controlFrameBitrate @unit(bps) = default(-1bps); @display("i=block/cogwheel"); } - diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc index 6ea5a27e188..444faadd653 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc @@ -108,25 +108,28 @@ static std::vector createHtSupportedEntries(Ieee80211Ht if (preambleFormat == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD) { auto mixedEntries = createHtEntries(Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED); result.insert(result.end(), mixedEntries.begin(), mixedEntries.end()); - // Supplement the Greenfield profile with the HT-mixed and mandatory - // Clause 16/18 capabilities required by IEEE 802.11-2024 19.1.1 and - // 19.1.4. The legacy mixed profile intentionally remains selectable-only - // to preserve its established rate-selection and mode-membership contract. - result.insert(result.end(), { - { true, &Ieee80211DsssCompliantModes::dsssMode1Mbps }, - { true, &Ieee80211DsssCompliantModes::dsssMode2Mbps }, - { true, &Ieee80211HrDsssCompliantModes::hrDsssMode5_5MbpsCckLongPreamble }, - { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps }, - { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode9Mbps }, - { true, &Ieee80211HrDsssCompliantModes::hrDsssMode11MbpsCckLongPreamble }, - { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode12Mbps }, - { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode18Mbps }, - { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode24Mbps }, - { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode36Mbps }, - { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode48Mbps }, - { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode54Mbps }, - }); } + // Every 2.4 GHz HT STA supports the mandatory Clause 16/18 modes, and a + // Greenfield STA additionally supports HT-mixed PPDUs (IEEE 802.11-2024 + // 19.1.1 and 19.1.4). These are supplementary capabilities rather than + // selectable operating modes, so they are kept out of createHtEntries(). + result.insert(result.end(), { + { true, &Ieee80211DsssCompliantModes::dsssMode1Mbps }, + { true, &Ieee80211DsssCompliantModes::dsssMode2Mbps }, + { true, &Ieee80211HrDsssCompliantModes::hrDsssMode2MbpsShortPreamble }, + { true, &Ieee80211HrDsssCompliantModes::hrDsssMode5_5MbpsCckLongPreamble }, + { true, &Ieee80211HrDsssCompliantModes::hrDsssMode5_5MbpsCckShortPreamble }, + { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode9Mbps }, + { true, &Ieee80211HrDsssCompliantModes::hrDsssMode11MbpsCckLongPreamble }, + { true, &Ieee80211HrDsssCompliantModes::hrDsssMode11MbpsCckShortPreamble }, + { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode12Mbps }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode18Mbps }, + { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode24Mbps }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode36Mbps }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode48Mbps }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode54Mbps }, + }); return result; } diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h index 88eefed78f5..ed1cbf2bf74 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h @@ -82,8 +82,8 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject // configured HT mode is a deliberate override translated only to HT-mixed. const IIeee80211Mode *getControlResponseMode(const IIeee80211Mode *mode, const IIeee80211Mode *configuredMode = nullptr) const; const IIeee80211Mode *getMandatoryControlResponseMode(const IIeee80211Mode *mode) const; - // Greenfield HT modes are converted to non-HT responses; mixed-profile HT - // modes retain the selected HT format. mandatory=false preserves the rate. + // 2.4 GHz HT modes are converted to non-HT responses. With mandatory=false, + // the input bitrate is used as a ceiling for selecting that non-HT mode. const IIeee80211Mode *getNonHtControlResponseMode(const IIeee80211Mode *mode, bool mandatory = true) const; static const Ieee80211ModeSet *findModeSet(const char *mode); diff --git a/tests/module/Ieee80211ConfiguredResponseRateSelection.test b/tests/module/Ieee80211ConfiguredResponseRateSelection.test index 8fd83776c35..c858fe791d5 100644 --- a/tests/module/Ieee80211ConfiguredResponseRateSelection.test +++ b/tests/module/Ieee80211ConfiguredResponseRateSelection.test @@ -1,7 +1,6 @@ %description: -Checks through the public rate-selection API that Greenfield ACK/BlockAck responses use -non-HT rates, mixed responses retain HT rates, and configured HT CTS rates use exact -HT-mixed counterparts. +Checks through the public rate-selection API that 2.4 GHz HT ACK/BlockAck responses use +non-HT rates and configured HT CTS rates use exact HT-mixed counterparts. %file: Test.cc #include @@ -100,9 +99,9 @@ class ConfiguredResponseProbe : public cSimpleModule auto basicBlockAckReq = makeShared(); assertNonHtResponse("QoS Basic BlockAck", qosRateSelection->computeResponseBlockAckFrameMode(&packet, basicBlockAckReq)); - assertHtMixedResponse("Dynamic mixed QoS ACK", dynamicQosRateSelection->computeResponseAckFrameMode(&mixedPacket, nullptr)); + assertNonHtResponse("Dynamic mixed QoS ACK", dynamicQosRateSelection->computeResponseAckFrameMode(&mixedPacket, nullptr)); assertHtMixedResponse("Dynamic mixed QoS CTS", dynamicQosRateSelection->computeResponseCtsFrameMode(&mixedPacket, nullptr)); - assertHtMixedResponse("Dynamic mixed QoS Basic BlockAck", dynamicQosRateSelection->computeResponseBlockAckFrameMode(&mixedPacket, basicBlockAckReq)); + assertNonHtResponse("Dynamic mixed QoS Basic BlockAck", dynamicQosRateSelection->computeResponseBlockAckFrameMode(&mixedPacket, basicBlockAckReq)); auto optionalHtMode = Ieee80211HtCompliantModes::getCompliantMode( &Ieee80211HtmcsTable::htMcs8BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, @@ -130,9 +129,9 @@ class ConfiguredResponseProbe : public cSimpleModule ASSERT(qosRateSelection->computeMode(&dataPacket, dataHeader, nullptr) == mixedSourceMode); assertHtMixedResponse("Rebound DCF CTS mixed", dcfRateSelection->computeResponseCtsFrameMode(&mixedPacket, nullptr)); assertHtMixedResponse("Rebound QoS CTS mixed", qosRateSelection->computeResponseCtsFrameMode(&mixedPacket, nullptr)); - assertHtMixedResponse("Rebound DCF ACK mixed", dcfRateSelection->computeResponseAckFrameMode(nullptr, nullptr)); - assertHtMixedResponse("Rebound QoS ACK mixed", qosRateSelection->computeResponseAckFrameMode(nullptr, nullptr)); - assertHtMixedResponse("Rebound QoS BlockAck mixed", qosRateSelection->computeResponseBlockAckFrameMode(nullptr, basicBlockAckReq)); + assertNonHtResponse("Rebound DCF ACK mixed", dcfRateSelection->computeResponseAckFrameMode(nullptr, nullptr)); + assertNonHtResponse("Rebound QoS ACK mixed", qosRateSelection->computeResponseAckFrameMode(nullptr, nullptr)); + assertNonHtResponse("Rebound QoS BlockAck mixed", qosRateSelection->computeResponseBlockAckFrameMode(nullptr, basicBlockAckReq)); wlan->emit(modesetChangedSignal, const_cast(Ieee80211ModeSet::getModeSet("n(greenfield-2.4Ghz)"))); ASSERT(dcfRateSelection->computeMode(&dataPacket, dataHeader) == sourceMode); ASSERT(qosRateSelection->computeMode(&dataPacket, dataHeader, nullptr) == sourceMode); @@ -142,7 +141,7 @@ class ConfiguredResponseProbe : public cSimpleModule assertNonHtResponse("Rebound QoS ACK Greenfield", qosRateSelection->computeResponseAckFrameMode(nullptr, nullptr)); assertNonHtResponse("Rebound QoS BlockAck Greenfield", qosRateSelection->computeResponseBlockAckFrameMode(nullptr, basicBlockAckReq)); - std::cout << "Mixed ACK/Basic BlockAck remain HT while Greenfield responses use non-HT 6 Mbps; configured HT CTS uses the exact HT-mixed MCS override and requires an eliciting mode tag.\n"; + std::cout << "Mixed and Greenfield ACK/Basic BlockAck use non-HT 6 Mbps; configured HT CTS uses the exact HT-mixed MCS override and requires an eliciting mode tag.\n"; } }; @@ -204,4 +203,4 @@ sim-time-limit = 1s %extraargs: -c ConfiguredResponses %contains: stdout -Mixed ACK/Basic BlockAck remain HT while Greenfield responses use non-HT 6 Mbps; configured HT CTS uses the exact HT-mixed MCS override and requires an eliciting mode tag. +Mixed and Greenfield ACK/Basic BlockAck use non-HT 6 Mbps; configured HT CTS uses the exact HT-mixed MCS override and requires an eliciting mode tag. diff --git a/tests/module/QosRateSelectionNullModeSetInitialization.test b/tests/module/QosRateSelectionNullModeSetInitialization.test new file mode 100644 index 00000000000..39c0e1573a2 --- /dev/null +++ b/tests/module/QosRateSelectionNullModeSetInitialization.test @@ -0,0 +1,53 @@ +%description: +QosRateSelection must reject reaching link-layer initialization without a mode set. + +%file: Test.cc +#include "inet/common/InitStages.h" +#include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" + +using namespace inet; +using namespace inet::ieee80211; + +namespace QosRateSelectionNullModeSetInitialization { + +class NullModeSetQosRateSelection : public QosRateSelection +{ + protected: + virtual void initialize(int stage) override + { + if (stage == INITSTAGE_LINK_LAYER) + QosRateSelection::initialize(stage); + } +}; + +Define_Module(NullModeSetQosRateSelection); + +} // namespace QosRateSelectionNullModeSetInitialization + +%file: test.ned +import inet.linklayer.ieee80211.mac.rateselection.QosRateSelection; + +simple NullModeSetQosRateSelection extends QosRateSelection +{ + parameters: + @class(NullModeSetQosRateSelection); + rateControlModule = ""; +} + +network TestQosRateSelectionNullModeSetInitialization +{ + submodules: + rateSelection: NullModeSetQosRateSelection; +} + +%inifile: omnetpp.ini +[General] +network = TestQosRateSelectionNullModeSetInitialization +ned-path = .;../../../../src;../../lib +cmdenv-express-mode = true +record-vector-results = false + +%exitcode: 1 + +%contains-regex: stderr +QosRateSelection module TestQosRateSelectionNullModeSetInitialization\.rateSelection has no mode set at link-layer initialization diff --git a/tests/unit/Ieee80211HtGreenfield_1.test b/tests/unit/Ieee80211HtGreenfield_1.test index 722665149cf..e705a999a90 100644 --- a/tests/unit/Ieee80211HtGreenfield_1.test +++ b/tests/unit/Ieee80211HtGreenfield_1.test @@ -1,13 +1,16 @@ %description: Checks that the 802.11n mixed and Greenfield mode profiles preserve distinct -preamble formats and timing, retain strict membership and supplementary Greenfield control-response support, including legacy fallback rates. Also checks VHT cache identity. +preamble formats and timing, retain strict membership, and support the legacy +2.4 GHz HT capabilities used for non-HT control responses. Also checks VHT cache identity. %includes: #include #include "inet/common/packet/Packet.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssOfdmMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ErpOfdmMode.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HrDsssMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h" @@ -94,10 +97,38 @@ ASSERT(mixedProfile->supportsMode(mixedMode)); ASSERT(greenfieldProfile->supportsMode(greenfieldMode)); ASSERT(greenfieldProfile->supportsMode(mixedMode)); ASSERT(!mixedProfile->supportsMode(greenfieldMode)); -ASSERT(!mixedProfile->supportsMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps)); -ASSERT(!mixedProfile->containsMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps)); -ASSERT(greenfieldProfile->supportsMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps)); -ASSERT(mixedProfile->getNonHtControlResponseMode(mixedMode) == mixedMode); +ASSERT(mixedProfile->getSifsTime() == SimTime(10, SIMTIME_US)); +ASSERT(greenfieldProfile->getSifsTime() == SimTime(10, SIMTIME_US)); +ASSERT(mixedProfile->getSlotTime() == SimTime(20, SIMTIME_US)); +ASSERT(greenfieldProfile->getSlotTime() == SimTime(20, SIMTIME_US)); +ASSERT(mixedProfile->getCwMin() == 15); +ASSERT(greenfieldProfile->getCwMin() == 15); +const IIeee80211Mode *mandatoryLegacyModes[] = { + &Ieee80211DsssCompliantModes::dsssMode1Mbps, + &Ieee80211DsssCompliantModes::dsssMode2Mbps, + &Ieee80211HrDsssCompliantModes::hrDsssMode2MbpsShortPreamble, + &Ieee80211HrDsssCompliantModes::hrDsssMode5_5MbpsCckLongPreamble, + &Ieee80211HrDsssCompliantModes::hrDsssMode5_5MbpsCckShortPreamble, + &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps, + &Ieee80211HrDsssCompliantModes::hrDsssMode11MbpsCckLongPreamble, + &Ieee80211HrDsssCompliantModes::hrDsssMode11MbpsCckShortPreamble, + &Ieee80211ErpOfdmCompliantModes::erpOfdmMode12Mbps, + &Ieee80211ErpOfdmCompliantModes::erpOfdmMode24Mbps, +}; +for (auto legacyMode : mandatoryLegacyModes) { + ASSERT(mixedProfile->supportsMode(legacyMode)); + ASSERT(greenfieldProfile->supportsMode(legacyMode)); + ASSERT(!mixedProfile->containsMode(legacyMode)); + ASSERT(!greenfieldProfile->containsMode(legacyMode)); +} +ASSERT(Ieee80211DsssCompliantModes::dsssMode1Mbps.getDataMode()->getBandwidth() == MHz(22)); +ASSERT(Ieee80211DsssCompliantModes::dsssMode2Mbps.getDataMode()->getBandwidth() == MHz(22)); +ASSERT(Ieee80211HrDsssCompliantModes::hrDsssMode2MbpsShortPreamble.getDataMode()->getBandwidth() == MHz(22)); +ASSERT(Ieee80211HrDsssCompliantModes::hrDsssMode5_5MbpsCckLongPreamble.getDataMode()->getBandwidth() == MHz(22)); +ASSERT(Ieee80211HrDsssCompliantModes::hrDsssMode5_5MbpsCckShortPreamble.getDataMode()->getBandwidth() == MHz(22)); +ASSERT(Ieee80211HrDsssCompliantModes::hrDsssMode11MbpsCckLongPreamble.getDataMode()->getBandwidth() == MHz(22)); +ASSERT(Ieee80211HrDsssCompliantModes::hrDsssMode11MbpsCckShortPreamble.getDataMode()->getBandwidth() == MHz(22)); +ASSERT(mixedProfile->getNonHtControlResponseMode(mixedMode) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); ASSERT(greenfieldProfile->getNonHtControlResponseMode(greenfieldMode) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); ASSERT(greenfieldProfile->getNonHtControlResponseMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode9Mbps) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); ASSERT(mixedProfile->getControlResponseMode(mixedMode) == mixedMode); @@ -146,9 +177,9 @@ ASSERT(configuredMixedResponse->getDataMode()->getMcsIndex() == greenfieldMcs15- ASSERT(configuredMixedResponse->getDataMode()->getBandwidth() == greenfieldMcs15->getDataMode()->getBandwidth()); ASSERT(configuredMixedResponse->getDataMode()->getNumberOfSpatialStreams() == greenfieldMcs15->getDataMode()->getNumberOfSpatialStreams()); ASSERT(configuredMixedResponse->getDataMode()->getGuardIntervalType() == greenfieldMcs15->getDataMode()->getGuardIntervalType()); -ASSERT(mixedProfile->getMandatoryControlResponseMode(mixedMcs7) == mixedMcs7); -ASSERT(mixedProfile->getMandatoryControlResponseMode(mixedMcs15) == mixedMcs7); -ASSERT(mixedProfile->getNonHtControlResponseMode(mixedMcs15, false) == mixedMcs15); +ASSERT(mixedProfile->getMandatoryControlResponseMode(mixedMcs7) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode24Mbps); +ASSERT(mixedProfile->getMandatoryControlResponseMode(mixedMcs15) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode24Mbps); +ASSERT(mixedProfile->getNonHtControlResponseMode(mixedMcs15, false) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode24Mbps); auto greenfieldMcs8Bw40 = Ieee80211HtCompliantModes::getCompliantMode( &Ieee80211HtmcsTable::htMcs8BW40MHz, @@ -178,13 +209,22 @@ Packet supportedLegacyPacket("supportedLegacy"); supportedLegacyPacket.addTag()->setMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); ASSERT(transmitter->getTestTransmissionMode(&supportedLegacyPacket) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); transmitter->setModeSet(mixedProfile); +ASSERT(transmitter->getTestTransmissionMode(&supportedLegacyPacket) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); try { - transmitter->getTestTransmissionMode(&supportedLegacyPacket); + transmitter->setMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); ASSERT(false); } catch (const cRuntimeError&) { + ASSERT(transmitter->getTestMode() == mixedMode); } transmitter->setModeSet(greenfieldProfile); +try { + transmitter->setMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); + ASSERT(false); +} +catch (const cRuntimeError&) { + ASSERT(transmitter->getTestMode() == greenfieldMode); +} try { transmitter->setMode(mixedMode); ASSERT(false); From 000f908735a77ff3e338b85851c0e51152995995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Thu, 20 Aug 2026 21:04:34 +0200 Subject: [PATCH 10/10] 802.11: document and test HT response constraints --- WHATSNEW | 7 + .../mac/rateselection/QosRateSelection.cc | 16 ++- .../mac/rateselection/QosRateSelection.ned | 8 +- .../mac/rateselection/RateSelection.cc | 16 ++- .../mac/rateselection/RateSelection.ned | 8 +- .../ieee80211/mode/Ieee80211ModeSet.h | 13 +- tests/module/Ieee80211HtMixedRuntime.test | 131 +++++++++++++++++ .../Ieee80211InvalidConfiguredCtsRate.test | 132 ++++++++++++++++++ .../Ieee80211LegacyResponseRateSelection.test | 110 +++++++++++++++ tests/unit/Ieee80211HtGreenfield_1.test | 36 +++++ 10 files changed, 465 insertions(+), 12 deletions(-) create mode 100644 tests/module/Ieee80211HtMixedRuntime.test create mode 100644 tests/module/Ieee80211InvalidConfiguredCtsRate.test create mode 100644 tests/module/Ieee80211LegacyResponseRateSelection.test diff --git a/WHATSNEW b/WHATSNEW index e79d52ecdfd..d537dedce87 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -119,6 +119,13 @@ Notable backward incompatible changes are the following: These changes are backward incompatible for C++ code that directly references the old combined ICMP error indication or tag types. +7. 802.11n control-response rate selection + + Ordinary ACK and Basic BlockAck responses in the n(mixed-2.4Ghz) profile now + use the mandatory non-HT fallback because BSSBasicRateSet is not modelled. + This can change airtime, timeout trajectories, throughput, and fingerprints + in existing simulations. An HT RTS continues to receive an HT-mixed CTS. + Notable backward compatible changes are the following: 1. IPv6 network configurator diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc index b82333b8e42..7dabfd28105 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc @@ -16,6 +16,20 @@ namespace ieee80211 { using namespace inet::physicallayer; +static const IIeee80211Mode *resolveConfiguredResponseCtsFrameMode(const Ieee80211ModeSet *modeSet, double bitrate, const char *modulePath) +{ + if (bitrate == -1) + return nullptr; + try { + return modeSet->getMode(bps(bitrate)); + } + catch (const cRuntimeError& error) { + throw cRuntimeError("%s has invalid responseCtsFrameBitrate=%g bps for operation mode '%s'; " + "the configured CTS rate must resolve to a selectable mode (HT RTS responses require an HT mode): %s", + modulePath, bitrate, modeSet->getName(), error.getFormattedMessage().c_str()); + } +} + Define_Module(QosRateSelection); void QosRateSelection::initialize(int stage) @@ -46,7 +60,7 @@ void QosRateSelection::resolveConfiguredModes(const Ieee80211ModeSet *newModeSet double responseBlockAckFrameBitrate = par("responseBlockAckFrameBitrate"); auto newResponseBlockAckFrameMode = responseBlockAckFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(responseBlockAckFrameBitrate)); double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); - auto newResponseCtsFrameMode = responseCtsFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(responseCtsFrameBitrate)); + auto newResponseCtsFrameMode = resolveConfiguredResponseCtsFrameMode(newModeSet, responseCtsFrameBitrate, getFullPath().c_str()); auto newFastestMandatoryMode = newModeSet->getFastestMandatoryMode(); modeSet = newModeSet; diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned index 39ccdbca8eb..3ad6e686846 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned @@ -34,10 +34,10 @@ simple QosRateSelection extends SimpleModule // a configured HT bitrate is an upper bound mapped to the highest mandatory // non-HT rate at or below it; it does not select an exact HT BlockAck PPDU. double responseBlockAckFrameBitrate @unit(bps) = default(-1bps); - // -1 selects the standard-derived automatic response. A configured HT value is a deliberate - // model override translated only to HT-mixed while preserving the resolved configured mode's - // MCS, bandwidth, NSS, and GI; it can therefore bypass IEEE 802.11-2024 - // 10.6.6.5.3/10.6.6.5.7 response constraints. + // -1 selects the standard-derived automatic response. A configured value must resolve to a + // selectable mode; for an HT RTS it must be HT and is translated to the corresponding HT-mixed + // CTS while preserving MCS, bandwidth, NSS, and GI. An explicitly configured HT value is a + // deliberate override and may bypass IEEE 802.11-2024 10.6.6.5.3/10.6.6.5.7 response constraints. double responseCtsFrameBitrate @unit(bps) = default(-1bps); double dataFrameBitrate @unit(bps) = default(-1bps); // Fastest diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc index 88efbd66b08..a7b182b91f2 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc @@ -20,6 +20,20 @@ namespace ieee80211 { using namespace inet::physicallayer; +static const IIeee80211Mode *resolveConfiguredResponseCtsFrameMode(const Ieee80211ModeSet *modeSet, double bitrate, const char *modulePath) +{ + if (bitrate == -1) + return nullptr; + try { + return modeSet->getMode(bps(bitrate)); + } + catch (const cRuntimeError& error) { + throw cRuntimeError("%s has invalid responseCtsFrameBitrate=%g bps for operation mode '%s'; " + "the configured CTS rate must resolve to a selectable mode (HT RTS responses require an HT mode): %s", + modulePath, bitrate, modeSet->getName(), error.getFormattedMessage().c_str()); + } +} + Define_Module(RateSelection); void RateSelection::initialize(int stage) @@ -65,7 +79,7 @@ void RateSelection::resolveConfiguredModes(const Ieee80211ModeSet *newModeSet) double responseAckFrameBitrate = par("responseAckFrameBitrate"); auto newResponseAckFrameMode = responseAckFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(responseAckFrameBitrate)); double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); - auto newResponseCtsFrameMode = responseCtsFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(responseCtsFrameBitrate)); + auto newResponseCtsFrameMode = resolveConfiguredResponseCtsFrameMode(newModeSet, responseCtsFrameBitrate, getFullPath().c_str()); auto newFastestMandatoryMode = newModeSet->getFastestMandatoryMode(); // Commit only after every configured mode has been resolved, so a failed diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned index bec41fd696b..2ac41407910 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned @@ -26,10 +26,10 @@ simple RateSelection extends SimpleModule like IRateSelection // a configured HT bitrate is an upper bound mapped to the highest mandatory // non-HT rate at or below it; it does not select an exact HT ACK PPDU. double responseAckFrameBitrate @unit(bps) = default(-1bps); - // -1 selects the standard-derived automatic response. A configured HT value is a deliberate - // model override translated only to HT-mixed while preserving the resolved configured mode's - // MCS, bandwidth, NSS, and GI; it can therefore bypass IEEE 802.11-2024 - // 10.6.6.5.3/10.6.6.5.7 response constraints. + // -1 selects the standard-derived automatic response. A configured value must resolve to a + // selectable mode; for an HT RTS it must be HT and is translated to the corresponding HT-mixed + // CTS while preserving MCS, bandwidth, NSS, and GI. An explicitly configured HT value is a + // deliberate override and may bypass IEEE 802.11-2024 10.6.6.5.3/10.6.6.5.7 response constraints. double responseCtsFrameBitrate @unit(bps) = default(-1bps); double dataFrameBitrate @unit(bps) = default(-1bps); // Fastest diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h index ed1cbf2bf74..36804a1d649 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h @@ -58,6 +58,11 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject const IIeee80211Mode *getMode(int index) { return entries[index].mode; } bool isMandatory(int index) { return entries[index].isMandatory; } + // containsMode() covers entries selectable as the persistent operating mode + // (for example through Ieee80211Transmitter::setMode()). supportsMode() + // additionally covers immutable PHY capabilities that may be selected per + // packet through Ieee80211ModeReq, such as legacy control responses in a + // 2.4 GHz HT profile. bool containsMode(const IIeee80211Mode *mode) const { return findModeIndex(mode) != -1; } bool supportsMode(const IIeee80211Mode *mode) const; bool getIsMandatory(const IIeee80211Mode *mode) const; @@ -78,8 +83,12 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject const IIeee80211Mode *getSlowerMandatoryMode(const IIeee80211Mode *mode) const; const IIeee80211Mode *getFasterMandatoryMode(const IIeee80211Mode *mode) const; - // Automatic responses follow IEEE 802.11-2024 10.6.6.5.3/10.6.6.5.7. A - // configured HT mode is a deliberate override translated only to HT-mixed. + // Automatic responses follow IEEE 802.11-2024 10.6.6.5.3/10.6.6.5.7. + // A configured CTS mode must be selectable and, for an HT RTS, must be HT; + // it is translated only to the corresponding HT-mixed response. An explicitly + // configured HT mode is a deliberate override and may bypass those response + // constraints. A legacy configured CTS rate is rejected by rate-selection + // initialization, while this API keeps the direct HT/legacy combination fatal. const IIeee80211Mode *getControlResponseMode(const IIeee80211Mode *mode, const IIeee80211Mode *configuredMode = nullptr) const; const IIeee80211Mode *getMandatoryControlResponseMode(const IIeee80211Mode *mode) const; // 2.4 GHz HT modes are converted to non-HT responses. With mandatory=false, diff --git a/tests/module/Ieee80211HtMixedRuntime.test b/tests/module/Ieee80211HtMixedRuntime.test new file mode 100644 index 00000000000..cd81007886e --- /dev/null +++ b/tests/module/Ieee80211HtMixedRuntime.test @@ -0,0 +1,131 @@ +%description: +Checks a deterministic n(mixed-2.4Ghz) exchange with HT-mixed RTS/CTS and data +followed by an ERP-OFDM ACK. Basic BlockAck response selection is covered by +the focused public QoS API test because enabling a bounded BAR exchange would +require additional association and agreement setup. + +%file: Test.cc +#include + +#include "inet/common/Simsignals.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ErpOfdmMode.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmission.h" + +using namespace inet; +using namespace inet::physicallayer; +using namespace inet::ieee80211; + +namespace Ieee80211HtMixedRuntime { + +static OPP_THREAD_LOCAL int htMixedRtsCount = 0; +static OPP_THREAD_LOCAL int htMixedCtsCount = 0; +static OPP_THREAD_LOCAL int htMixedDataCount = 0; +static OPP_THREAD_LOCAL int erpOfdmAckCount = 0; + +class HtMixedCheckingRadio : public Ieee80211Radio, public cListener +{ + protected: + virtual void initialize(int stage) override + { + Ieee80211Radio::initialize(stage); + if (stage == INITSTAGE_LOCAL) + subscribe(transmissionStartedSignal, this); + } + + virtual void receiveSignal(cComponent *, simsignal_t signalID, cObject *obj, cObject *) override + { + if (signalID != transmissionStartedSignal) + return; + auto transmission = check_and_cast(obj); + auto mode = dynamic_cast(transmission->getMode()); + auto phyHeader = Ieee80211Radio::peekIeee80211PhyHeaderAtFront(transmission->getPacket()); + if (transmission->getPreambleDuration() < SIMTIME_ZERO || transmission->getHeaderDuration() < SIMTIME_ZERO || transmission->getDataDuration() < SIMTIME_ZERO || + transmission->getPreambleDuration() + transmission->getHeaderDuration() + transmission->getDataDuration() != transmission->getDuration()) + throw cRuntimeError("Invalid preamble/header/data duration decomposition"); + if (mode != nullptr && (transmission->getHeaderDuration() != SIMTIME_ZERO || + transmission->getDataDuration() != mode->getDataMode()->getDuration(B(phyHeader->getLengthField())))) + throw cRuntimeError("HT transmission did not keep SIG in the preamble and data in the data interval"); + auto macHeader = transmission->getPacket()->peekDataAt(phyHeader->getChunkLength()); + bool isHtMixed = mode != nullptr && mode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED; + if (dynamicPtrCast(macHeader) && isHtMixed) + htMixedRtsCount++; + if (dynamicPtrCast(macHeader) && isHtMixed) + htMixedCtsCount++; + if (dynamicPtrCast(macHeader) && isHtMixed) + htMixedDataCount++; + if (dynamicPtrCast(macHeader) && dynamic_cast(transmission->getMode()) != nullptr) { + if (transmission->getMode()->getDataMode()->getBandwidth() != MHz(20)) + throw cRuntimeError("ERP-OFDM ACK did not use the 20 MHz channel bandwidth"); + erpOfdmAckCount++; + } + } + + virtual void finish() override + { + Ieee80211Radio::finish(); + if (htMixedRtsCount == 0 || htMixedCtsCount == 0 || htMixedDataCount == 0 || erpOfdmAckCount == 0) + throw cRuntimeError("Expected HT-mixed RTS/CTS and data with ERP-OFDM ACK response"); + std::cout << "Observed " << htMixedRtsCount << " HT-mixed RTS, " << htMixedCtsCount << " HT-mixed CTS, " << htMixedDataCount << " HT-mixed data, and " << erpOfdmAckCount << " ERP-OFDM ACK response(s).\n"; + } +}; + +Define_Module(HtMixedCheckingRadio); + +} // namespace Ieee80211HtMixedRuntime + +%file: test.ned +import inet.networklayer.configurator.ipv4.Ipv4NetworkConfigurator; +import inet.node.inet.AdhocHost; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211Radio; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; + +module HtMixedCheckingRadio extends Ieee80211Radio +{ + parameters: + @class(HtMixedCheckingRadio); +} + +network TestHtMixedRuntime +{ + submodules: + configurator: Ipv4NetworkConfigurator; + radioMedium: Ieee80211ScalarRadioMedium; + host[2]: AdhocHost; +} + +%inifile: omnetpp.ini +[General] +include ../../../../examples/wireless/lan80211/omnetpp-ht-greenfield.ini +network = TestHtMixedRuntime +abstract = false +ned-path = .;../../../../src;../../../../examples;../../lib +cmdenv-express-mode = true +record-vector-results = false +record-eventlog = false +seed-set = 0 +**.wlan[*].radio.typename = "HtMixedCheckingRadio" + +[Config HtMixedRuntime] +abstract = false +network = TestHtMixedRuntime +sim-time-limit = 2s +*.host[0].numApps = 1 +*.host[0].app[0].typename = "UdpBasicApp" +*.host[0].app[0].destAddresses = "host[1]" +*.host[0].app[0].destPort = 1000 +*.host[0].app[0].messageLength = 100B +*.host[0].app[0].startTime = 100ms +*.host[0].app[0].sendInterval = 100ms +*.host[1].numApps = 1 +*.host[1].app[0].typename = "UdpSink" +*.host[1].app[0].localPort = 1000 +**.wlan[*].opMode = "n(mixed-2.4Ghz)" +**.wlan[*].mac.dcf.rtsPolicy.rtsThreshold = 1B + +%extraargs: -c HtMixedRuntime + +%contains-regex: stdout +Observed [1-9][0-9]* HT-mixed RTS, [1-9][0-9]* HT-mixed CTS, [1-9][0-9]* HT-mixed data, and [1-9][0-9]* ERP-OFDM ACK response\(s\). diff --git a/tests/module/Ieee80211InvalidConfiguredCtsRate.test b/tests/module/Ieee80211InvalidConfiguredCtsRate.test new file mode 100644 index 00000000000..3dabcc05775 --- /dev/null +++ b/tests/module/Ieee80211InvalidConfiguredCtsRate.test @@ -0,0 +1,132 @@ +%description: +Checks that a non-selectable legacy responseCtsFrameBitrate is rejected during +rate-selection resolution for both DCF and QoS selections and both 2.4 GHz HT profiles. + +%file: Test.cc +#include +#include +#include + +#include "inet/common/InitStages.h" +#include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" +#include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" + +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +namespace Ieee80211InvalidConfiguredCtsRate { + +template +static void checkRejected(const char *selectorName, Selector& selector) +{ + double bitrate = selector.par("responseCtsFrameBitrate"); + char formattedBitrate[64]; + std::snprintf(formattedBitrate, sizeof(formattedBitrate), "%g", bitrate); + for (const char *profile : {"n(mixed-2.4Ghz)", "n(greenfield-2.4Ghz)"}) { + bool rejected = false; + try { + selector.resolveConfiguredModesForTest(Ieee80211ModeSet::getModeSet(profile)); + } + catch (const cRuntimeError& error) { + rejected = true; + auto message = error.getFormattedMessage(); + if (message.find(std::string("responseCtsFrameBitrate=") + formattedBitrate) == std::string::npos || + message.find("operation mode '" + std::string(profile) + "'") == std::string::npos || + message.find("selectable mode") == std::string::npos) + throw cRuntimeError("%s diagnostic for %s at %g bps is incomplete: %s", selectorName, profile, bitrate, message.c_str()); + } + if (!rejected) + throw cRuntimeError("%s accepted legacy CTS bitrate %g bps in %s", selectorName, bitrate, profile); + } +} + +class TestRateSelection : public RateSelection +{ + public: + void resolveConfiguredModesForTest(const Ieee80211ModeSet *modeSet) { resolveConfiguredModes(modeSet); } + + protected: + virtual void initialize(int stage) override + { + if (stage == INITSTAGE_LINK_LAYER) { + checkRejected("DCF RateSelection", *this); + std::cout << "DCF rejected configured legacy responseCtsFrameBitrate for both HT profiles.\n"; + } + } +}; + +class TestQosRateSelection : public QosRateSelection +{ + public: + void resolveConfiguredModesForTest(const Ieee80211ModeSet *modeSet) { resolveConfiguredModes(modeSet); } + + protected: + virtual void initialize(int stage) override + { + if (stage == INITSTAGE_LINK_LAYER) { + checkRejected("QoS RateSelection", *this); + std::cout << "QoS rejected configured legacy responseCtsFrameBitrate for both HT profiles.\n"; + } + } +}; + +Define_Module(TestRateSelection); +Define_Module(TestQosRateSelection); + +} // namespace Ieee80211InvalidConfiguredCtsRate + +%file: test.ned +import inet.linklayer.ieee80211.mac.rateselection.QosRateSelection; +import inet.linklayer.ieee80211.mac.rateselection.RateSelection; + +simple TestRateSelection extends RateSelection +{ + parameters: + @class(TestRateSelection); + rateControlModule = ""; + responseCtsFrameBitrate = default(6Mbps); +} + +simple TestQosRateSelection extends QosRateSelection +{ + parameters: + @class(TestQosRateSelection); + rateControlModule = ""; + responseCtsFrameBitrate = default(6Mbps); +} + +simple TestRateSelection24 extends TestRateSelection +{ + parameters: + responseCtsFrameBitrate = default(24Mbps); +} + +simple TestQosRateSelection24 extends TestQosRateSelection +{ + parameters: + responseCtsFrameBitrate = default(24Mbps); +} + +network TestInvalidConfiguredCtsRate +{ + submodules: + dcfRateSelection6: TestRateSelection; + dcfRateSelection24: TestRateSelection24; + qosRateSelection6: TestQosRateSelection; + qosRateSelection24: TestQosRateSelection24; +} + +%inifile: omnetpp.ini +[General] +network = TestInvalidConfiguredCtsRate +ned-path = .;../../../../src;../../lib +cmdenv-express-mode = true +record-vector-results = false +seed-set = 0 + +%contains: stdout +DCF rejected configured legacy responseCtsFrameBitrate for both HT profiles. +%contains: stdout +QoS rejected configured legacy responseCtsFrameBitrate for both HT profiles. diff --git a/tests/module/Ieee80211LegacyResponseRateSelection.test b/tests/module/Ieee80211LegacyResponseRateSelection.test new file mode 100644 index 00000000000..5fc607b867b --- /dev/null +++ b/tests/module/Ieee80211LegacyResponseRateSelection.test @@ -0,0 +1,110 @@ +%description: +Checks the public DCF and QoS response-rate APIs for representative legacy +operation modes, preserving mandatory ACK/CTS and Basic BlockAck selections. + +%file: Test.cc +#include + +#include "inet/common/packet/Packet.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" +#include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" +#include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" + +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +namespace Ieee80211LegacyResponseRateSelection { + +class TestRateSelection : public RateSelection +{ + public: + void setModeSetForTest(const Ieee80211ModeSet *newModeSet) { modeSet = newModeSet; } +}; + +class TestQosRateSelection : public QosRateSelection +{ + public: + void setModeSetForTest(const Ieee80211ModeSet *newModeSet) { modeSet = const_cast(newModeSet); } +}; + +struct LegacyProfileCase { + const char *name; + bps sourceBitrate; + bps expectedResponseBitrate; + bps expectedBlockAckBitrate; +}; + +static void checkCase(const LegacyProfileCase& testCase) +{ + auto modeSet = Ieee80211ModeSet::getModeSet(testCase.name); + auto sourceMode = modeSet->getMode(testCase.sourceBitrate); + auto expectedMode = modeSet->getMode(testCase.expectedResponseBitrate); + auto expectedBlockAckMode = modeSet->getMode(testCase.expectedBlockAckBitrate); + Packet packet("elicitingFrame"); + packet.addTag()->setMode(sourceMode); + auto basicBlockAckReq = makeShared(); + + TestRateSelection dcfRateSelection; + dcfRateSelection.setModeSetForTest(modeSet); + ASSERT(dcfRateSelection.computeResponseAckFrameMode(&packet, nullptr) == expectedMode); + ASSERT(dcfRateSelection.computeResponseCtsFrameMode(&packet, nullptr) == expectedMode); + + TestQosRateSelection qosRateSelection; + qosRateSelection.setModeSetForTest(modeSet); + ASSERT(qosRateSelection.computeResponseAckFrameMode(&packet, nullptr) == expectedMode); + ASSERT(qosRateSelection.computeResponseCtsFrameMode(&packet, nullptr) == expectedMode); + auto blockAckMode = qosRateSelection.computeResponseBlockAckFrameMode(&packet, basicBlockAckReq); + if (blockAckMode != expectedBlockAckMode) + throw cRuntimeError("%s Basic BlockAck returned %s, expected %s", testCase.name, blockAckMode->getName(), expectedBlockAckMode->getName()); +} + +class LegacyResponseProbe : public cSimpleModule +{ + protected: + virtual int numInitStages() const override { return NUM_INIT_STAGES; } + + virtual void initialize(int stage) override + { + if (stage != INITSTAGE_LAST) + return; + const LegacyProfileCase cases[] = { + {"a", Mbps(54), Mbps(24), Mbps(54)}, + {"b", Mbps(11), Mbps(11), Mbps(11)}, + {"g(mixed)", Mbps(54), Mbps(24), Mbps(54)}, + }; + for (const auto& testCase : cases) + checkCase(testCase); + std::cout << "Legacy a, b, and g(mixed) DCF/QoS ACK/CTS and Basic BlockAck responses preserved.\n"; + } +}; + +Define_Module(LegacyResponseProbe); + +} // namespace Ieee80211LegacyResponseRateSelection + +%file: test.ned +simple LegacyResponseProbe +{ + parameters: + @class(LegacyResponseProbe); +} + +network TestLegacyResponseRateSelection +{ + submodules: + probe: LegacyResponseProbe; +} + +%inifile: omnetpp.ini +[General] +network = TestLegacyResponseRateSelection +ned-path = .;../../../../src;../../lib +cmdenv-express-mode = true +record-vector-results = false +seed-set = 0 + +%contains: stdout +Legacy a, b, and g(mixed) DCF/QoS ACK/CTS and Basic BlockAck responses preserved. diff --git a/tests/unit/Ieee80211HtGreenfield_1.test b/tests/unit/Ieee80211HtGreenfield_1.test index e705a999a90..55d7ba35a63 100644 --- a/tests/unit/Ieee80211HtGreenfield_1.test +++ b/tests/unit/Ieee80211HtGreenfield_1.test @@ -5,6 +5,7 @@ preamble formats and timing, retain strict membership, and support the legacy %includes: #include +#include #include "inet/common/packet/Packet.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssMode.h" @@ -59,6 +60,37 @@ static bool rejectsMandatoryLookup(const Ieee80211ModeSet *modeSet, const IIeee8 } } +static bool rejectsLegacyConfiguredCts(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *htMode, const IIeee80211Mode *legacyMode) +{ + try { + modeSet->getControlResponseMode(htMode, legacyMode); + return false; + } + catch (const cRuntimeError& error) { + return std::string(error.getFormattedMessage()).find("HT RTS requires an HT-mixed CTS response") != std::string::npos; + } +} + +static void assertTransmitterDurationDecomposition(const IIeee80211Mode *mode) +{ + auto duration = mode->getDuration(B(0)); + auto preambleDuration = mode->getPreambleMode()->getDuration(); + auto modeledDataDuration = mode->getDataMode()->getDuration(B(0)); + bool headerIncludedInPreamble = duration == preambleDuration + modeledDataDuration; + auto headerDuration = headerIncludedInPreamble ? SIMTIME_ZERO : mode->getHeaderMode()->getDuration(); + auto dataDuration = headerIncludedInPreamble ? modeledDataDuration : duration - headerDuration - preambleDuration; + ASSERT(preambleDuration >= SIMTIME_ZERO); + ASSERT(headerDuration >= SIMTIME_ZERO); + ASSERT(dataDuration >= SIMTIME_ZERO); + ASSERT(preambleDuration + headerDuration + dataDuration == duration); + if (dynamic_cast(mode) != nullptr) + ASSERT(headerDuration == SIMTIME_ZERO); + if (dynamic_cast(mode) != nullptr) + ASSERT(dataDuration > modeledDataDuration); + if (dynamic_cast(mode) != nullptr && dynamic_cast(mode) == nullptr) + ASSERT(headerDuration > SIMTIME_ZERO); +} + %activity: auto mixedProfile = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"); auto greenfieldProfile = Ieee80211ModeSet::getModeSet("n(greenfield-2.4Ghz)"); @@ -144,6 +176,7 @@ ASSERT(greenfieldProfile->supportsMode(greenfieldMcs8)); // modelled, 10.6.6.5.3 selects mandatory one-stream MCS 0 for the CTS. ASSERT(greenfieldProfile->getControlResponseMode(greenfieldMcs8) == mixedMode); ASSERT(greenfieldProfile->getControlResponseMode(greenfieldMcs8, greenfieldMode) == mixedMode); +ASSERT(rejectsLegacyConfiguredCts(greenfieldProfile, greenfieldMode, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps)); auto greenfieldMcs7 = Ieee80211HtCompliantModes::getCompliantMode( &Ieee80211HtmcsTable::htMcs7BW20MHz, @@ -286,6 +319,8 @@ ASSERT(vhtGreenfield5GhzMode->getPreambleMode()->getPreambleFormat() == Ieee8021 ASSERT(vhtMixed2GhzMode->getCenterFrequencyMode() == Ieee80211VhtMode::BAND_2_4GHZ); ASSERT(vhtMixed5GhzMode->getDuration(B(0)) == vhtMixed5GhzMode->getPreambleMode()->getDuration() + vhtMixed5GhzMode->getDataMode()->getDuration(B(0))); ASSERT(vhtMixed5GhzMode->getDataMode()->getDuration(B(0)) >= SIMTIME_ZERO); +assertTransmitterDurationDecomposition(ofdmProfile->getMode(Mbps(6))); +assertTransmitterDurationDecomposition(vhtMixed5GhzMode); auto erpMixedProfile = Ieee80211ModeSet::getModeSet("g(mixed)"); auto erpOnlyProfile = Ieee80211ModeSet::getModeSet("g(erp)"); @@ -294,6 +329,7 @@ auto erpOnlyMode = &Ieee80211ErpOfdmCompliantModes::erpOnlyOfdmMode6Mbps; Ieee80211DsssOfdmMode dsssOfdmMode("dsss-ofdm", nullptr, nullptr, nullptr, nullptr, &Ieee80211OfdmCompliantModes::ofdmDataMode6MbpsCS20MHz); ASSERT(erpMixedProfile != nullptr); ASSERT(erpOnlyProfile != nullptr); +assertTransmitterDurationDecomposition(erpMixedProfile->getMode(Mbps(6))); ASSERT(erpMixedMode != erpOnlyMode); ASSERT(erpMixedMode->getSlotTime() == SimTime(20, SIMTIME_US)); ASSERT(erpOnlyMode->getSlotTime() == SimTime(9, SIMTIME_US));