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/examples/wireless/lan80211/omnetpp-ht-greenfield.ini b/examples/wireless/lan80211/omnetpp-ht-greenfield.ini new file mode 100644 index 00000000000..9d59b48fcc6 --- /dev/null +++ b/examples/wireless/lan80211/omnetpp-ht-greenfield.ini @@ -0,0 +1,37 @@ +[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 + +*.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/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc index c8579ac4424..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) @@ -23,23 +37,44 @@ 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)); - 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)); + if (modeSet == nullptr) + throw cRuntimeError("QosRateSelection module %s has no mode set at link-layer initialization", getFullPath().c_str()); + resolveConfiguredModes(modeSet); } } +void QosRateSelection::resolveConfiguredModes(const Ieee80211ModeSet *newModeSet) +{ + 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"); + auto newResponseAckFrameMode = responseAckFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(responseAckFrameBitrate)); + double responseBlockAckFrameBitrate = par("responseBlockAckFrameBitrate"); + auto newResponseBlockAckFrameMode = responseBlockAckFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(responseBlockAckFrameBitrate)); + double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); + auto newResponseCtsFrameMode = resolveConfiguredResponseCtsFrameMode(newModeSet, responseCtsFrameBitrate, getFullPath().c_str()); + 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) { const auto& modeReqTag = packet->findTag(); @@ -71,36 +106,22 @@ bool QosRateSelection::isControlResponseFrame(const Ptr& dataOrMgmtHeader) { - // TODO BSSBasicRateSet, alternate rate + if (responseAckFrameMode) + return modeSet->getNonHtControlResponseMode(responseAckFrameMode, false); auto mode = getMode(packet, dataOrMgmtHeader); - ASSERT(modeSet->containsMode(mode)); - if (!responseAckFrameMode) { - if (modeSet->getIsMandatory(mode)) - return mode; - else if (auto slowerMode = modeSet->getSlowerMandatoryMode(mode)) - return slowerMode; - else - throw cRuntimeError("Mandatory mode not found"); - } - else - return 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 modeSet->getMandatoryControlResponseMode(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) { - if (modeSet->getIsMandatory(mode)) - return mode; - else if (auto slowerMode = modeSet->getSlowerMandatoryMode(mode)) - return slowerMode; - else - throw cRuntimeError("Mandatory mode not found"); - } - else - return responseCtsFrameMode; + ASSERT(modeSet->supportsMode(mode)); + // 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); } // @@ -111,10 +132,15 @@ const IIeee80211Mode *QosRateSelection::computeResponseCtsFrameMode(Packet *pack // const IIeee80211Mode *QosRateSelection::computeResponseBlockAckFrameMode(Packet *packet, const Ptr& blockAckReq) { - if (dynamicPtrCast(blockAckReq)) - return responseBlockAckFrameMode ? responseBlockAckFrameMode : getMode(packet, blockAckReq); - else + if (!dynamicPtrCast(blockAckReq)) throw cRuntimeError("Unknown BlockAckReq frame type"); + if (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. + return modeSet->getNonHtControlResponseMode(mode, false); } const IIeee80211Mode *QosRateSelection::computeDataOrMgmtFrameMode(const Ptr& dataOrMgmtHeader) @@ -235,8 +261,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(); + resolveConfiguredModes(check_and_cast(obj)); } } @@ -248,4 +273,3 @@ void QosRateSelection::frameTransmitted(Packet *packet, const Ptr& header); virtual const physicallayer::IIeee80211Mode *computeControlFrameMode(const Ptr& header, TxopProcedure *txopProcedure); @@ -78,4 +79,3 @@ class INET_API QosRateSelection : public IQosRateSelection, public ModeSetListen } /* namespace inet */ #endif - diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned index b3b96441781..3ad6e686846 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned @@ -26,8 +26,18 @@ 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 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 @@ -38,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.cc b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc index 726b52c3f09..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) @@ -29,19 +43,9 @@ 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)); - double responseAckFrameBitrate = par("responseAckFrameBitrate"); - responseAckFrameMode = (responseAckFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseAckFrameBitrate)); - double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); - responseCtsFrameMode = (responseCtsFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseCtsFrameBitrate)); - fastestMandatoryMode = modeSet->getFastestMandatoryMode(); + if (modeSet == nullptr) + throw cRuntimeError("RateSelection module %s has no mode set at link-layer initialization", getFullPath().c_str()); + resolveConfiguredModes(modeSet); // WATCH(dataOrMgmtRateControl); // WATCH(*((cObject**)&fastestMandatoryMode)); @@ -60,6 +64,37 @@ void RateSelection::initialize(int stage) } } +void RateSelection::resolveConfiguredModes(const Ieee80211ModeSet *newModeSet) +{ + 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"); + auto newResponseAckFrameMode = responseAckFrameBitrate == -1 ? nullptr : newModeSet->getMode(bps(responseAckFrameBitrate)); + double responseCtsFrameBitrate = par("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 + // 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) { const auto& modeReqTag = packet->findTag(); @@ -79,24 +114,25 @@ 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 responseAckFrameMode; - else { - auto mode = getMode(packet, dataOrMgmtHeader); - ASSERT(modeSet->containsMode(mode)); - return modeSet->getIsMandatory(mode) ? mode : modeSet->getSlowerMandatoryMode(mode); // TODO BSSBasicRateSet - } + 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 modeSet->getMandatoryControlResponseMode(mode); } const IIeee80211Mode *RateSelection::computeResponseCtsFrameMode(Packet *packet, const Ptr& rtsFrame) { - if (responseCtsFrameMode) - return responseCtsFrameMode; - else { - auto mode = getMode(packet, rtsFrame); - ASSERT(modeSet->containsMode(mode)); - return modeSet->getIsMandatory(mode) ? mode : modeSet->getSlowerMandatoryMode(mode); // TODO BSSBasicRateSet - } + auto mode = getMode(packet, rtsFrame); + ASSERT(modeSet->supportsMode(mode)); + // 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. @@ -151,8 +187,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(); + resolveConfiguredModes(check_and_cast(obj)); } } @@ -170,4 +205,3 @@ void RateSelection::setFrameMode(Packet *packet, const Ptr& header); virtual const physicallayer::IIeee80211Mode *computeControlFrameMode(const Ptr& header); @@ -77,4 +78,3 @@ class INET_API RateSelection : public IRateSelection, public SimpleModule, publi } // namespace inet #endif - diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned index b60bba95a51..2ac41407910 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned @@ -22,7 +22,14 @@ 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 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 @@ -33,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/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..444faadd653 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" @@ -24,7 +25,124 @@ namespace physicallayer { Register_Abstract_Class(Ieee80211ModeSet); -const DelayedInitializer> Ieee80211ModeSet::modeSets([]() { return new std::vector { +#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 std::vector createHtEntries(Ieee80211HtPreambleMode::HighTroughputPreambleFormat preambleFormat) +{ + return { + HT_MODE_ENTRIES_20(preambleFormat) + HT_MODE_ENTRIES_40(preambleFormat) + }; +} + +static std::vector createHtSupportedEntries(Ieee80211HtPreambleMode::HighTroughputPreambleFormat preambleFormat) +{ + auto result = createHtEntries(preambleFormat); + if (preambleFormat == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD) { + auto mixedEntries = createHtEntries(Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED); + result.insert(result.end(), mixedEntries.begin(), mixedEntries.end()); + } + // 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; +} + +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 + +OPP_THREAD_LOCAL const DelayedInitializer> Ieee80211ModeSet::modeSets([]() { return new std::vector { Ieee80211ModeSet("a", { { true, &Ieee80211OfdmCompliantModes::ofdmMode6MbpsCS20MHz }, { false, &Ieee80211OfdmCompliantModes::ofdmMode9MbpsCS20MHz }, @@ -76,72 +194,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) }, @@ -456,8 +511,17 @@ const DelayedInitializer> Ieee80211ModeSet::modeSe }),}; }); 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) + 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); std::stable_sort(nonConstEntries->begin(), nonConstEntries->end(), EntryNetBitrateComparator()); @@ -481,6 +545,110 @@ int Ieee80211ModeSet::findModeIndex(const IIeee80211Mode *mode) const return -1; } +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); + if (entry.isMandatory && mode != nullptr) + mandatoryHtMcsIndexes.insert(mode->getDataMode()->getMcsIndex()); + } + for (const auto& sourceEntry : supportedEntries) { + auto source = dynamic_cast(sourceEntry.mode); + 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()->getBandwidth() == source->getDataMode()->getBandwidth() && + 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 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. + 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; +} + +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; + 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 { int index = findModeIndex(mode); @@ -495,6 +663,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); @@ -598,6 +780,78 @@ const IIeee80211Mode *Ieee80211ModeSet::getFasterMandatoryMode(const IIeee80211M return nullptr; } +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); + if (it == controlResponseModes.end()) { + if (configuredMode != nullptr) + return getNonHtControlResponseMode(configuredMode, false); + return getMandatoryControlResponseMode(mode); + } + 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 = 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()); + return configuredIt->second; +} + +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()); + if (!nonHtControlResponseEntries.empty() && (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 (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) + return mode; + if (!mandatory) + 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; + } + 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) { for (size_t index = 0; index < (&modeSets)->size(); index++) { @@ -626,4 +880,3 @@ const Ieee80211ModeSet *Ieee80211ModeSet::getModeSet(const char *mode) } // namespace physicallayer } // namespace inet - diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h index 98155bf77f6..36804a1d649 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; @@ -30,16 +30,25 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject protected: std::string name; const std::vector entries; + // 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: - static const DelayedInitializer> modeSets; + static OPP_THREAD_LOCAL const DelayedInitializer> modeSets; protected: 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: 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; } @@ -49,9 +58,18 @@ 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; + // 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; 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 +83,18 @@ 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 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, + // 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); static const Ieee80211ModeSet *getModeSet(const char *mode); @@ -84,4 +114,3 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject } // namespace inet #endif - 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/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.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/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.cc b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc index c70d545570f..c0e32ae25aa 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,34 @@ 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) { + 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; - 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; } } @@ -143,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/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/Ieee80211ConfiguredResponseRateSelection.test b/tests/module/Ieee80211ConfiguredResponseRateSelection.test new file mode 100644 index 00000000000..c858fe791d5 --- /dev/null +++ b/tests/module/Ieee80211ConfiguredResponseRateSelection.test @@ -0,0 +1,206 @@ +%description: +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 +#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" +#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" + +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +namespace Ieee80211ConfiguredResponseRateSelection { + +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 (actual mode: %s, bitrate: %g)", name, mode->getName(), mode->getDataMode()->getNetBitrate().get()); +} + +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 +{ + 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")); + 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) + 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)); + assertHtMixedResponse("QoS CTS HT", qosRateSelection->computeResponseCtsFrameMode(&packet, nullptr)); + assertNonHtResponse("QoS CTS non-HT", qosRateSelection->computeResponseCtsFrameMode(&nonHtPacket, nullptr)); + + 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)); + 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)); + 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 + // 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 << "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"; + } +}; + +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 +*.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 +*.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 +**.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 +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/Ieee80211HtGreenfieldRuntime.test b/tests/module/Ieee80211HtGreenfieldRuntime.test new file mode 100644 index 00000000000..60d013e74f3 --- /dev/null +++ b/tests/module/Ieee80211HtGreenfieldRuntime.test @@ -0,0 +1,132 @@ +%description: + +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 + +#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" +#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: + 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()); + 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; + 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 (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"; + } +}; + +Define_Module(HtPreambleCheckingRadio); + +} // namespace Ieee80211HtGreenfieldRuntime + +%file: test.ned + +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 +{ + parameters: + @class(HtPreambleCheckingRadio); +} + +network TestHtGreenfield +{ + parameters: + int numHosts = default(2); + submodules: + configurator: Ipv4NetworkConfigurator; + radioMedium: Ieee80211ScalarRadioMedium; + host[numHosts]: AdhocHost; +} + +%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 = "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[1].numApps = 1 +*.host[1].app[0].typename = "UdpSink" +*.host[1].app[0].localPort = 1000 +*.host[0].app[0].sendInterval = 100ms +**.wlan[*].opMode = "n(greenfield-2.4Ghz)" +**.wlan[*].mac.dcf.rtsPolicy.rtsThreshold = 1B + +%extraargs: -c HtPreambleRuntime + +%contains-regex: stdout +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/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/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/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 new file mode 100644 index 00000000000..55d7ba35a63 --- /dev/null +++ b/tests/unit/Ieee80211HtGreenfield_1.test @@ -0,0 +1,347 @@ +%description: +Checks that the 802.11n mixed and Greenfield mode profiles preserve distinct +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 + +#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" +#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" + +%global: +using namespace inet; +using namespace inet::physicallayer; + +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) +{ + 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; +} + +static bool rejectsUnknownMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) +{ + try { + modeSet->getMode(mode); + return false; + } + catch (const cRuntimeError&) { + return true; + } +} + +static bool rejectsMandatoryLookup(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) +{ + try { + modeSet->getIsMandatory(mode); + return false; + } + catch (const cRuntimeError&) { + return true; + } +} + +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)"); +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)); +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); +ASSERT(greenfieldProfile->getFasterMode(mixedMode) == nullptr); +ASSERT(rejectsMandatoryLookup(mixedProfile, greenfieldMode)); +ASSERT(rejectsMandatoryLookup(greenfieldProfile, mixedMode)); +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(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); +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); +ASSERT(rejectsLegacyConfiguredCts(greenfieldProfile, greenfieldMode, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps)); + +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); +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) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode24Mbps); +ASSERT(mixedProfile->getMandatoryControlResponseMode(mixedMcs15) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode24Mbps); +ASSERT(mixedProfile->getNonHtControlResponseMode(mixedMcs15, false) == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode24Mbps); + +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); +// 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; +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); +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->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); +} +catch (const cRuntimeError&) { + ASSERT(transmitter->getTestMode() == greenfieldMode); +} +transmitter->setModeSet(nullptr); +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, + Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, + Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG); +ASSERT(greenfield5GhzMode->getCenterFrequencyMode() == Ieee80211HtMode::BAND_5GHZ); +ASSERT(greenfield5GhzMode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD); +ASSERT(!greenfieldProfile->containsMode(greenfield5GhzMode)); +ASSERT(!greenfieldProfile->supportsMode(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); +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)"); +auto erpMixedMode = &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps; +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)); +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 +HT mixed and Greenfield modes have distinct cached identities and timing.