diff --git a/WHATSNEW b/WHATSNEW index 5fdd7668423..e98e81ea3c0 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -186,6 +186,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.cc b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc index d87f581231f..c5b7502c9a0 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc @@ -7,6 +7,8 @@ #include "inet/linklayer/ieee80211/mac/Ieee80211Mac.h" +#include + #include #include "inet/common/INETUtils.h" @@ -67,24 +69,12 @@ void Ieee80211Mac::initialize(int stage) radioModule->subscribe(IRadio::receptionStateChangedSignal, this); radioModule->subscribe(IRadio::transmissionStateChangedSignal, this); radioModule->subscribe(IRadio::receivedSignalPartChangedSignal, this); + getContainingNicModule(this)->subscribe(modesetChangedSignal, this); radio = check_and_cast(radioModule); ds = check_and_cast(getSubmodule("ds")); rx = check_and_cast(getSubmodule("rx")); tx = check_and_cast(getSubmodule("tx")); - int operationalHtSpatialStreamLimit = std::min(radio->getAntenna()->getNumAntennas(), - modeSet->getMaximumNumberOfSpatialStreams()); - std::set operationalChannelWidths; - if (modeSet->isHtOperationSupported()) { - const auto *transmitter = dynamic_cast(radio->getTransmitter()); - const auto *receiver = dynamic_cast(radio->getReceiver()); - if (transmitter == nullptr || receiver == nullptr) - throw cRuntimeError("HT operation requires Ieee80211Transmitter and Ieee80211Receiver"); - for (auto channelWidth : modeSet->getHtSupportedChannelWidths()) - if (transmitter->isHtChannelWidthSupported(channelWidth) && - receiver->isHtChannelWidthSupported(channelWidth)) - operationalChannelWidths.insert(channelWidth); - } - mib->updateLocalHtCapabilities(modeSet, operationalChannelWidths, operationalHtSpatialStreamLimit); + updateLocalHtCapabilities(); emit(modesetChangedSignal, modeSet); if (isUp()) initializeRadioMode(); @@ -97,6 +87,24 @@ void Ieee80211Mac::initialize(int stage) } } +void Ieee80211Mac::updateLocalHtCapabilities() +{ + int operationalHtSpatialStreamLimit = std::min(radio->getAntenna()->getNumAntennas(), + modeSet->getMaximumNumberOfSpatialStreams()); + std::set operationalChannelWidths; + if (modeSet->isHtOperationSupported()) { + const auto *transmitter = dynamic_cast(radio->getTransmitter()); + const auto *receiver = dynamic_cast(radio->getReceiver()); + if (transmitter == nullptr || receiver == nullptr) + throw cRuntimeError("HT operation requires Ieee80211Transmitter and Ieee80211Receiver"); + for (auto channelWidth : modeSet->getHtSupportedChannelWidths()) + if (transmitter->isHtChannelWidthSupported(channelWidth) && + receiver->isHtChannelWidthSupported(channelWidth)) + operationalChannelWidths.insert(channelWidth); + } + mib->updateLocalHtCapabilities(modeSet, operationalChannelWidths, operationalHtSpatialStreamLimit); +} + void Ieee80211Mac::initializeRadioMode() { const char *initialRadioMode = par("initialRadioMode"); @@ -346,6 +354,30 @@ void Ieee80211Mac::receiveSignal(cComponent *source, simsignal_t signalID, intva } } +void Ieee80211Mac::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) +{ + Enter_Method("%s", cComponent::getSignalName(signalID)); + if (signalID == modesetChangedSignal && obj != modeSet) + applyModeSet(check_and_cast(obj)); +} + +void Ieee80211Mac::applyModeSet(const physicallayer::Ieee80211ModeSet *newModeSet) +{ + Enter_Method_Silent(); + modeSet = const_cast(newModeSet); + updateLocalHtCapabilities(); +} + +std::function Ieee80211Mac::saveModeSetState() +{ + Enter_Method_Silent(); + auto restoreMib = mib->saveHtState(); + return [this, state = std::make_tuple(modeSet), restoreMib]() mutable { + std::tie(modeSet) = std::move(state); + restoreMib(); + }; +} + void Ieee80211Mac::configureRadioMode(IRadio::RadioMode radioMode) { if (radio->getRadioMode() != radioMode) { diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h index 1114deaed00..3fb5c2ec704 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h @@ -21,6 +21,7 @@ #include "inet/linklayer/ieee80211/mac/coordinationfunction/Pcf.h" #include "inet/linklayer/ieee80211/mib/Ieee80211Mib.h" #include "inet/physicallayer/wireless/common/contract/packetlevel/IRadio.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.h" namespace inet { namespace ieee80211 { @@ -35,10 +36,13 @@ class Ieee80211MacHeader; * exact operation of the MAC depend on the plugged-in components (see IUpperMac, * IRx, ITx, IContention and other interface classes). */ -class INET_API Ieee80211Mac : public MacProtocolBase +class INET_API Ieee80211Mac : public MacProtocolBase, public physicallayer::IIeee80211ModeSetListener { public: static simsignal_t frameTransmissionOutcomeSignal; + virtual const physicallayer::Ieee80211ModeSet *getModeSet() const override { return modeSet; } + virtual std::function saveModeSetState() override; + virtual void applyModeSet(const physicallayer::Ieee80211ModeSet *modeSet) override; protected: FcsMode fcsMode; @@ -65,8 +69,10 @@ class INET_API Ieee80211Mac : public MacProtocolBase virtual int numInitStages() const override { return NUM_INIT_STAGES; } virtual void initialize(int) override; virtual void initializeRadioMode(); + void updateLocalHtCapabilities(); virtual void receiveSignal(cComponent *source, simsignal_t signalID, intval_t value, cObject *details) override; + virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) override; using MacProtocolBase::receiveSignal; virtual void configureRadioMode(physicallayer::IRadio::RadioMode radioMode); virtual void configureNetworkInterface() override; diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.ned b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.ned index 51f4198660a..b3ffdfb777e 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"); @@ -135,4 +135,3 @@ module Ieee80211Mac extends MacProtocolBase like IIeee80211Mac @display("p=250,200"); } } - diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc index 1f6ea0615cb..ec065a96cb6 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc @@ -7,6 +7,8 @@ #include "inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h" +#include + #include "inet/common/ModuleAccess.h" #include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" #include "inet/linklayer/ieee80211/mac/contract/IRx.h" @@ -125,13 +127,26 @@ void Dcaf::expectedChannelAccess(simtime_t time) void Dcaf::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) { Enter_Method("%s", cComponent::getSignalName(signalID)); + if (signalID == modesetChangedSignal && obj != modeSet) + applyModeSet(check_and_cast(obj)); +} - if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - calculateTimingParameters(); - } +void Dcaf::applyModeSet(const physicallayer::Ieee80211ModeSet *newModeSet) +{ + Enter_Method_Silent(); + modeSet = const_cast(newModeSet); + calculateTimingParameters(); } +std::function Dcaf::saveModeSetState() +{ + Enter_Method_Silent(); + return [this, state = std::make_tuple(modeSet, slotTime, sifs, ifs, eifs, cw, cwMin, cwMax)]() mutable { + std::tie(modeSet, slotTime, sifs, ifs, eifs, cw, cwMin, cwMax) = std::move(state); + }; +} + + } /* namespace ieee80211 */ } /* namespace inet */ diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h index 2fab0d312c8..eb15a2dbb7d 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h @@ -19,8 +19,11 @@ namespace ieee80211 { class INET_API Dcaf : public IChannelAccess, public IContention::ICallback, public IRecoveryProcedure::ICwCalculator, public ModeSetListener { + public: + virtual std::function saveModeSetState() override; + virtual void applyModeSet(const physicallayer::Ieee80211ModeSet *modeSet) override; + protected: - physicallayer::Ieee80211ModeSet *modeSet = nullptr; IContention *contention = nullptr; IChannelAccess::ICallback *callback = nullptr; diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc index e220635a876..0d8dff39627 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc @@ -7,6 +7,8 @@ #include "inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h" +#include + #include "inet/common/ModuleAccess.h" #include "inet/common/Simsignals.h" #include "inet/networklayer/common/NetworkInterface.h" @@ -189,13 +191,26 @@ int Edcaf::getCwMin(AccessCategory ac, int aCwMin) void Edcaf::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) { Enter_Method("%s", cComponent::getSignalName(signalID)); + if (signalID == modesetChangedSignal && obj != modeSet) + applyModeSet(check_and_cast(obj)); +} - if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - calculateTimingParameters(); - } +void Edcaf::applyModeSet(const physicallayer::Ieee80211ModeSet *newModeSet) +{ + Enter_Method_Silent(); + modeSet = const_cast(newModeSet); + calculateTimingParameters(); } +std::function Edcaf::saveModeSetState() +{ + Enter_Method_Silent(); + return [this, state = std::make_tuple(modeSet, slotTime, sifs, ifs, eifs, cw, cwMin, cwMax)]() mutable { + std::tie(modeSet, slotTime, sifs, ifs, eifs, cw, cwMin, cwMax) = std::move(state); + }; +} + + } // namespace ieee80211 } // namespace inet diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h index e737cad9162..8e25569caa0 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h @@ -30,6 +30,10 @@ namespace ieee80211 { */ class INET_API Edcaf : public IChannelAccess, public IContention::ICallback, public IRecoveryProcedure::ICwCalculator, public ModeSetListener { + public: + virtual std::function saveModeSetState() override; + virtual void applyModeSet(const physicallayer::Ieee80211ModeSet *modeSet) override; + protected: IContention *contention = nullptr; IChannelAccess::ICallback *callback = nullptr; diff --git a/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.cc b/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.cc index 15dd3838eb5..f84574d5447 100644 --- a/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.cc +++ b/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.cc @@ -7,6 +7,8 @@ #include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" +#include + #include "inet/common/ModuleAccess.h" #include "inet/common/Simsignals.h" #include "inet/networklayer/common/NetworkInterface.h" @@ -23,11 +25,25 @@ void ModeSetListener::initialize(int stage) void ModeSetListener::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) { Enter_Method("%s", cComponent::getSignalName(signalID)); + if (signalID == modesetChangedSignal && obj != modeSet) + applyModeSet(check_and_cast(obj)); +} - if (signalID == modesetChangedSignal) - modeSet = check_and_cast(obj); +void ModeSetListener::applyModeSet(const physicallayer::Ieee80211ModeSet *newModeSet) +{ + Enter_Method_Silent(); + modeSet = const_cast(newModeSet); } +std::function ModeSetListener::saveModeSetState() +{ + Enter_Method_Silent(); + return [this, state = std::make_tuple(modeSet)]() mutable { + std::tie(modeSet) = std::move(state); + }; +} + + } /* namespace ieee80211 */ } /* namespace inet */ diff --git a/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.h b/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.h index 05581613050..0a2ecc63001 100644 --- a/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.h +++ b/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.h @@ -10,12 +10,18 @@ #include "inet/common/SimpleModule.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.h" namespace inet { namespace ieee80211 { -class INET_API ModeSetListener : public SimpleModule, public cListener +class INET_API ModeSetListener : public SimpleModule, public cListener, public physicallayer::IIeee80211ModeSetListener { + public: + virtual const physicallayer::Ieee80211ModeSet *getModeSet() const override { return modeSet; } + virtual std::function saveModeSetState() override; + virtual void applyModeSet(const physicallayer::Ieee80211ModeSet *modeSet) override; + protected: physicallayer::Ieee80211ModeSet *modeSet = nullptr; diff --git a/src/inet/linklayer/ieee80211/mac/ratecontrol/AarfRateControl.cc b/src/inet/linklayer/ieee80211/mac/ratecontrol/AarfRateControl.cc index 0b2e47b79f7..f43245df0b0 100644 --- a/src/inet/linklayer/ieee80211/mac/ratecontrol/AarfRateControl.cc +++ b/src/inet/linklayer/ieee80211/mac/ratecontrol/AarfRateControl.cc @@ -14,6 +14,16 @@ using namespace inet::physicallayer; Define_Module(AarfRateControl); +std::function AarfRateControl::saveModeSetState() +{ + Enter_Method_Silent(); + auto restoreBase = ModeSetListener::saveModeSetState(); + return [this, savedStations = stations, restoreBase]() mutable { + stations.swap(savedStations); + restoreBase(); + }; +} + void AarfRateControl::initialize(int stage) { RateControlBase::initialize(stage); diff --git a/src/inet/linklayer/ieee80211/mac/ratecontrol/AarfRateControl.h b/src/inet/linklayer/ieee80211/mac/ratecontrol/AarfRateControl.h index f9ddd7f5222..1fa323b11f5 100644 --- a/src/inet/linklayer/ieee80211/mac/ratecontrol/AarfRateControl.h +++ b/src/inet/linklayer/ieee80211/mac/ratecontrol/AarfRateControl.h @@ -50,6 +50,7 @@ class INET_API AarfRateControl : public RateControlBase virtual void increaseRateIfTimerIsExpired(State& state); public: + virtual std::function saveModeSetState() override; virtual const physicallayer::IIeee80211Mode *getRate(const MacAddress& receiverAddress) override; virtual void frameTransmitted(Packet *frame, int retryCount, bool isSuccessful, bool isGivenUp) override; virtual void frameReceived(Packet *frame) override; diff --git a/src/inet/linklayer/ieee80211/mac/ratecontrol/OnoeRateControl.cc b/src/inet/linklayer/ieee80211/mac/ratecontrol/OnoeRateControl.cc index a49f35d0c30..be2acddff39 100644 --- a/src/inet/linklayer/ieee80211/mac/ratecontrol/OnoeRateControl.cc +++ b/src/inet/linklayer/ieee80211/mac/ratecontrol/OnoeRateControl.cc @@ -14,6 +14,16 @@ using namespace inet::physicallayer; Define_Module(OnoeRateControl); +std::function OnoeRateControl::saveModeSetState() +{ + Enter_Method_Silent(); + auto restoreBase = ModeSetListener::saveModeSetState(); + return [this, savedStations = stations, restoreBase]() mutable { + stations.swap(savedStations); + restoreBase(); + }; +} + void OnoeRateControl::initialize(int stage) { RateControlBase::initialize(stage); diff --git a/src/inet/linklayer/ieee80211/mac/ratecontrol/OnoeRateControl.h b/src/inet/linklayer/ieee80211/mac/ratecontrol/OnoeRateControl.h index ecee1d849e4..5995abb8c61 100644 --- a/src/inet/linklayer/ieee80211/mac/ratecontrol/OnoeRateControl.h +++ b/src/inet/linklayer/ieee80211/mac/ratecontrol/OnoeRateControl.h @@ -48,6 +48,7 @@ class INET_API OnoeRateControl : public RateControlBase virtual void computeModeIfTimerIsExpired(State& state); public: + virtual std::function saveModeSetState() override; virtual const physicallayer::IIeee80211Mode *getRate(const MacAddress& receiverAddress) override; virtual void frameTransmitted(Packet *frame, int retryCount, bool isSuccessful, bool isGivenUp) override; virtual void frameReceived(Packet *frame) override; diff --git a/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.cc b/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.cc index 551a31c4c9e..e91291f31ed 100644 --- a/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.cc +++ b/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.cc @@ -64,10 +64,16 @@ void RateControlBase::receiveSignal(cComponent *source, simsignal_t signalID, cO { Enter_Method("%s", cComponent::getSignalName(signalID)); - if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - resetRateControl(); - } + if (signalID == modesetChangedSignal && obj != modeSet) + applyModeSet(check_and_cast(obj)); +} + +void RateControlBase::applyModeSet(const Ieee80211ModeSet *newModeSet) +{ + Enter_Method_Silent(); + modeSet = const_cast(newModeSet); + getInitialMode(); // Validate fixed initial rates even before the first peer is used. + resetRateControl(); } } /* namespace ieee80211 */ diff --git a/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.h b/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.h index 2a61b5028fa..9ceb1e18d54 100644 --- a/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.h +++ b/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.h @@ -18,6 +18,7 @@ namespace ieee80211 { class INET_API RateControlBase : public ModeSetListener, public IRateControl { public: + virtual void applyModeSet(const physicallayer::Ieee80211ModeSet *modeSet) override; static simsignal_t datarateChangedSignal; protected: diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.cc index 6408b443687..c43806f9f60 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.cc @@ -14,6 +14,29 @@ namespace ieee80211 { using namespace inet::physicallayer; +const IIeee80211Mode *selectGroupAddressedMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *requestedMode) +{ + // IEEE Std 802.11-2024, 10.6.5.1 and 10.6.5.4. The model advertises + // mandatory legacy operational modes as its BSS basic legacy rate set. + bool hasMandatoryLegacyMode = false; + const IIeee80211Mode *legacyMode = nullptr; + for (const auto *candidate : modeSet->getLegacyOperationalModes()) { + if (!modeSet->getIsMandatory(candidate)) + continue; + hasMandatoryLegacyMode = true; + if (candidate->getDataMode()->getNetBitrate() > requestedMode->getDataMode()->getNetBitrate()) + continue; + if (candidate == requestedMode) + return candidate; + if (legacyMode == nullptr || candidate->getDataMode()->getNetBitrate() > legacyMode->getDataMode()->getNetBitrate()) + legacyMode = candidate; + } + if (legacyMode == nullptr && hasMandatoryLegacyMode) + throw cRuntimeError("No mandatory legacy mode at or below requested group-addressed mode '%s' in mode set '%s'", + requestedMode->getName(), modeSet->getName()); + return legacyMode != nullptr ? legacyMode : requestedMode; +} + namespace { static const IIeee80211Mode *getLegacyFallback(const Ieee80211ModeSet *modeSet, @@ -68,6 +91,11 @@ static bool isCompatibleHtMode(const IIeee80211Mode *mode, const Ieee80211Mib::P bandwidth > negotiated.operation.operatingChannelWidth) return false; + // IEEE Std 802.11-2024, 19.1.1 and 19.1.4: an HT-greenfield PPDU may only + // be transmitted to a receiver that advertised HT-greenfield support. + if (mode->isHtGreenfield() && !receiverCapabilities.receiverGreenfield) + return false; + // IEEE Std 802.11-2024, 10.17 and Table 9-224: a short guard interval // is usable only when the receiver advertised it for this channel width. if (mode->isHtShortGuardInterval()) { @@ -111,20 +139,26 @@ const IIeee80211Mode *selectPeerCompatibleMode(const Ieee80211ModeSet *modeSet, return mode; if (modeSet == nullptr) throw cRuntimeError("Cannot select a peer-compatible HT mode without an IEEE 802.11 mode set"); - if (!modeSet->containsMode(mode)) - throw cRuntimeError("HT mode '%s' is not contained in IEEE 802.11 mode set '%s'", + if (!modeSet->supportsMode(mode)) + throw cRuntimeError("HT mode '%s' is not supported by IEEE 802.11 mode set '%s'", mode->getName(), modeSet->getName()); if (peerHtState == nullptr || !peerHtState->valid || !peerHtState->negotiatedCapabilities.localTxPeerRx.valid) return getLegacyFallback(modeSet, mode, peerAddress); - if (isCompatibleHtMode(mode, peerHtState)) - return mode; + + const IIeee80211Mode *effectiveMode = mode; + if (effectiveMode->isHtGreenfield() && !peerHtState->negotiatedCapabilities.localTxPeerRx.receiverGreenfield) + effectiveMode = modeSet->findHtMixedMode(mode); + if (effectiveMode != nullptr && isCompatibleHtMode(effectiveMode, peerHtState)) + return effectiveMode; auto candidateBitrate = mode->getDataMode()->getNetBitrate(); const IIeee80211Mode *bestMode = nullptr; for (int i = 0; i < modeSet->getNumModes(); i++) { const auto *candidate = modeSet->getMode(i); - if (candidate->getHtMcsIndex() < 0 || + if (candidate->isHtGreenfield() && !peerHtState->negotiatedCapabilities.localTxPeerRx.receiverGreenfield) + candidate = modeSet->findHtMixedMode(candidate); + if (candidate == nullptr || candidate->getHtMcsIndex() < 0 || candidate->getDataMode()->getNetBitrate() > candidateBitrate || !isCompatibleHtMode(candidate, peerHtState)) continue; diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h b/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h index ba5eaebf764..d00ea491dbc 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h +++ b/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h @@ -14,6 +14,14 @@ namespace inet { namespace ieee80211 { +/** + * Selects the fastest mandatory legacy mode at or below the requested bitrate, + * preserving an exact mandatory request. Throws if mandatory legacy modes exist + * but none meets the bound; preserves the request if that mandatory set is empty. + */ +INET_API const physicallayer::IIeee80211Mode *selectGroupAddressedMode( + const physicallayer::Ieee80211ModeSet *modeSet, const physicallayer::IIeee80211Mode *requestedMode); + /** * Selects a mode that is compatible with the negotiated receive capabilities * of a peer. Non-HT modes are returned unchanged. A null peer state denotes diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc index add5ec700f1..788a6bab198 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc @@ -7,6 +7,8 @@ #include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" +#include + #include "inet/common/ModuleAccess.h" #include "inet/common/Simsignals.h" #include "inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h" @@ -18,6 +20,23 @@ namespace ieee80211 { using namespace inet::physicallayer; +static const IIeee80211Mode *resolveConfiguredResponseCtsFrameMode(const Ieee80211ModeSet *modeSet, double bitrate, Hz bandwidth, int numSpatialStreams, simtime_t guardInterval, const char *modulePath) +{ + if (bitrate == -1) + return nullptr; + try { + auto result = modeSet->getMode(bps(bitrate), bandwidth, numSpatialStreams, guardInterval, true); + if (modeSet->isHtOperationSupported() && result->getHtMcsIndex() < 0) + throw cRuntimeError("legacy mode '%s' is not selectable for HT CTS responses", result->getName()); + return result; + } + 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) @@ -27,20 +46,9 @@ void QosRateSelection::initialize(int stage) mib.reference(this, "mibModule", true); 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()); + updateModes(); } } @@ -56,7 +64,7 @@ void QosRateSelection::ensurePerReceiverModesResolved() throw cRuntimeError("dataFrameBitratePerReceiver: cannot resolve receiver interface module path '%s'", path.c_str()); auto networkInterface = check_and_cast(module); try { - auto mode = modeSet->getMode(bps(value.doubleValueInUnit("bps")), Hz(par("dataFrameBandwidth")), par("dataFrameNumSpatialStreams")); + auto mode = modeSet->getMode(bps(value.doubleValueInUnit("bps")), Hz(par("dataFrameBandwidth")), par("dataFrameNumSpatialStreams"), par("dataFrameGuardInterval")); perReceiverDataFrameMode[networkInterface->getMacAddress()] = mode; } catch (const cRuntimeError& e) { @@ -65,6 +73,39 @@ void QosRateSelection::ensurePerReceiverModesResolved() } } +void QosRateSelection::updateModes() +{ + if (modeSet == nullptr) + return; + double multicastFrameBitrate = par("multicastFrameBitrate"); + auto newMulticastFrameMode = multicastFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(multicastFrameBitrate)); + double dataFrameBitrate = par("dataFrameBitrate"); + auto newDataFrameMode = dataFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(dataFrameBitrate), Hz(par("dataFrameBandwidth")), par("dataFrameNumSpatialStreams"), par("dataFrameGuardInterval")); + double mgmtFrameBitrate = par("mgmtFrameBitrate"); + auto newMgmtFrameMode = mgmtFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(mgmtFrameBitrate)); + double controlFrameBitrate = par("controlFrameBitrate"); + auto newControlFrameMode = controlFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(controlFrameBitrate)); + double responseAckFrameBitrate = par("responseAckFrameBitrate"); + auto newResponseAckFrameMode = responseAckFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(responseAckFrameBitrate), Hz(par("responseAckFrameBandwidth")), par("responseAckFrameNumSpatialStreams"), par("responseAckFrameGuardInterval"), true); + double responseBlockAckFrameBitrate = par("responseBlockAckFrameBitrate"); + auto newResponseBlockAckFrameMode = responseBlockAckFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(responseBlockAckFrameBitrate), Hz(par("responseBlockAckFrameBandwidth")), par("responseBlockAckFrameNumSpatialStreams"), par("responseBlockAckFrameGuardInterval"), true); + double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); + auto newResponseCtsFrameMode = resolveConfiguredResponseCtsFrameMode(modeSet, responseCtsFrameBitrate, Hz(par("responseCtsFrameBandwidth")), par("responseCtsFrameNumSpatialStreams"), par("responseCtsFrameGuardInterval"), getFullPath().c_str()); + auto newFastestMandatoryMode = modeSet->getFastestMandatoryMode(); + + multicastFrameMode = newMulticastFrameMode; + dataFrameMode = newDataFrameMode; + mgmtFrameMode = newMgmtFrameMode; + controlFrameMode = newControlFrameMode; + responseAckFrameMode = newResponseAckFrameMode; + responseBlockAckFrameMode = newResponseBlockAckFrameMode; + responseCtsFrameMode = newResponseCtsFrameMode; + fastestMandatoryMode = newFastestMandatoryMode; + lastTransmittedFrameMode.clear(); + perReceiverDataFrameMode.clear(); + perReceiverResolved = false; +} + const IIeee80211Mode *QosRateSelection::getMode(Packet *packet, const Ptr& header) { const auto& modeReqTag = packet->findTag(); @@ -96,40 +137,27 @@ bool QosRateSelection::isControlResponseFrame(const Ptr& dataOrMgmtHeader) { - // TODO BSSBasicRateSet, alternate rate - auto mode = getMode(packet, dataOrMgmtHeader); - ASSERT(modeSet->containsMode(mode)); - const IIeee80211Mode *responseMode; - if (!responseAckFrameMode) { - if (modeSet->getIsMandatory(mode)) - responseMode = mode; - else if (auto slowerMode = modeSet->getSlowerMandatoryMode(mode)) - responseMode = slowerMode; - else - throw cRuntimeError("Mandatory mode not found"); + const IIeee80211Mode *responseMode = nullptr; + if (responseAckFrameMode) + responseMode = modeSet->getNonHtControlResponseMode(responseAckFrameMode, false); + else { + 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. + responseMode = modeSet->getMandatoryControlResponseMode(mode); } - else - responseMode = responseAckFrameMode; - return getPeerCompatibleMode(dataOrMgmtHeader->getTransmitterAddress(), responseMode); + return dataOrMgmtHeader ? getPeerCompatibleMode(dataOrMgmtHeader->getTransmitterAddress(), responseMode) : responseMode; } const IIeee80211Mode *QosRateSelection::computeResponseCtsFrameMode(Packet *packet, const Ptr& rtsFrame) { - // TODO BSSBasicRateSet, alternate rate auto mode = getMode(packet, rtsFrame); - ASSERT(modeSet->containsMode(mode)); - const IIeee80211Mode *responseMode; - if (!responseCtsFrameMode) { - if (modeSet->getIsMandatory(mode)) - responseMode = mode; - else if (auto slowerMode = modeSet->getSlowerMandatoryMode(mode)) - responseMode = slowerMode; - else - throw cRuntimeError("Mandatory mode not found"); - } - else - responseMode = responseCtsFrameMode; - return getPeerCompatibleMode(rtsFrame->getTransmitterAddress(), responseMode); + 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. + auto responseMode = modeSet->getControlResponseMode(mode, responseCtsFrameMode); + return rtsFrame ? getPeerCompatibleMode(rtsFrame->getTransmitterAddress(), responseMode) : responseMode; } // @@ -140,19 +168,32 @@ const IIeee80211Mode *QosRateSelection::computeResponseCtsFrameMode(Packet *pack // const IIeee80211Mode *QosRateSelection::computeResponseBlockAckFrameMode(Packet *packet, const Ptr& blockAckReq) { - if (dynamicPtrCast(blockAckReq)) { - auto mode = responseBlockAckFrameMode ? responseBlockAckFrameMode : getMode(packet, blockAckReq); - return getPeerCompatibleMode(blockAckReq->getTransmitterAddress(), mode); - } - else + if (!dynamicPtrCast(blockAckReq)) throw cRuntimeError("Unknown BlockAckReq frame type"); + const IIeee80211Mode *responseMode = nullptr; + if (responseBlockAckFrameMode) + responseMode = modeSet->getNonHtControlResponseMode(responseBlockAckFrameMode, false); + else { + 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. + responseMode = modeSet->getNonHtControlResponseMode(mode, false); + } + return blockAckReq ? getPeerCompatibleMode(blockAckReq->getTransmitterAddress(), responseMode) : responseMode; } const IIeee80211Mode *QosRateSelection::computeDataOrMgmtFrameMode(const Ptr& dataOrMgmtHeader) { + if (dataOrMgmtHeader->getReceiverAddress().isMulticast()) { + const auto *requestedMode = multicastFrameMode; + if (requestedMode == nullptr) + requestedMode = dynamicPtrCast(dataOrMgmtHeader) ? dataFrameMode : mgmtFrameMode; + return selectGroupAddressedMode(modeSet, requestedMode != nullptr ? requestedMode : fastestMandatoryMode); + } // Per-receiver override for originated unicast data frames (see dataFrameBitratePerReceiver). // Wins over the interface-wide dataFrameMode / rate control; group-addressed and management - // frames are left to the existing rules below. + // frames were handled above. if (dynamicPtrCast(dataOrMgmtHeader) && !dataOrMgmtHeader->getReceiverAddress().isMulticast()) { ensurePerReceiverModesResolved(); auto it = perReceiverDataFrameMode.find(dataOrMgmtHeader->getReceiverAddress()); @@ -163,46 +204,9 @@ const IIeee80211Mode *QosRateSelection::computeDataOrMgmtFrameMode(const PtrgetReceiverAddress(), dataFrameMode); if (dynamicPtrCast(dataOrMgmtHeader) && mgmtFrameMode) return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), mgmtFrameMode); - // This subclause describes the rate selection rules for group addressed data and management frames, excluding - // the following: - // — Non-STBC Beacon and non-STBC PSMP frames - // — STBC group addressed data and management frames - // — Data frames located in an FMS stream (see 10.23.7) - if (dataOrMgmtHeader->getReceiverAddress().isMulticast()) { - // If the BSSBasicRateSet parameter is not empty, a data or management frame (excluding the frames listed - // above) with a group address in the Address 1 field shall be transmitted in a non-HT PPDU using one of the - // rates included in the BSSBasicRateSet parameter or the rate chosen by the AP, described in 10.23.7, if the data - // frames are part of an FMS stream. - // TODO BSSBasicRateSet - // If the BSSBasicRateSet parameter is empty and the BSSBasicMCSSet parameter is not empty, the frame shall - // be transmitted in an HT PPDU using one of the MCSs included in the BSSBasicMCSSet parameter. - - // If both the BSSBasicRateSet parameter and the BSSBasicMCSSet parameter are empty (e.g., a scanning STA - // that is not yet associated with a BSS), the frame shall be transmitted in a non-HT PPDU using one of the - // mandatory PHY rates. - // The rate control is not consulted for these frames. It adapts to the feedback of one - // peer, and a group-addressed frame has no peer: it is never acknowledged, so nothing - // would ever correct a rate chosen for it. - return fastestMandatoryMode; - } - // A data or management frame not identified in 9.7.5.1 through 9.7.5.5 shall be sent using any data rate or MCS - // subject to the following constraints: - // — A STA shall not transmit a frame using a rate or MCS that is not supported by the receiver STA or - // STAs, as reported in any Supported Rates element, Extended Supported Rates element, or - // Supported MCS field in management frames transmitted by the receiver STA. - // — A STA shall not transmit a frame using a value for the CH_BANDWIDTH parameter of the - // TXVECTOR that is not supported by the receiver STA. - // — A STA shall not initiate transmission of a frame at a data rate higher than the greatest rate in the - // OperationalRateSet or the HTOperationalMCSset, which are parameters of the MLME- - // JOIN.request primitive. - else { - // TODO Supported Rates element, Extended Supported Rates element - // TODO OperationalRateSet or the HTOperationalMCSset - if (dataOrMgmtRateControl) - return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), dataOrMgmtRateControl->getRate(dataOrMgmtHeader->getReceiverAddress())); - else - return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), fastestMandatoryMode); - } + if (dataOrMgmtRateControl) + return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), dataOrMgmtRateControl->getRate(dataOrMgmtHeader->getReceiverAddress())); + return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), fastestMandatoryMode); } const IIeee80211Mode *QosRateSelection::computeControlFrameMode(const Ptr& header, TxopProcedure *txopProcedure) @@ -273,11 +277,29 @@ const IIeee80211Mode *QosRateSelection::computeMode(Packet *packet, const Ptr(obj)); +} - if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - fastestMandatoryMode = modeSet->getFastestMandatoryMode(); - } +void QosRateSelection::applyModeSet(const physicallayer::Ieee80211ModeSet *newModeSet) +{ + Enter_Method_Silent(); + modeSet = const_cast(newModeSet); + updateModes(); + if (getSimulation()->getContextType() != CTX_INITIALIZE) + ensurePerReceiverModesResolved(); +} + +std::function QosRateSelection::saveModeSetState() +{ + Enter_Method_Silent(); + return [this, state = std::make_tuple(modeSet, fastestMandatoryMode, multicastFrameMode, dataFrameMode, mgmtFrameMode, + controlFrameMode, responseAckFrameMode, responseCtsFrameMode, responseBlockAckFrameMode, + lastTransmittedFrameMode, perReceiverDataFrameMode, perReceiverResolved)]() mutable { + std::tie(modeSet, fastestMandatoryMode, multicastFrameMode, dataFrameMode, mgmtFrameMode, + controlFrameMode, responseAckFrameMode, responseCtsFrameMode, responseBlockAckFrameMode, + lastTransmittedFrameMode, perReceiverDataFrameMode, perReceiverResolved) = std::move(state); + }; } void QosRateSelection::frameTransmitted(Packet *packet, const Ptr& header) @@ -288,11 +310,14 @@ void QosRateSelection::frameTransmitted(Packet *packet, const PtrgetHtMcsIndex() < 0) + // IEEE Std 802.11-2024, 10.6.5.8: Peer compatibility filtering is currently + // supported for HT (802.11n) modes using negotiated PeerHtState. Non-HT + // modes (legacy and VHT) return unchanged because VHT capability negotiation + // (VHT Capabilities/Operation elements) is not yet modeled in MIB. + if (mode == nullptr || peerAddress.isMulticast() || !mib || mib->mode == Ieee80211Mib::INDEPENDENT || mode->getHtMcsIndex() < 0) return mode; return selectPeerCompatibleMode(modeSet, mib->findPeerHtState(peerAddress), mode, peerAddress); } } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h index 569ef742d37..9914fe825b5 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h @@ -32,11 +32,14 @@ namespace ieee80211 { */ class INET_API QosRateSelection : public IQosRateSelection, public ModeSetListener { + public: + virtual std::function saveModeSetState() override; + virtual void applyModeSet(const physicallayer::Ieee80211ModeSet *modeSet) override; + protected: IRateControl *dataOrMgmtRateControl = nullptr; ModuleRefByPar mib; - const physicallayer::Ieee80211ModeSet *modeSet = nullptr; std::map lastTransmittedFrameMode; // originator frame modes @@ -59,7 +62,7 @@ class INET_API QosRateSelection : public IQosRateSelection, public ModeSetListen virtual int numInitStages() const override { return NUM_INIT_STAGES; } virtual void initialize(int stage) override; virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) override; - + virtual void updateModes(); // Builds perReceiverDataFrameMode on first use. Deferred out of initialize() because peer // MAC addresses are assigned during INITSTAGE_LINK_LAYER with undefined intra-stage module // ordering; the first transmitted data frame occurs after all init stages, so this is race-free. @@ -92,4 +95,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 35bf0e1ace5..eb62ded5346 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned @@ -27,13 +27,37 @@ simple QosRateSelection extends SimpleModule double multicastFrameBitrate @unit(bps) = default(-1bps); + // Configured response rates must identify one selectable PHY mode. Use the matching + // Bandwidth, NumSpatialStreams, and GuardInterval qualifiers to disambiguate rates. + // Unspecified qualifiers are wildcards; ambiguous matches are rejected. Qualifiers + // are ignored for an automatic (-1bps) response. GI must match the modeled mode. + // -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); + double responseAckFrameBandwidth @unit(Hz) = default(nan Hz); + int responseAckFrameNumSpatialStreams = default(-1); + double responseAckFrameGuardInterval @unit(s) = default(-1s); + // -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); + double responseBlockAckFrameBandwidth @unit(Hz) = default(nan Hz); + int responseBlockAckFrameNumSpatialStreams = default(-1); + double responseBlockAckFrameGuardInterval @unit(s) = default(-1s); + // -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 responseCtsFrameBandwidth @unit(Hz) = default(nan Hz); + int responseCtsFrameNumSpatialStreams = default(-1); + double responseCtsFrameGuardInterval @unit(s) = default(-1s); double dataFrameBitrate @unit(bps) = default(-1bps); // Fastest - double dataFrameBandwidth @unit(Hz) = default(nan Hz); // Unspecified by default - int dataFrameNumSpatialStreams = default(-1); // Unspecified by default + double dataFrameBandwidth @unit(Hz) = default(nan Hz); // Explicit mode qualifier when dataFrameBitrate is specified + int dataFrameNumSpatialStreams = default(-1); // Explicit mode qualifier when dataFrameBitrate is specified + double dataFrameGuardInterval @unit(s) = default(-1s); // Explicit mode qualifier when dataFrameBitrate is specified; negative means unspecified, otherwise it must equal the selected PHY mode's modeled GI // Per-receiver unicast data-frame rate. Keys are peer interface module paths (e.g. // "host1.wlan[0]"), resolved to MAC addresses at run time; values are bitrates (bps). diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc index 9d28a5142cd..b901e505593 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc @@ -7,6 +7,8 @@ #include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h" +#include + #include "inet/common/ModuleAccess.h" #include "inet/common/Simsignals.h" #include "inet/linklayer/ieee80211/mac/contract/IRateControl.h" @@ -22,6 +24,23 @@ namespace ieee80211 { using namespace inet::physicallayer; +static const IIeee80211Mode *resolveConfiguredResponseCtsFrameMode(const Ieee80211ModeSet *modeSet, double bitrate, Hz bandwidth, int numSpatialStreams, simtime_t guardInterval, const char *modulePath) +{ + if (bitrate == -1) + return nullptr; + try { + auto result = modeSet->getMode(bps(bitrate), bandwidth, numSpatialStreams, guardInterval, true); + if (modeSet->isHtOperationSupported() && result->getHtMcsIndex() < 0) + throw cRuntimeError("legacy mode '%s' is not selectable for HT CTS responses", result->getName()); + return result; + } + 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) @@ -32,19 +51,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()); + updateModes(); // WATCH(dataOrMgmtRateControl); // WATCH(*((cObject**)&fastestMandatoryMode)); @@ -75,7 +84,7 @@ void RateSelection::ensurePerReceiverModesResolved() throw cRuntimeError("dataFrameBitratePerReceiver: cannot resolve receiver interface module path '%s'", path.c_str()); auto networkInterface = check_and_cast(module); try { - auto mode = modeSet->getMode(bps(value.doubleValueInUnit("bps")), Hz(par("dataFrameBandwidth")), par("dataFrameNumSpatialStreams")); + auto mode = modeSet->getMode(bps(value.doubleValueInUnit("bps")), Hz(par("dataFrameBandwidth")), par("dataFrameNumSpatialStreams"), par("dataFrameGuardInterval")); perReceiverDataFrameMode[networkInterface->getMacAddress()] = mode; } catch (const cRuntimeError& e) { @@ -84,6 +93,38 @@ void RateSelection::ensurePerReceiverModesResolved() } } +void RateSelection::updateModes() +{ + if (modeSet == nullptr) + return; + double multicastFrameBitrate = par("multicastFrameBitrate"); + auto newMulticastFrameMode = multicastFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(multicastFrameBitrate)); + double dataFrameBitrate = par("dataFrameBitrate"); + auto newDataFrameMode = dataFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(dataFrameBitrate), Hz(par("dataFrameBandwidth")), par("dataFrameNumSpatialStreams"), par("dataFrameGuardInterval")); + double mgmtFrameBitrate = par("mgmtFrameBitrate"); + auto newMgmtFrameMode = mgmtFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(mgmtFrameBitrate)); + double controlFrameBitrate = par("controlFrameBitrate"); + auto newControlFrameMode = controlFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(controlFrameBitrate)); + double responseAckFrameBitrate = par("responseAckFrameBitrate"); + auto newResponseAckFrameMode = responseAckFrameBitrate == -1 ? nullptr : modeSet->getMode(bps(responseAckFrameBitrate), Hz(par("responseAckFrameBandwidth")), par("responseAckFrameNumSpatialStreams"), par("responseAckFrameGuardInterval"), true); + double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); + auto newResponseCtsFrameMode = resolveConfiguredResponseCtsFrameMode(modeSet, responseCtsFrameBitrate, Hz(par("responseCtsFrameBandwidth")), par("responseCtsFrameNumSpatialStreams"), par("responseCtsFrameGuardInterval"), getFullPath().c_str()); + auto newFastestMandatoryMode = modeSet->getFastestMandatoryMode(); + + // Commit only after every configured mode has been resolved, so a failed + // runtime operation-mode change leaves the previous state intact. + multicastFrameMode = newMulticastFrameMode; + dataFrameMode = newDataFrameMode; + mgmtFrameMode = newMgmtFrameMode; + controlFrameMode = newControlFrameMode; + responseAckFrameMode = newResponseAckFrameMode; + responseCtsFrameMode = newResponseCtsFrameMode; + fastestMandatoryMode = newFastestMandatoryMode; + lastTransmittedFrameMode.clear(); + perReceiverDataFrameMode.clear(); + perReceiverResolved = false; +} + const IIeee80211Mode *RateSelection::getMode(Packet *packet, const Ptr& header) { const auto& modeReqTag = packet->findTag(); @@ -103,26 +144,30 @@ 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. + const IIeee80211Mode *responseMode = nullptr; if (responseAckFrameMode) - return getPeerCompatibleMode(dataOrMgmtHeader->getTransmitterAddress(), responseAckFrameMode); + responseMode = modeSet->getNonHtControlResponseMode(responseAckFrameMode, false); else { auto mode = getMode(packet, dataOrMgmtHeader); - ASSERT(modeSet->containsMode(mode)); - auto responseMode = modeSet->getIsMandatory(mode) ? mode : modeSet->getSlowerMandatoryMode(mode); // TODO BSSBasicRateSet - return getPeerCompatibleMode(dataOrMgmtHeader->getTransmitterAddress(), responseMode); + 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. + responseMode = modeSet->getMandatoryControlResponseMode(mode); } + return dataOrMgmtHeader ? getPeerCompatibleMode(dataOrMgmtHeader->getTransmitterAddress(), responseMode) : responseMode; } const IIeee80211Mode *RateSelection::computeResponseCtsFrameMode(Packet *packet, const Ptr& rtsFrame) { - if (responseCtsFrameMode) - return getPeerCompatibleMode(rtsFrame->getTransmitterAddress(), responseCtsFrameMode); - else { - auto mode = getMode(packet, rtsFrame); - ASSERT(modeSet->containsMode(mode)); - auto responseMode = modeSet->getIsMandatory(mode) ? mode : modeSet->getSlowerMandatoryMode(mode); // TODO BSSBasicRateSet - return getPeerCompatibleMode(rtsFrame->getTransmitterAddress(), responseMode); - } + 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. + auto responseMode = modeSet->getControlResponseMode(mode, responseCtsFrameMode); + return rtsFrame ? getPeerCompatibleMode(rtsFrame->getTransmitterAddress(), responseMode) : responseMode; } // 802.11-1999 Std. @@ -140,28 +185,28 @@ const IIeee80211Mode *RateSelection::computeResponseCtsFrameMode(Packet *packet, // const IIeee80211Mode *RateSelection::computeDataOrMgmtFrameMode(const Ptr& dataOrMgmtHeader) { + if (dataOrMgmtHeader->getReceiverAddress().isMulticast()) { + const auto *requestedMode = multicastFrameMode; + if (requestedMode == nullptr) + requestedMode = dynamicPtrCast(dataOrMgmtHeader) ? dataFrameMode : mgmtFrameMode; + return selectGroupAddressedMode(modeSet, requestedMode != nullptr ? requestedMode : fastestMandatoryMode); + } // Per-receiver override for originated unicast data frames (see dataFrameBitratePerReceiver). // Wins over the interface-wide dataFrameMode / rate control; group-addressed and management - // frames are left to the existing rules below. + // frames were handled above. if (dynamicPtrCast(dataOrMgmtHeader) && !dataOrMgmtHeader->getReceiverAddress().isMulticast()) { ensurePerReceiverModesResolved(); auto it = perReceiverDataFrameMode.find(dataOrMgmtHeader->getReceiverAddress()); if (it != perReceiverDataFrameMode.end()) return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), it->second); } - if (dataOrMgmtHeader->getReceiverAddress().isMulticast() && multicastFrameMode) - return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), multicastFrameMode); if (dynamicPtrCast(dataOrMgmtHeader) && dataFrameMode) return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), dataFrameMode); if (dynamicPtrCast(dataOrMgmtHeader) && mgmtFrameMode) return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), mgmtFrameMode); - // Rate control adapts to the feedback of one peer, and a group-addressed frame has no peer: - // it is never acknowledged, so nothing would ever correct a rate chosen for it. Group-addressed - // frames therefore take a mandatory rate, as the clause above requires. - if (dataOrMgmtRateControl && !dataOrMgmtHeader->getReceiverAddress().isMulticast()) + if (dataOrMgmtRateControl) return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), dataOrMgmtRateControl->getRate(dataOrMgmtHeader->getReceiverAddress())); - else - return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), fastestMandatoryMode); + return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), fastestMandatoryMode); } // 802.11-1999 Std. @@ -187,11 +232,29 @@ const IIeee80211Mode *RateSelection::computeMode(Packet *packet, const Ptr(obj)); +} - if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - fastestMandatoryMode = modeSet->getFastestMandatoryMode(); - } +void RateSelection::applyModeSet(const physicallayer::Ieee80211ModeSet *newModeSet) +{ + Enter_Method_Silent(); + modeSet = const_cast(newModeSet); + updateModes(); + if (getSimulation()->getContextType() != CTX_INITIALIZE) + ensurePerReceiverModesResolved(); +} + +std::function RateSelection::saveModeSetState() +{ + Enter_Method_Silent(); + return [this, state = std::make_tuple(modeSet, fastestMandatoryMode, multicastFrameMode, dataFrameMode, mgmtFrameMode, + controlFrameMode, responseAckFrameMode, responseCtsFrameMode, + lastTransmittedFrameMode, perReceiverDataFrameMode, perReceiverResolved)]() mutable { + std::tie(modeSet, fastestMandatoryMode, multicastFrameMode, dataFrameMode, mgmtFrameMode, + controlFrameMode, responseAckFrameMode, responseCtsFrameMode, + lastTransmittedFrameMode, perReceiverDataFrameMode, perReceiverResolved) = std::move(state); + }; } void RateSelection::frameTransmitted(Packet *packet, const Ptr& header) @@ -221,11 +284,14 @@ void RateSelection::emitDatarateSelected(cComponent *emitter, const PtrgetHtMcsIndex() < 0) + // IEEE Std 802.11-2024, 10.6.5.8: Peer compatibility filtering is currently + // supported for HT (802.11n) modes using negotiated PeerHtState. Non-HT + // modes (legacy and VHT) return unchanged because VHT capability negotiation + // (VHT Capabilities/Operation elements) is not yet modeled in MIB. + if (mode == nullptr || peerAddress.isMulticast() || !mib || mib->mode == Ieee80211Mib::INDEPENDENT || mode->getHtMcsIndex() < 0) return mode; return selectPeerCompatibleMode(modeSet, mib->findPeerHtState(peerAddress), mode, peerAddress); } } // namespace ieee80211 } // namespace inet - diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h index 69441331453..9a3ba460e44 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h @@ -14,6 +14,7 @@ #include "inet/linklayer/ieee80211/mac/contract/IRateSelection.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" #include "inet/linklayer/ieee80211/mib/Ieee80211Mib.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.h" namespace inet { namespace ieee80211 { @@ -31,8 +32,13 @@ namespace ieee80211 { * 9.7.6.4 Rate selection for control frames that are not control response frames * 9.7.6.5 Rate selection for control response frames */ -class INET_API RateSelection : public IRateSelection, public SimpleModule, public cListener // FIXME +class INET_API RateSelection : public IRateSelection, public SimpleModule, public cListener, public physicallayer::IIeee80211ModeSetListener // FIXME { + public: + virtual const physicallayer::Ieee80211ModeSet *getModeSet() const override { return modeSet; } + virtual std::function saveModeSetState() override; + virtual void applyModeSet(const physicallayer::Ieee80211ModeSet *modeSet) override; + protected: IRateControl *dataOrMgmtRateControl = nullptr; ModuleRefByPar mib; @@ -59,7 +65,7 @@ class INET_API RateSelection : public IRateSelection, public SimpleModule, publi virtual int numInitStages() const override { return NUM_INIT_STAGES; } virtual void initialize(int stage) override; virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) override; - + virtual void updateModes(); // Builds perReceiverDataFrameMode on first use. Deferred out of initialize() because peer // MAC addresses are assigned during INITSTAGE_LINK_LAYER with undefined intra-stage module // ordering; the first transmitted data frame occurs after all init stages, so this is race-free. @@ -100,4 +106,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 89f63916421..0943cf6bed3 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned @@ -23,12 +23,30 @@ simple RateSelection extends SimpleModule like IRateSelection double multicastFrameBitrate @unit(bps) = default(-1bps); + // Configured response rates must identify one selectable PHY mode. Use the matching + // Bandwidth, NumSpatialStreams, and GuardInterval qualifiers to disambiguate rates. + // Unspecified qualifiers are wildcards; ambiguous matches are rejected. Qualifiers + // are ignored for an automatic (-1bps) response. GI must match the modeled mode. + // -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); + double responseAckFrameBandwidth @unit(Hz) = default(nan Hz); + int responseAckFrameNumSpatialStreams = default(-1); + double responseAckFrameGuardInterval @unit(s) = default(-1s); + // -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 responseCtsFrameBandwidth @unit(Hz) = default(nan Hz); + int responseCtsFrameNumSpatialStreams = default(-1); + double responseCtsFrameGuardInterval @unit(s) = default(-1s); double dataFrameBitrate @unit(bps) = default(-1bps); // Fastest - double dataFrameBandwidth @unit(Hz) = default(nan Hz); // Unspecified by default - int dataFrameNumSpatialStreams = default(-1); // Unspecified by default + double dataFrameBandwidth @unit(Hz) = default(nan Hz); // Explicit mode qualifier when dataFrameBitrate is specified + int dataFrameNumSpatialStreams = default(-1); // Explicit mode qualifier when dataFrameBitrate is specified + double dataFrameGuardInterval @unit(s) = default(-1s); // Explicit mode qualifier when dataFrameBitrate is specified; negative means unspecified, otherwise it must equal the selected PHY mode's modeled GI // Per-receiver unicast data-frame rate. Keys are peer interface module paths (e.g. // "host1.wlan[0]"), resolved to MAC addresses at run time; values are bitrates (bps). diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.cc b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.cc index 3c3bc33903a..acf01610c92 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.cc +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.cc @@ -7,6 +7,8 @@ #include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.h" +#include + #include "inet/common/INETUtils.h" #include "inet/common/ModuleAccess.h" #include "inet/common/ProtocolTag_m.h" @@ -43,35 +45,47 @@ void Ieee80211MgmtBase::initialize(int stage) void Ieee80211MgmtBase::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) { Enter_Method("%s", cComponent::getSignalName(signalID)); + if (signalID == modesetChangedSignal && obj != modeSet) + applyModeSet(check_and_cast(obj)); +} - if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - supportedRates = Ieee80211SupportedRatesElement(); - extendedSupportedRates = Ieee80211ExtendedSupportedRatesElement(); - int rateIndex = 0; - int extendedRateIndex = 0; - // Supported Rates carries the legacy OperationalRateSet only. HT/VHT - // MCS support is advertised through the corresponding capabilities - // elements (IEEE Std 802.11-2024, 9.4.2.3, 9.4.2.54.4, 11.1.4.6). - for (const auto *mode : modeSet->getLegacyOperationalModes()) { - bool isBasicRate = modeSet->getIsMandatory(mode); - double rate = mode->getDataMode()->getNetBitrate().get(); - if (rateIndex < 8) { - supportedRates.rate[rateIndex] = rate; - supportedRates.basicRate[rateIndex] = isBasicRate; - rateIndex++; - } - else if (extendedRateIndex < 255) { - extendedSupportedRates.rate[extendedRateIndex] = rate; - extendedSupportedRates.basicRate[extendedRateIndex] = isBasicRate; - extendedRateIndex++; - } - else - throw cRuntimeError("Mode set '%s' contains more than 263 legacy operational rates", modeSet->getName()); +void Ieee80211MgmtBase::applyModeSet(const physicallayer::Ieee80211ModeSet *newModeSet) +{ + Enter_Method_Silent(); + modeSet = const_cast(newModeSet); + supportedRates = Ieee80211SupportedRatesElement(); + extendedSupportedRates = Ieee80211ExtendedSupportedRatesElement(); + int rateIndex = 0; + int extendedRateIndex = 0; + // Supported Rates carries the legacy OperationalRateSet only. HT/VHT + // MCS support is advertised through the corresponding capabilities + // elements (IEEE Std 802.11-2024, 9.4.2.3, 9.4.2.54.4, 11.1.4.6). + for (const auto *mode : modeSet->getLegacyOperationalModes()) { + bool isBasicRate = modeSet->getIsMandatory(mode); + double rate = mode->getDataMode()->getNetBitrate().get(); + if (rateIndex < 8) { + supportedRates.rate[rateIndex] = rate; + supportedRates.basicRate[rateIndex] = isBasicRate; + rateIndex++; + } + else if (extendedRateIndex < 255) { + extendedSupportedRates.rate[extendedRateIndex] = rate; + extendedSupportedRates.basicRate[extendedRateIndex] = isBasicRate; + extendedRateIndex++; } - supportedRates.numRates = rateIndex; - extendedSupportedRates.numRates = extendedRateIndex; + else + throw cRuntimeError("Mode set '%s' contains more than 263 legacy operational rates", modeSet->getName()); } + supportedRates.numRates = rateIndex; + extendedSupportedRates.numRates = extendedRateIndex; +} + +std::function Ieee80211MgmtBase::saveModeSetState() +{ + Enter_Method_Silent(); + return [this, state = std::make_tuple(modeSet, supportedRates, extendedSupportedRates)]() mutable { + std::tie(modeSet, supportedRates, extendedSupportedRates) = std::move(state); + }; } void Ieee80211MgmtBase::addHtCapabilities(const Ptr& frame) const diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.h b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.h index 145da89e902..79c41175a8a 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.h +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.h @@ -19,6 +19,7 @@ #include "inet/networklayer/contract/IInterfaceTable.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Band.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.h" namespace inet { @@ -28,8 +29,13 @@ namespace ieee80211 { * Abstract base class for 802.11 infrastructure mode management components. * */ -class INET_API Ieee80211MgmtBase : public OperationalBase, public cListener +class INET_API Ieee80211MgmtBase : public OperationalBase, public cListener, public physicallayer::IIeee80211ModeSetListener { + public: + virtual const physicallayer::Ieee80211ModeSet *getModeSet() const override { return modeSet; } + virtual std::function saveModeSetState() override; + virtual void applyModeSet(const physicallayer::Ieee80211ModeSet *modeSet) override; + protected: // configuration ModuleRefByPar mib; diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrameSerializer.cc b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrameSerializer.cc index 09f3ffbd05b..72e1d578b46 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrameSerializer.cc +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrameSerializer.cc @@ -33,8 +33,10 @@ Register_Serializer(Ieee80211ReassociationResponseFrame, Ieee80211MgmtFrameSeria static constexpr uint8_t HT_CAPABILITIES_ELEMENT_ID = 45; static constexpr uint8_t HT_OPERATION_ELEMENT_ID = 61; +static constexpr uint8_t SSID_ELEMENT_ID = 0; static constexpr uint8_t SUPPORTED_RATES_ELEMENT_ID = 1; static constexpr uint8_t EXTENDED_SUPPORTED_RATES_ELEMENT_ID = 50; +static constexpr uint8_t MAX_SSID_LENGTH = 32; static constexpr uint8_t MAX_SUPPORTED_RATES = 8; static constexpr uint16_t MAX_EXTENDED_SUPPORTED_RATES = 255; static constexpr double SUPPORTED_RATE_UNIT = 0.5; @@ -43,6 +45,23 @@ static constexpr uint16_t ASSOCIATION_ID_MARKER = 0xC000; static constexpr uint16_t ASSOCIATION_ID_MASK = 0x3FFF; static constexpr int MAX_LOGICAL_ASSOCIATION_ID = 2007; +static void validateSsidLength(size_t length) +{ + // IEEE Std 802.11-2024, 9.4.2.2: the SSID field contains zero to + // 32 octets. Zero octets indicates the wildcard SSID. + if (length > MAX_SSID_LENGTH) + throw cRuntimeError("Malformed SSID element length: %zu exceeds maximum %d", length, MAX_SSID_LENGTH); +} + +static void writeSsidElement(MemoryOutputStream& stream, const char *SSID) +{ + size_t length = strlen(SSID); + validateSsidLength(length); + stream.writeByte(SSID_ELEMENT_ID); + stream.writeByte(static_cast(length)); + stream.writeBytes(reinterpret_cast(SSID), B(length)); +} + static void validateSupportedRatesCount(int numRates) { // IEEE Std 802.11-2024, 9.4.2.3: the Supported Rates field contains @@ -452,18 +471,16 @@ static uint16_t encodeAssociationId(Ieee80211StatusCode statusCode, int aid) return 0; } -static int decodeAssociationId(Ieee80211StatusCode statusCode, uint16_t wireAid) +static int decodeAssociationId(Ieee80211AssociationResponseFrame& frame, uint16_t wireAid) { - if (statusCode == SC_SUCCESSFUL) { - if ((wireAid & ASSOCIATION_ID_MARKER) != ASSOCIATION_ID_MARKER) - throw cRuntimeError("Malformed successful Association Response AID: missing marker 0xC000"); + if (frame.getStatusCode() == SC_SUCCESSFUL) { const int aid = wireAid & ASSOCIATION_ID_MASK; - if (aid < 1 || aid > MAX_LOGICAL_ASSOCIATION_ID) - throw cRuntimeError("Malformed successful Association Response AID: %d", aid); - return aid; + if ((wireAid & ASSOCIATION_ID_MARKER) == ASSOCIATION_ID_MARKER && aid >= 1 && aid <= MAX_LOGICAL_ASSOCIATION_ID) + return aid; + frame.markIncorrect(); } - if (wireAid != 0) - throw cRuntimeError("Malformed unsuccessful Association Response AID: expected zero, got 0x%04x", wireAid); + else if (wireAid != 0) + frame.markIncorrect(); return 0; } @@ -498,11 +515,7 @@ void Ieee80211MgmtFrameSerializer::serializeFields(MemoryOutputStream& stream, c else if (auto probeRequestFrame = dynamicPtrCast(chunk)) { // type = ST_PROBEREQUEST; // 1 SSID - const char *SSID = probeRequestFrame->getSSID(); - unsigned int length = strlen(SSID); - stream.writeByte(0); // FIXME dummy, what is it? - stream.writeByte(length); - stream.writeBytes((uint8_t *)SSID, B(length)); + writeSsidElement(stream, probeRequestFrame->getSSID()); // 2 Supported rates writeSupportedRateElements(stream, probeRequestFrame); writeHtElements(stream, probeRequestFrame, HT_CAPABILITIES_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED); @@ -519,12 +532,7 @@ void Ieee80211MgmtFrameSerializer::serializeFields(MemoryOutputStream& stream, c // 3 Current AP address stream.writeMacAddress(reassociationRequestFrame->getCurrentAP()); // 4 SSID - const char *SSID = reassociationRequestFrame->getSSID(); - unsigned int length = strlen(SSID); - // FIXME buffer.writeByte(buf + packetLength, ???); - stream.writeByte(0); // FIXME - stream.writeByte(length); - stream.writeBytes((uint8_t *)SSID, B(length)); + writeSsidElement(stream, reassociationRequestFrame->getSSID()); // 5 Supported rates writeSupportedRateElements(stream, reassociationRequestFrame); writeHtElements(stream, reassociationRequestFrame, HT_CAPABILITIES_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED); @@ -542,11 +550,7 @@ void Ieee80211MgmtFrameSerializer::serializeFields(MemoryOutputStream& stream, c // 2 Listen interval stream.writeUint16Le(0); // FIXME // 3 SSID - const char *SSID = associationRequestFrame->getSSID(); - unsigned int length = strlen(SSID); - stream.writeByte(0); // FIXME dummy, what is it? - stream.writeByte(length); - stream.writeBytes((uint8_t *)SSID, B(length)); + writeSsidElement(stream, associationRequestFrame->getSSID()); // 4 Supported rates writeSupportedRateElements(stream, associationRequestFrame); writeHtElements(stream, associationRequestFrame, HT_CAPABILITIES_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED); @@ -597,11 +601,7 @@ void Ieee80211MgmtFrameSerializer::serializeFields(MemoryOutputStream& stream, c // 3 Capability stream.writeUint16Le(0); // FIXME set capability // 4 Service Set Identifier (SSID) - const char *SSID = beaconFrame->getSSID(); - unsigned int length = strlen(SSID); - stream.writeByte(0); // FIXME - stream.writeByte(length); - stream.writeBytes((uint8_t *)SSID, B(length)); + writeSsidElement(stream, beaconFrame->getSSID()); // 5 Supported rates writeSupportedRateElements(stream, beaconFrame); writeHtElements(stream, beaconFrame, HT_CAPABILITIES_ALLOWED | HT_OPERATION_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED | BASIC_HT_MCS_SET_PRESENT); @@ -636,11 +636,7 @@ void Ieee80211MgmtFrameSerializer::serializeFields(MemoryOutputStream& stream, c // 3 Capability stream.writeUint16Le(0); // FIXME // 4 SSID - const char *SSID = probeResponseFrame->getSSID(); - unsigned int length = strlen(SSID); - stream.writeByte(0); // FIXME - stream.writeByte(length); - stream.writeBytes((uint8_t *)SSID, B(length)); + writeSsidElement(stream, probeResponseFrame->getSSID()); // 5 Supported rates writeSupportedRateElements(stream, probeResponseFrame); writeHtElements(stream, probeResponseFrame, HT_CAPABILITIES_ALLOWED | HT_OPERATION_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED | BASIC_HT_MCS_SET_PRESENT); @@ -735,7 +731,7 @@ const Ptr Ieee80211MgmtFrameSerializer::deserializeFields(MemoryInputStre auto frame = makeShared(); stream.readUint16Le(); frame->setStatusCode((Ieee80211StatusCode)stream.readUint16Le()); - frame->setAid(decodeAssociationId(frame->getStatusCode(), stream.readUint16Le())); + frame->setAid(decodeAssociationId(*frame, stream.readUint16Le())); Ieee80211SupportedRatesElement supRat; deserializeSupportedRates(stream, *frame, supRat); @@ -747,7 +743,7 @@ const Ptr Ieee80211MgmtFrameSerializer::deserializeFields(MemoryInputStre auto frame = makeShared(); stream.readUint16Le(); frame->setStatusCode((Ieee80211StatusCode)stream.readUint16Le()); - frame->setAid(decodeAssociationId(frame->getStatusCode(), stream.readUint16Le())); + frame->setAid(decodeAssociationId(*frame, stream.readUint16Le())); Ieee80211SupportedRatesElement supRat; deserializeSupportedRates(stream, *frame, supRat); diff --git a/src/inet/linklayer/ieee80211/mib/Ieee80211HtCapabilities.h b/src/inet/linklayer/ieee80211/mib/Ieee80211HtCapabilities.h index 2174ce5c387..3b62b3399d4 100644 --- a/src/inet/linklayer/ieee80211/mib/Ieee80211HtCapabilities.h +++ b/src/inet/linklayer/ieee80211/mib/Ieee80211HtCapabilities.h @@ -72,6 +72,7 @@ struct Ieee80211HtDirectionalCapabilities Ieee80211HtMcsNssMap mcsNss; std::array supportedMcs = {}; bool receiverLdpc = false; + bool receiverGreenfield = false; bool receiverShortGi20 = false; bool receiverShortGi40 = false; int receiverMaxAmpduLengthExponent = 0; @@ -116,9 +117,11 @@ inline Ieee80211NegotiatedHtCapabilities negotiateHtCapabilities(const Ieee80211 negotiated.localRxPeerTx.mcsNss.maxMcsPerNss[nss] = mcs; } } - // LDPC and short-GI bits advertise receiver capability, so they are directional. + // LDPC, Greenfield, and short-GI bits advertise receiver capability, so they are directional. negotiated.localTxPeerRx.receiverLdpc = peer.ldpc; negotiated.localRxPeerTx.receiverLdpc = local.ldpc; + negotiated.localTxPeerRx.receiverGreenfield = peer.greenfield; + negotiated.localRxPeerTx.receiverGreenfield = local.greenfield; negotiated.localTxPeerRx.receiverShortGi20 = peer.shortGi20; negotiated.localRxPeerTx.receiverShortGi20 = local.shortGi20; negotiated.localTxPeerRx.receiverShortGi40 = peer.shortGi40; diff --git a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.cc b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.cc index e5c8fd008a3..4ec66ed169c 100644 --- a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.cc +++ b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.cc @@ -7,8 +7,9 @@ #include "inet/linklayer/ieee80211/mib/Ieee80211Mib.h" -#include +#include +#include #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Band.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" @@ -112,6 +113,13 @@ const Ieee80211HtOperation& Ieee80211Mib::getHtOperation() const return htOperation; } +std::function Ieee80211Mib::saveHtState() +{ + return [this, state = std::make_tuple(localHtCapabilitiesValid, localHtCapabilities, htOperation, configuredSecondaryChannelOffset, primaryChannelAvailable, peerHtStates)]() mutable { + std::tie(localHtCapabilitiesValid, localHtCapabilities, htOperation, configuredSecondaryChannelOffset, primaryChannelAvailable, peerHtStates) = std::move(state); + }; +} + void Ieee80211Mib::updateLocalHtCapabilities(const physicallayer::Ieee80211ModeSet *modeSet, const std::set& operationalChannelWidths, int operationalHtSpatialStreamLimit) { @@ -164,6 +172,7 @@ void Ieee80211Mib::updateLocalHtCapabilities(const physicallayer::Ieee80211ModeS localHtCapabilities.txMcsNss.maxMcsPerNss[nss] = std::max(localHtCapabilities.txMcsNss.maxMcsPerNss[nss], mcs % 8); } } + localHtCapabilities.greenfield = modeSet->isHtGreenfieldSupported(); if (localHtCapabilities.supportedChannelWidths.empty()) throw cRuntimeError("HT operation mode set '%s' does not provide an HT channel width", modeSet->getName()); localHtCapabilities.maxAmpduLengthExponent = par("htMaxAmpduLengthExponent"); diff --git a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.h b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.h index 691257c2f07..9432c7a003e 100644 --- a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.h +++ b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.h @@ -8,6 +8,8 @@ #ifndef __INET_IEEE80211MIB_H #define __INET_IEEE80211MIB_H +#include + #include "inet/common/SimpleModule.h" #include "inet/linklayer/common/MacAddress.h" #include "inet/linklayer/ieee80211/mib/Ieee80211HtCapabilities.h" @@ -100,6 +102,7 @@ class INET_API Ieee80211Mib : public SimpleModule short allocateAssociationId(const MacAddress& address); void releaseAssociationId(const MacAddress& address); void clearAssociationIds(); + std::function saveHtState(); void updateLocalHtCapabilities(const physicallayer::Ieee80211ModeSet *modeSet, const std::set& operationalChannelWidths, int operationalHtSpatialStreamLimit); bool isHtOperationSupported() const { return localHtCapabilitiesValid; } diff --git a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned index 70474b3d95b..c8afc96c288 100644 --- a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned +++ b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned @@ -22,7 +22,7 @@ simple Ieee80211Mib extends SimpleModule @class(Ieee80211Mib); // Model-backed subset of IEEE Std 802.11-2024 HT capability/operation state; not a full Annex C MIB. int htMaxAmpduLengthExponent = default(0); // maximum received A-MPDU length exponent (0..3) - int htSecondaryChannelOffset = default(0); // BSS operation policy: 0=20 MHz, 1=40 MHz above, 3=40 MHz below; bounded by the mode set + int htSecondaryChannelOffset = default(0); // BSS operation policy: 0=20 MHz, 1=40 MHz above, 3=40 MHz below; requires PHY width support. The packet-level Ieee80211Transmitter/Receiver support only 20 MHz and reject nonzero offsets. int htProtectionMode = default(0); // 0=none, 1=nonmember, 2=20 MHz, 3=non-HT mixed displayStringTextFormat = default("Address: {address}{ssidStr}\n{modeStr}{stationTypeStr}{qosStr}{associatedStr}"); @display("i=block/table"); diff --git a/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarSignalAnalogModel.cc b/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarSignalAnalogModel.cc index 459463d7c98..4d0a86c78c7 100644 --- a/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarSignalAnalogModel.cc +++ b/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarSignalAnalogModel.cc @@ -12,7 +12,7 @@ namespace inet { namespace physicallayer { ScalarSignalAnalogModel::ScalarSignalAnalogModel(const simtime_t preambleDuration, simtime_t headerDuration, simtime_t dataDuration, Hz centerFrequency, Hz bandwidth, W power) : - NarrowbandSignalAnalogModel(preambleDuration, dataDuration, headerDuration, centerFrequency, bandwidth), + NarrowbandSignalAnalogModel(preambleDuration, headerDuration, dataDuration, centerFrequency, bandwidth), power(power) { } diff --git a/src/inet/physicallayer/wireless/ieee80211/bitlevel/Ieee80211LayeredOfdmTransmitter.cc b/src/inet/physicallayer/wireless/ieee80211/bitlevel/Ieee80211LayeredOfdmTransmitter.cc index 30eaa47132d..31d98a3cab4 100644 --- a/src/inet/physicallayer/wireless/ieee80211/bitlevel/Ieee80211LayeredOfdmTransmitter.cc +++ b/src/inet/physicallayer/wireless/ieee80211/bitlevel/Ieee80211LayeredOfdmTransmitter.cc @@ -312,7 +312,7 @@ const ITransmission *Ieee80211LayeredOfdmTransmitter::createTransmission(const I // TODO: compute channel const simtime_t preambleDuration = mode->getPreambleLength(); const simtime_t headerDuration = mode->getHeaderMode()->getDuration(); - const simtime_t dataDuration = mode->getDataMode()->getDuration(packet->getDataLength()); + const simtime_t dataDuration = mode->getDataDuration(packet->getDataLength()); return new Ieee80211Transmission(transmitter, packet, startTime, endTime, preambleDuration, headerDuration, dataDuration, startPosition, endPosition, startOrientation, endOrientation, packetModel, bitModel, symbolModel, sampleModel, analogModel, mode, nullptr); } @@ -331,4 +331,3 @@ Ieee80211LayeredOfdmTransmitter::~Ieee80211LayeredOfdmTransmitter() } // namespace physicallayer } // namespace inet - diff --git a/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.h b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.h new file mode 100644 index 00000000000..d714adf87aa --- /dev/null +++ b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.h @@ -0,0 +1,39 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +#ifndef __INET_IIEEE80211MODESETLISTENER_H +#define __INET_IIEEE80211MODESETLISTENER_H + +#include + +#include "inet/common/INETDefs.h" + +namespace inet { +namespace physicallayer { + +class Ieee80211ModeSet; + +/** + * Transaction participant for a radio's mode-set changes. Stateful subscribers + * to modesetChanged must implement this role to support recoverable changes. + * The radio captures every participant before applying any change, applies all + * participants before publishing the signal, and restores them if a call fails. + * Snapshot callbacks are one-shot, must not throw or emit signals, and restore + * all state affected by applyModeSet. Applying a change must not notify observers. + */ +class INET_API IIeee80211ModeSetListener +{ + public: + virtual ~IIeee80211ModeSetListener() = default; + virtual const Ieee80211ModeSet *getModeSet() const = 0; + virtual std::function saveModeSetState() = 0; + virtual void applyModeSet(const Ieee80211ModeSet *modeSet) = 0; +}; + +} // namespace physicallayer +} // namespace inet + +#endif diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/IIeee80211Mode.h b/src/inet/physicallayer/wireless/ieee80211/mode/IIeee80211Mode.h index aa615b7d19e..4a104cb221d 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/IIeee80211Mode.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/IIeee80211Mode.h @@ -42,8 +42,13 @@ class INET_API IIeee80211DataMode : public cObject, public IPrintableObject virtual bps getGrossBitrate() const = 0; virtual b getPaddingLength(b dataLength) const = 0; virtual b getCompleteLength(b dataLength) const = 0; + // Returns the raw duration of the encoded data symbol train. PPDU-format + // rules may round this duration at the enclosing mode level. virtual const simtime_t getDuration(b dataLength) const = 0; virtual const simtime_t getSymbolInterval() const = 0; + // Returns the guard interval used by the data symbols, or -1 when the PHY + // has no meaningful guard interval (for example, non-OFDM modes). + virtual const simtime_t getGuardInterval() const { return -1; } virtual const IModulation *getModulation() const = 0; virtual int getNumberOfSpatialStreams() const = 0; }; @@ -58,6 +63,9 @@ class INET_API IIeee80211Mode : public cObject, public IPrintableObject // Returns whether this mode uses the optional 400 ns HT guard interval. // Non-HT modes deliberately report false. virtual bool isHtShortGuardInterval() const = 0; + // Returns whether this mode uses the HT Greenfield preamble format. + // Non-HT modes and HT-mixed modes report false. + virtual bool isHtGreenfield() const { return false; } virtual int getLegacyCwMin() const = 0; virtual int getLegacyCwMax() const = 0; virtual const char *getName() const = 0; @@ -68,6 +76,9 @@ class INET_API IIeee80211Mode : public cObject, public IPrintableObject IIeee80211HeaderMode *_getHeaderMode() const { return const_cast(getHeaderMode()); } IIeee80211DataMode *_getDataMode() const { return const_cast(getDataMode()); } virtual const simtime_t getDuration(b dataLength) const = 0; + virtual const simtime_t getPreambleDuration() const { return getPreambleMode()->getDuration(); } + virtual const simtime_t getHeaderDuration() const { return getHeaderMode()->getDuration(); } + virtual const simtime_t getDataDuration(b dataLength) const { return getDuration(dataLength) - getPreambleDuration() - getHeaderDuration(); } virtual const simtime_t getSlotTime() const = 0; virtual const simtime_t getSifsTime() const = 0; virtual const simtime_t getRifsTime() const = 0; @@ -84,4 +95,3 @@ class INET_API IIeee80211Mode : public cObject, public IPrintableObject } // namespace inet #endif - diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.cc b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.cc index 319128cce20..884fc3864f0 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.cc +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.cc @@ -161,6 +161,16 @@ unsigned int Ieee80211HtPreambleMode::computeNumberOfHTLongTrainings(unsigned in return numberOfSpaceTimeStreams == 3 ? 4 : numberOfSpaceTimeStreams; } +const simtime_t Ieee80211HtPreambleMode::getDurationBeforeHeader() const +{ + if (preambleFormat == HT_PREAMBLE_MIXED) + return getNonHTShortTrainingSequenceDuration() + getNonHTLongTrainingFieldDuration() + legacySignalMode->getDuration(); + else if (preambleFormat == HT_PREAMBLE_GREENFIELD) + return getHTGreenfieldShortTrainingFieldDuration() + getFirstHTLongTrainingFieldDuration(); + else + throw cRuntimeError("Unknown preamble format"); +} + const simtime_t Ieee80211HtPreambleMode::getDuration() const { // 20.3.7 Mathematical description of signals @@ -178,12 +188,8 @@ const simtime_t Ieee80211HtPreambleMode::getDuration() const bps Ieee80211HtSignalMode::computeGrossBitrate() const { unsigned int numberOfCodedBitsPerSymbol = modulation->getSubcarrierModulation()->getCodeWordSize() * getNumberOfDataSubcarriers(); - if (guardIntervalType == HT_GUARD_INTERVAL_LONG) - return bps(numberOfCodedBitsPerSymbol / getSymbolInterval()); - else if (guardIntervalType == HT_GUARD_INTERVAL_SHORT) - return bps(numberOfCodedBitsPerSymbol / getShortGISymbolInterval()); - else - throw cRuntimeError("Unknown guard interval type"); + // IEEE Std 802.11-2024, 19.3.11.11.6: the short GI applies only to the Data field. + return bps(numberOfCodedBitsPerSymbol / getSymbolInterval()); } bps Ieee80211HtSignalMode::computeNetBitrate() const @@ -239,6 +245,16 @@ bps Ieee80211HtModeBase::getGrossBitrate() const return grossBitrate; } +const simtime_t Ieee80211HtDataMode::getGuardInterval() const +{ + if (guardIntervalType == HT_GUARD_INTERVAL_LONG) + return getGIDuration(); + else if (guardIntervalType == HT_GUARD_INTERVAL_SHORT) + return getShortGIDuration(); + else + throw cRuntimeError("Unknown guard interval type"); +} + int Ieee80211HtModeBase::getNumberOfDataSubcarriers() const { return Ieee80211Htmcs::getNumberOfDataSubcarriers(bandwidth, mcsIndex); @@ -285,6 +301,22 @@ const simtime_t Ieee80211HtDataMode::getDuration(b dataLength) const return numberOfSymbols * getSymbolInterval(); } +const simtime_t Ieee80211HtMode::getDuration(b dataLength) const +{ + auto dataDuration = dataMode->getDuration(dataLength); + if (preambleMode->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED && + dataMode->getGuardIntervalType() == Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) + { + // IEEE Std 802.11-2024, 19.4.3, Eq. (19-90): mixed-format short-GI + // Data airtime is rounded up to a 4 us boundary. Eq. (19-92) leaves + // greenfield short-GI Data airtime at its raw 3.6 us symbol duration. + auto longGiSymbolInterval = dataMode->getDFTPeriod() + dataMode->getGIDuration(); + auto numberOfLongGiSymbols = (dataDuration.raw() + longGiSymbolInterval.raw() - 1) / longGiSymbolInterval.raw(); + dataDuration = SimTime::fromRaw(numberOfLongGiSymbols * longGiSymbolInterval.raw()); + } + return preambleMode->getDuration() + dataDuration; +} + const simtime_t Ieee80211HtMode::getSlotTime() const { if (centerFrequencyMode == BAND_2_4GHZ) @@ -326,7 +358,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 +380,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; @@ -442,7 +474,9 @@ const DI Ieee80211HtmcsTable::htMcs29BW40MHz([](){ return new Ie const DI Ieee80211HtmcsTable::htMcs30BW40MHz([](){ return new Ieee80211Htmcs(30, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(40));}); const DI Ieee80211HtmcsTable::htMcs31BW40MHz([](){ return new Ieee80211Htmcs(31, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Ieee80211HtCompliantCodes::htConvolutionalCode5_6, MHz(40));}); -const DI Ieee80211HtmcsTable::htMcs32BW40MHz([](){ return new Ieee80211Htmcs(32, &BpskModulation::singleton, &BpskModulation::singleton, &BpskModulation::singleton, &BpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode1_2, MHz(40));}); +// IEEE Std 802.11-2024, Table 19-35: optional MCS 32 is one BPSK stream. +// This corrects the previous incorrect 4-stream all-BPSK constructor. +const DI Ieee80211HtmcsTable::htMcs32BW40MHz([](){ return new Ieee80211Htmcs(32, &BpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode1_2, MHz(40));}); const DI Ieee80211HtmcsTable::htMcs33BW20MHz([](){ return new Ieee80211Htmcs(33, &Qam16Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode1_2, MHz(20));}); const DI Ieee80211HtmcsTable::htMcs34BW20MHz([](){ return new Ieee80211Htmcs(34, &Qam64Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode1_2, MHz(20));}); @@ -490,7 +524,9 @@ const DI Ieee80211HtmcsTable::htMcs72BW20MHz([](){ return new Ie const DI Ieee80211HtmcsTable::htMcs73BW20MHz([](){ return new Ieee80211Htmcs(73, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam16Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(20));}); const DI Ieee80211HtmcsTable::htMcs74BW20MHz([](){ return new Ieee80211Htmcs(74, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam16Modulation::singleton, &Qam16Modulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(20));}); const DI Ieee80211HtmcsTable::htMcs75BW20MHz([](){ return new Ieee80211Htmcs(75, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(20));}); -const DI Ieee80211HtmcsTable::htMcs76BW20MHz([](){ return new Ieee80211Htmcs(76, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(20));}); +// IEEE Std 802.11-2024, Table 19-38 (continued): MCS 76 uses 64-QAM for streams 1-3 and 16-QAM for stream 4. +// This corrects the previous incorrect QPSK modulation for the 4th stream. +const DI Ieee80211HtmcsTable::htMcs76BW20MHz([](){ return new Ieee80211Htmcs(76, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam16Modulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(20));}); const DI Ieee80211HtmcsTable::htMcs33BW40MHz([](){ return new Ieee80211Htmcs(33, &Qam16Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode1_2, MHz(40));}); const DI Ieee80211HtmcsTable::htMcs34BW40MHz([](){ return new Ieee80211Htmcs(34, &Qam64Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode1_2, MHz(40));}); @@ -536,7 +572,9 @@ const DI Ieee80211HtmcsTable::htMcs69BW40MHz([](){ return new Ie const DI Ieee80211HtmcsTable::htMcs70BW40MHz([](){ return new Ieee80211Htmcs(70, &Qam64Modulation::singleton, &Qam16Modulation::singleton, &Qam16Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(40));}); const DI Ieee80211HtmcsTable::htMcs71BW40MHz([](){ return new Ieee80211Htmcs(71, &Qam64Modulation::singleton, &Qam16Modulation::singleton, &Qam16Modulation::singleton, &Qam16Modulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(40));}); const DI Ieee80211HtmcsTable::htMcs72BW40MHz([](){ return new Ieee80211Htmcs(72, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &QpskModulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(40));}); -const DI Ieee80211HtmcsTable::htMcs73BW40MHz([](){ return new Ieee80211Htmcs(73, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(40));}); +// IEEE Std 802.11-2024, Table 19-40: MCS 73 uses 64-QAM for streams 1-2 and 16-QAM for stream 3. +// This corrects the previous incorrect 64-QAM modulation for the 3rd stream. +const DI Ieee80211HtmcsTable::htMcs73BW40MHz([](){ return new Ieee80211Htmcs(73, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam16Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(40));}); const DI Ieee80211HtmcsTable::htMcs74BW40MHz([](){ return new Ieee80211Htmcs(74, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam16Modulation::singleton, &Qam16Modulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(40));}); const DI Ieee80211HtmcsTable::htMcs75BW40MHz([](){ return new Ieee80211Htmcs(75, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(40));}); @@ -544,4 +582,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 9184765f083..b35858ad69d 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h @@ -153,6 +153,7 @@ class INET_API Ieee80211HtPreambleMode : public IIeee80211PreambleMode, public I virtual const simtime_t getSecondAndSubsequentHTLongTrainingFielDuration() const { return 4E-6; } // HT-LTFs, s = 2,3,..,n virtual unsigned int getNumberOfHtLongTrainings() const { return numberOfHTLongTrainings; } + virtual const simtime_t getDurationBeforeHeader() const; virtual const simtime_t getDuration() const override; virtual Ptr createPreamble() const override { return makeShared(); } @@ -218,7 +219,8 @@ class INET_API Ieee80211HtDataMode : public IIeee80211DataMode, public Ieee80211 virtual bps getGrossBitrate() const override { return Ieee80211HtModeBase::getGrossBitrate(); } virtual const Ieee80211Htmcs *getModulationAndCodingScheme() const { return modulationAndCodingScheme; } virtual const Ieee80211HtCode *getCode() const { return modulationAndCodingScheme->getCode(); } - virtual const simtime_t getSymbolInterval() const override { return Ieee80211HtTimingRelatedParametersBase::getSymbolInterval(); } + virtual const simtime_t getGuardInterval() const override; + virtual const simtime_t getSymbolInterval() const override { return getDFTPeriod() + getGuardInterval(); } virtual const Ieee80211OfdmModulation *getModulation() const override { return modulationAndCodingScheme->getModulation(); } }; @@ -246,6 +248,7 @@ class INET_API Ieee80211HtMode : public Ieee80211ModeBase virtual const Ieee80211HtDataMode *getDataMode() const override { return dataMode; } virtual int getHtMcsIndex() const override { return dataMode->getMcsIndex(); } virtual bool isHtShortGuardInterval() const override { return dataMode->getGuardIntervalType() == Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT; } + virtual bool isHtGreenfield() const override { return preambleMode->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD; } virtual const Ieee80211HtPreambleMode *getPreambleMode() const override { return preambleMode; } virtual const Ieee80211HtSignalMode *getHeaderMode() const override { return preambleMode->getSignalMode(); } virtual const Ieee80211OfdmSignalMode *getLegacySignalMode() const { return preambleMode->getLegacySignalMode(); } @@ -263,7 +266,10 @@ class INET_API Ieee80211HtMode : public Ieee80211ModeBase virtual int getMpduMaxLength() const override { return 65535; } // in octets virtual BandMode getCenterFrequencyMode() const { return centerFrequencyMode; } - virtual const simtime_t getDuration(b dataLength) const override { return preambleMode->getDuration() + dataMode->getDuration(dataLength); } + virtual const simtime_t getDuration(b dataLength) const override; + virtual const simtime_t getPreambleDuration() const override { return preambleMode->getDurationBeforeHeader(); } + virtual const simtime_t getHeaderDuration() const override { return preambleMode->getDuration() - getPreambleDuration(); } + virtual const simtime_t getDataDuration(b dataLength) const override { return getDuration(dataLength) - preambleMode->getDuration(); } }; // A specification of the high-throughput (HT) physical layer (PHY) @@ -462,7 +468,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(); diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc index 5a53166e2cc..49f00bec69a 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc @@ -9,6 +9,8 @@ #include #include +#include +#include #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ErpOfdmMode.h" @@ -25,7 +27,147 @@ namespace physicallayer { Register_Abstract_Class(Ieee80211ModeSet); -const DelayedInitializer> Ieee80211ModeSet::modeSets([]() { return new std::vector { +std::vector Ieee80211ModeSet::completeHtGuardIntervalVariants(const char *name, const std::vector& entries) +{ + if (strcmp(name, "n(mixed-2.4Ghz)") && strcmp(name, "n(greenfield-2.4Ghz)")) + return entries; + + std::vector completeEntries = entries; + // IEEE Std 802.11-2024, Table 19-6 defines the 800 ns and 400 ns GIs. + // Add only the alternate GI for each mode explicitly declared above; + // declaration order, mandatory flags, and the historical catalog remain + // authoritative for this operation mode. + auto numberOfBaseEntries = completeEntries.size(); + for (size_t index = 0; index < numberOfBaseEntries; index++) { + auto htMode = dynamic_cast(completeEntries[index].mode); + if (htMode == nullptr) + continue; + auto dataMode = htMode->getDataMode(); + auto alternateGuardInterval = dataMode->getGuardIntervalType() == Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG ? + Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT : Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG; + auto alternateMode = Ieee80211HtCompliantModes::getCompliantMode( + dataMode->getModulationAndCodingScheme(), htMode->getCenterFrequencyMode(), + htMode->getPreambleMode()->getPreambleFormat(), alternateGuardInterval); + auto alternateAlreadyPresent = std::any_of(completeEntries.begin(), completeEntries.end(), [alternateMode](const Entry& entry) { return entry.mode == alternateMode; }); + if (!alternateAlreadyPresent) + completeEntries.push_back({false, alternateMode}); + } + return completeEntries; +} + +#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 }, + { true, &Ieee80211DsssCompliantModes::dsssMode2Mbps, true }, + { true, &Ieee80211HrDsssCompliantModes::hrDsssMode2MbpsShortPreamble }, + { true, &Ieee80211HrDsssCompliantModes::hrDsssMode5_5MbpsCckLongPreamble, true }, + { true, &Ieee80211HrDsssCompliantModes::hrDsssMode5_5MbpsCckShortPreamble }, + { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps, true }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode9Mbps }, + { true, &Ieee80211HrDsssCompliantModes::hrDsssMode11MbpsCckLongPreamble, true }, + { true, &Ieee80211HrDsssCompliantModes::hrDsssMode11MbpsCckShortPreamble }, + { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode12Mbps, true }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode18Mbps }, + { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode24Mbps, true }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode36Mbps }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode48Mbps }, + { false, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode54Mbps }, + }); + return result; +} + +#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, true }, { false, &Ieee80211OfdmCompliantModes::ofdmMode9MbpsCS20MHz, true }, @@ -220,7 +362,73 @@ const DelayedInitializer> Ieee80211ModeSet::modeSe { true, &Ieee80211HrDsssCompliantModes::hrDsssMode11MbpsCckLongPreamble, true }, { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode12Mbps, true }, { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode24Mbps, true } - }, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs0BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG), PhyType::HT, true), + }, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs0BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG), PhyType::HT, true, createHtSupportedEntries(Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED)), + Ieee80211ModeSet("n(greenfield-2.4Ghz)", { + { true, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs0BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) }, + { true, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs1BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) }, + { true, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs2BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) }, + { true, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs3BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) }, + { true, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs4BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) }, + { true, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs5BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) }, + { true, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs6BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) }, + { true, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs7BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs8BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs9BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs10BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs11BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs12BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs13BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs14BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs15BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs16BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs17BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs18BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs19BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs20BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs21BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs22BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs23BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs24BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs25BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs26BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs27BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs28BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs29BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs30BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs31BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs0BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs1BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs2BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs3BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs4BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs5BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs6BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs7BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs8BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs9BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs10BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs11BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs12BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs13BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs14BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs15BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs16BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs17BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs18BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs19BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs20BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs21BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs22BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs23BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs24BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs25BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs26BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs27BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs28BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs29BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs30BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) }, + { false, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs31BW40MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) } + }, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs0BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG), PhyType::HT, true, createHtSupportedEntries(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) }, @@ -542,12 +750,16 @@ const DelayedInitializer> Ieee80211ModeSet::modeSe }, Ieee80211VhtCompliantModes::getCompliantMode(&Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, Ieee80211VhtMode::BAND_5GHZ, Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG), PhyType::VHT),}; }); Ieee80211ModeSet::Ieee80211ModeSet(const char *name, const std::vector entries, const IIeee80211Mode *referenceMode, - PhyType phyType, bool htOperationSupported) : + PhyType phyType, bool htOperationSupported, const std::vector supportedEntries) : name(name), - entries(entries), + entries(completeHtGuardIntervalVariants(name, entries)), phyType(phyType), referenceMode(referenceMode), - htOperationSupported(htOperationSupported) + htOperationSupported(htOperationSupported), + supportedEntries(supportedEntries.empty() ? this->entries : completeHtGuardIntervalVariants(name, supportedEntries)), + controlResponseModes(createControlResponseModes(this->supportedEntries)), + htMixedControlResponseModes(createHtMixedControlResponseModes(this->supportedEntries)), + nonHtControlResponseEntries(createNonHtControlResponseEntries(this->supportedEntries)) { if (this->entries.empty()) throw cRuntimeError("IEEE 802.11 mode set '%s' must contain at least one mode", this->name.c_str()); @@ -578,6 +790,8 @@ Ieee80211ModeSet::Ieee80211ModeSet(const char *name, const std::vector en if (this->referenceMode->getLegacyCwMin() < 0 || this->referenceMode->getLegacyCwMax() < this->referenceMode->getLegacyCwMin()) throw cRuntimeError("Reference mode '%s' in IEEE 802.11 mode set '%s' has invalid contention window bounds", this->referenceMode->getName(), this->name.c_str()); std::vector *nonConstEntries = const_cast *>(&this->entries); + // Keep equal-bitrate modes in declaration order because unqualified lookups + // intentionally preserve the historically preferred mode. std::stable_sort(nonConstEntries->begin(), nonConstEntries->end(), EntryNetBitrateComparator()); // Explicit Supported-Rates eligibility on the authoritative Entry keeps // HT/VHT MCSs out without concrete-type or name-based inference. The @@ -585,7 +799,7 @@ Ieee80211ModeSet::Ieee80211ModeSet(const char *name, const std::vector en // 9.4.2.3 and 11.1.4.6); management splits it across the primary and // Extended Supported Rates elements when necessary. for (bool mandatory : {true, false}) { - for (const auto& entry : this->entries) { + for (const auto& entry : this->supportedEntries) { if (entry.isLegacyOperational && entry.isMandatory == mandatory) this->legacyOperationalModes.push_back(entry.mode); } @@ -598,10 +812,14 @@ Ieee80211ModeSet::Ieee80211ModeSet(const char *name, const std::vector en for (const auto *mode : this->legacyOperationalModes) { if (mode == nullptr) throw cRuntimeError("IEEE 802.11 mode set '%s' contains a null legacy operational mode", this->name.c_str()); - int modeIndex = findModeIndex(mode); - if (modeIndex < 0) - throw cRuntimeError("Legacy operational mode '%s' is not contained in IEEE 802.11 mode set '%s'", mode->getName(), this->name.c_str()); - if (!this->entries[modeIndex].isLegacyOperational) + bool found = false; + for (const auto& entry : this->supportedEntries) { + if (entry.mode == mode && entry.isLegacyOperational) { + found = true; + break; + } + } + if (!found) throw cRuntimeError("Legacy operational mode '%s' is not marked eligible in IEEE 802.11 mode set '%s'", mode->getName(), this->name.c_str()); } for (const auto& entry : this->entries) { @@ -617,6 +835,8 @@ Ieee80211ModeSet::Ieee80211ModeSet(const char *name, const std::vector en htSupportedChannelWidths.insert(bandwidth); if (entry.mode->isHtShortGuardInterval()) htShortGuardIntervalChannelWidths.insert(bandwidth); + if (entry.mode->isHtGreenfield()) + htGreenfieldSupported = true; } if (htOperationSupported) { for (int mcsIndex = 0; mcsIndex < 8; mcsIndex++) @@ -649,6 +869,109 @@ 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) + 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); @@ -660,44 +983,109 @@ int Ieee80211ModeSet::getModeIndex(const IIeee80211Mode *mode) const bool Ieee80211ModeSet::getIsMandatory(const IIeee80211Mode *mode) const { - return entries[getModeIndex(mode)].isMandatory; + int index = findModeIndex(mode); + if (index >= 0) + return entries[index].isMandatory; + for (const auto *legacyMode : legacyOperationalModes) { + if (legacyMode == mode) { + for (const auto& entry : supportedEntries) { + if (entry.mode == mode) + return entry.isMandatory; + } + } + } + throw cRuntimeError("Unknown mode"); +} + +const IIeee80211Mode *Ieee80211ModeSet::findMode(const IIeee80211Mode *mode) const +{ + int index = findModeIndex(mode); + return index >= 0 ? entries[index].mode : nullptr; } -const IIeee80211Mode *Ieee80211ModeSet::findMode(bps bitrate, Hz bandwidth, int numSpatialStreams) const +const IIeee80211Mode *Ieee80211ModeSet::getMode(const IIeee80211Mode *mode) const { - return findMode(bitrate - Mbps(0.05), bitrate + Mbps(0.05), bandwidth, numSpatialStreams); + auto result = findMode(mode); + if (result == nullptr) + throw cRuntimeError("Unknown mode in operation mode: '%s'", getName()); + return result; } -const IIeee80211Mode *Ieee80211ModeSet::findMode(bps minBitrate, bps maxBitrate, Hz bandwidth, int numSpatialStreams) const +const IIeee80211Mode *Ieee80211ModeSet::findCompatibleMode(const IIeee80211Mode *mode) const { + if (mode == nullptr) + return nullptr; + + const auto sourceDataMode = mode->getDataMode(); + const auto sourceBitrate = sourceDataMode->getNetBitrate(); + const auto sourceBandwidth = sourceDataMode->getBandwidth(); + const auto sourceGuardInterval = sourceDataMode->getGuardInterval(); + const auto minBitrate = sourceBitrate - Mbps(0.05); + const auto maxBitrate = sourceBitrate + Mbps(0.05); + for (const auto& entry : entries) { + const auto candidateDataMode = entry.mode->getDataMode(); + const auto candidateBandwidth = candidateDataMode->getBandwidth(); + const auto candidateGuardInterval = candidateDataMode->getGuardInterval(); + const bool bandwidthMatches = (std::isnan(sourceBandwidth.get()) && std::isnan(candidateBandwidth.get())) || + (!std::isnan(sourceBandwidth.get()) && !std::isnan(candidateBandwidth.get()) && sourceBandwidth == candidateBandwidth); + // GI = -1 indicates unconstrained guard interval (e.g., non-OFDM modes). + // Treat GI = -1 as matching any candidate GI, and require exact match when both are >= 0. + const bool guardIntervalMatches = (sourceGuardInterval < SIMTIME_ZERO) || (candidateGuardInterval < SIMTIME_ZERO) || + (sourceGuardInterval >= SIMTIME_ZERO && candidateGuardInterval == sourceGuardInterval); + if (minBitrate <= candidateDataMode->getNetBitrate() && candidateDataMode->getNetBitrate() <= maxBitrate && + bandwidthMatches && candidateDataMode->getNumberOfSpatialStreams() == sourceDataMode->getNumberOfSpatialStreams() && + guardIntervalMatches) + return entry.mode; + } + return nullptr; +} + +const IIeee80211Mode *Ieee80211ModeSet::findMode(bps bitrate, Hz bandwidth, int numSpatialStreams, simtime_t guardInterval, bool requireUnique) const +{ + return findMode(bitrate - Mbps(0.05), bitrate + Mbps(0.05), bandwidth, numSpatialStreams, guardInterval, requireUnique); +} + +const IIeee80211Mode *Ieee80211ModeSet::findMode(bps minBitrate, bps maxBitrate, Hz bandwidth, int numSpatialStreams, simtime_t guardInterval, bool requireUnique) const +{ + const IIeee80211Mode *result = nullptr; for (size_t index = 0; index < entries.size(); index++) { auto mode = entries[index].mode; auto dataMode = mode->getDataMode(); auto bitrate = dataMode->getNetBitrate(); + bool guardIntervalMatches = guardInterval < SIMTIME_ZERO || + dataMode->getGuardInterval() == guardInterval; if (minBitrate <= bitrate && bitrate <= maxBitrate && (std::isnan(bandwidth.get()) || dataMode->getBandwidth() == bandwidth) && - (numSpatialStreams == -1 || dataMode->getNumberOfSpatialStreams() == numSpatialStreams)) + (numSpatialStreams == -1 || dataMode->getNumberOfSpatialStreams() == numSpatialStreams) && + guardIntervalMatches) { - return entries[index].mode; + if (!requireUnique) + return mode; + if (result != nullptr && result != mode) + throw cRuntimeError("Ambiguous mode for bitrate range (%g - %g) bps in operation mode '%s'; specify bandwidth, number of spatial streams, and guard interval", + minBitrate.get(), maxBitrate.get(), getName()); + result = mode; } } - return nullptr; + return result; } -const IIeee80211Mode *Ieee80211ModeSet::getMode(bps bitrate, Hz bandwidth, int numSpatialStreams) const +const IIeee80211Mode *Ieee80211ModeSet::getMode(bps bitrate, Hz bandwidth, int numSpatialStreams, simtime_t guardInterval, bool requireUnique) const { - const IIeee80211Mode *mode = getMode(bitrate - Mbps(0.05), bitrate + Mbps(0.05), bandwidth, numSpatialStreams); + const IIeee80211Mode *mode = getMode(bitrate - Mbps(0.05), bitrate + Mbps(0.05), bandwidth, numSpatialStreams, guardInterval, requireUnique); if (mode == nullptr) - throw cRuntimeError("Unknown bitrate: %g in operation mode: '%s'", bitrate.get(), getName()); + throw cRuntimeError("Unknown mode for bitrate %g bps, bandwidth %g Hz, %d spatial streams, and %s guard interval in operation mode '%s'", + bitrate.get(), bandwidth.get(), numSpatialStreams, guardInterval.str().c_str(), getName()); else return mode; } -const IIeee80211Mode *Ieee80211ModeSet::getMode(bps minBitrate, bps maxBitrate, Hz bandwidth, int numSpatialStreams) const +const IIeee80211Mode *Ieee80211ModeSet::getMode(bps minBitrate, bps maxBitrate, Hz bandwidth, int numSpatialStreams, simtime_t guardInterval, bool requireUnique) const { - const IIeee80211Mode *mode = findMode(minBitrate, maxBitrate, bandwidth, numSpatialStreams); + const IIeee80211Mode *mode = findMode(minBitrate, maxBitrate, bandwidth, numSpatialStreams, guardInterval, requireUnique); if (mode == nullptr) - throw cRuntimeError("Unknown bitrate: (%g - %g) in operation mode: '%s'", minBitrate.get(), maxBitrate.get(), getName()); + throw cRuntimeError("Unknown mode for bitrate range (%g - %g) bps, bandwidth %g Hz, %d spatial streams, and %s guard interval in operation mode '%s'", + minBitrate.get(), maxBitrate.get(), bandwidth.get(), numSpatialStreams, guardInterval.str().c_str(), getName()); else return mode; } @@ -715,19 +1103,25 @@ const IIeee80211Mode *Ieee80211ModeSet::getFastestMode() const const IIeee80211Mode *Ieee80211ModeSet::getSlowerMode(const IIeee80211Mode *mode) const { int index = findModeIndex(mode); - if (index > 0) - return entries[index - 1].mode; - else - return nullptr; + if (index > 0) { + auto bitrate = mode->getDataMode()->getNetBitrate(); + for (int i = index - 1; i >= 0; i--) + if (entries[i].mode->getDataMode()->getNetBitrate() < bitrate) + return entries[i].mode; + } + return nullptr; } const IIeee80211Mode *Ieee80211ModeSet::getFasterMode(const IIeee80211Mode *mode) const { int index = findModeIndex(mode); - if (index >= 0 && index < (int)entries.size() - 1) - return entries[index + 1].mode; - else - return nullptr; + if (index >= 0) { + auto bitrate = mode->getDataMode()->getNetBitrate(); + for (size_t i = index + 1; i < entries.size(); i++) + if (entries[i].mode->getDataMode()->getNetBitrate() > bitrate) + return entries[i].mode; + } + return nullptr; } const IIeee80211Mode *Ieee80211ModeSet::getSlowestMandatoryMode() const @@ -758,24 +1152,127 @@ const IIeee80211Mode *Ieee80211ModeSet::getFastestLegacyOperationalMode() const return legacyOperationalModes.empty() ? nullptr : legacyOperationalModes.back(); } +const IIeee80211Mode *Ieee80211ModeSet::getMandatoryModeAtOrBelow(const IIeee80211Mode *mode) const +{ + // Returns the highest-bitrate mandatory mode whose bitrate is <= the given mode's bitrate. + // For equal-bitrate mandatory modes, returns the first-encountered entry (strict > comparison). + // This may return a different mode object than the input when the input is mandatory and + // shares bitrate with another mandatory mode, but the resulting rate is behavior-equivalent. + const auto bitrate = mode->getDataMode()->getNetBitrate(); + const IIeee80211Mode *result = nullptr; + for (const auto& entry : entries) { + const auto entryBitrate = entry.mode->getDataMode()->getNetBitrate(); + if (entry.isMandatory && entryBitrate <= bitrate && + (result == nullptr || entryBitrate > result->getDataMode()->getNetBitrate())) + result = entry.mode; + } + return result; +} + const IIeee80211Mode *Ieee80211ModeSet::getSlowerMandatoryMode(const IIeee80211Mode *mode) const { - int index = findModeIndex(mode); - if (index > 0) - for (int i = index - 1; i >= 0; i--) - if (entries[i].isMandatory) - return entries[i].mode; - return nullptr; + const auto bitrate = mode->getDataMode()->getNetBitrate(); + const IIeee80211Mode *result = nullptr; + for (const auto& entry : entries) { + const auto entryBitrate = entry.mode->getDataMode()->getNetBitrate(); + if (entry.isMandatory && entryBitrate < bitrate && + (result == nullptr || entryBitrate > result->getDataMode()->getNetBitrate())) + result = entry.mode; + } + return result; } const IIeee80211Mode *Ieee80211ModeSet::getFasterMandatoryMode(const IIeee80211Mode *mode) const { - int index = findModeIndex(mode); - if (index >= 0) - for (size_t i = index + 1; i < entries.size(); i++) - if (entries[i].isMandatory) - return entries[i].mode; - return nullptr; + const auto bitrate = mode->getDataMode()->getNetBitrate(); + const IIeee80211Mode *result = nullptr; + for (const auto& entry : entries) { + const auto entryBitrate = entry.mode->getDataMode()->getNetBitrate(); + if (entry.isMandatory && entryBitrate > bitrate && + (result == nullptr || entryBitrate < result->getDataMode()->getNetBitrate())) + result = entry.mode; + } + return result; +} + +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 IIeee80211Mode *Ieee80211ModeSet::findHtMixedMode(const IIeee80211Mode *mode) const +{ + if (mode == nullptr) + return nullptr; + auto it = htMixedControlResponseModes.find(mode); + return it != htMixedControlResponseModes.end() ? it->second : nullptr; } const Ieee80211ModeSet *Ieee80211ModeSet::findModeSet(const char *mode) @@ -806,4 +1303,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 e14627dbcbe..d3218205b9c 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h @@ -28,7 +28,6 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject VHT, }; - protected: class INET_API Entry { public: bool isMandatory; @@ -52,18 +51,28 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject std::array htMcsMandatory = {}; std::set htSupportedChannelWidths; std::set htShortGuardIntervalChannelWidths; + bool htGreenfieldSupported = false; bool htOperationSupported = false; + // 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: + static std::vector completeHtGuardIntervalVariants(const char *name, const std::vector& entries); 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, const IIeee80211Mode *referenceMode, - PhyType phyType, bool htOperationSupported = false); + PhyType phyType, bool htOperationSupported = false, const std::vector supportedEntries = {}); virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override { return stream << "Ieee80211ModeSet, name = " << name; } @@ -85,23 +94,52 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject const std::set& getHtSupportedChannelWidths() const { return htSupportedChannelWidths; } const std::set& getHtShortGuardIntervalChannelWidths() const { return htShortGuardIntervalChannelWidths; } bool isHtShortGuardIntervalSupported(Hz bandwidth) const { return htShortGuardIntervalChannelWidths.count(bandwidth) != 0; } + bool isHtGreenfieldSupported() const { return htGreenfieldSupported; } + // 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; - - 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; - const IIeee80211Mode *getMode(bps minBitrate, bps maxBitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1) const; + // Finds a mode with the same PHY tuple as mode. Unlike findMode(), this + // treats an absent guard interval (negative value) as an exact value. + const IIeee80211Mode *findCompatibleMode(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; + // requireUnique rejects multiple matching entries instead of selecting the first. + const IIeee80211Mode *findMode(bps bitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1, simtime_t guardInterval = -1, bool requireUnique = false) const; + const IIeee80211Mode *findMode(bps minBitrate, bps maxBitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1, simtime_t guardInterval = -1, bool requireUnique = false) const; + const IIeee80211Mode *getMode(bps bitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1, simtime_t guardInterval = -1, bool requireUnique = false) const; + const IIeee80211Mode *getMode(bps minBitrate, bps maxBitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1, simtime_t guardInterval = -1, bool requireUnique = false) const; const IIeee80211Mode *getSlowestMode() const; const IIeee80211Mode *getFastestMode() const; const IIeee80211Mode *getSlowerMode(const IIeee80211Mode *mode) const; const IIeee80211Mode *getFasterMode(const IIeee80211Mode *mode) const; const IIeee80211Mode *getSlowestMandatoryMode() const; const IIeee80211Mode *getFastestMandatoryMode() const; + const IIeee80211Mode *getMandatoryModeAtOrBelow(const IIeee80211Mode *mode) const; 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; + // Returns the HT-mixed equivalent mode for an HT mode, or nullptr if not an HT mode or not mapped. + const IIeee80211Mode *findHtMixedMode(const IIeee80211Mode *mode) const; + static const Ieee80211ModeSet *findModeSet(const char *mode); static const Ieee80211ModeSet *getModeSet(const char *mode); @@ -125,4 +163,3 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject } // namespace inet #endif - diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211OfdmMode.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211OfdmMode.h index 6ca84f780f3..608943efba3 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211OfdmMode.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211OfdmMode.h @@ -124,6 +124,7 @@ class INET_API Ieee80211OfdmDataMode : public IIeee80211DataMode, public Ieee802 virtual b getPaddingLength(b dataLength) const override; virtual b getCompleteLength(b dataLength) const override; virtual const simtime_t getDuration(b dataLength) const override; + virtual const simtime_t getGuardInterval() const override { return getGIDuration(); } const Ieee80211OfdmCode *getCode() const { return code; } virtual const simtime_t getSymbolInterval() const override { return Ieee80211OfdmTimingRelatedParametersBase::getSymbolInterval(); } @@ -265,4 +266,3 @@ class INET_API Ieee80211OfdmCompliantModes } // 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..cd3b3eb82e4 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.cc +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.cc @@ -241,6 +241,13 @@ unsigned int Ieee80211VhtPreambleMode::computeNumberOfHTLongTrainings(unsigned i return numberOfSpaceTimeStreams == 3 ? 4 : numberOfSpaceTimeStreams; } +const simtime_t Ieee80211VhtPreambleMode::getDurationBeforeHeader() const +{ + // IEEE Std 802.11-2024, 21.3.2: the L-SIG duration is part of the + // pre-header timing of the supported VHT mixed format. + return getNonHTShortTrainingSequenceDuration() + getNonHTLongTrainingFieldDuration() + getLSIGDuration(); +} + const simtime_t Ieee80211VhtPreambleMode::getDuration() const { // 21.3.4 Mathematical description of signals @@ -251,12 +258,9 @@ const simtime_t Ieee80211VhtPreambleMode::getDuration() const bps Ieee80211VhtSignalMode::computeGrossBitrate() const { unsigned int numberOfCodedBitsPerSymbol = modulation->getSubcarrierModulation()->getCodeWordSize() * getNumberOfDataSubcarriers(); - if (guardIntervalType == HT_GUARD_INTERVAL_LONG) - return bps(numberOfCodedBitsPerSymbol / getSymbolInterval()); - else if (guardIntervalType == HT_GUARD_INTERVAL_SHORT) - return bps(numberOfCodedBitsPerSymbol / getShortGISymbolInterval()); - else - throw cRuntimeError("Unknown guard interval type"); + // IEEE Std 802.11-2024, Table 21-5: VHT-SIG fields use TSYML even + // when the Data field uses short GI; their signaling rate is GI-independent. + return bps(numberOfCodedBitsPerSymbol / getSymbolInterval()); } bps Ieee80211VhtSignalMode::computeNetBitrate() const @@ -639,6 +643,20 @@ const simtime_t Ieee80211VhtDataMode::getDuration(b dataLength) const return numberOfSymbols * getSymbolInterval(); } +const simtime_t Ieee80211VhtMode::getDataDuration(b dataBitLength) const +{ + auto dataDuration = dataMode->getDuration(dataBitLength); + if (dataMode->getGuardInterval() == dataMode->getShortGIDuration()) { + // IEEE Std 802.11-2024, 21.4.3, Eq. (21-109): short-GI VHT data + // airtime is the raw TSYMS train rounded up to a TSYML boundary. + // This corrects the previous implementation that used the raw short-GI symbol train. + const auto longGiSymbolInterval = dataMode->getDFTPeriod() + dataMode->getGIDuration(); + const auto numberOfLongGiSymbols = (dataDuration.raw() + longGiSymbolInterval.raw() - 1) / longGiSymbolInterval.raw(); + dataDuration = SimTime::fromRaw(numberOfLongGiSymbols * longGiSymbolInterval.raw()); + } + return dataDuration; +} + const simtime_t Ieee80211VhtMode::getSlotTime() const { if (centerFrequencyMode == BAND_5GHZ) @@ -672,10 +690,14 @@ Ieee80211VhtCompliantModes::~Ieee80211VhtCompliantModes() const Ieee80211VhtMode *Ieee80211VhtCompliantModes::getCompliantMode(const Ieee80211Vhtmcs *mcsMode, Ieee80211VhtMode::BandMode centerFrequencyMode, Ieee80211VhtPreambleMode::HighTroughputPreambleFormat preambleFormat, Ieee80211VhtModeBase::GuardIntervalType guardIntervalType) { + // IEEE Std 802.11-2024, 21.3.2 permits VHT PPDUs only in the mixed + // preamble format represented by this mode implementation. + if (preambleFormat != Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED) + throw cRuntimeError("Unsupported VHT preamble format: only HT_PREAMBLE_MIXED is supported (IEEE Std 802.11-2024, 21.3.2)"); 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(), guardIntervalType, nss, centerFrequencyMode, preambleFormat); + auto mode = singleton.modeCache.find(vhtModeId); if (mode == singleton.modeCache.end()) { const Ieee80211OfdmSignalMode *legacySignal = nullptr; const Ieee80211VhtSignalMode *htSignal = nullptr; @@ -693,7 +715,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 +1094,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 aa4aa3639b0..394086ec06f 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.h @@ -96,6 +96,8 @@ class INET_API Ieee80211VhtSignalMode : public IIeee80211HeaderMode, public Ieee virtual b getLength() const override; virtual bps getNetBitrate() const override { return Ieee80211VhtModeBase::getNetBitrate(); } virtual bps getGrossBitrate() const override { return Ieee80211VhtModeBase::getGrossBitrate(); } + // IEEE Std 802.11-2024, Table 21-5: VHT-SIG uses the long-GI symbol + // interval independently of the data field's selected guard interval. virtual const simtime_t getSymbolInterval() const override { return Ieee80211HtTimingRelatedParametersBase::getSymbolInterval(); } virtual const Ieee80211OfdmModulation *getModulation() const override { return modulation; } virtual const Ieee80211VhtCode *getCode() const { return code; } @@ -148,6 +150,7 @@ class INET_API Ieee80211VhtPreambleMode : public IIeee80211PreambleMode, public virtual const simtime_t getSecondAndSubsequentHTLongTrainingFielDuration() const { return 4E-6; } // HT-LTFs, s = 2,3,..,n virtual unsigned int getNumberOfHtLongTrainings() const { return numberOfHTLongTrainings; } + virtual const simtime_t getDurationBeforeHeader() const; virtual const simtime_t getDuration() const override; virtual Ptr createPreamble() const override { return makeShared(); } @@ -237,7 +240,10 @@ class INET_API Ieee80211VhtDataMode : public IIeee80211DataMode, public Ieee8021 virtual bps getGrossBitrate() const override { return Ieee80211VhtModeBase::getGrossBitrate(); } virtual const Ieee80211Vhtmcs *getModulationAndCodingScheme() const { return modulationAndCodingScheme; } virtual const Ieee80211VhtCode *getCode() const { return modulationAndCodingScheme->getCode(); } - virtual const simtime_t getSymbolInterval() const override { return Ieee80211HtTimingRelatedParametersBase::getSymbolInterval(); } + virtual const simtime_t getGuardInterval() const override { return guardIntervalType == HT_GUARD_INTERVAL_LONG ? getGIDuration() : getShortGIDuration(); } + // IEEE Std 802.11-2024, Tables 21-5 and 21-8: the VHT Data symbol + // interval is TSYML for long GI and TSYMS for short GI. + virtual const simtime_t getSymbolInterval() const override { return getDFTPeriod() + getGuardInterval(); } virtual const Ieee80211OfdmModulation *getModulation() const override { return modulationAndCodingScheme->getModulation(); } }; @@ -280,7 +286,10 @@ class INET_API Ieee80211VhtMode : public Ieee80211ModeBase virtual int getMpduMaxLength() const override { return 65535; } // in octets virtual BandMode getCenterFrequencyMode() const { return centerFrequencyMode; } - virtual const simtime_t getDuration(b dataBitLength) const override { return preambleMode->getDuration() + dataMode->getDuration(dataBitLength); } + virtual const simtime_t getDuration(b dataBitLength) const override { return preambleMode->getDuration() + getDataDuration(dataBitLength); } + virtual const simtime_t getPreambleDuration() const override { return preambleMode->getDurationBeforeHeader(); } + virtual const simtime_t getHeaderDuration() const override { return preambleMode->getDuration() - getPreambleDuration(); } + virtual const simtime_t getDataDuration(b dataBitLength) const override; }; // A specification of the high-throughput (HT) physical layer (PHY) @@ -679,7 +688,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/Ieee80211ControlInfo.msg b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211ControlInfo.msg index 84d97310414..b819d14e951 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211ControlInfo.msg +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211ControlInfo.msg @@ -17,9 +17,9 @@ namespace inet::physicallayer; // class Ieee80211ConfigureRadioCommand extends ConfigureRadioCommand { - string opMode; // new default operation mode or "" if not set. - const Ieee80211ModeSet *modeSet; // new default mode set or nullptr if not set. - const IIeee80211Mode *mode; // new default transmission mode or nullptr if not set. + string opMode; // new default operation mode or "" if not set; ignored when modeSet is set. + const Ieee80211ModeSet *modeSet; // new default mode set or nullptr if not set; takes precedence over opMode. + const IIeee80211Mode *mode; // new default transmission mode or nullptr if not set; atomically validated against the resolved mode set. IIeee80211Band *band; // new default band or nullptr if not set. Ieee80211Channel *channel; // new default band and channel or nullptr if not set. int channelNumber = -1; // new default channel number in the range [0, numChannels] or -1 if not set. diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc index 56114b6c2c6..eea8e96af86 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc @@ -7,8 +7,14 @@ #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" +#include +#include + +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.h" + #include "inet/common/packet/chunk/BitCountChunk.h" #include "inet/common/ProtocolTag_m.h" +#include "inet/common/Simsignals.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" @@ -58,13 +64,17 @@ void Ieee80211Radio::handleUpperCommand(cMessage *message) Ieee80211ConfigureRadioCommand *configureCommand = dynamic_cast(message->getControlInfo()); if (configureCommand != nullptr) { const char *opMode = configureCommand->getOpMode(); - if (*opMode) - setModeSet(Ieee80211ModeSet::getModeSet(opMode)); const Ieee80211ModeSet *modeSet = configureCommand->getModeSet(); - if (modeSet != nullptr) - setModeSet(modeSet); + // NOTE: When both modeSet and opMode are present, modeSet takes precedence + // and opMode is silently ignored. This differs from the previous behavior + // where both were applied sequentially (with modeSet as final state). + const Ieee80211ModeSet *newModeSet = modeSet != nullptr ? modeSet : (*opMode ? Ieee80211ModeSet::getModeSet(opMode) : nullptr); const IIeee80211Mode *mode = configureCommand->getMode(); - if (mode != nullptr) + if (newModeSet != nullptr && mode != nullptr) + setModeSetAndMode(newModeSet, mode); + else if (newModeSet != nullptr) + setModeSet(newModeSet); + else if (mode != nullptr) setMode(mode); const IIeee80211Band *band = configureCommand->getBand(); if (band != nullptr) @@ -82,13 +92,92 @@ void Ieee80211Radio::handleUpperCommand(cMessage *message) void Ieee80211Radio::setModeSet(const Ieee80211ModeSet *modeSet) { - Ieee80211Transmitter *ieee80211Transmitter = const_cast(check_and_cast(transmitter)); - Ieee80211Receiver *ieee80211Receiver = const_cast(check_and_cast(receiver)); - ieee80211Transmitter->setModeSet(modeSet); - ieee80211Receiver->setModeSet(modeSet); - EV << "Changing radio mode set to " << modeSet << endl; + Enter_Method("setModeSet"); + changeModeSet(modeSet, nullptr, false); +} + +void Ieee80211Radio::setModeSetAndMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) +{ + Enter_Method("setModeSetAndMode"); + changeModeSet(modeSet, mode, true); +} + +void Ieee80211Radio::changeModeSet(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode, bool explicitMode) +{ + if (changingModeSet) + throw cRuntimeError("Reentrant radio mode-set change"); + if (modeSet != nullptr && mode != nullptr && !modeSet->containsMode(mode)) + throw cRuntimeError("Invalid mode"); + auto transmitter = const_cast(check_and_cast(this->transmitter)); + auto receiver = const_cast(check_and_cast(this->receiver)); + const auto *oldTransmitterModeSet = transmitter->getModeSet(); + const auto *oldReceiverModeSet = receiver->getModeSet(); + const auto *oldMode = transmitter->getMode(); + auto oldReceptionTimer = receptionTimer; + + // Discover the same subscribers that receive the hierarchical notification, + // deduplicating participants subscribed at more than one level. Capture all + // state first: a failing participant may have partially changed itself. + std::vector participants; + std::vector> restore; + for (cComponent *component = this; component != nullptr; component = component->getParentModule()) { + for (auto listener : component->getLocalSignalListeners(modesetChangedSignal)) { + auto participant = dynamic_cast(listener); + if (participant != nullptr && std::find(participants.begin(), participants.end(), participant) == participants.end()) + participants.push_back(participant); + } + } + if (modeSet == nullptr && !participants.empty()) + throw cRuntimeError("Cannot clear the radio mode set while MAC mode-set consumers are attached"); + for (auto participant : participants) + restore.push_back(participant->saveModeSetState()); + + changingModeSet = true; + try { + if (explicitMode) + transmitter->setModeSetAndMode(modeSet, mode); + else + transmitter->setModeSet(modeSet); + receiver->setModeSet(modeSet); + for (auto participant : participants) + participant->applyModeSet(modeSet); + } + catch (...) { + transmitter->setModeSetAndMode(oldTransmitterModeSet, oldMode); + receiver->setModeSet(oldReceiverModeSet); + for (auto it = restore.rbegin(); it != restore.rend(); ++it) + (*it)(); + receptionTimer = oldReceptionTimer; + changingModeSet = false; + throw; + } receptionTimer = nullptr; - emit(listeningChangedSignal, 0); + // The transaction is committed. Observer failures must not undo a state + // already published to earlier listeners. Keep the reentrancy guard during + // publication so every listener observes the same committed mode set. + std::exception_ptr observerFailure; + if (getComponentType() != nullptr) { + try { + if (modeSet != nullptr) + emit(modesetChangedSignal, const_cast(modeSet)); + } + catch (...) { + observerFailure = std::current_exception(); + } + // Listening changes are independent committed facts: the medium must get + // its publication attempt even when a mode-set observer throws. + try { + emit(listeningChangedSignal, 0); + } + catch (...) { + if (!observerFailure) + observerFailure = std::current_exception(); + } + } + changingModeSet = false; + if (observerFailure) + std::rethrow_exception(observerFailure); + EV << "Changing radio mode set to " << modeSet << " and mode to " << transmitter->getMode() << endl; } void Ieee80211Radio::setMode(const IIeee80211Mode *mode) @@ -97,7 +186,8 @@ void Ieee80211Radio::setMode(const IIeee80211Mode *mode) ieee80211Transmitter->setMode(mode); EV << "Changing radio mode to " << mode << endl; receptionTimer = nullptr; - emit(listeningChangedSignal, 0); + if (getComponentType() != nullptr) + emit(listeningChangedSignal, 0); } void Ieee80211Radio::setBand(const IIeee80211Band *band) @@ -334,4 +424,3 @@ const Ptr Ieee80211Radio::peekIeee80211PhyHeaderAtFron } // namespace physicallayer } // namespace inet - diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h index aa416de6ed8..976b43b23db 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h @@ -30,11 +30,14 @@ class INET_API Ieee80211Radio : public FlatRadioBase static const Ptr peekIeee80211PhyHeaderAtFront(const Packet *packet, b length = b(-1), int flags = 0); protected: + bool changingModeSet = false; FcsMode fcsMode = FCS_MODE_UNDEFINED; protected: virtual void initialize(int stage) override; + void changeModeSet(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode, bool explicitMode); + virtual void handleUpperCommand(cMessage *message) override; virtual void insertFcs(const Ptr& phyHeader) const; @@ -46,7 +49,12 @@ class INET_API Ieee80211Radio : public FlatRadioBase public: Ieee80211Radio(); + // These setters snapshot transactional mode-set consumers before applying + // the catalog, and restore them if an update throws. Notifications publish + // committed state; listener exceptions propagate without rolling it back. + // Behavioral consumers implement IIeee80211ModeSetListener. virtual void setModeSet(const Ieee80211ModeSet *modeSet); + virtual void setModeSetAndMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode); virtual void setMode(const IIeee80211Mode *mode); virtual void setBand(const IIeee80211Band *band); virtual void setChannel(const Ieee80211Channel *channel); @@ -57,4 +65,3 @@ class INET_API Ieee80211Radio : public FlatRadioBase } // namespace inet #endif - diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.ned b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.ned index c32061114d3..0ac071271e4 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"); @@ -47,6 +47,6 @@ module Ieee80211Radio extends FlatRadioBase *.channelNumber = this.channelNumber; @class(Ieee80211Radio); @signal[radioChannelChanged](type=long); + @signal[modesetChanged](type=inet::physicallayer::Ieee80211ModeSet); @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 4c9d2ecd6d1..ae6804b4a84 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.h b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h index 450e8b36c45..49de67c6980 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h @@ -37,6 +37,7 @@ class INET_API Ieee80211Receiver : public FlatReceiverBase virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override; + const Ieee80211ModeSet *getModeSet() const { return modeSet; } virtual void setModeSet(const Ieee80211ModeSet *modeSet); virtual void setBand(const IIeee80211Band *band); virtual void setChannel(const Ieee80211Channel *channel); 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 6c76381a790..1ba824545ea 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,16 +75,31 @@ const Ieee80211Channel *Ieee80211Transmitter::computeTransmissionChannel(const P void Ieee80211Transmitter::setModeSet(const Ieee80211ModeSet *modeSet) { if (this->modeSet != modeSet) { + auto newMode = mode; + if (mode != nullptr && modeSet != nullptr && !modeSet->containsMode(mode)) { + newMode = modeSet->findCompatibleMode(mode); + if (newMode == nullptr) + throw cRuntimeError("Cannot map current mode to operation mode '%s' without changing bitrate, bandwidth, spatial streams, or guard interval", modeSet->getName()); + } + else if (modeSet == nullptr) + newMode = nullptr; this->modeSet = modeSet; - if (mode != nullptr) - mode = modeSet != nullptr ? modeSet->getMode(mode->getDataMode()->getNetBitrate()) : nullptr; + mode = newMode; } } +void Ieee80211Transmitter::setModeSetAndMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) +{ + if (modeSet != nullptr && mode != nullptr && !modeSet->containsMode(mode)) + throw cRuntimeError("Invalid mode"); + this->modeSet = modeSet; + this->mode = mode; +} + void Ieee80211Transmitter::setMode(const IIeee80211Mode *mode) { if (this->mode != mode) { - if (modeSet->findMode(mode->getDataMode()->getNetBitrate(), mode->getDataMode()->getBandwidth()) == nullptr) + if (modeSet != nullptr && mode != nullptr && !modeSet->containsMode(mode)) throw cRuntimeError("Invalid mode"); this->mode = mode; } @@ -151,9 +166,12 @@ const ITransmission *Ieee80211Transmitter::createTransmission(const IRadio *tran const Coord& endPosition = mobility->getCurrentPosition(); 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 preambleDuration = transmissionMode->getPreambleDuration(); + const simtime_t headerDuration = transmissionMode->getHeaderDuration(); + const simtime_t dataDuration = transmissionMode->getDataDuration(B(phyHeader->getLengthField())); + 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); } @@ -161,4 +179,3 @@ const ITransmission *Ieee80211Transmitter::createTransmission(const IRadio *tran } // namespace physicallayer } // namespace inet - diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h index 05b6f29194c..3ac8ebcea24 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h @@ -38,7 +38,13 @@ class INET_API Ieee80211Transmitter : public FlatTransmitterBase virtual const IIeee80211Mode *computeTransmissionMode(const Packet *packet) const; virtual const Ieee80211Channel *computeTransmissionChannel(const Packet *packet) const; + // Re-selects the current mode only when bitrate, bandwidth, NSS, and GI + // remain compatible. Use setModeSetAndMode for an explicit transition. + const Ieee80211ModeSet *getModeSet() const { return modeSet; } + const IIeee80211Mode *getMode() const { return mode; } virtual void setModeSet(const Ieee80211ModeSet *modeSet); + // Applies a mode set and an explicitly selected mode as one validated update. + virtual void setModeSetAndMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode); virtual void setMode(const IIeee80211Mode *mode); virtual void setBand(const IIeee80211Band *band); virtual void setChannel(const Ieee80211Channel *channel); 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/fingerprint/showcases.csv b/tests/fingerprint/showcases.csv index ef401f4edb1..5162a8b12f2 100644 --- a/tests/fingerprint/showcases.csv +++ b/tests/fingerprint/showcases.csv @@ -341,5 +341,5 @@ /showcases/wireless/throughput/, -f omnetpp.ini -c General -r 0, 1s, 030e-c416/tplx;66e6-0bca/~tNl;3cb6-43bd/~tND;e6a5-bde0/tyf, PASS, wireless Ipv4 -/showcases/wireless/txop/, -f omnetpp.ini -c General -r 0, 5s, d2b6-a5d1/tplx;3a6f-4c28/~tNl;c87d-3f3a/tyf, PASS, wireless Ipv4 +/showcases/wireless/txop/, -f omnetpp.ini -c General -r 0, 5s, 1ecd-df80/tplx;86dd-208d/~tNl;c87d-3f3a/tyf, PASS, wireless Ipv4 diff --git a/tests/module/Ieee80211ConfiguredResponseRateSelection.test b/tests/module/Ieee80211ConfiguredResponseRateSelection.test new file mode 100644 index 00000000000..e8212250c11 --- /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("10:00:00:00:00:00")); + 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..7648df48f17 --- /dev/null +++ b/tests/module/Ieee80211HtGreenfieldRuntime.test @@ -0,0 +1,133 @@ +%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->getPreambleDuration() != mode->getPreambleDuration() || + transmission->getHeaderDuration() != mode->getHeaderDuration() || transmission->getHeaderDuration() <= SIMTIME_ZERO || + transmission->getDataDuration() != mode->getDataDuration(B(phyHeader->getLengthField())))) + throw cRuntimeError("HT transmission did not preserve the mode's timed header phase"); + 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/Ieee80211HtHeterogeneousGreenfieldRuntime.test b/tests/module/Ieee80211HtHeterogeneousGreenfieldRuntime.test new file mode 100644 index 00000000000..e753cf7ce9e --- /dev/null +++ b/tests/module/Ieee80211HtHeterogeneousGreenfieldRuntime.test @@ -0,0 +1,204 @@ +%description: +Verifies bidirectional unicast delivery and dynamic PPDU format selection in a +heterogeneous BSS with one n(greenfield-2.4Ghz) station, an n(mixed-2.4Ghz) +access point, and an n(mixed-2.4Ghz) station. Because the AP is mixed-only, the +Greenfield station must recognize that the peer cannot receive Greenfield PPDUs +and dynamically select the compatible HT-mixed format for uplink transmissions. + +%file: Test.cc +#include + +#include "inet/common/Simsignals.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.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 Ieee80211HtHeterogeneousGreenfieldRuntime { + +static OPP_THREAD_LOCAL int greenfieldStaMixedDataCount = 0; +static OPP_THREAD_LOCAL int greenfieldStaGreenfieldDataCount = 0; +static OPP_THREAD_LOCAL int greenfieldStaMixedCtsCount = 0; +static OPP_THREAD_LOCAL int mixedStaDataCount = 0; +static OPP_THREAD_LOCAL int apForwardedDataCount = 0; +static OPP_THREAD_LOCAL int nonHtAckCount = 0; + +class HeterogeneousHtCheckingRadio : public Ieee80211Radio, public cListener +{ + using cListener::finish; + + 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()); + bool isHtGreenfield = mode != nullptr && mode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD; + bool isHtMixed = mode != nullptr && mode->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED; + + std::string fullPath = getFullPath(); + bool isHost0 = fullPath.find("host[0]") != std::string::npos; + bool isHost1 = fullPath.find("host[1]") != std::string::npos; + bool isAp = fullPath.find("ap") != std::string::npos; + + if (dynamicPtrCast(macHeader)) { + if (isHost0) { + if (isHtGreenfield) + greenfieldStaGreenfieldDataCount++; + else if (isHtMixed) + greenfieldStaMixedDataCount++; + } + else if (isHost1 && isHtMixed) + mixedStaDataCount++; + else if (isAp && isHtMixed) + apForwardedDataCount++; + } + if (dynamicPtrCast(macHeader) && isHtMixed && isHost0) + greenfieldStaMixedCtsCount++; + if (dynamicPtrCast(macHeader) && mode == nullptr) + nonHtAckCount++; + } + + virtual void finish() override + { + Ieee80211Radio::finish(); + if (greenfieldStaGreenfieldDataCount > 0) + throw cRuntimeError("Greenfield station sent %d HT-Greenfield frame(s) to mixed-only AP", greenfieldStaGreenfieldDataCount); + if (greenfieldStaMixedDataCount == 0 || greenfieldStaMixedCtsCount == 0 || mixedStaDataCount == 0 || apForwardedDataCount == 0 || nonHtAckCount == 0) + throw cRuntimeError("Expected bidirectional HT-mixed data, GF-STA HT-mixed CTS responses, and non-HT ACK exchanges"); + std::cout << "Observed " << greenfieldStaMixedDataCount << " GF-STA HT-mixed data, " + << greenfieldStaMixedCtsCount << " GF-STA HT-mixed CTS, " + << mixedStaDataCount << " Mixed-STA data, " + << apForwardedDataCount << " AP forwarded data, 0 Greenfield data, and " + << nonHtAckCount << " non-HT ACK response(s).\n"; + } +}; + +Define_Module(HeterogeneousHtCheckingRadio); + +} // namespace Ieee80211HtHeterogeneousGreenfieldRuntime + +%file: test.ned + +import inet.networklayer.configurator.ipv4.Ipv4NetworkConfigurator; +import inet.node.inet.WirelessHost; +import inet.node.wireless.AccessPoint; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211Radio; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; + +module HeterogeneousHtCheckingRadio extends Ieee80211Radio +{ + parameters: + @class(HeterogeneousHtCheckingRadio); +} + +network TestHeterogeneousHtGreenfield +{ + parameters: + int numHosts = default(2); + submodules: + configurator: Ipv4NetworkConfigurator; + radioMedium: Ieee80211ScalarRadioMedium; + host[numHosts]: WirelessHost { + wlan[*].mgmt.typename = "Ieee80211MgmtStaSimplified"; + wlan[*].agent.typename = ""; + } + ap: AccessPoint { + wlan[*].mgmt.typename = "Ieee80211MgmtApSimplified"; + } +} + +%inifile: omnetpp.ini + +[General] +network = TestHeterogeneousHtGreenfield +abstract = false +ned-path = .;../../../../src;../../lib +cmdenv-express-mode = true +record-vector-results = false +record-eventlog = false +seed-set = 0 + +**.constraintAreaMinX = 0m +**.constraintAreaMinY = 0m +**.constraintAreaMinZ = 0m +**.constraintAreaMaxX = 600m +**.constraintAreaMaxY = 400m +**.constraintAreaMaxZ = 0m + +**.arp.typename = "GlobalArp" + +*.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 = 240m +*.host[1].mobility.initialX = 260m + +**.wlan[*].radio.typename = "HeterogeneousHtCheckingRadio" +**.wlan[*].mac.dcf.rtsPolicy.rtsThreshold = 1B + +[Config HeterogeneousHtRuntime] +abstract = false +sim-time-limit = 2s +*.numHosts = 2 + +# Node configuration: host[0] is Greenfield, AP and host[1] are Mixed-HT +*.host[0].wlan[*].opMode = "n(greenfield-2.4Ghz)" +*.host[0].wlan[*].bitrate = 13Mbps +*.host[1].wlan[*].opMode = "n(mixed-2.4Ghz)" +*.host[1].wlan[*].bitrate = 13Mbps +*.ap.wlan[*].opMode = "n(mixed-2.4Ghz)" +*.ap.wlan[*].bitrate = 13Mbps + +# Bidirectional UDP traffic between host[0] and host[1] through AP +*.host[0].numApps = 2 +*.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[0].app[1].typename = "UdpSink" +*.host[0].app[1].localPort = 2000 + +*.host[1].numApps = 2 +*.host[1].app[0].typename = "UdpSink" +*.host[1].app[0].localPort = 1000 + +*.host[1].app[1].typename = "UdpBasicApp" +*.host[1].app[1].destAddresses = "host[0]" +*.host[1].app[1].destPort = 2000 +*.host[1].app[1].messageLength = 100B +*.host[1].app[1].startTime = 150ms +*.host[1].app[1].sendInterval = 100ms + +%extraargs: -c HeterogeneousHtRuntime + +%contains-regex: stdout +Observed [1-9][0-9]* GF-STA HT-mixed data, [1-9][0-9]* GF-STA HT-mixed CTS, [1-9][0-9]* Mixed-STA data, [1-9][0-9]* AP forwarded data, 0 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..297ae4de410 --- /dev/null +++ b/tests/module/Ieee80211HtMixedRuntime.test @@ -0,0 +1,132 @@ +%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->getPreambleDuration() != mode->getPreambleDuration() || + transmission->getHeaderDuration() != mode->getHeaderDuration() || transmission->getHeaderDuration() <= SIMTIME_ZERO || + transmission->getDataDuration() != mode->getDataDuration(B(phyHeader->getLengthField())))) + throw cRuntimeError("HT transmission did not preserve the mode's timed header phase"); + 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..294f3939aa0 --- /dev/null +++ b/tests/module/Ieee80211InvalidConfiguredCtsRate.test @@ -0,0 +1,134 @@ +%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) { applyModeSet(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) { applyModeSet(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 = ""; + mibModule = ""; + responseCtsFrameBitrate = default(6Mbps); +} + +simple TestQosRateSelection extends QosRateSelection +{ + parameters: + @class(TestQosRateSelection); + rateControlModule = ""; + mibModule = ""; + 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/Ieee80211MixedHtDiscovery_1.test b/tests/module/Ieee80211MixedHtDiscovery_1.test new file mode 100644 index 00000000000..ae7c47c571b --- /dev/null +++ b/tests/module/Ieee80211MixedHtDiscovery_1.test @@ -0,0 +1,141 @@ +%description: +Two mixed HT APs (DCF and HCF) advertise legacy basic-rate Beacons over the +radio medium. Legacy STAs discover them by passive scanning and associate. +The observation includes selected transmit modes, actual Beacon reception, +and terminal association state, so an HT Beacon cannot pass this test. + +%file: Ieee80211MixedHtDiscovery.cc +#include "inet/linklayer/ieee80211/mac/Ieee80211Mac.h" +#include "inet/linklayer/ieee80211/mib/Ieee80211Mib.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" + +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +class DiscoveryMac : public Ieee80211Mac +{ + public: + int beaconsSent = 0; + int beaconsReceived = 0; + protected: + virtual void sendDownFrame(Packet *packet) override + { + const auto& header = packet->peekAtFront(); + if (header->getType() == ST_BEACON) { + auto mode = packet->getTag()->getMode(); + ASSERT(mode->getHtMcsIndex() < 0); + ASSERT(modeSet->containsMode(mode)); + ASSERT(modeSet->getIsMandatory(mode)); + beaconsSent++; + } + Ieee80211Mac::sendDownFrame(packet); + } + virtual void handleLowerPacket(Packet *packet) override + { + const auto& header = packet->peekAtFront(); + if (header->getType() == ST_BEACON) { + ASSERT(packet->getTag()->getMode()->getHtMcsIndex() < 0); + beaconsReceived++; + } + Ieee80211Mac::handleLowerPacket(packet); + } +}; +Define_Module(DiscoveryMac); + +class Ieee80211MixedHtDiscoveryTest : public cSimpleModule +{ + protected: + virtual void finish() override + { + for (int i = 0; i < 2; i++) { + auto apNic = getParentModule()->getSubmodule("ap", i)->getSubmodule("wlan", 0); + auto staNic = getParentModule()->getSubmodule("sta", i)->getSubmodule("wlan", 0); + auto ap = check_and_cast(apNic->getSubmodule("mib")); + auto sta = check_and_cast(staNic->getSubmodule("mib")); + ASSERT(ap->isHtOperationSupported()); + ASSERT(!sta->isHtOperationSupported()); + ASSERT(sta->bssStationData.isAssociated); + ASSERT(sta->bssData.bssid == ap->address); + ASSERT(ap->bssAccessPointData.stations.at(sta->address) == Ieee80211Mib::ASSOCIATED); + ASSERT(ap->findPeerHtState(sta->address) == nullptr); + ASSERT(sta->findPeerHtState(ap->address) == nullptr); + ASSERT(check_and_cast(apNic->getSubmodule("mac"))->beaconsSent > 0); + ASSERT(check_and_cast(staNic->getSubmodule("mac"))->beaconsReceived > 0); + } + std::cout << "Legacy passive discovery and association with mixed HT DCF and HCF APs verified.\n"; + } +}; +Define_Module(Ieee80211MixedHtDiscoveryTest); + +%file: test.ned +import inet.common.SimpleModule; +import inet.linklayer.ieee80211.mac.Ieee80211Mac; +import inet.node.inet.WirelessHost; +import inet.node.wireless.AccessPoint; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; + +module DiscoveryMac extends Ieee80211Mac +{ + parameters: + @class(::DiscoveryMac); +} + +simple Ieee80211MixedHtDiscoveryTest extends SimpleModule +{ + parameters: + @class(::Ieee80211MixedHtDiscoveryTest); +} + +network DiscoveryNetwork +{ + submodules: + radioMedium: Ieee80211ScalarRadioMedium; + ap[2]: AccessPoint; + sta[2]: WirelessHost; + test: Ieee80211MixedHtDiscoveryTest; +} + +%inifile: omnetpp.ini +[General] +network = DiscoveryNetwork +ned-path = .;../../../../src;../../lib +sim-time-limit = 100ms +seed-set = 0 +cmdenv-express-mode = false +record-vector-results = false +record-scalar-results = false +**.mobility.initFromDisplayString = false +**.mobility.initialX = 10m +**.mobility.initialY = 10m +*.ap[*].wlan[*].opMode = "n(mixed-2.4Ghz)" +*.sta[*].wlan[*].opMode = "g(mixed)" +**.wlan[*].bitrate = -1bps +**.wlan[*].radio.bandName = "2.4 GHz" +**.wlan[*].radio.channelNumber = 6 +**.wlan[*].radio.transmitter.power = 100mW +**.wlan[*].radio.receiver.sensitivity = -85dBm +**.wlan[*].radio.receiver.snirThreshold = 4dB +**.wlan[*].mac.typename = "DiscoveryMac" +*.ap[0].wlan[0].mac.qosStation = false +*.ap[1].wlan[0].mac.qosStation = true +*.ap[*].wlan[0].mgmt.typename = "Ieee80211MgmtAp" +*.ap[0].wlan[0].mgmt.ssid = "mixed-dcf" +*.ap[1].wlan[0].mgmt.ssid = "mixed-hcf" +*.ap[*].wlan[0].mgmt.beaconInterval = 5ms +*.sta[*].wlan[0].mgmt.typename = "Ieee80211MgmtSta" +*.sta[*].wlan[0].mgmt.numChannels = 1 +*.sta[*].wlan[0].agent.typename = "Ieee80211AgentSta" +*.sta[*].wlan[0].agent.activeScan = false +*.sta[0].wlan[0].agent.startingTime = 0s +*.sta[1].wlan[0].agent.startingTime = 1ms +*.sta[0].wlan[0].agent.defaultSsid = "mixed-dcf" +*.sta[1].wlan[0].agent.defaultSsid = "mixed-hcf" +*.sta[*].wlan[0].agent.channelsToScan = "6" +*.sta[*].wlan[0].agent.minChannelTime = 10ms +*.sta[*].wlan[0].agent.maxChannelTime = 10ms +*.sta[*].wlan[0].agent.authenticationTimeout = 20ms +*.sta[*].wlan[0].agent.associationTimeout = 20ms + +%contains: stdout +Legacy passive discovery and association with mixed HT DCF and HCF APs verified. diff --git a/tests/module/Ieee80211ModeSetTransition_1.test b/tests/module/Ieee80211ModeSetTransition_1.test new file mode 100644 index 00000000000..fbb09387ff6 --- /dev/null +++ b/tests/module/Ieee80211ModeSetTransition_1.test @@ -0,0 +1,392 @@ +%description: +Exercise both radio mode-set setters with DCF and HCF selectors. Successful +HT/legacy transitions refresh PHY-limited capabilities and actual Beacon bodies. +Fixed-rate failures restore all subscribed catalogs, caches, peer negotiations, +and channel-access windows. Observer failures leave published state committed, +publish both notifications, and reach RadioMedium's listening-change handler. + +%file: Ieee80211ModeSetTransition.cc + +#include "inet/common/Simsignals.h" +#include "inet/physicallayer/wireless/common/medium/RadioMedium.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Mac.h" +#include "inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h" +#include "inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h" +#include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h" +#include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h" + +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +class TransitionRadioMedium : public RadioMedium +{ + public: + int listeningNotifications = 0; + cComponent *lastListeningSource = nullptr; + protected: + virtual void receiveSignal(cComponent *source, simsignal_t signal, intval_t value, cObject *details) override + { + Enter_Method_Silent(); + RadioMedium::receiveSignal(source, signal, value, details); + if (signal == IRadio::listeningChangedSignal) { + listeningNotifications++; + lastListeningSource = source; + } + } +}; +Define_Module(TransitionRadioMedium); + +class TransitionMgmtAp : public Ieee80211MgmtAp +{ + bool probing = false; + public: + void checkAdvertisement() + { + Enter_Method_Silent(); + probing = true; + sendBeacon(); + probing = false; + } + protected: + virtual void sendDown(Packet *packet) override + { + if (!probing) { + Ieee80211MgmtAp::sendDown(packet); + return; + } + const auto& body = packet->peekAtFront(); + ASSERT(body->getHtCapabilitiesPresent() == modeSet->isHtOperationSupported()); + ASSERT(body->getHtOperationPresent() == modeSet->isHtOperationSupported()); + const auto& rates = body->getSupportedRates(); + const auto& extended = body->getExtendedSupportedRates(); + const auto& modes = modeSet->getLegacyOperationalModes(); + ASSERT(rates.numRates + extended.numRates == (int)modes.size()); + for (int i = 0; i < (int)modes.size(); i++) { + double rate = i < rates.numRates ? rates.rate[i] : extended.rate[i - rates.numRates]; + bool basic = i < rates.numRates ? rates.basicRate[i] : extended.basicRate[i - rates.numRates]; + ASSERT(rate == modes[i]->getDataMode()->getNetBitrate().get()); + ASSERT(basic == modeSet->getIsMandatory(modes[i])); + } + if (modeSet->isHtOperationSupported()) { + for (int i = 0; i < 77; i++) + ASSERT(body->getHtCapabilities().rxMcsSupported[i] == (i < 8)); + ASSERT(!body->getHtCapabilities().supportedChannelWidth40Mhz); + ASSERT(!body->getHtCapabilities().shortGi40); + } + delete packet; + } +}; +Define_Module(TransitionMgmtAp); + +class CachingModeSetObserver : public cListener +{ + public: + const Ieee80211ModeSet *modeSet = nullptr; + int notifications = 0; + virtual void receiveSignal(cComponent *source, simsignal_t signal, cObject *value, cObject *details) override + { + modeSet = check_and_cast(value); + notifications++; + } +}; + +class Ieee80211ModeSetTransitionTest : public cSimpleModule, public cListener +{ + bool rejectNotification = false; + bool rejectListeningNotification = false; + int listeningNotifications = 0; + int notifications = 0; + const Ieee80211ModeSet *observedModeSet = nullptr; + CachingModeSetObserver cachingObserver; + MacAddress peer = MacAddress("02:00:00:00:01:ff"); + + std::vector participants(cModule *radio) + { + std::vector result; + for (cComponent *component = radio; component; component = component->getParentModule()) + for (auto listener : component->getLocalSignalListeners(modesetChangedSignal)) + if (auto participant = dynamic_cast(listener)) + result.push_back(participant); + return result; + } + + void check(cModule *nic, const Ieee80211ModeSet *expected, bool peerExpected) + { + auto radio = check_and_cast(nic->getSubmodule("radio")); + ASSERT(check_and_cast(radio->getTransmitter())->getModeSet() == expected); + ASSERT(check_and_cast(radio->getReceiver())->getModeSet() == expected); + for (auto participant : participants(radio)) + ASSERT(participant->getModeSet() == expected); + auto mib = check_and_cast(nic->getSubmodule("mib")); + ASSERT(mib->isHtOperationSupported() == expected->isHtOperationSupported()); + ASSERT(mib->requirePrimaryChannel() == 6); + ASSERT((mib->findPeerHtState(peer) != nullptr) == peerExpected); + for (int i = 0; i < 77; i++) { + ASSERT(mib->localHtCapabilities.rxMcsSupported[i] == (expected->isHtOperationSupported() && i < 8)); + ASSERT(mib->getHtOperation().basicMcsSupported[i] == (expected->isHtOperationSupported() && i < 8)); + } + if (peerExpected) { + auto state = mib->findPeerHtState(peer); + ASSERT(state->negotiatedCapabilities.localTxPeerRx.valid); + for (int i = 8; i < 77; i++) + ASSERT(!state->negotiatedCapabilities.localTxPeerRx.supportedMcs[i]); + } + check_and_cast(nic->getSubmodule("mgmt"))->checkAdvertisement(); + auto data = makeShared(); + auto beacon = makeShared(); + beacon->setType(ST_BEACON); + for (auto address : {MacAddress::BROADCAST_ADDRESS, MacAddress("01:00:5e:00:00:01"), peer}) { + data->setReceiverAddress(address); + beacon->setReceiverAddress(address); + Packet packet("selection probe"); + auto mac = nic->getSubmodule("mac"); + auto dcf = check_and_cast(mac->getSubmodule("dcf")->getSubmodule("rateSelection")); + auto qos = check_and_cast(mac->getSubmodule("hcf")->getSubmodule("rateSelection")); + for (const auto& header : {Ptr(data), Ptr(beacon)}) { + auto nonQosMode = dcf->computeMode(&packet, header); + auto qosMode = qos->computeMode(&packet, header, nullptr); + ASSERT(expected->containsMode(nonQosMode)); + ASSERT(expected->containsMode(qosMode)); + if (address.isMulticast()) { + ASSERT(nonQosMode->getHtMcsIndex() < 0); + ASSERT(qosMode->getHtMcsIndex() < 0); + ASSERT(expected->getIsMandatory(nonQosMode)); + ASSERT(expected->getIsMandatory(qosMode)); + } + } + } + } + + protected: + virtual void initialize() override { scheduleAt(SimTime(1, SIMTIME_US), new cMessage("switch")); } + virtual void receiveSignal(cComponent *source, simsignal_t signal, intval_t value, cObject *details) override + { + Enter_Method_Silent(); + ASSERT(signal == IRadio::listeningChangedSignal); + listeningNotifications++; + if (rejectListeningNotification) + throw cRuntimeError("test observer rejects listening change"); + } + virtual void receiveSignal(cComponent *source, simsignal_t signal, cObject *value, cObject *details) override + { + Enter_Method_Silent(); + notifications++; + auto radio = check_and_cast(source); + auto target = check_and_cast(value); + observedModeSet = target; + ASSERT(cachingObserver.modeSet == target); + ASSERT(cachingObserver.notifications == notifications); + // Publication must follow *all* internal updates, irrespective of where + // this observer appears in the subscriber list. + auto nic = radio->getParentModule(); + for (auto participant : participants(radio)) + ASSERT(participant->getModeSet() == target); + ASSERT(check_and_cast(nic->getSubmodule("mib"))->isHtOperationSupported() == target->isHtOperationSupported()); + check_and_cast(nic->getSubmodule("mgmt"))->checkAdvertisement(); + if (rejectNotification) + throw cRuntimeError("test observer rejects mode set"); + } + + virtual void handleMessage(cMessage *message) override + { + delete message; + for (int index : {1, 2, 0}) { + auto nic = getParentModule()->getSubmodule("ap", index)->getSubmodule("wlan", 0); + auto radio = check_and_cast(nic->getSubmodule("radio")); + auto mib = check_and_cast(nic->getSubmodule("mib")); + auto ht = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"); + auto legacy = Ieee80211ModeSet::getModeSet("g(mixed)"); + radio->setMode(ht->getMode(Mbps(24))); + cachingObserver.modeSet = ht; + observedModeSet = ht; + radio->subscribe(modesetChangedSignal, &cachingObserver); + // Hierarchical delivery guarantees the radio's ordinary caching + // listener runs before this ordinary throwing listener on its parent. + nic->subscribe(modesetChangedSignal, this); + mib->setPeerHtCapabilities(peer, mib->localHtCapabilities, mib->getHtOperation()); + check(nic, ht, true); + auto oldGeneration = mib->findPeerHtState(peer)->generation; + auto tx = check_and_cast(radio->getTransmitter()); + auto oldMode = tx->getMode(); + auto dcf = nic->getSubmodule("mac")->getSubmodule("dcf"); + auto hcf = nic->getSubmodule("mac")->getSubmodule("hcf"); + auto channelAccess = check_and_cast(dcf->getSubmodule("channelAccess")); + auto edcaf = check_and_cast(hcf->getSubmodule("edca")->getSubmodule("edcaf", 0)); + channelAccess->incrementCw(); + edcaf->incrementCw(); + int oldCw = channelAccess->getCw(); + int oldQosCw = edcaf->getCw(); + // Populate both adaptive histories before any failed transition. + auto arf = check_and_cast(dcf->getSubmodule("rateControl")); + auto onoe = check_and_cast(hcf->getSubmodule("rateControl")); + auto oldArf = arf->getRate(peer); + auto oldOnoe = onoe->getRate(peer); + if (index != 0) { + for (bool explicitMode : {false, true}) { + bool rejected = false; + try { + if (explicitMode) + radio->setModeSetAndMode(legacy, legacy->getMode(Mbps(24))); + else + radio->setModeSet(legacy); + } + catch (const cRuntimeError&) { rejected = true; } + ASSERT(rejected); + ASSERT(notifications == 0); + check(nic, ht, true); + ASSERT(tx->getMode() == oldMode); + ASSERT(mib->findPeerHtState(peer)->generation == oldGeneration); + ASSERT(channelAccess->getCw() == oldCw); + ASSERT(edcaf->getCw() == oldQosCw); + ASSERT(arf->getRate(peer) == oldArf); + ASSERT(onoe->getRate(peer) == oldOnoe); + } + nic->unsubscribe(modesetChangedSignal, this); + radio->unsubscribe(modesetChangedSignal, &cachingObserver); + continue; + } + // Throw after an ordinary observer has cached the published set. + // Both observers and all behavioral participants must retain it. + rejectNotification = true; + auto medium = check_and_cast(getParentModule()->getSubmodule("radioMedium")); + int oldListeningNotifications = medium->listeningNotifications; + bool rejected = false; + try { radio->setModeSetAndMode(legacy, legacy->getMode(Mbps(24))); } + catch (const cRuntimeError&) { rejected = true; } + ASSERT(rejected); + ASSERT(medium->listeningNotifications == oldListeningNotifications + 1); + ASSERT(medium->lastListeningSource == radio); + ASSERT(notifications == 1); + check(nic, legacy, false); + ASSERT(tx->getMode() == legacy->getMode(Mbps(24))); + ASSERT(cachingObserver.modeSet == tx->getModeSet()); + ASSERT(observedModeSet == tx->getModeSet()); + rejectNotification = false; + radio->setModeSet(legacy); + check(nic, legacy, false); + radio->setModeSetAndMode(ht, ht->getMode(Mbps(65), MHz(20), 1, 800e-9)); + check(nic, ht, false); + mib->setPeerHtCapabilities(peer, mib->localHtCapabilities, mib->getHtOperation()); + check(nic, ht, true); + ASSERT(notifications == 3); + ASSERT(cachingObserver.modeSet == tx->getModeSet()); + ASSERT(observedModeSet == tx->getModeSet()); + // The medium subscribes on the radio, so it processes listening + // changes before the throwing observer on the parent interface. + radio->setMode(ht->getMode(Mbps(24))); + nic->subscribe(IRadio::listeningChangedSignal, this); + for (bool explicitMode : {false, true}) { + for (bool rejectModeSet : {false, true}) { + rejectNotification = rejectModeSet; + rejectListeningNotification = true; + int oldNotifications = notifications; + int oldListening = listeningNotifications; + int oldMediumListening = medium->listeningNotifications; + auto target = rejectModeSet ? ht : legacy; + bool caught = false; + try { + if (explicitMode) + radio->setModeSetAndMode(target, target->getMode(Mbps(24))); + else + radio->setModeSet(target); + } + catch (const cRuntimeError& error) { + caught = true; + const char *expected = rejectModeSet ? "test observer rejects mode set" : "test observer rejects listening change"; + ASSERT(std::string(error.what()).find(expected) != std::string::npos); + } + ASSERT(caught); + ASSERT(notifications == oldNotifications + 1); + ASSERT(listeningNotifications == oldListening + 1); + ASSERT(medium->listeningNotifications == oldMediumListening + 1); + ASSERT(medium->lastListeningSource == radio); + check(nic, target, false); + } + } + rejectNotification = false; + rejectListeningNotification = false; + // A subsequent transition also proves the reentrancy guard cleared. + radio->setModeSet(ht); + check(nic, ht, false); + nic->unsubscribe(IRadio::listeningChangedSignal, this); + nic->unsubscribe(modesetChangedSignal, this); + radio->unsubscribe(modesetChangedSignal, &cachingObserver); + } + std::cout << "Mode-set switching, advertisements, peer state, both selectors, and rollback verified.\n"; + endSimulation(); + } +}; +Define_Module(Ieee80211ModeSetTransitionTest); + +%file: test.ned +import inet.common.SimpleModule; +import inet.node.wireless.AccessPoint; +import inet.linklayer.ieee80211.mgmt.Ieee80211MgmtAp; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; + +simple TransitionMgmtAp extends Ieee80211MgmtAp +{ + parameters: + @class(::TransitionMgmtAp); +} + +module TransitionRadioMedium extends Ieee80211ScalarRadioMedium +{ + parameters: + @class(::TransitionRadioMedium); +} + +simple Ieee80211ModeSetTransitionTest extends SimpleModule +{ + parameters: + @class(::Ieee80211ModeSetTransitionTest); +} + +network TransitionNetwork +{ + submodules: + radioMedium: TransitionRadioMedium; + ap[3]: AccessPoint; + test: Ieee80211ModeSetTransitionTest; +} + +%inifile: omnetpp.ini +[General] +network = TransitionNetwork +ned-path = .;../../../../src;../../lib +sim-time-limit = 1ms +seed-set = 0 +cmdenv-express-mode = false +record-vector-results = false +record-scalar-results = false +*.radioMedium.listeningFilter = true +**.mobility.initFromDisplayString = false +**.mobility.initialX = 10m +**.mobility.initialY = 10m +**.wlan[*].opMode = "n(mixed-2.4Ghz)" +**.wlan[*].bitrate = -1bps +**.wlan[*].radio.bandName = "2.4 GHz" +**.wlan[*].radio.channelNumber = 6 +**.wlan[*].radio.transmitter.power = 100mW +**.wlan[*].mac.qosStation = true +*.ap[*].wlan[0].mgmt.typename = "TransitionMgmtAp" +*.ap[*].wlan[0].mgmt.ssid = "transition" +*.ap[*].wlan[0].mgmt.beaconInterval = 1s +*.ap[*].wlan[0].mac.dcf.rateControl.typename = "AarfRateControl" +*.ap[*].wlan[0].mac.hcf.rateControl.typename = "OnoeRateControl" + +*.ap[1].wlan[0].mac.dcf.rateSelection.dataFrameBitrate = 65Mbps +*.ap[1].wlan[0].mac.dcf.rateSelection.dataFrameBandwidth = 20MHz +*.ap[1].wlan[0].mac.dcf.rateSelection.dataFrameNumSpatialStreams = 1 +*.ap[1].wlan[0].mac.dcf.rateSelection.dataFrameGuardInterval = 800ns +*.ap[2].wlan[0].mac.hcf.rateSelection.dataFrameBitrate = 65Mbps +*.ap[2].wlan[0].mac.hcf.rateSelection.dataFrameBandwidth = 20MHz +*.ap[2].wlan[0].mac.hcf.rateSelection.dataFrameNumSpatialStreams = 1 +*.ap[2].wlan[0].mac.hcf.rateSelection.dataFrameGuardInterval = 800ns + +%contains: stdout +Mode-set switching, advertisements, peer state, both selectors, and rollback verified. diff --git a/tests/module/Ieee80211QualifiedResponseRateSelection.test b/tests/module/Ieee80211QualifiedResponseRateSelection.test new file mode 100644 index 00000000000..61c17a11672 --- /dev/null +++ b/tests/module/Ieee80211QualifiedResponseRateSelection.test @@ -0,0 +1,189 @@ +%description: +Exercise production DCF/QoS configured response resolution, ambiguity rejection, +qualifier preservation for HT-mixed CTS, and optional multicast rate ceilings. + +%file: Test.cc +#include +#include +#include "inet/common/InitStages.h" +#include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h" +#include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +namespace Ieee80211QualifiedResponseRateSelection { + +template +static void checkSelector(Selector& selector, bool qos) +{ + for (const char *profile : {"n(mixed-2.4Ghz)", "n(greenfield-2.4Ghz)"}) { + auto modes = Ieee80211ModeSet::getModeSet(profile); + for (const char *kind : {"Ack", "Cts", "BlockAck"}) { + if (!qos && std::string(kind) == "BlockAck") + continue; + std::string stem = std::string("response") + kind + "Frame"; + auto bitrate = stem + "Bitrate"; + auto bandwidth = stem + "Bandwidth"; + auto streams = stem + "NumSpatialStreams"; + auto gi = stem + "GuardInterval"; + selector.par(bitrate.c_str()).setDoubleValue(13e6); + bool rejected = false; + try { selector.applyModeSet(modes); } + catch (const cRuntimeError& error) { + rejected = error.getFormattedMessage().find("Ambiguous mode") != std::string::npos; + } + ASSERT(rejected); + selector.par(bandwidth.c_str()).setDoubleValue(20e6); + selector.par(gi.c_str()).setDoubleValue(800e-9); + for (int nss : {1, 2}) { + selector.par(streams.c_str()).setIntValue(nss); + selector.applyModeSet(modes); + auto configured = selector.configured(kind); + ASSERT(configured->getHtMcsIndex() == (nss == 1 ? 1 : 8)); + ASSERT(configured->getDataMode()->getBandwidth() == MHz(20)); + ASSERT(configured->getDataMode()->getGuardInterval() == SimTime(800, SIMTIME_NS)); + if (std::string(kind) == "Cts") { + Packet rts("rts"); + rts.addTag()->setMode(configured); + auto response = selector.computeResponseCtsFrameMode(&rts, nullptr); + ASSERT(!response->isHtGreenfield()); + ASSERT(response->getHtMcsIndex() == configured->getHtMcsIndex()); + ASSERT(response->getDataMode()->getBandwidth() == configured->getDataMode()->getBandwidth()); + ASSERT(response->getDataMode()->getNumberOfSpatialStreams() == nss); + ASSERT(response->getDataMode()->getGuardInterval() == configured->getDataMode()->getGuardInterval()); + } + } + // A conflicting explicit GI must not silently choose another tuple. + selector.par(gi.c_str()).setDoubleValue(400e-9); + rejected = false; + try { selector.applyModeSet(modes); } + catch (const cRuntimeError&) { rejected = true; } + ASSERT(rejected); + // Width and short GI also distinguish a configured HT tuple. This + // exercises selection only; packet-level HT40 operation is unsupported. + selector.par(bitrate.c_str()).setDoubleValue(15e6); + selector.par(bandwidth.c_str()).setDoubleValue(40e6); + selector.par(streams.c_str()).setIntValue(1); + selector.applyModeSet(modes); + auto wideShort = selector.configured(kind); + ASSERT(wideShort->getHtMcsIndex() == 0); + ASSERT(wideShort->getDataMode()->getBandwidth() == MHz(40)); + ASSERT(wideShort->isHtShortGuardInterval()); + if (std::string(kind) == "Cts") { + Packet rts("wideShortRts"); + rts.addTag()->setMode(wideShort); + auto response = selector.computeResponseCtsFrameMode(&rts, nullptr); + ASSERT(!response->isHtGreenfield()); + ASSERT(response->getHtMcsIndex() == 0); + ASSERT(response->getDataMode()->getBandwidth() == MHz(40)); + ASSERT(response->getDataMode()->getNumberOfSpatialStreams() == 1); + ASSERT(response->isHtShortGuardInterval()); + } + selector.par(bitrate.c_str()).setDoubleValue(-1); + selector.applyModeSet(modes); // automatic ignores the qualifiers + ASSERT(selector.configured(kind) == nullptr); + selector.par(bandwidth.c_str()).setDoubleValue(NaN); + selector.par(streams.c_str()).setIntValue(-1); + selector.par(gi.c_str()).setDoubleValue(-1); + } + } + for (const char *profile : {"a", "g(mixed)"}) { + for (int rate : {6, 9, 12, 18, 24}) { + selector.par("multicastFrameBitrate").setDoubleValue(rate * 1e6); + selector.applyModeSet(Ieee80211ModeSet::getModeSet(profile)); + auto header = makeShared(); + header->setReceiverAddress(MacAddress::BROADCAST_ADDRESS); + auto selected = selector.groupMode(header); + ASSERT(selected->getDataMode()->getNetBitrate() == Mbps(rate == 9 ? 6 : rate == 18 ? 12 : rate)); + } + } +} + +class DcfProbe : public RateSelection +{ + public: + const IIeee80211Mode *configured(const char *kind) const { return std::string(kind) == "Ack" ? responseAckFrameMode : responseCtsFrameMode; } + const IIeee80211Mode *groupMode(const Ptr& header) { return computeMode(nullptr, header); } + protected: + void initialize(int stage) override { + if (stage == INITSTAGE_LINK_LAYER) { + checkSelector(*this, false); + std::cout << "DCF qualified responses and multicast ceilings verified.\n"; + } + } +}; +class QosProbe : public QosRateSelection +{ + public: + const IIeee80211Mode *configured(const char *kind) const { return std::string(kind) == "Ack" ? responseAckFrameMode : std::string(kind) == "Cts" ? responseCtsFrameMode : responseBlockAckFrameMode; } + const IIeee80211Mode *groupMode(const Ptr& header) { return computeMode(nullptr, header, nullptr); } + protected: + void initialize(int stage) override { + if (stage == INITSTAGE_LINK_LAYER) { + checkSelector(*this, true); + std::cout << "QoS qualified responses and multicast ceilings verified.\n"; + } + } +}; +Define_Module(DcfProbe); +Define_Module(QosProbe); +} + +%file: test.ned +import inet.linklayer.ieee80211.mac.rateselection.RateSelection; +import inet.linklayer.ieee80211.mac.rateselection.QosRateSelection; +simple DcfProbe extends RateSelection +{ + parameters: + @class(DcfProbe); + rateControlModule = ""; + mibModule = ""; + multicastFrameBitrate @mutable; + responseAckFrameBitrate @mutable; + responseAckFrameBandwidth @mutable; + responseAckFrameNumSpatialStreams @mutable; + responseAckFrameGuardInterval @mutable; + responseCtsFrameBitrate @mutable; + responseCtsFrameBandwidth @mutable; + responseCtsFrameNumSpatialStreams @mutable; + responseCtsFrameGuardInterval @mutable; +} +simple QosProbe extends QosRateSelection +{ + parameters: + @class(QosProbe); + rateControlModule = ""; + mibModule = ""; + multicastFrameBitrate @mutable; + responseAckFrameBitrate @mutable; + responseAckFrameBandwidth @mutable; + responseAckFrameNumSpatialStreams @mutable; + responseAckFrameGuardInterval @mutable; + responseCtsFrameBitrate @mutable; + responseCtsFrameBandwidth @mutable; + responseCtsFrameNumSpatialStreams @mutable; + responseCtsFrameGuardInterval @mutable; + responseBlockAckFrameBitrate @mutable; + responseBlockAckFrameBandwidth @mutable; + responseBlockAckFrameNumSpatialStreams @mutable; + responseBlockAckFrameGuardInterval @mutable; +} +network Test +{ + submodules: + dcf: DcfProbe; + qos: QosProbe; +} +%inifile: omnetpp.ini +[General] +network = Test +ned-path = .;../../../../src;../../lib +cmdenv-express-mode = true +record-vector-results = false +seed-set = 0 +%contains: stdout +DCF qualified responses and multicast ceilings verified. +%contains: stdout +QoS qualified responses and multicast ceilings verified. diff --git a/tests/module/Ieee80211TransmissionTiming.test b/tests/module/Ieee80211TransmissionTiming.test new file mode 100644 index 00000000000..563072ddea0 --- /dev/null +++ b/tests/module/Ieee80211TransmissionTiming.test @@ -0,0 +1,106 @@ +%description: +Verify packet transmitter chronological preamble/header/data durations and analog +model boundaries for HT-mixed, HT-Greenfield, VHT, and legacy modes. + +%file: Test.cc +#include +#include "inet/common/InitStages.h" +#include "inet/common/packet/chunk/ByteCountChunk.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmission.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" +#include "inet/physicallayer/wireless/common/contract/bitlevel/ISignalAnalogModel.h" +using namespace inet; +using namespace inet::physicallayer; +namespace Ieee80211TransmissionTiming { +class TimingRadio : public Ieee80211Radio +{ + public: + void encapsulateForTest(Packet *packet) const { encapsulate(packet); } +}; +Define_Module(TimingRadio); +class TimingProbe : public cSimpleModule +{ + protected: + int numInitStages() const override { return NUM_INIT_STAGES; } + void initialize(int stage) override { + if (stage != INITSTAGE_LAST) + return; + auto radio = check_and_cast(getSimulation()->getModuleByPath("Test.host.wlan[0].radio")); + auto transmitter = check_and_cast(radio->getSubmodule("transmitter")); + int count = 0; + for (const char *profile : {"n(mixed-2.4Ghz)", "n(greenfield-2.4Ghz)", "ac", "a", "b", "g(mixed)"}) { + auto modes = Ieee80211ModeSet::getModeSet(profile); + for (int i = 0; i < modes->getNumModes(); i++) { + auto mode = modes->getMode(i); + // One antenna suffices; use every one-stream catalog variant. + if (mode->getDataMode()->getNumberOfSpatialStreams() > 1) + continue; + transmitter->setModeSetAndMode(modes, mode); + for (int length : {1, 100, 1500}) { + Packet packet("timing"); + packet.insertAtBack(makeShared(B(length))); + packet.addTag()->setMode(mode); + radio->encapsulateForTest(&packet); + auto transmission = transmitter->createTransmission(radio, &packet, SimTime(1)); + ASSERT(transmission->getPreambleDuration() == mode->getPreambleDuration()); + ASSERT(transmission->getHeaderDuration() == mode->getHeaderDuration()); + ASSERT(transmission->getHeaderDuration() > SIMTIME_ZERO); + ASSERT(transmission->getDataDuration() == mode->getDataDuration(B(length))); + ASSERT(transmission->getDuration() == mode->getDuration(B(length))); + ASSERT(transmission->getPreambleDuration() + transmission->getHeaderDuration() + transmission->getDataDuration() == transmission->getDuration()); + auto analog = transmission->getAnalogModel(); + ASSERT(analog->getPreambleDuration() == transmission->getPreambleDuration()); + ASSERT(analog->getHeaderDuration() == transmission->getHeaderDuration()); + ASSERT(analog->getDataDuration() == transmission->getDataDuration()); + delete transmission; + count++; + } + } + } + ASSERT(count > 100); + std::cout << "HT, VHT, and legacy packet transmission phases verified.\n"; + } +}; +Define_Module(TimingProbe); +} +%file: test.ned +import inet.node.inet.AdhocHost; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211Radio; +module TimingRadio extends Ieee80211Radio +{ + parameters: + @class(TimingRadio); +} +simple TimingProbe +{ + parameters: + @class(TimingProbe); +} +network Test +{ + submodules: + radioMedium: Ieee80211ScalarRadioMedium; + host: AdhocHost; + probe: TimingProbe; +} +%inifile: omnetpp.ini +[General] +network = Test +ned-path = .;../../../../src;../../lib +cmdenv-express-mode = true +record-vector-results = false +seed-set = 0 +sim-time-limit = 0s +**.host.wlan[0].opMode = "a" +**.host.wlan[0].radio.typename = "TimingRadio" +**.constraintAreaMinX = 0m +**.constraintAreaMinY = 0m +**.constraintAreaMinZ = 0m +**.constraintAreaMaxX = 100m +**.constraintAreaMaxY = 100m +**.constraintAreaMaxZ = 0m +%contains: stdout +HT, VHT, and legacy packet transmission phases verified. diff --git a/tests/module/QosRateSelectionNullModeSetInitialization.test b/tests/module/QosRateSelectionNullModeSetInitialization.test new file mode 100644 index 00000000000..976921dadc1 --- /dev/null +++ b/tests/module/QosRateSelectionNullModeSetInitialization.test @@ -0,0 +1,54 @@ +%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 = ""; + mibModule = ""; +} + +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..8358beb9972 --- /dev/null +++ b/tests/module/RateSelectionNullModeSetInitialization.test @@ -0,0 +1,54 @@ +%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 = ""; + mibModule = ""; +} + +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/Ieee80211HtCapabilities_1.test b/tests/unit/Ieee80211HtCapabilities_1.test index dee6a669f79..a56cb51a211 100644 --- a/tests/unit/Ieee80211HtCapabilities_1.test +++ b/tests/unit/Ieee80211HtCapabilities_1.test @@ -20,6 +20,7 @@ local.rxMcsSupported[2] = true; local.rxMcsSupported[3] = true; local.txMcsNss.maxMcsPerNss[0] = 3; local.ldpc = true; +local.greenfield = true; local.shortGi20 = true; local.maxAmpduLengthExponent = 1; @@ -47,6 +48,8 @@ ASSERT(!negotiated.localRxPeerTx.supportedMcs[1]); ASSERT(negotiated.localRxPeerTx.supportedMcs[2]); ASSERT(!negotiated.localTxPeerRx.receiverLdpc); ASSERT(negotiated.localRxPeerTx.receiverLdpc); +ASSERT(!negotiated.localTxPeerRx.receiverGreenfield); +ASSERT(negotiated.localRxPeerTx.receiverGreenfield); ASSERT(negotiated.localTxPeerRx.receiverMaxAmpduLengthExponent == 3); ASSERT(negotiated.localRxPeerTx.receiverMaxAmpduLengthExponent == 1); diff --git a/tests/unit/Ieee80211HtGreenfield_1.test b/tests/unit/Ieee80211HtGreenfield_1.test new file mode 100644 index 00000000000..d6a515de3e8 --- /dev/null +++ b/tests/unit/Ieee80211HtGreenfield_1.test @@ -0,0 +1,367 @@ +%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, b dataLength = B(0)) +{ + auto duration = mode->getDuration(dataLength); + auto preambleDuration = mode->getPreambleMode()->getDuration(); + auto modeledDataDuration = mode->getDataMode()->getDuration(dataLength); + auto dataDuration = mode->getDataDuration(dataLength); + bool headerIncludedInPreamble = duration == preambleDuration + dataDuration; + auto headerDuration = headerIncludedInPreamble ? SIMTIME_ZERO : mode->getHeaderMode()->getDuration(); + ASSERT(preambleDuration >= SIMTIME_ZERO); + ASSERT(headerDuration >= SIMTIME_ZERO); + ASSERT(dataDuration >= SIMTIME_ZERO); + ASSERT(preambleDuration + headerDuration + dataDuration == duration); + if (dynamic_cast(mode) != nullptr || 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)); +} +const IIeee80211Mode *supplementaryOnlyLegacyModes[] = { + &Ieee80211HrDsssCompliantModes::hrDsssMode2MbpsShortPreamble, + &Ieee80211HrDsssCompliantModes::hrDsssMode5_5MbpsCckShortPreamble, + &Ieee80211HrDsssCompliantModes::hrDsssMode11MbpsCckShortPreamble, +}; +for (auto legacyMode : supplementaryOnlyLegacyModes) { + 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); +transmitter->setMode(&Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); +ASSERT(transmitter->getTestMode() == &Ieee80211ErpOfdmCompliantModes::erpOfdmMode6Mbps); +transmitter->setMode(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); +bool rejectedVhtGreenfield = false; +try { + Ieee80211VhtCompliantModes::getCompliantMode( + &Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, + Ieee80211VhtMode::BAND_5GHZ, + Ieee80211VhtPreambleMode::HT_PREAMBLE_GREENFIELD, + Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG); +} +catch (const cRuntimeError&) { + rejectedVhtGreenfield = true; +} +ASSERT(rejectedVhtGreenfield); +auto vhtMixed2GhzMode = Ieee80211VhtCompliantModes::getCompliantMode( + &Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, + Ieee80211VhtMode::BAND_2_4GHZ, + Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, + Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG); +ASSERT(vhtMixed5GhzMode != vhtMixed2GhzMode); +ASSERT(vhtMixed5GhzMode->getCenterFrequencyMode() == Ieee80211VhtMode::BAND_5GHZ); +ASSERT(vhtMixed2GhzMode->getCenterFrequencyMode() == Ieee80211VhtMode::BAND_2_4GHZ); +ASSERT(vhtMixed5GhzMode->getDuration(B(0)) == vhtMixed5GhzMode->getPreambleMode()->getDuration() + vhtMixed5GhzMode->getDataMode()->getDuration(B(0))); +ASSERT(vhtMixed5GhzMode->getDataMode()->getDuration(B(0)) >= SIMTIME_ZERO); +auto vhtShortGi5GhzMode = Ieee80211VhtCompliantModes::getCompliantMode( + &Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, + Ieee80211VhtMode::BAND_5GHZ, + Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, + Ieee80211VhtModeBase::HT_GUARD_INTERVAL_SHORT); +assertTransmitterDurationDecomposition(ofdmProfile->getMode(Mbps(6)), B(0)); +assertTransmitterDurationDecomposition(ofdmProfile->getMode(Mbps(6)), B(64)); +assertTransmitterDurationDecomposition(vhtMixed5GhzMode, B(0)); +assertTransmitterDurationDecomposition(vhtMixed5GhzMode, B(64)); +assertTransmitterDurationDecomposition(vhtShortGi5GhzMode, B(0)); +assertTransmitterDurationDecomposition(vhtShortGi5GhzMode, B(64)); +assertTransmitterDurationDecomposition(mixedMode, B(0)); +assertTransmitterDurationDecomposition(mixedMode, B(64)); +assertTransmitterDurationDecomposition(greenfieldMode, B(0)); +assertTransmitterDurationDecomposition(greenfieldMode, B(64)); + +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. diff --git a/tests/unit/Ieee80211HtGuardInterval_1.test b/tests/unit/Ieee80211HtGuardInterval_1.test new file mode 100644 index 00000000000..aa9d38b49b7 --- /dev/null +++ b/tests/unit/Ieee80211HtGuardInterval_1.test @@ -0,0 +1,561 @@ +%description: +Validate complete IEEE 802.11 HT long/short guard-interval catalog, lookup, and airtime. + +%includes: +#include +#include +#include + +#include "inet/common/Simsignals.h" +#include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h" +#include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h" + +%global: +using namespace inet; +using namespace inet::physicallayer; + +class TestIeee80211Transmitter : public Ieee80211Transmitter +{ + public: + const IIeee80211Mode *getSelectedMode() const { return mode; } + const Ieee80211ModeSet *getSelectedModeSet() const { return modeSet; } +}; + +class TestIeee80211Receiver : public Ieee80211Receiver +{ + public: + const Ieee80211ModeSet *getSelectedModeSet() const { return modeSet; } +}; + +class TestIeee80211Radio : public Ieee80211Radio +{ + public: + void setup(ITransmitter *tx, IReceiver *rx) + { + transmitter = tx; + receiver = rx; + setComponentType(cModuleType::get("inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211Radio")); + } +}; + +class TestModeSetListener : public cListener +{ + public: + const Ieee80211ModeSet *receivedModeSet = nullptr; + int notifications = 0; + + virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) override + { + if (signalID == modesetChangedSignal) { + receivedModeSet = dynamic_cast(obj); + notifications++; + } + } +}; + +class TestRateSelectionAccessor : public ieee80211::RateSelection +{ + public: + static const IIeee80211Mode *getDataFrameMode(const ieee80211::RateSelection *rs) { return static_cast(rs)->dataFrameMode; } + static const IIeee80211Mode *getMulticastFrameMode(const ieee80211::RateSelection *rs) { return static_cast(rs)->multicastFrameMode; } + static const IIeee80211Mode *getMgmtFrameMode(const ieee80211::RateSelection *rs) { return static_cast(rs)->mgmtFrameMode; } + static const IIeee80211Mode *getControlFrameMode(const ieee80211::RateSelection *rs) { return static_cast(rs)->controlFrameMode; } + static const IIeee80211Mode *getResponseAckFrameMode(const ieee80211::RateSelection *rs) { return static_cast(rs)->responseAckFrameMode; } + static const IIeee80211Mode *getResponseCtsFrameMode(const ieee80211::RateSelection *rs) { return static_cast(rs)->responseCtsFrameMode; } + static const IIeee80211Mode *getFastestMandatoryMode(const ieee80211::RateSelection *rs) { return static_cast(rs)->fastestMandatoryMode; } + static const Ieee80211ModeSet *readModeSet(const ieee80211::RateSelection *rs) { return static_cast(rs)->modeSet; } + static void notifyModeSet(ieee80211::RateSelection *rs, const Ieee80211ModeSet *ms) { + static_cast(rs)->receiveSignal(nullptr, modesetChangedSignal, const_cast(ms), nullptr); + } + static const Ieee80211ModeSet *getModeSet(const ieee80211::RateSelection *rs) { return static_cast(rs)->modeSet; } + static void applyModeSet(ieee80211::RateSelection *rs, const Ieee80211ModeSet *ms) { + static_cast(rs)->receiveSignal(nullptr, modesetChangedSignal, const_cast(ms), nullptr); + } +}; + +class TestQosRateSelectionAccessor : public ieee80211::QosRateSelection +{ + public: + static const IIeee80211Mode *getDataFrameMode(const ieee80211::QosRateSelection *rs) { return static_cast(rs)->dataFrameMode; } + static const IIeee80211Mode *getResponseBlockAckFrameMode(const ieee80211::QosRateSelection *rs) { return static_cast(rs)->responseBlockAckFrameMode; } + static const IIeee80211Mode *getFastestMandatoryMode(const ieee80211::QosRateSelection *rs) { return static_cast(rs)->fastestMandatoryMode; } + static const Ieee80211ModeSet *readModeSet(const ieee80211::QosRateSelection *rs) { return static_cast(rs)->modeSet; } + static void notifyModeSet(ieee80211::QosRateSelection *rs, const Ieee80211ModeSet *ms) { + static_cast(rs)->receiveSignal(nullptr, modesetChangedSignal, const_cast(ms), nullptr); + } + static const Ieee80211ModeSet *getModeSet(const ieee80211::QosRateSelection *rs) { return static_cast(rs)->modeSet; } + static void applyModeSet(ieee80211::QosRateSelection *rs, const Ieee80211ModeSet *ms) { + static_cast(rs)->receiveSignal(nullptr, modesetChangedSignal, const_cast(ms), nullptr); + } +}; + +class TestIeee80211Mgmt : public ieee80211::Ieee80211MgmtBase +{ + protected: + virtual void handleTimer(cMessage *frame) override {} + virtual void handleCommand(int msgkind, cObject *ctrl) override {} + virtual void handleAuthenticationFrame(Packet *packet, const Ptr& header) override {} + virtual void handleDeauthenticationFrame(Packet *packet, const Ptr& header) override {} + virtual void handleAssociationRequestFrame(Packet *packet, const Ptr& header) override {} + virtual void handleAssociationResponseFrame(Packet *packet, const Ptr& header) override {} + virtual void handleReassociationRequestFrame(Packet *packet, const Ptr& header) override {} + virtual void handleReassociationResponseFrame(Packet *packet, const Ptr& header) override {} + virtual void handleDisassociationFrame(Packet *packet, const Ptr& header) override {} + virtual void handleBeaconFrame(Packet *packet, const Ptr& header) override {} + virtual void handleProbeRequestFrame(Packet *packet, const Ptr& header) override {} + virtual void handleProbeResponseFrame(Packet *packet, const Ptr& header) override {} + + public: + void notifyModeSet(const Ieee80211ModeSet *modeSet) + { + receiveSignal(nullptr, modesetChangedSignal, const_cast(modeSet), nullptr); + } + + const ieee80211::Ieee80211SupportedRatesElement& getSupportedRates() const { return supportedRates; } +}; + +%activity: +// Reject unsupported VHT greenfield before any mixed-format cache request. +bool rejectedVhtGreenfieldBeforeMixed = false; +try { + Ieee80211VhtCompliantModes::getCompliantMode(&Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, + Ieee80211VhtMode::BAND_5GHZ, Ieee80211VhtPreambleMode::HT_PREAMBLE_GREENFIELD, + Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG); +} +catch (cRuntimeError&) { + rejectedVhtGreenfieldBeforeMixed = true; +} +ASSERT(rejectedVhtGreenfieldBeforeMixed); + +const auto modeSet = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"); +ASSERT(modeSet->getNumModes() == 135); + +TestIeee80211Mgmt mgmt; +mgmt.notifyModeSet(modeSet); +const auto& htSupportedRates = mgmt.getSupportedRates(); +const double expectedHtSupportedRates[] = {1, 2, 5.5, 6, 11, 12, 24}; +ASSERT(htSupportedRates.numRates == 7); +for (int i = 0; i < htSupportedRates.numRates; i++) { + ASSERT(htSupportedRates.rate[i] == expectedHtSupportedRates[i]); + if (i > 0) + ASSERT(htSupportedRates.rate[i - 1] < htSupportedRates.rate[i]); +} + +mgmt.notifyModeSet(Ieee80211ModeSet::getModeSet("a")); +const auto& legacySupportedRates = mgmt.getSupportedRates(); +const double expectedLegacySupportedRates[] = {6, 12, 24, 9, 18, 36, 48, 54}; +ASSERT(legacySupportedRates.numRates == 8); +for (int i = 0; i < legacySupportedRates.numRates; i++) { + ASSERT(legacySupportedRates.rate[i] == expectedLegacySupportedRates[i]); + ASSERT(legacySupportedRates.basicRate[i] == (i < 3)); +} + +using Key = std::tuple; +std::map modes; +int mandatoryCount = 0; +for (int index = 0; index < modeSet->getNumModes(); index++) { + auto mode = dynamic_cast(modeSet->getMode(index)); + if (mode == nullptr) + continue; + auto dataMode = mode->getDataMode(); + int bandwidth = dataMode->getBandwidth() == MHz(20) ? 20 : dataMode->getBandwidth() == MHz(40) ? 40 : 0; + int mcs = dataMode->getMcsIndex(); + auto guardIntervalType = dataMode->getGuardIntervalType(); + ASSERT(bandwidth != 0); + ASSERT((bandwidth == 20 || bandwidth == 40) && 0 <= mcs && mcs <= 31); + ASSERT(modes.emplace(Key(bandwidth, mcs, guardIntervalType), mode).second); + + bool mustBeMandatory = bandwidth == 20 && mcs <= 7 && + guardIntervalType == Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG; + ASSERT(modeSet->isMandatory(index) == mustBeMandatory); + if (modeSet->isMandatory(index)) + mandatoryCount++; +} +ASSERT(mandatoryCount == 8); + +for (int index = 0; index < modeSet->getNumModes(); index++) { + auto mode = modeSet->getMode(index); + auto slowerMode = modeSet->getSlowerMode(mode); + auto fasterMode = modeSet->getFasterMode(mode); + ASSERT(slowerMode == nullptr || slowerMode->getDataMode()->getNetBitrate() < mode->getDataMode()->getNetBitrate()); + ASSERT(fasterMode == nullptr || fasterMode->getDataMode()->getNetBitrate() > mode->getDataMode()->getNetBitrate()); +} + +for (int bandwidth : {20, 40}) { + for (int mcs = 0; mcs <= 31; mcs++) { + auto longMode = modes.at(Key(bandwidth, mcs, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG)); + auto shortMode = modes.at(Key(bandwidth, mcs, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT)); + auto longData = longMode->getDataMode(); + auto shortData = shortMode->getDataMode(); + ASSERT(longData->getGuardInterval() == SimTime(800, SIMTIME_NS)); + ASSERT(shortData->getGuardInterval() == SimTime(400, SIMTIME_NS)); + ASSERT(longData->getSymbolInterval() == SimTime(4, SIMTIME_US)); + ASSERT(shortData->getSymbolInterval() == SimTime(3600, SIMTIME_NS)); + ASSERT(std::fabs(shortData->getNetBitrate().get() * 9 - longData->getNetBitrate().get() * 10) < 1); + + // IEEE Std 802.11-2024, 19.3.11.11.6: short GI is Data-only. + auto longSignal = longMode->getHeaderMode(); + auto shortSignal = shortMode->getHeaderMode(); + ASSERT(longSignal->getSymbolInterval() == SimTime(4, SIMTIME_US)); + ASSERT(shortSignal->getSymbolInterval() == SimTime(4, SIMTIME_US)); + ASSERT(longSignal->getDuration() == SimTime(8, SIMTIME_US)); + ASSERT(shortSignal->getDuration() == SimTime(8, SIMTIME_US)); + ASSERT(longSignal->getNetBitrate() == shortSignal->getNetBitrate()); + ASSERT(longSignal->getGrossBitrate() == shortSignal->getGrossBitrate()); + } +} +ASSERT(modes.find(Key(20, 32, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG)) == modes.end()); + +auto mixedLong = modes.at(Key(20, 0, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG)); +auto mixedShort = modes.at(Key(20, 0, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT)); +ASSERT(mixedLong->getDataMode()->getDuration(B(24)) == SimTime(36, SIMTIME_US)); +ASSERT(mixedShort->getDataMode()->getDuration(B(24)) == SimTime(32400, SIMTIME_NS)); +ASSERT(mixedShort->getDataMode()->getDuration(B(27)) == SimTime(36, SIMTIME_US)); +ASSERT(mixedLong->getDataMode()->getDuration(B(30)) == SimTime(44, SIMTIME_US)); +ASSERT(mixedShort->getDataMode()->getDuration(B(30)) == SimTime(39600, SIMTIME_NS)); + +// IEEE Std 802.11-2024, 19.4.3: Eq. (19-90) rounds mixed-format +// short-GI Data to 4 us, while Eq. (19-92) keeps greenfield Data raw. +ASSERT(mixedShort->getDataDuration(B(24)) == SimTime(36, SIMTIME_US)); +ASSERT(mixedShort->getDataDuration(B(27)) == SimTime(36, SIMTIME_US)); +ASSERT(mixedShort->getDataDuration(B(30)) == SimTime(40, SIMTIME_US)); + +// Preserve representative historical optional-rate timing and bitrate values. +auto mixedShortMcs8 = modes.at(Key(20, 8, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT)); +auto mixedShortMcs0Bw40 = modes.at(Key(40, 0, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT)); +ASSERT(std::fabs(mixedShortMcs8->getDataMode()->getNetBitrate().get() - 14.4444444444444e6) < 1); +ASSERT(mixedShortMcs8->getDataMode()->getDuration(B(24)) == SimTime(18, SIMTIME_US)); +ASSERT(mixedShortMcs8->getDataDuration(B(24)) == SimTime(20, SIMTIME_US)); +ASSERT(mixedShortMcs8->getDuration(B(24)) == SimTime(60, SIMTIME_US)); +ASSERT(mixedShortMcs0Bw40->getDataMode()->getNetBitrate() == Mbps(15)); +ASSERT(mixedShortMcs0Bw40->getDataMode()->getDuration(B(24)) == SimTime(14400, SIMTIME_NS)); +ASSERT(mixedShortMcs0Bw40->getDataDuration(B(24)) == SimTime(16, SIMTIME_US)); +ASSERT(mixedShortMcs0Bw40->getDuration(B(24)) == SimTime(52, SIMTIME_US)); +auto greenfieldShort = Ieee80211HtCompliantModes::getCompliantMode( + &Ieee80211HtmcsTable::htMcs8BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, + Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, + Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT); +ASSERT(greenfieldShort->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD); +ASSERT(greenfieldShort->getDataMode()->getNumberOfSpatialStreams() == 2); +ASSERT(greenfieldShort->getDataDuration(B(24)) == SimTime(18, SIMTIME_US)); +ASSERT(greenfieldShort->getDataDuration(B(30)) == SimTime(21600, SIMTIME_NS)); + +auto mixedLongOneSymbol = modes.at(Key(20, 15, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG)); +auto mixedShortOneSymbol = modes.at(Key(20, 15, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT)); +auto greenfieldShortOneSymbol = Ieee80211HtCompliantModes::getCompliantMode( + &Ieee80211HtmcsTable::htMcs15BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, + Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, + Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT); +for (auto mode : {mixedLongOneSymbol, mixedShortOneSymbol, greenfieldShortOneSymbol}) { + auto dataDuration = mode->getDataDuration(B(24)); + ASSERT(dataDuration >= SIMTIME_ZERO); + ASSERT(mode->getDuration(B(24)) == mode->getPreambleDuration() + mode->getHeaderDuration() + dataDuration); +} +ASSERT(mixedLongOneSymbol->getDataDuration(B(24)) == SimTime(4, SIMTIME_US)); +ASSERT(mixedShortOneSymbol->getDataDuration(B(24)) == SimTime(4, SIMTIME_US)); +ASSERT(greenfieldShortOneSymbol->getDataDuration(B(24)) == SimTime(3600, SIMTIME_NS)); +ASSERT(mixedShortOneSymbol->getPreambleDuration() == SimTime(20, SIMTIME_US)); +ASSERT(mixedShortOneSymbol->getHeaderDuration() == SimTime(20, SIMTIME_US)); +ASSERT(greenfieldShortOneSymbol->getPreambleDuration() == SimTime(16, SIMTIME_US)); +ASSERT(greenfieldShortOneSymbol->getHeaderDuration() == SimTime(12, SIMTIME_US)); + +auto unspecified65 = dynamic_cast(modeSet->getMode(Mbps(65), MHz(20), 1)); +auto long65 = dynamic_cast(modeSet->getMode(Mbps(65), MHz(20), 1, SimTime(800, SIMTIME_NS))); +auto short65 = dynamic_cast(modeSet->getMode(Mbps(65), MHz(20), 1, SimTime(400, SIMTIME_NS))); +ASSERT(unspecified65->getDataMode()->getMcsIndex() == 7); +ASSERT(unspecified65->getDataMode()->getGuardIntervalType() == Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG); +ASSERT(long65->getDataMode()->getMcsIndex() == 7); +ASSERT(short65->getDataMode()->getMcsIndex() == 6); + +TestIeee80211Transmitter transmitter; +transmitter.setModeSet(modeSet); +transmitter.setMode(short65); +ASSERT(transmitter.getSelectedMode() == short65); +bool rejectedModeOutsideSet = false; +try { + transmitter.setMode(greenfieldShortOneSymbol); +} +catch (cRuntimeError&) { + rejectedModeOutsideSet = true; +} +ASSERT(rejectedModeOutsideSet); +ASSERT(transmitter.getSelectedMode() == short65); + +auto unspecified135 = dynamic_cast(modeSet->getMode(Mbps(135), MHz(40), 1)); +ASSERT(unspecified135->getDataMode()->getMcsIndex() == 6); +ASSERT(unspecified135->getDataMode()->getGuardIntervalType() == Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT); +ASSERT(modeSet->findMode(Mbps(65), MHz(20), 1, SimTime(600, SIMTIME_NS)) == nullptr); +const auto legacyOfdmModeSet = Ieee80211ModeSet::getModeSet("a"); +ASSERT(legacyOfdmModeSet->findMode(Mbps(6), Hz(NaN), -1, SimTime(800, SIMTIME_NS)) != nullptr); +ASSERT(legacyOfdmModeSet->findMode(Mbps(6), Hz(NaN), -1, SimTime(400, SIMTIME_NS)) == nullptr); + +// Mode-set remapping preserves the complete modeled PHY tuple. In +// particular, a negative GI is an actual absence for compatibility lookup, +// not findMode()'s public wildcard. +const auto erpModeSet = Ieee80211ModeSet::getModeSet("g(erp)"); +const auto pModeSet = Ieee80211ModeSet::getModeSet("p"); +const auto aMode = legacyOfdmModeSet->getMode(Mbps(6), MHz(20), 1, SimTime(800, SIMTIME_NS)); +const auto erpMode = erpModeSet->findCompatibleMode(aMode); +ASSERT(erpMode != nullptr); +ASSERT(erpMode->getDataMode()->getNetBitrate() == aMode->getDataMode()->getNetBitrate()); +ASSERT(erpMode->getDataMode()->getBandwidth() == aMode->getDataMode()->getBandwidth()); +ASSERT(erpMode->getDataMode()->getGuardInterval() == aMode->getDataMode()->getGuardInterval()); + +const auto dsssModeSet = Ieee80211ModeSet::getModeSet("b"); +const auto mixedModeSet = Ieee80211ModeSet::getModeSet("g(mixed)"); +const auto dsssMode = dsssModeSet->getMode(Mbps(1)); +ASSERT(dsssMode->getDataMode()->getGuardInterval() < SIMTIME_ZERO); +ASSERT(mixedModeSet->findCompatibleMode(dsssMode) != nullptr); + +TestIeee80211Transmitter legacyTransmitter; +legacyTransmitter.setModeSet(legacyOfdmModeSet); +legacyTransmitter.setMode(aMode); +legacyTransmitter.setModeSet(erpModeSet); +ASSERT(legacyTransmitter.getSelectedModeSet() == erpModeSet); +ASSERT(legacyTransmitter.getSelectedMode() == erpMode); +const auto modeBeforeRejectedTransition = legacyTransmitter.getSelectedMode(); +bool rejectedIncompatibleModeSet = false; +try { + legacyTransmitter.setModeSet(pModeSet); +} +catch (cRuntimeError&) { + rejectedIncompatibleModeSet = true; +} +ASSERT(rejectedIncompatibleModeSet); +ASSERT(legacyTransmitter.getSelectedModeSet() == erpModeSet); +ASSERT(legacyTransmitter.getSelectedMode() == modeBeforeRejectedTransition); +const auto pMode = pModeSet->getMode(2); // the declared 6 Mbps 10 MHz mode +legacyTransmitter.setModeSetAndMode(pModeSet, pMode); +ASSERT(legacyTransmitter.getSelectedModeSet() == pModeSet); +ASSERT(legacyTransmitter.getSelectedMode() == pMode); + +Ieee80211VhtCompliantModes::getCompliantMode(&Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, + Ieee80211VhtMode::BAND_5GHZ, Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, + Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG); +bool rejectedVhtGreenfieldAfterMixed = false; +try { + Ieee80211VhtCompliantModes::getCompliantMode(&Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, + Ieee80211VhtMode::BAND_5GHZ, Ieee80211VhtPreambleMode::HT_PREAMBLE_GREENFIELD, + Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG); +} +catch (cRuntimeError&) { + rejectedVhtGreenfieldAfterMixed = true; +} +ASSERT(rejectedVhtGreenfieldAfterMixed); +const auto vhtModeSet = Ieee80211ModeSet::getModeSet("ac"); +const auto vhtLongMcs0 = Ieee80211VhtCompliantModes::getCompliantMode( + &Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, Ieee80211VhtMode::BAND_5GHZ, + Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG); +const auto vhtShortMcs0 = Ieee80211VhtCompliantModes::getCompliantMode( + &Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, Ieee80211VhtMode::BAND_5GHZ, + Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211VhtModeBase::HT_GUARD_INTERVAL_SHORT); +// IEEE Std 802.11-2024, Tables 21-5/21-8 and 21.4.3, Eqs. (21-109)/(21-110). +ASSERT(vhtLongMcs0->getDataMode()->getSymbolInterval() == SimTime(4, SIMTIME_US)); +ASSERT(vhtShortMcs0->getDataMode()->getSymbolInterval() == SimTime(3600, SIMTIME_NS)); +ASSERT(vhtLongMcs0->getHeaderMode()->getSymbolInterval() == SimTime(4, SIMTIME_US)); +ASSERT(vhtShortMcs0->getHeaderMode()->getSymbolInterval() == SimTime(4, SIMTIME_US)); +ASSERT(vhtLongMcs0->getHeaderMode()->getNetBitrate() == vhtShortMcs0->getHeaderMode()->getNetBitrate()); +ASSERT(vhtLongMcs0->getHeaderMode()->getGrossBitrate() == vhtShortMcs0->getHeaderMode()->getGrossBitrate()); +ASSERT(vhtShortMcs0->getDataMode()->getDuration(B(24)) == SimTime(32400, SIMTIME_NS)); +ASSERT(vhtShortMcs0->getDataDuration(B(24)) == SimTime(36, SIMTIME_US)); +ASSERT(vhtShortMcs0->getDataMode()->getDuration(B(27)) == SimTime(36, SIMTIME_US)); +ASSERT(vhtShortMcs0->getDataDuration(B(27)) == SimTime(36, SIMTIME_US)); +ASSERT(vhtShortMcs0->getDataMode()->getDuration(B(30)) == SimTime(39600, SIMTIME_NS)); +ASSERT(vhtShortMcs0->getDataDuration(B(30)) == SimTime(40, SIMTIME_US)); +ASSERT(vhtLongMcs0->getDataMode()->getDuration(B(24)) == SimTime(36, SIMTIME_US)); +ASSERT(vhtLongMcs0->getDataDuration(B(24)) == SimTime(36, SIMTIME_US)); +for (auto vhtMode : {vhtLongMcs0, vhtShortMcs0}) + for (auto dataLength : {B(24), B(27), B(30)}) + ASSERT(vhtMode->getDuration(dataLength) == vhtMode->getPreambleDuration() + vhtMode->getHeaderDuration() + vhtMode->getDataDuration(dataLength)); +bool foundVhtLongGuardInterval = false; +bool foundVhtShortGuardInterval = false; +for (int index = 0; index < vhtModeSet->getNumModes(); index++) { + auto vhtMode = dynamic_cast(vhtModeSet->getMode(index)); + if (vhtMode == nullptr) + continue; + auto vhtDataMode = vhtMode->getDataMode(); + auto guardInterval = vhtDataMode->getGuardInterval(); + auto resolvedMode = vhtModeSet->findMode(vhtDataMode->getNetBitrate(), vhtDataMode->getBandwidth(), vhtDataMode->getNumberOfSpatialStreams(), guardInterval); + ASSERT(resolvedMode != nullptr); + ASSERT(resolvedMode->getDataMode()->getGuardInterval() == guardInterval); + foundVhtLongGuardInterval |= guardInterval == SimTime(800, SIMTIME_NS); + foundVhtShortGuardInterval |= guardInterval == SimTime(400, SIMTIME_NS); +} +ASSERT(foundVhtLongGuardInterval); +ASSERT(foundVhtShortGuardInterval); + +auto vhtOneSymbol = dynamic_cast(vhtModeSet->getFastestMode()); +ASSERT(vhtOneSymbol != nullptr); +ASSERT(vhtOneSymbol->getDataDuration(B(24)) == SimTime(4, SIMTIME_US)); +ASSERT(vhtOneSymbol->getPreambleDuration() == SimTime(20, SIMTIME_US)); +ASSERT(vhtOneSymbol->getHeaderDuration() >= SIMTIME_ZERO); +ASSERT(vhtOneSymbol->getDuration(B(24)) == vhtOneSymbol->getPreambleDuration() + vhtOneSymbol->getHeaderDuration() + vhtOneSymbol->getDataDuration(B(24))); + +for (const auto candidateSet : {modeSet, vhtModeSet}) { + for (int index = 0; index < candidateSet->getNumModes(); index++) { + auto mode = candidateSet->getMode(index); + auto slowerMode = candidateSet->getSlowerMode(mode); + auto fasterMode = candidateSet->getFasterMode(mode); + auto slowerMandatoryMode = candidateSet->getSlowerMandatoryMode(mode); + auto fasterMandatoryMode = candidateSet->getFasterMandatoryMode(mode); + ASSERT(slowerMode == nullptr || slowerMode->getDataMode()->getNetBitrate() < mode->getDataMode()->getNetBitrate()); + ASSERT(fasterMode == nullptr || fasterMode->getDataMode()->getNetBitrate() > mode->getDataMode()->getNetBitrate()); + ASSERT(slowerMandatoryMode == nullptr || slowerMandatoryMode->getDataMode()->getNetBitrate() < mode->getDataMode()->getNetBitrate()); + ASSERT(fasterMandatoryMode == nullptr || fasterMandatoryMode->getDataMode()->getNetBitrate() > mode->getDataMode()->getNetBitrate()); + } +} + +// The equal-rate mandatory candidate is selected by bitrate, not entry index. +auto equalRateVhtMode = Ieee80211VhtCompliantModes::getCompliantMode( + &Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss2, Ieee80211VhtMode::BAND_5GHZ, + Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG); +auto mandatoryAtEqualRate = vhtModeSet->getMandatoryModeAtOrBelow(equalRateVhtMode); +ASSERT(mandatoryAtEqualRate != nullptr); +ASSERT(mandatoryAtEqualRate->getDataMode()->getNetBitrate() == equalRateVhtMode->getDataMode()->getNetBitrate()); +ASSERT(vhtModeSet->getIsMandatory(mandatoryAtEqualRate)); + +transmitter.setModeSetAndMode(vhtModeSet, vhtOneSymbol); +ASSERT(transmitter.getSelectedModeSet() == vhtModeSet); +ASSERT(transmitter.getSelectedMode() == vhtOneSymbol); +bool rejectedCombinedModeUpdate = false; +try { + transmitter.setModeSetAndMode(modeSet, greenfieldShortOneSymbol); +} +catch (cRuntimeError&) { + rejectedCombinedModeUpdate = true; +} +ASSERT(rejectedCombinedModeUpdate); +ASSERT(transmitter.getSelectedModeSet() == vhtModeSet); +ASSERT(transmitter.getSelectedMode() == vhtOneSymbol); + +// Verify Ieee80211Radio setModeSet and setModeSetAndMode publish modesetChangedSignal +TestIeee80211Transmitter radioTx; +TestIeee80211Receiver radioRx; +TestIeee80211Radio radio; +radio.setup(&radioTx, &radioRx); + +TestModeSetListener radioListener; +radio.subscribe(modesetChangedSignal, &radioListener); + +radio.setModeSet(vhtModeSet); +ASSERT(radioTx.getSelectedModeSet() == vhtModeSet); +ASSERT(radioRx.getSelectedModeSet() == vhtModeSet); +ASSERT(radioListener.notifications == 1); +ASSERT(radioListener.receivedModeSet == vhtModeSet); + +radio.setModeSetAndMode(modeSet, modeSet->getMode(0)); +ASSERT(radioTx.getSelectedModeSet() == modeSet); +ASSERT(radioTx.getSelectedMode() == modeSet->getMode(0)); +ASSERT(radioRx.getSelectedModeSet() == modeSet); +ASSERT(radioListener.notifications == 2); +ASSERT(radioListener.receivedModeSet == modeSet); + +bool radioRejectedInvalidMode = false; +try { + radio.setModeSetAndMode(modeSet, vhtOneSymbol); +} +catch (cRuntimeError&) { + radioRejectedInvalidMode = true; +} +ASSERT(radioRejectedInvalidMode); +ASSERT(radioTx.getSelectedModeSet() == modeSet); +ASSERT(radioRx.getSelectedModeSet() == modeSet); +ASSERT(radioListener.notifications == 2); + +// Verify RateSelection and QosRateSelection dynamic mode-set updates +auto *rateSelection = check_and_cast(cModuleType::get("inet.linklayer.ieee80211.mac.rateselection.RateSelection")->create("rateSelection", this)); +rateSelection->par("rateControlModule").setStringValue(""); +rateSelection->par("mibModule").setStringValue(""); +rateSelection->par("dataFrameBitrate").setDoubleValue(54e6); +rateSelection->par("multicastFrameBitrate").setDoubleValue(6e6); +rateSelection->par("mgmtFrameBitrate").setDoubleValue(6e6); +rateSelection->par("controlFrameBitrate").setDoubleValue(6e6); +rateSelection->par("responseAckFrameBitrate").setDoubleValue(6e6); +rateSelection->par("responseCtsFrameBitrate").setDoubleValue(-1); +rateSelection->finalizeParameters(); +rateSelection->buildInside(); + +TestRateSelectionAccessor::applyModeSet(rateSelection, erpModeSet); +ASSERT(TestRateSelectionAccessor::getModeSet(rateSelection) == erpModeSet); +ASSERT(erpModeSet->containsMode(TestRateSelectionAccessor::getDataFrameMode(rateSelection))); +ASSERT(erpModeSet->containsMode(TestRateSelectionAccessor::getMulticastFrameMode(rateSelection))); +ASSERT(erpModeSet->containsMode(TestRateSelectionAccessor::getMgmtFrameMode(rateSelection))); +ASSERT(erpModeSet->containsMode(TestRateSelectionAccessor::getControlFrameMode(rateSelection))); +ASSERT(erpModeSet->containsMode(TestRateSelectionAccessor::getResponseAckFrameMode(rateSelection))); +ASSERT(TestRateSelectionAccessor::getResponseCtsFrameMode(rateSelection) == nullptr); +ASSERT(erpModeSet->containsMode(TestRateSelectionAccessor::getFastestMandatoryMode(rateSelection))); +ASSERT(TestRateSelectionAccessor::getFastestMandatoryMode(rateSelection) == erpModeSet->getMode(Mbps(24))); + +// Dynamically switch rateSelection to HT mode set +TestRateSelectionAccessor::applyModeSet(rateSelection, modeSet); +ASSERT(TestRateSelectionAccessor::getModeSet(rateSelection) == modeSet); +ASSERT(modeSet->containsMode(TestRateSelectionAccessor::getDataFrameMode(rateSelection))); +ASSERT(!erpModeSet->containsMode(TestRateSelectionAccessor::getDataFrameMode(rateSelection))); +ASSERT(modeSet->containsMode(TestRateSelectionAccessor::getMulticastFrameMode(rateSelection))); +ASSERT(!erpModeSet->containsMode(TestRateSelectionAccessor::getMulticastFrameMode(rateSelection))); +ASSERT(modeSet->containsMode(TestRateSelectionAccessor::getMgmtFrameMode(rateSelection))); +ASSERT(modeSet->containsMode(TestRateSelectionAccessor::getControlFrameMode(rateSelection))); +ASSERT(modeSet->containsMode(TestRateSelectionAccessor::getResponseAckFrameMode(rateSelection))); +ASSERT(TestRateSelectionAccessor::getResponseCtsFrameMode(rateSelection) == nullptr); +ASSERT(modeSet->containsMode(TestRateSelectionAccessor::getFastestMandatoryMode(rateSelection))); + +// Also verify HT-specific fixed mode configured with bandwidth, stream, and guard interval +auto *rateSelectionHt = check_and_cast(cModuleType::get("inet.linklayer.ieee80211.mac.rateselection.RateSelection")->create("rateSelectionHt", this)); +rateSelectionHt->par("rateControlModule").setStringValue(""); +rateSelectionHt->par("mibModule").setStringValue(""); +rateSelectionHt->par("dataFrameBitrate").setDoubleValue(65e6); +rateSelectionHt->par("dataFrameBandwidth").setDoubleValue(20e6); +rateSelectionHt->par("dataFrameNumSpatialStreams").setIntValue(1); +rateSelectionHt->par("dataFrameGuardInterval").setDoubleValue(400e-9); +rateSelectionHt->par("multicastFrameBitrate").setDoubleValue(6e6); +rateSelectionHt->par("mgmtFrameBitrate").setDoubleValue(6e6); +rateSelectionHt->par("controlFrameBitrate").setDoubleValue(6e6); +rateSelectionHt->par("responseAckFrameBitrate").setDoubleValue(6e6); +rateSelectionHt->par("responseCtsFrameBitrate").setDoubleValue(6.5e6); +rateSelectionHt->finalizeParameters(); +rateSelectionHt->buildInside(); + +TestRateSelectionAccessor::applyModeSet(rateSelectionHt, modeSet); +ASSERT(TestRateSelectionAccessor::getModeSet(rateSelectionHt) == modeSet); +ASSERT(modeSet->containsMode(TestRateSelectionAccessor::getDataFrameMode(rateSelectionHt))); +ASSERT(TestRateSelectionAccessor::getDataFrameMode(rateSelectionHt) == short65); +ASSERT(modeSet->containsMode(TestRateSelectionAccessor::getResponseCtsFrameMode(rateSelectionHt))); + +auto *qosRateSelection = check_and_cast(cModuleType::get("inet.linklayer.ieee80211.mac.rateselection.QosRateSelection")->create("qosRateSelection", this)); +qosRateSelection->par("rateControlModule").setStringValue(""); +qosRateSelection->par("mibModule").setStringValue(""); +qosRateSelection->par("dataFrameBitrate").setDoubleValue(54e6); +qosRateSelection->par("multicastFrameBitrate").setDoubleValue(6e6); +qosRateSelection->par("mgmtFrameBitrate").setDoubleValue(6e6); +qosRateSelection->par("controlFrameBitrate").setDoubleValue(6e6); +qosRateSelection->par("responseAckFrameBitrate").setDoubleValue(6e6); +qosRateSelection->par("responseBlockAckFrameBitrate").setDoubleValue(6e6); +qosRateSelection->par("responseCtsFrameBitrate").setDoubleValue(-1); +qosRateSelection->finalizeParameters(); +qosRateSelection->buildInside(); + +TestQosRateSelectionAccessor::applyModeSet(qosRateSelection, erpModeSet); +ASSERT(TestQosRateSelectionAccessor::getModeSet(qosRateSelection) == erpModeSet); +ASSERT(erpModeSet->containsMode(TestQosRateSelectionAccessor::getDataFrameMode(qosRateSelection))); +ASSERT(erpModeSet->containsMode(TestQosRateSelectionAccessor::getResponseBlockAckFrameMode(qosRateSelection))); +ASSERT(erpModeSet->containsMode(TestQosRateSelectionAccessor::getFastestMandatoryMode(qosRateSelection))); + +TestQosRateSelectionAccessor::applyModeSet(qosRateSelection, modeSet); +ASSERT(TestQosRateSelectionAccessor::getModeSet(qosRateSelection) == modeSet); +ASSERT(modeSet->containsMode(TestQosRateSelectionAccessor::getDataFrameMode(qosRateSelection))); +ASSERT(!erpModeSet->containsMode(TestQosRateSelectionAccessor::getDataFrameMode(qosRateSelection))); +ASSERT(modeSet->containsMode(TestQosRateSelectionAccessor::getResponseBlockAckFrameMode(qosRateSelection))); +ASSERT(!erpModeSet->containsMode(TestQosRateSelectionAccessor::getResponseBlockAckFrameMode(qosRateSelection))); +ASSERT(modeSet->containsMode(TestQosRateSelectionAccessor::getFastestMandatoryMode(qosRateSelection))); + +EV << "HT guard interval catalog, timing, and lookup checks passed.\n"; + +%contains: stdout +HT guard interval catalog, timing, and lookup checks passed. diff --git a/tests/unit/Ieee80211HtModeSet_1.test b/tests/unit/Ieee80211HtModeSet_1.test index 44877fd9d68..67a2ba58f8b 100644 --- a/tests/unit/Ieee80211HtModeSet_1.test +++ b/tests/unit/Ieee80211HtModeSet_1.test @@ -43,6 +43,17 @@ static const IIeee80211Mode *findHtMode(const Ieee80211ModeSet *modeSet, int mcs const auto *htModeSet = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"); const auto *vhtOnlyModeSet = Ieee80211ModeSet::getModeSet("ac"); +// 13 Mbps identifies both one-stream MCS 1 and two-stream MCS 8. +bool ambiguousRejected = false; +try { htModeSet->getMode(Mbps(13), MHz(20), -1, SimTime(800, SIMTIME_NS), true); } +catch (const cRuntimeError& error) { + ambiguousRejected = error.getFormattedMessage().find("Ambiguous mode") != std::string::npos; +} +ASSERT(ambiguousRejected); +ASSERT(htModeSet->getMode(Mbps(13), MHz(20), 1, SimTime(800, SIMTIME_NS), true)->getHtMcsIndex() == 1); +ASSERT(htModeSet->getMode(Mbps(13), MHz(20), 2, SimTime(800, SIMTIME_NS), true)->getHtMcsIndex() == 8); +ASSERT(htModeSet->findMode(Mbps(9999), MHz(20), 1, -1, true) == nullptr); + ASSERT(htModeSet->isHtOperationSupported()); ASSERT(!vhtOnlyModeSet->isHtOperationSupported()); diff --git a/tests/unit/Ieee80211MgmtFrameSerializer_1.test b/tests/unit/Ieee80211MgmtFrameSerializer_1.test index f387d6b4c81..96467753136 100644 --- a/tests/unit/Ieee80211MgmtFrameSerializer_1.test +++ b/tests/unit/Ieee80211MgmtFrameSerializer_1.test @@ -172,6 +172,36 @@ static void checkMalformedSupportedRates(const std::vector& prefix) ASSERT(zeroLength->getSupportedRates().numRates == 0); } +template +static void checkMalformedAssociationIds() +{ + for (uint16_t status : {0, 0x1234}) { + const std::vector invalidAids = status == 0 ? + std::vector{0x0001, 0x4001, 0x8001, 0xC000, 0xC7D8, 0xFFFF} : + std::vector{0x0001, 0xC000, 0xC001}; + for (auto aid : invalidAids) { + auto bytes = appendTrailingInformationElement({ + 0x00, 0x00, uint8_t(status), uint8_t(status >> 8), uint8_t(aid), uint8_t(aid >> 8), + 0x01, 0x01, 0x0C, 0x32, 0x01, 0x18 + }); + auto frame = deserializeMalformedBody(bytes); + MemoryInputStream stream(bytes); + Ieee80211MgmtFrameSerializer serializer; + auto decoded = serializer.deserialize(stream, typeid(T)); + ASSERT(decoded->isIncorrect()); + ASSERT(stream.getPosition() == B(bytes.size())); + ASSERT(frame->isIncorrect()); + ASSERT(frame->isComplete()); + ASSERT(frame->getStatusCode() == status); + ASSERT(frame->getAid() == 0); + ASSERT(frame->getSupportedRates().numRates == 1); + ASSERT(frame->getSupportedRates().rate[0] == 6); + ASSERT(frame->getExtendedSupportedRates().numRates == 1); + ASSERT(frame->getExtendedSupportedRates().rate[0] == 12); + } + } +} + %activity: { @@ -267,17 +297,8 @@ static void checkMalformedSupportedRates(const std::vector& prefix) ASSERT(frame->getAid() == 0x0245); } -bool missingAssociationIdMarkerRejected = false; -try { - deserializeBody({ - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x01, 0x01, 0x0C - }); -} -catch (const cRuntimeError&) { - missingAssociationIdMarkerRejected = true; -} -ASSERT(missingAssociationIdMarkerRejected); +checkMalformedAssociationIds(); +checkMalformedAssociationIds(); bool successfulZeroAssociationIdRejected = false; try { diff --git a/tests/unit/Ieee80211PeerModeSelection_1.test b/tests/unit/Ieee80211PeerModeSelection_1.test index b472fd4e291..9ba90515ba3 100644 --- a/tests/unit/Ieee80211PeerModeSelection_1.test +++ b/tests/unit/Ieee80211PeerModeSelection_1.test @@ -28,7 +28,7 @@ static const IIeee80211Mode *findHtMode(const Ieee80211ModeSet *modeSet, int mcs } static Ieee80211Mib::PeerHtState makePeerState(std::initializer_list mcsIndexes, - std::initializer_list bandwidths, Hz operatingChannelWidth, bool shortGi20 = false, bool shortGi40 = false) + std::initializer_list bandwidths, Hz operatingChannelWidth, bool shortGi20 = false, bool shortGi40 = false, bool greenfield = false) { Ieee80211Mib::PeerHtState state; state.valid = true; @@ -40,6 +40,7 @@ static Ieee80211Mib::PeerHtState makePeerState(std::initializer_list mcsInd receiverCapabilities.supportedChannelWidths.insert(bandwidth); receiverCapabilities.receiverShortGi20 = shortGi20; receiverCapabilities.receiverShortGi40 = shortGi40; + receiverCapabilities.receiverGreenfield = greenfield; state.negotiatedCapabilities.operation.operatingChannelWidth = operatingChannelWidth; return state; } @@ -77,6 +78,32 @@ const auto *modeOutsideSet = Ieee80211HtCompliantModes::getCompliantMode(&Ieee80 Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG); ASSERT(!modeSet->containsMode(modeOutsideSet)); +// Group-addressed configured optional rates are upper bounds over the basic +// legacy set (modeled by mandatory legacy operational modes). +for (const char *profile : {"a", "g(mixed)"}) { + const auto *groupSet = Ieee80211ModeSet::getModeSet(profile); + for (int rate : {6, 9, 12, 18, 24}) { + const auto *requested = groupSet->getMode(Mbps(rate)); + auto expected = Mbps(rate == 9 ? 6 : rate == 18 ? 12 : rate); + const auto *selected = selectGroupAddressedMode(groupSet, requested); + ASSERT(selected->getDataMode()->getNetBitrate() == expected); + if (rate != 9 && rate != 18) + ASSERT(selected == requested); + } +} +const auto *legacySet = Ieee80211ModeSet::getModeSet("a"); +const auto *six = legacySet->getMode(Mbps(6)); +const auto *nine = legacySet->getMode(Mbps(9)); +Ieee80211ModeSet aboveBound("aboveBound", {{true, nine, true}}, nine, Ieee80211ModeSet::PhyType::OFDM); +bool groupBoundRejected = false; +try { selectGroupAddressedMode(&aboveBound, six); } +catch (const cRuntimeError& error) { + groupBoundRejected = error.getFormattedMessage().find("No mandatory legacy mode at or below") != std::string::npos; +} +ASSERT(groupBoundRejected); +Ieee80211ModeSet noMandatory("noMandatory", {{false, nine, true}}, nine, Ieee80211ModeSet::PhyType::OFDM); +ASSERT(selectGroupAddressedMode(&noMandatory, nine) == nine); + const MacAddress peer("02:00:00:00:00:01"); auto shortGiPeer = makePeerState({8}, {MHz(20)}, MHz(20), true); @@ -134,6 +161,10 @@ const auto *shortGiFallback = selectPeerCompatibleMode(modeSet, &shortGiDisabled ASSERT(shortGiFallback == mcs8Long); ASSERT(!shortGiFallback->isHtShortGuardInterval()); +auto shortGiDisabledOnlyMcs0Peer = makePeerState({0}, {MHz(20)}, MHz(20)); +const auto *shortGiMcs0Fallback = selectPeerCompatibleMode(modeSet, &shortGiDisabledOnlyMcs0Peer, mcs8Short, peer); +ASSERT(shortGiMcs0Fallback == mcs0Long); + auto compatiblePeer = makePeerState({2}, {MHz(20)}, MHz(40)); ASSERT(selectPeerCompatibleMode(modeSet, &compatiblePeer, mcs2Long, peer) == mcs2Long); @@ -168,6 +199,47 @@ ASSERT(firstSelection == secondSelection); ASSERT(firstSelection->getHtMcsIndex() == 8); ASSERT(!firstSelection->isHtShortGuardInterval()); +// Greenfield mode set supports HT-mixed control responses (e.g. CTS) per +// IEEE 802.11-2024 10.6.6.5.7 even though HT-mixed modes are not selectable entries. +const auto *greenfieldModeSet = Ieee80211ModeSet::getModeSet("n(greenfield-2.4Ghz)"); +const auto *htMixedCtsMode = mcs0Long; // htMcs0BW20MHz mixed format +ASSERT(!greenfieldModeSet->containsMode(htMixedCtsMode)); +ASSERT(greenfieldModeSet->supportsMode(htMixedCtsMode)); +auto greenfieldPeer = makePeerState({0}, {MHz(20)}, MHz(20)); +const auto *greenfieldCtsSelection = selectPeerCompatibleMode(greenfieldModeSet, &greenfieldPeer, htMixedCtsMode, peer); +ASSERT(greenfieldCtsSelection == htMixedCtsMode); + +bool outsideGreenfieldModeRejected = false; +try { + selectPeerCompatibleMode(greenfieldModeSet, &greenfieldPeer, modeOutsideSet, peer); +} +catch (const cRuntimeError&) { + outsideGreenfieldModeRejected = true; +} +ASSERT(outsideGreenfieldModeRejected); + +const auto *mcs0Greenfield = findHtMode(greenfieldModeSet, 0, MHz(20), Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG); +ASSERT(mcs0Greenfield != nullptr); +ASSERT(mcs0Greenfield->isHtGreenfield()); + +// When destination advertises Greenfield reception, HT-Greenfield candidate mode is selected. +auto greenfieldCapablePeer = makePeerState({0}, {MHz(20)}, MHz(20), false, false, true); +const auto *greenfieldSelection = selectPeerCompatibleMode(greenfieldModeSet, &greenfieldCapablePeer, mcs0Greenfield, peer); +ASSERT(greenfieldSelection == mcs0Greenfield); +ASSERT(greenfieldSelection->isHtGreenfield()); + +// When destination did not advertise Greenfield reception, isCompatibleHtMode rejects Greenfield mode, +// falling back to the legal HT-mixed equivalent mode. +auto mixedOnlyDestinationPeer = makePeerState({0}, {MHz(20)}, MHz(20), false, false, false); +const auto *mixedFallbackSelection = selectPeerCompatibleMode(greenfieldModeSet, &mixedOnlyDestinationPeer, mcs0Greenfield, peer); +ASSERT(mixedFallbackSelection == htMixedCtsMode); +ASSERT(!mixedFallbackSelection->isHtGreenfield()); + +// HT-mixed mode candidates remain unaffected regardless of Greenfield receiver capability. +const auto *mixedCandidateSelection = selectPeerCompatibleMode(modeSet, &mixedOnlyDestinationPeer, mcs0Long, peer); +ASSERT(mixedCandidateSelection == mcs0Long); +ASSERT(!mixedCandidateSelection->isHtGreenfield()); + EV << "Peer-compatible HT mode selection checks verified.\n"; %contains: stdout diff --git a/tests/unit/Ieee80211SupportedRates_1.test b/tests/unit/Ieee80211SupportedRates_1.test index a0e82cec048..e25270baab5 100644 --- a/tests/unit/Ieee80211SupportedRates_1.test +++ b/tests/unit/Ieee80211SupportedRates_1.test @@ -412,6 +412,84 @@ assertTiming(pModeSet, SimTime(32, SIMTIME_US), SimTime(13, SIMTIME_US), SimTime assertTiming(nModeSet, SimTime(10, SIMTIME_US), SimTime(20, SIMTIME_US), SimTime(24, SIMTIME_US), 15, 1023); assertTiming(acModeSet, SimTime(16, SIMTIME_US), SimTime(9, SIMTIME_US), SimTime(24, SIMTIME_US), 15, 1023); +// IEEE Std 802.11-2024, 9.4.2.2: SSID length verification (0 to 32 octets) +for (size_t len : {size_t(0), size_t(1), size_t(32)}) { + std::string ssid(len, 'x'); + auto probeReq = makeShared(); + probeReq->setSSID(ssid.c_str()); + Ieee80211SupportedRatesElement rates; + rates.numRates = 1; + rates.rate[0] = 1.0; + rates.basicRate[0] = true; + probeReq->setSupportedRates(rates); + probeReq->setChunkLength(B(2 + len + 3)); + + auto bytes = serializeFrame(probeReq); + auto decoded = deserializeFrame(bytes); + ASSERT(std::string(decoded->getSSID()) == ssid); +} + +// 33-octet SSID exceeds standard maximum and must be rejected on serialization +{ + std::string overlengthSsid(33, 'x'); + auto probeReq = makeShared(); + probeReq->setSSID(overlengthSsid.c_str()); + Ieee80211SupportedRatesElement rates; + rates.numRates = 1; + rates.rate[0] = 1.0; + rates.basicRate[0] = true; + probeReq->setSupportedRates(rates); + probeReq->setChunkLength(B(2 + 33 + 3)); + + bool overlengthRejected = false; + try { + serializeFrame(probeReq); + } + catch (const cRuntimeError&) { + overlengthRejected = true; + } + ASSERT(overlengthRejected); +} + +// Deserializing a frame with element length = 33 must throw +{ + std::vector rawProbeReq = { + 0x00, 33 // Element 0 (SSID), length 33 + }; + for (int i = 0; i < 33; i++) + rawProbeReq.push_back('x'); + rawProbeReq.push_back(0x01); // Element 1 (Supported Rates) + rawProbeReq.push_back(1); // Length 1 + rawProbeReq.push_back(0x82); // 1.0 Mbps basic + + bool deserializationRejected = false; + try { + deserializeFrame(rawProbeReq); + } + catch (const cRuntimeError&) { + deserializationRejected = true; + } + ASSERT(deserializationRejected); +} + +// Deserializing a truncated SSID element must throw +{ + std::vector truncatedProbeReq = { + 0x00, 10 // Element 0 (SSID), length 10 but only 3 payload bytes follow + }; + for (int i = 0; i < 3; i++) + truncatedProbeReq.push_back('a'); + + bool truncatedRejected = false; + try { + deserializeFrame(truncatedProbeReq); + } + catch (const cRuntimeError&) { + truncatedRejected = true; + } + ASSERT(truncatedRejected); +} + EV << "Supported Rates and AID validation checks passed.\n"; %contains: stdout diff --git a/tests/unit/ScalarSignalAnalogModel_1.test b/tests/unit/ScalarSignalAnalogModel_1.test new file mode 100644 index 00000000000..1b7e85c256a --- /dev/null +++ b/tests/unit/ScalarSignalAnalogModel_1.test @@ -0,0 +1,26 @@ +%description: +Scalar transmission and reception analog models preserve distinct chronological +preamble, header, and data durations when forwarding to their shared base. + +%includes: +#include "inet/physicallayer/wireless/common/analogmodel/scalar/ScalarTransmissionAnalogModel.h" +#include "inet/physicallayer/wireless/common/analogmodel/scalar/ScalarReceptionAnalogModel.h" +using namespace inet; +using namespace inet::physicallayer; + +%activity: +const auto preamble = SimTime(16, SIMTIME_US); +const auto header = SimTime(4, SIMTIME_US); +const auto data = SimTime(120, SIMTIME_US); +ScalarTransmissionAnalogModel tx(preamble, header, data, GHz(2.4), MHz(20), mW(1)); +ScalarReceptionAnalogModel rx(preamble, header, data, GHz(2.4), MHz(20), mW(1)); +for (const ISignalAnalogModel *model : {static_cast(&tx), static_cast(&rx)}) { + ASSERT(model->getPreambleDuration() == preamble); + ASSERT(model->getHeaderDuration() == header); + ASSERT(model->getDataDuration() == data); + ASSERT(model->getDuration() == preamble + header + data); +} +EV << "Scalar TX/RX analog phase durations preserved.\n"; + +%contains: stdout +Scalar TX/RX analog phase durations preserved.