diff --git a/WHATSNEW b/WHATSNEW index 5fdd7668423..4495828e8c6 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -29,6 +29,15 @@ Notable backward incompatible changes are the following: adaptive rate control, either where a station transmits to several peers or where it sends group-addressed traffic. +2. IEEE 802.11 PHY mode API + + External implementations of IIeee80211DataMode must implement the new pure + virtual getGuardInterval() query. Return the modeled guard interval as a + simtime_t, or -1 when the PHY has no guard interval. The bitrate-based + Ieee80211ModeSet getMode() and findMode() overloads also take a trailing, + defaulted guardInterval argument. Ordinary existing calls still compile; + member-function pointers must use the new signature. See the migration guide. + Notable backward compatible changes are the following: 1. IEEE 802.11 per-station rate statistics @@ -67,6 +76,30 @@ Notable backward compatible changes are the following: default. Serializers written against an earlier INET version need no source change. See the migration guide for the new signatures. +5. IEEE 802.11 HT/VHT guard intervals and timing + + HT MCS 32, 73, and 76 definitions are corrected. HT/VHT signal fields use + long-GI symbol timing independently of the data GI. Mixed-format HT and VHT + short-GI data airtimes are rounded to 4 us boundaries; HT greenfield retains + its unrounded short-GI airtime. Results and fingerprints may change for + simulations using the affected modes and frame formats. + + RateSelection and QosRateSelection now accept dataFrameGuardInterval to + qualify a configured dataFrameBitrate. The default, -1s, leaves the GI + unspecified; an explicit value must match the selected mode's modeled GI. + The interface now coordinates explicitly registered mode-set consumers, + updating MAC, management, rate selection, and active contention before + publishing modesetChanged. Subscribe at the interface; the attached radio + and MAC no longer publish this event. Failures remain fatal. External PHY + and contention implementations have new contracts described in the + migration guide. + + Group-addressed frames use a legacy basic rate when that set is nonempty. + Eligible configured basic rates are preserved; other configured rates fall + back to the fastest legacy basic rate. For example, a configured 54 Mbps + group rate becomes 24 Mbps in the default mixed legacy catalog. This changes + airtime and fingerprints in affected existing scenarios. + INET-4.7 (July 2026) — feature release -------------------------------------- diff --git a/doc/src/migration-guide/index.rst b/doc/src/migration-guide/index.rst index 942633d269f..b44cf4a6837 100644 --- a/doc/src/migration-guide/index.rst +++ b/doc/src/migration-guide/index.rst @@ -4,6 +4,59 @@ Migrating Code from INET 3.x ============================ Release: |release| +Migrating IEEE 802.11 PHY Modes +------------------------------ + +External implementations of ``IIeee80211DataMode`` must now implement the pure +virtual guard-interval query: + +.. code-block:: c++ + + const simtime_t getGuardInterval() const override; + +Return the modeled guard interval in simulation time units. For a PHY without a +guard interval, use an explicit override returning ``-1``. FHSS, DSSS, HR-DSSS, +and IR use this value; OFDM, HT, and VHT return their modeled interval. + +The bitrate-based ``Ieee80211ModeSet::getMode()`` and ``findMode()`` overloads +now take a trailing ``simtime_t guardInterval = -1`` argument. Existing ordinary +calls can omit it. Update member-function pointer declarations to include this +argument and supply it when invoking through a pointer. Rebuild external code +against the changed interface. + +``Ieee80211Interface`` now implements ``IIeee80211ModeSetCoordinator``. +``Ieee80211Mac``, ``Ieee80211MgmtBase``, ``RateSelection``, and +``ModeSetListener`` register their ``IIeee80211ModeSetListener`` contract during +initialization. External subclasses must preserve base initialization and place +mode-set state updates in ``applyModeSet()``, not ``receiveSignal()``. Custom +interface compositions must provide the coordinator contract and register their +consumers explicitly. Register the MAC in ``MAC_STATE`` and independent derived +consumers in ``DERIVED_STATE``. Duplicate registration in the same phase is +idempotent; registration after initial application is rejected. Detach a consumer +before deleting it; membership cannot change during a transition. + +Observe ``modesetChanged`` at the interface. It is published once after initial +link-layer setup, and once after every successful runtime application, including +reapplication of the same catalog. The interface is the source; the MAC and the +attached radio no longer publish this signal. The borrowed mode-set payload is +immutable. Observers do not participate in the transaction. Standalone radios +without ``modeSetCoordinatorModule`` continue to publish their own notification. +Participant and observer failures remain fatal simulation errors; partial changes +are not rolled back and continuation is unsupported. + +HT-capable radio implementations must provide ``IIeee80211Radio`` in addition to +``IRadio``. Its ``isHtChannelWidthSupported()`` query covers both PHY directions, +and ``getChannel()`` returns the borrowed configured channel or null. Generic +legacy radios do not need this contract. AP channel/band validation now precedes +the initial mode-set notification as well as runtime publication. + +External ``IContention`` implementations must implement the new pure virtual +``updateTimingParameters(ifs, eifs, slotTime)`` method. On a runtime timing change, +retain completed whole backoff slots and the remaining random draw, restart the +applicable IFS and any unfinished slot, and update the expected grant time. +Unchanged timing preserves the existing schedule. This application must not emit +an intermediate mode-set notification or generate a new random backoff. + Migrating ``FieldsChunkSerializer`` Subclasses --------------------------------------------- diff --git a/src/inet/linklayer/ieee80211/Ieee80211Interface.cc b/src/inet/linklayer/ieee80211/Ieee80211Interface.cc new file mode 100644 index 00000000000..3d9651e31d5 --- /dev/null +++ b/src/inet/linklayer/ieee80211/Ieee80211Interface.cc @@ -0,0 +1,106 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +#include "inet/linklayer/ieee80211/Ieee80211Interface.h" + +#include "inet/common/ModuleAccess.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" + +namespace inet { +namespace ieee80211 { + +using namespace inet::physicallayer; + +Define_Module(Ieee80211Interface); + +void Ieee80211Interface::initialize(int stage) +{ + NetworkInterface::initialize(stage); + if (stage == INITSTAGE_LINK_LAYER) { + auto mac = check_and_cast(getSubmodule("mac")); + modeSet = mac->getModeSet(); + beginModeSetChange(modeSet); + completeModeSetChange(modeSet); + modeSetInitialized = true; + } + else if (stage == INITSTAGE_NETWORK_CONFIGURATION) { + // All child link-layer wiring is ready before the initial completed fact. + changingModeSet = true; + modeSetPublished = true; + emit(modesetChangedSignal, const_cast(modeSet)); + changingModeSet = false; + } +} + +void Ieee80211Interface::registerModeSetConsumer(cModule *consumer, Phase phase) +{ + Enter_Method_Silent(); + if (changingModeSet || modeSetInitialized) + throw cRuntimeError("Mode-set consumers must register before initialization completes"); + if (consumer == nullptr || getContainingNicModule(consumer) != this || + dynamic_cast(consumer) == nullptr) + throw cRuntimeError("Mode-set consumer must implement the contract in this interface"); + if (phase != MAC_STATE && phase != DERIVED_STATE) + throw cRuntimeError("Invalid mode-set consumer phase"); + if ((consumer == getSubmodule("mac")) != (phase == MAC_STATE)) + throw cRuntimeError("Only the configured MAC owns the mode-set MAC_STATE phase"); + auto result = modeSetConsumers.emplace(consumer->getId(), phase); + if (!result.second && result.first->second != phase) + throw cRuntimeError("Mode-set consumer registered in two phases"); +} + +void Ieee80211Interface::unregisterModeSetConsumer(cModule *consumer) +{ + Enter_Method_Silent(); + if (changingModeSet) + throw cRuntimeError("Cannot detach a mode-set consumer during a transition"); + if (consumer == nullptr || getContainingNicModule(consumer) != this) + throw cRuntimeError("Cannot detach a mode-set consumer from another interface"); + modeSetConsumers.erase(consumer->getId()); +} + +void Ieee80211Interface::beginModeSetChange(const Ieee80211ModeSet *modeSet) +{ + Enter_Method_Silent(); + if (changingModeSet) + throw cRuntimeError("Reentrant interface mode-set change"); + if (modeSet == nullptr) + throw cRuntimeError("Cannot clear the mode set of an IEEE 802.11 interface"); + for (const char *name : {"mac", "mgmt"}) { + auto module = getSubmodule(name); + if (module == nullptr || modeSetConsumers.count(module->getId()) == 0) + throw cRuntimeError("Required mode-set consumer '%s' is not registered", name); + } + for (const auto& entry : modeSetConsumers) + if (getSimulation()->getModule(entry.first) == nullptr) + throw cRuntimeError("Mode-set consumer was deleted without unregistering"); + pendingModeSet = modeSet; + changingModeSet = true; +} + +void Ieee80211Interface::completeModeSetChange(const Ieee80211ModeSet *modeSet) +{ + Enter_Method_Silent(); + if (!changingModeSet || pendingModeSet != modeSet) + throw cRuntimeError("Mode-set completion does not match the pending transition"); + for (auto phase : {MAC_STATE, DERIVED_STATE}) { + for (const auto& entry : modeSetConsumers) { + if (entry.second != phase) + continue; + auto consumer = getSimulation()->getModule(entry.first); + check_and_cast(consumer)->applyModeSet(modeSet); + } + } + this->modeSet = modeSet; + if (modeSetPublished) + emit(modesetChangedSignal, const_cast(modeSet)); + pendingModeSet = nullptr; + changingModeSet = false; +} + +} // namespace ieee80211 +} // namespace inet diff --git a/src/inet/linklayer/ieee80211/Ieee80211Interface.h b/src/inet/linklayer/ieee80211/Ieee80211Interface.h new file mode 100644 index 00000000000..c03a4b89bc7 --- /dev/null +++ b/src/inet/linklayer/ieee80211/Ieee80211Interface.h @@ -0,0 +1,38 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +#ifndef __INET_IEEE80211INTERFACE_H +#define __INET_IEEE80211INTERFACE_H + +#include "inet/networklayer/common/NetworkInterface.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.h" + +namespace inet { +namespace ieee80211 { + +class INET_API Ieee80211Interface : public NetworkInterface, public physicallayer::IIeee80211ModeSetCoordinator +{ + protected: + std::map modeSetConsumers; + const physicallayer::Ieee80211ModeSet *pendingModeSet = nullptr; + const physicallayer::Ieee80211ModeSet *modeSet = nullptr; + bool changingModeSet = false; + bool modeSetInitialized = false; + bool modeSetPublished = false; + + virtual void initialize(int stage) override; + + public: + virtual void registerModeSetConsumer(cModule *consumer, Phase phase) override; + virtual void unregisterModeSetConsumer(cModule *consumer) override; + virtual void beginModeSetChange(const physicallayer::Ieee80211ModeSet *modeSet) override; + virtual void completeModeSetChange(const physicallayer::Ieee80211ModeSet *modeSet) override; +}; + +} // namespace ieee80211 +} // namespace inet + +#endif diff --git a/src/inet/linklayer/ieee80211/Ieee80211Interface.ned b/src/inet/linklayer/ieee80211/Ieee80211Interface.ned index e32eaba59fc..edac5618a58 100644 --- a/src/inet/linklayer/ieee80211/Ieee80211Interface.ned +++ b/src/inet/linklayer/ieee80211/Ieee80211Interface.ned @@ -15,6 +15,7 @@ import inet.linklayer.ieee80211.mgmt.IIeee80211Mgmt; import inet.linklayer.ieee80211.mib.Ieee80211Mib; import inet.networklayer.common.NetworkInterface; import inet.physicallayer.wireless.common.contract.packetlevel.IRadio; +import inet.physicallayer.wireless.ieee80211.contract.packetlevel.IIeee80211ModeSetCoordinator; // // Implements an IEEE 802.11 network interface. It implements @@ -58,9 +59,11 @@ import inet.physicallayer.wireless.common.contract.packetlevel.IRadio; // Despite its appearance, Ieee80211Interface is not a plain compound module. // It has an underlying custom C++ class that inherits from `cModule`. // -module Ieee80211Interface extends NetworkInterface like IWirelessInterface +module Ieee80211Interface extends NetworkInterface like IWirelessInterface, IIeee80211ModeSetCoordinator { parameters: + @class(Ieee80211Interface); + @signal[modesetChanged](type=inet::physicallayer::Ieee80211ModeSet); string interfaceTableModule; string energySourceModule = default(""); string opMode @enum("a","b","g(erp)","g(mixed)","n(mixed-2.4Ghz)","p","ac") = default("g(mixed)"); @@ -73,6 +76,7 @@ module Ieee80211Interface extends NetworkInterface like IWirelessInterface **.bitrate = this.bitrate; mac.modeSet = default(this.opMode); mac.*.rateSelection.dataFrameBitrate = default(this.bitrate); + radio.modeSetCoordinatorModule = default(absPath(".")); *.macModule = default(absPath(".mac")); *.mibModule = default(absPath(".mib")); *.interfaceTableModule = default(absPath(this.interfaceTableModule)); diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc index d87f581231f..9943ba2b3e5 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc @@ -11,6 +11,7 @@ #include "inet/common/INETUtils.h" #include "inet/common/ModuleAccess.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.h" #include "inet/common/ProtocolTag_m.h" #include "inet/common/packet/Message.h" #include "inet/common/packet/Packet.h" @@ -26,10 +27,10 @@ #include "inet/linklayer/ieee80211/mac/contract/IRx.h" #include "inet/linklayer/ieee80211/mac/contract/ITx.h" #include "inet/networklayer/contract/IInterfaceTable.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211ControlInfo_m.h" -#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211Radio.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" -#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h" namespace inet { namespace ieee80211 { @@ -58,6 +59,8 @@ void Ieee80211Mac::initialize(int stage) fcsMode = parseFcsMode(par("fcsMode")); mib.reference(this, "mibModule", true); mib->qos = par("qosStation"); + radio = check_and_cast(gate("lowerLayerOut")->getNextGate()->getOwnerModule()); + check_and_cast(getContainingNicModule(this))->registerModeSetConsumer(this, physicallayer::IIeee80211ModeSetCoordinator::MAC_STATE); } else if (stage == INITSTAGE_LINK_LAYER) { cModule *llcModule = gate("upperLayerOut")->getNextGate()->getOwnerModule(); @@ -71,21 +74,6 @@ void Ieee80211Mac::initialize(int stage) 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); - emit(modesetChangedSignal, modeSet); if (isUp()) initializeRadioMode(); rx = check_and_cast(getSubmodule("rx")); @@ -97,6 +85,30 @@ void Ieee80211Mac::initialize(int stage) } } +void Ieee80211Mac::updateLocalHtCapabilities() +{ + int operationalHtSpatialStreamLimit = std::min(radio->getAntenna()->getNumAntennas(), + modeSet->getMaximumNumberOfSpatialStreams()); + std::set operationalChannelWidths; + const IIeee80211Band *operationBand = nullptr; + if (modeSet->isHtOperationSupported()) { + const auto *phy = dynamic_cast(radio.get()); + if (phy == nullptr) + throw cRuntimeError("HT operation requires the IIeee80211Radio capability contract"); + if (mib->bssStationData.stationType == Ieee80211Mib::ACCESS_POINT) { + mib->requirePrimaryChannel(); + const auto *channel = phy->getChannel(); + if (channel == nullptr || channel->getBand() == nullptr) + throw cRuntimeError("HT operation requires a configured channel and band"); + operationBand = channel->getBand(); + } + for (auto channelWidth : modeSet->getHtSupportedChannelWidths()) + if (phy->isHtChannelWidthSupported(channelWidth)) + operationalChannelWidths.insert(channelWidth); + } + mib->updateLocalHtCapabilities(modeSet, operationalChannelWidths, operationalHtSpatialStreamLimit, operationBand); +} + void Ieee80211Mac::initializeRadioMode() { const char *initialRadioMode = par("initialRadioMode"); @@ -346,6 +358,19 @@ 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)); + // Mode-set application uses the coordinator contract, not notifications. +} + +void Ieee80211Mac::applyModeSet(const physicallayer::Ieee80211ModeSet *newModeSet) +{ + Enter_Method_Silent(); + modeSet = const_cast(newModeSet); + updateLocalHtCapabilities(); +} + 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..f6d1977b1e3 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,12 @@ 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 void applyModeSet(const physicallayer::Ieee80211ModeSet *modeSet) override; protected: FcsMode fcsMode; @@ -65,8 +68,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..9e3a7d5c54f 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.ned +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.ned @@ -92,7 +92,6 @@ module Ieee80211Mac extends MacProtocolBase like IIeee80211Mac @display("i=block/layer"); @class(Ieee80211Mac); @signal[linkBroken](type=inet::Packet); // TODO this signal is only present for the statistic to pass the signal check, to be removed - @signal[modesetChanged](type=inet::physicallayer::Ieee80211ModeSet); @signal[frameTransmissionOutcome](type=inet::Packet); @statistic[packetSentToUpper](title="packets sent to upper layer"; record=count,sum(packetBytes),vector(packetBytes); interpolationmode=none); @statistic[packetSentToLower](title="packets sent to lower layer"; record=count,sum(packetBytes),vector(packetBytes); interpolationmode=none); diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc index 1f6ea0615cb..93c0203593c 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc @@ -22,7 +22,7 @@ Define_Module(Dcaf); void Dcaf::initialize(int stage) { if (stage == INITSTAGE_LOCAL) { - getContainingNicModule(this)->subscribe(modesetChangedSignal, this); + ModeSetListener::initialize(stage); } else if (stage == INITSTAGE_LINK_LAYER) { // TODO calculateTimingParameters() @@ -65,7 +65,8 @@ void Dcaf::calculateTimingParameters() cwMin = modeSet->getCwMin(); if (cwMax == -1) cwMax = modeSet->getCwMax(); - cw = cwMin; + // Model reconfiguration preserves retry backoff within the new bounds. + cw = std::min(cwMax, std::max(cwMin, cw)); EV_DEBUG << "Contention window parameters are initialized: cw = " << cw << ", cwMin = " << cwMin << ", cwMax = " << cwMax << std::endl; } @@ -125,11 +126,16 @@ void Dcaf::expectedChannelAccess(simtime_t time) void Dcaf::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) { Enter_Method("%s", cComponent::getSignalName(signalID)); + // Mode-set application uses the coordinator contract, not notifications. +} - if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - calculateTimingParameters(); - } +void Dcaf::applyModeSet(const physicallayer::Ieee80211ModeSet *newModeSet) +{ + Enter_Method_Silent(); + modeSet = const_cast(newModeSet); + calculateTimingParameters(); + if (contention != nullptr) + contention->updateTimingParameters(ifs, eifs, slotTime); } } /* namespace ieee80211 */ diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h index 2fab0d312c8..804e9d0093c 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h @@ -19,8 +19,10 @@ namespace ieee80211 { class INET_API Dcaf : public IChannelAccess, public IContention::ICallback, public IRecoveryProcedure::ICwCalculator, public ModeSetListener { + public: + 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..f6956cfe826 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc @@ -29,7 +29,7 @@ Edcaf::~Edcaf() void Edcaf::initialize(int stage) { if (stage == INITSTAGE_LOCAL) { - getContainingNicModule(this)->subscribe(modesetChangedSignal, this); + ModeSetListener::initialize(stage); ac = getAccessCategory(par("accessCategory")); contention = check_and_cast(getSubmodule("contention")); collisionController = check_and_cast(getModuleByPath(par("collisionControllerModule"))); @@ -74,7 +74,8 @@ void Edcaf::calculateTimingParameters() cwMin = getCwMin(ac, modeSet->getCwMin()); if (cwMax == -1) cwMax = getCwMax(ac, modeSet->getCwMax(), modeSet->getCwMin()); - cw = cwMin; + // Model reconfiguration preserves retry backoff within the new bounds. + cw = std::min(cwMax, std::max(cwMin, cw)); EV_DEBUG << "Contention window parameters are initialized: cw = " << cw << ", cwMin = " << cwMin << ", cwMax = " << cwMax << std::endl; } @@ -189,11 +190,16 @@ 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)); + // Mode-set application uses the coordinator contract, not notifications. +} - if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - calculateTimingParameters(); - } +void Edcaf::applyModeSet(const physicallayer::Ieee80211ModeSet *newModeSet) +{ + Enter_Method_Silent(); + modeSet = const_cast(newModeSet); + calculateTimingParameters(); + if (contention != nullptr) + contention->updateTimingParameters(ifs, eifs, slotTime); } } // namespace ieee80211 diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h index e737cad9162..8add90acd76 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h @@ -30,6 +30,9 @@ namespace ieee80211 { */ class INET_API Edcaf : public IChannelAccess, public IContention::ICallback, public IRecoveryProcedure::ICwCalculator, public ModeSetListener { + public: + 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..79803680ef3 100644 --- a/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.cc +++ b/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.cc @@ -8,6 +8,7 @@ #include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" #include "inet/common/ModuleAccess.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.h" #include "inet/common/Simsignals.h" #include "inet/networklayer/common/NetworkInterface.h" @@ -17,15 +18,19 @@ namespace ieee80211 { void ModeSetListener::initialize(int stage) { if (stage == INITSTAGE_LOCAL) - getContainingNicModule(this)->subscribe(modesetChangedSignal, this); + check_and_cast(getContainingNicModule(this))->registerModeSetConsumer(this, physicallayer::IIeee80211ModeSetCoordinator::DERIVED_STATE); } void ModeSetListener::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) { Enter_Method("%s", cComponent::getSignalName(signalID)); + // Mode-set application uses the coordinator contract, not notifications. +} - if (signalID == modesetChangedSignal) - modeSet = check_and_cast(obj); +void ModeSetListener::applyModeSet(const physicallayer::Ieee80211ModeSet *newModeSet) +{ + Enter_Method_Silent(); + modeSet = const_cast(newModeSet); } } /* namespace ieee80211 */ diff --git a/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.h b/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.h index 05581613050..d0e5c270d51 100644 --- a/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.h +++ b/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.h @@ -10,12 +10,17 @@ #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 void applyModeSet(const physicallayer::Ieee80211ModeSet *modeSet) override; + protected: physicallayer::Ieee80211ModeSet *modeSet = nullptr; diff --git a/src/inet/linklayer/ieee80211/mac/contention/Contention.cc b/src/inet/linklayer/ieee80211/mac/contention/Contention.cc index 4a1471e28f1..e4339774eca 100644 --- a/src/inet/linklayer/ieee80211/mac/contention/Contention.cc +++ b/src/inet/linklayer/ieee80211/mac/contention/Contention.cc @@ -85,6 +85,36 @@ void Contention::startContention(int cw, simtime_t ifs, simtime_t eifs, simtime_ handleWithFSM(START); } +void Contention::updateTimingParameters(simtime_t ifs, simtime_t eifs, simtime_t slotTime) +{ + Enter_Method_Silent(); + ASSERT(ifs >= 0 && eifs >= 0 && slotTime >= 0); + if (this->ifs == ifs && this->eifs == eifs && this->slotTime == slotTime) + return; + bool activeBackoff = fsm.getState() == IFS_AND_BACKOFF; + bool pendingEifs = endEifsTime > simTime(); + if (activeBackoff && this->slotTime > SIMTIME_ZERO) + computeRemainingBackoffSlots(); + this->ifs = ifs; + this->eifs = eifs; + this->slotTime = slotTime; + // Modeling policy for runtime reconfiguration: restart the applicable IFS + // and any unfinished slot, retaining the remaining integer backoff count. + if (pendingEifs) + endEifsTime = simTime() + eifs; + if (activeBackoff) { + backoffOptimizationDelta = SIMTIME_ZERO; + scheduledTransmissionTime = simTime() + (pendingEifs ? std::max(ifs, eifs) : ifs) + backoffSlots * slotTime; + cancelEvent(startTxEvent); + scheduleAt(scheduledTransmissionTime, startTxEvent); + // Keep EDCA's collision arbitration aligned without publishing an + // intermediate mode-set state or announcing a new random backoff. + callback->expectedChannelAccess(scheduledTransmissionTime); + if (hasGUI()) + updateDisplayString(scheduledTransmissionTime); + } +} + void Contention::handleWithFSM(EventType event) { emit(stateChangedSignal, fsm.getState()); diff --git a/src/inet/linklayer/ieee80211/mac/contention/Contention.h b/src/inet/linklayer/ieee80211/mac/contention/Contention.h index 3042528c88d..33528b753ac 100644 --- a/src/inet/linklayer/ieee80211/mac/contention/Contention.h +++ b/src/inet/linklayer/ieee80211/mac/contention/Contention.h @@ -71,6 +71,8 @@ class INET_API Contention : public SimpleModule, public IContention // TODO also add a switchToReception() method? because switching takes time, so we dont automatically switch to tx after completing a transmission! (as we may want to transmit immediate frames afterwards) virtual void startContention(int cw, simtime_t ifs, simtime_t eifs, simtime_t slotTime, ICallback *callback) override; + virtual void updateTimingParameters(simtime_t ifs, simtime_t eifs, simtime_t slotTime) override; + virtual void mediumStateChanged(bool mediumFree) override; virtual void corruptedFrameReceived() override; virtual bool isContentionInProgress() override { return fsm.getState() != IDLE; } diff --git a/src/inet/linklayer/ieee80211/mac/contract/IContention.h b/src/inet/linklayer/ieee80211/mac/contract/IContention.h index c29703bf6ab..63c2285960a 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IContention.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IContention.h @@ -46,6 +46,10 @@ class INET_API IContention virtual ~IContention() {} virtual void startContention(int cw, simtime_t ifs, simtime_t eifs, simtime_t slotTime, ICallback *callback) = 0; + // Runtime timing change: retain completed whole backoff slots and restart the + // applicable IFS with the new timing. An unchanged tuple preserves the schedule. + // Does not draw a new backoff or emit notifications while a mode set is applied. + virtual void updateTimingParameters(simtime_t ifs, simtime_t eifs, simtime_t slotTime) = 0; virtual bool isContentionInProgress() = 0; // notifications diff --git a/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.cc b/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.cc index 551a31c4c9e..5dbc3d3fe09 100644 --- a/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.cc +++ b/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.cc @@ -64,10 +64,15 @@ void RateControlBase::receiveSignal(cComponent *source, simsignal_t signalID, cO { Enter_Method("%s", cComponent::getSignalName(signalID)); - if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - resetRateControl(); - } + // Mode-set application uses the coordinator contract, not notifications. +} + +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..048699e5980 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.cc @@ -14,6 +14,22 @@ 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. + const IIeee80211Mode *legacyMode = nullptr; + for (const auto *candidate : modeSet->getLegacyOperationalModes()) { + if (!modeSet->getIsMandatory(candidate)) + continue; + if (candidate == requestedMode) + return candidate; + if (legacyMode == nullptr || candidate->getDataMode()->getNetBitrate() > legacyMode->getDataMode()->getNetBitrate()) + legacyMode = candidate; + } + return legacyMode != nullptr ? legacyMode : requestedMode; +} + namespace { static const IIeee80211Mode *getLegacyFallback(const Ieee80211ModeSet *modeSet, diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h b/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h index ba5eaebf764..e89cf445dd8 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h +++ b/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h @@ -14,6 +14,9 @@ namespace inet { namespace ieee80211 { +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..977e0411eb8 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc @@ -27,20 +27,7 @@ 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)); + updateModes(); } } @@ -56,7 +43,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 +52,30 @@ void QosRateSelection::ensurePerReceiverModesResolved() } } +void QosRateSelection::updateModes() +{ + if (modeSet == nullptr) + return; + 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"), par("dataFrameGuardInterval")); + 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)); + fastestMandatoryMode = modeSet->getFastestMandatoryMode(); + lastTransmittedFrameMode.clear(); + perReceiverDataFrameMode.clear(); + perReceiverResolved = false; +} + const IIeee80211Mode *QosRateSelection::getMode(Packet *packet, const Ptr& header) { const auto& modeReqTag = packet->findTag(); @@ -101,10 +112,8 @@ const IIeee80211Mode *QosRateSelection::computeResponseAckFrameMode(Packet *pack ASSERT(modeSet->containsMode(mode)); const IIeee80211Mode *responseMode; if (!responseAckFrameMode) { - if (modeSet->getIsMandatory(mode)) - responseMode = mode; - else if (auto slowerMode = modeSet->getSlowerMandatoryMode(mode)) - responseMode = slowerMode; + if (auto mandatoryMode = modeSet->getMandatoryModeAtOrBelow(mode)) + responseMode = mandatoryMode; else throw cRuntimeError("Mandatory mode not found"); } @@ -120,10 +129,8 @@ const IIeee80211Mode *QosRateSelection::computeResponseCtsFrameMode(Packet *pack ASSERT(modeSet->containsMode(mode)); const IIeee80211Mode *responseMode; if (!responseCtsFrameMode) { - if (modeSet->getIsMandatory(mode)) - responseMode = mode; - else if (auto slowerMode = modeSet->getSlowerMandatoryMode(mode)) - responseMode = slowerMode; + if (auto mandatoryMode = modeSet->getMandatoryModeAtOrBelow(mode)) + responseMode = mandatoryMode; else throw cRuntimeError("Mandatory mode not found"); } @@ -150,9 +157,15 @@ const IIeee80211Mode *QosRateSelection::computeResponseBlockAckFrameMode(Packet 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 +176,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 +249,16 @@ const IIeee80211Mode *QosRateSelection::computeMode(Packet *packet, const Ptr(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(); } void QosRateSelection::frameTransmitted(Packet *packet, const Ptr& header) @@ -288,6 +269,10 @@ void QosRateSelection::frameTransmitted(Packet *packet, const PtrgetHtMcsIndex() < 0) return mode; return selectPeerCompatibleMode(modeSet, mib->findPeerHtState(peerAddress), mode, peerAddress); diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h index 569ef742d37..3922e34b3ce 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h @@ -32,11 +32,13 @@ namespace ieee80211 { */ class INET_API QosRateSelection : public IQosRateSelection, public ModeSetListener { + public: + 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,6 +61,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 diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned index 35bf0e1ace5..88ec4706b60 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned @@ -32,8 +32,9 @@ simple QosRateSelection extends SimpleModule double responseCtsFrameBitrate @unit(bps) = default(-1bps); 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..ae687daf962 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc @@ -8,6 +8,7 @@ #include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h" #include "inet/common/ModuleAccess.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.h" #include "inet/common/Simsignals.h" #include "inet/linklayer/ieee80211/mac/contract/IRateControl.h" #include "inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h" @@ -28,23 +29,11 @@ void RateSelection::initialize(int stage) { if (stage == INITSTAGE_LOCAL) { mib.reference(this, "mibModule", true); - getContainingNicModule(this)->subscribe(modesetChangedSignal, this); + check_and_cast(getContainingNicModule(this))->registerModeSetConsumer(this, physicallayer::IIeee80211ModeSetCoordinator::DERIVED_STATE); } 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(); + updateModes(); // WATCH(dataOrMgmtRateControl); // WATCH(*((cObject**)&fastestMandatoryMode)); @@ -75,7 +64,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 +73,28 @@ void RateSelection::ensurePerReceiverModesResolved() } } +void RateSelection::updateModes() +{ + if (modeSet == nullptr) + return; + 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"), par("dataFrameGuardInterval")); + 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(); + lastTransmittedFrameMode.clear(); + perReceiverDataFrameMode.clear(); + perReceiverResolved = false; +} + const IIeee80211Mode *RateSelection::getMode(Packet *packet, const Ptr& header) { const auto& modeReqTag = packet->findTag(); @@ -108,7 +119,7 @@ const IIeee80211Mode *RateSelection::computeResponseAckFrameMode(Packet *packet, else { auto mode = getMode(packet, dataOrMgmtHeader); ASSERT(modeSet->containsMode(mode)); - auto responseMode = modeSet->getIsMandatory(mode) ? mode : modeSet->getSlowerMandatoryMode(mode); // TODO BSSBasicRateSet + auto responseMode = modeSet->getMandatoryModeAtOrBelow(mode); // TODO BSSBasicRateSet return getPeerCompatibleMode(dataOrMgmtHeader->getTransmitterAddress(), responseMode); } } @@ -120,7 +131,7 @@ const IIeee80211Mode *RateSelection::computeResponseCtsFrameMode(Packet *packet, else { auto mode = getMode(packet, rtsFrame); ASSERT(modeSet->containsMode(mode)); - auto responseMode = modeSet->getIsMandatory(mode) ? mode : modeSet->getSlowerMandatoryMode(mode); // TODO BSSBasicRateSet + auto responseMode = modeSet->getMandatoryModeAtOrBelow(mode); // TODO BSSBasicRateSet return getPeerCompatibleMode(rtsFrame->getTransmitterAddress(), responseMode); } } @@ -140,28 +151,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 +198,16 @@ const IIeee80211Mode *RateSelection::computeMode(Packet *packet, const Ptr(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(); } void RateSelection::frameTransmitted(Packet *packet, const Ptr& header) @@ -221,6 +237,10 @@ void RateSelection::emitDatarateSelected(cComponent *emitter, const PtrgetHtMcsIndex() < 0) return mode; return selectPeerCompatibleMode(modeSet, mib->findPeerHtState(peerAddress), mode, peerAddress); diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h index 69441331453..e7d7ffb9009 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,12 @@ 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 void applyModeSet(const physicallayer::Ieee80211ModeSet *modeSet) override; + protected: IRateControl *dataOrMgmtRateControl = nullptr; ModuleRefByPar mib; @@ -59,6 +64,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 diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned index 89f63916421..82938a1df50 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned @@ -27,8 +27,9 @@ simple RateSelection extends SimpleModule like IRateSelection double responseCtsFrameBitrate @unit(bps) = default(-1bps); 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/Ieee80211MgmtApBase.cc b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtApBase.cc index 329f439ccc0..e119af0f88d 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtApBase.cc +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtApBase.cc @@ -17,7 +17,7 @@ #include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtApBase.h" #include "inet/physicallayer/wireless/common/contract/packetlevel/IRadio.h" -#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211Radio.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.h" namespace inet { @@ -69,13 +69,10 @@ const physicallayer::IIeee80211Band *Ieee80211MgmtApBase::getHtOperationBand() c { if (radio == nullptr) throw cRuntimeError("HT Operation channel conversion requires a configured radioModule"); - const auto *radioContract = dynamic_cast(radio); - if (radioContract == nullptr) - throw cRuntimeError("HT Operation channel conversion requires radioModule to reference a radio, got %s", radio->getClassName()); - const auto *transmitter = dynamic_cast(radioContract->getTransmitter()); - if (transmitter == nullptr) - throw cRuntimeError("HT Operation channel conversion requires radioModule's transmitter to provide an IEEE 802.11 channel"); - const auto *channel = transmitter->getChannel(); + const auto *phy = dynamic_cast(radio); + if (phy == nullptr) + throw cRuntimeError("HT Operation channel conversion requires the IIeee80211Radio capability contract"); + const auto *channel = phy->getChannel(); if (channel == nullptr || channel->getBand() == nullptr) throw cRuntimeError("HT Operation channel conversion requires radioModule's IEEE 802.11 transmitter to have a configured channel and band"); return channel->getBand(); diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.cc b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.cc index 3c3bc33903a..8ed143121e6 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.cc +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.cc @@ -9,6 +9,7 @@ #include "inet/common/INETUtils.h" #include "inet/common/ModuleAccess.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.h" #include "inet/common/ProtocolTag_m.h" #include "inet/common/lifecycle/LifecycleOperation.h" #include "inet/common/lifecycle/ModuleOperations.h" @@ -34,7 +35,7 @@ void Ieee80211MgmtBase::initialize(int stage) myIface = getContainingNicModule(this); numMgmtFramesReceived = 0; numMgmtFramesDropped = 0; - getContainingNicModule(this)->subscribe(modesetChangedSignal, this); + check_and_cast(getContainingNicModule(this))->registerModeSetConsumer(this, physicallayer::IIeee80211ModeSetCoordinator::DERIVED_STATE); WATCH(numMgmtFramesReceived); WATCH(numMgmtFramesDropped); } @@ -43,35 +44,38 @@ void Ieee80211MgmtBase::initialize(int stage) void Ieee80211MgmtBase::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) { Enter_Method("%s", cComponent::getSignalName(signalID)); + // Mode-set application uses the coordinator contract, not notifications. +} - 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; } 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..a296b5ecdf2 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,12 @@ 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 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/Ieee80211Mib.cc b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.cc index e5c8fd008a3..ef1c2aabbc1 100644 --- a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.cc +++ b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.cc @@ -8,7 +8,6 @@ #include "inet/linklayer/ieee80211/mib/Ieee80211Mib.h" #include - #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Band.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" @@ -113,7 +112,8 @@ const Ieee80211HtOperation& Ieee80211Mib::getHtOperation() const } void Ieee80211Mib::updateLocalHtCapabilities(const physicallayer::Ieee80211ModeSet *modeSet, - const std::set& operationalChannelWidths, int operationalHtSpatialStreamLimit) + const std::set& operationalChannelWidths, int operationalHtSpatialStreamLimit, + const physicallayer::IIeee80211Band *operationBand) { // The radio publishes its initial channel at PHYSICAL_LAYER before the MAC // publishes its mode set at LINK_LAYER. Preserve that independent BSS @@ -182,6 +182,12 @@ void Ieee80211Mib::updateLocalHtCapabilities(const physicallayer::Ieee80211ModeS if (protectionMode < 0 || protectionMode > 3) throw cRuntimeError("htProtectionMode must be between 0 and 3"); htOperation.protectionMode = static_cast(protectionMode); + if (operationBand != nullptr) { + // Validate/fallback before peers see the rebuilt operation. This also + // renegotiates peers once, against the final channel width and offset. + setPrimaryChannel(requirePrimaryChannel(), operationBand); + return; + } for (auto& entry : peerHtStates) { if (entry.second.valid) { entry.second.negotiatedCapabilities = negotiateHtCapabilities(localHtCapabilities, diff --git a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.h b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.h index 691257c2f07..b29af3d8355 100644 --- a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.h +++ b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.h @@ -101,7 +101,8 @@ class INET_API Ieee80211Mib : public SimpleModule void releaseAssociationId(const MacAddress& address); void clearAssociationIds(); void updateLocalHtCapabilities(const physicallayer::Ieee80211ModeSet *modeSet, - const std::set& operationalChannelWidths, int operationalHtSpatialStreamLimit); + const std::set& operationalChannelWidths, int operationalHtSpatialStreamLimit, + const physicallayer::IIeee80211Band *operationBand = nullptr); bool isHtOperationSupported() const { return localHtCapabilitiesValid; } bool hasPrimaryChannel() const { return primaryChannelAvailable; } int requirePrimaryChannel() const; 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/IIeee80211ModeSetCoordinator.h b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.h new file mode 100644 index 00000000000..2161081afd5 --- /dev/null +++ b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.h @@ -0,0 +1,40 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +#ifndef __INET_IIEEE80211MODESETCOORDINATOR_H +#define __INET_IIEEE80211MODESETCOORDINATOR_H + +#include "inet/common/INETDefs.h" + +namespace inet { +namespace physicallayer { + +class Ieee80211ModeSet; + +/** + * Same-interface mode-set transaction owner. Registration is explicit and unrelated + * to signal subscriptions. Consumers are modules implementing IIeee80211ModeSetListener. + * MAC_STATE precedes DERIVED_STATE; consumers within DERIVED_STATE are independent. + * Begin validates membership before PHY mutation; complete applies consumers and + * publishes the completed fact. Any failure after begin is fatal, without rollback. + */ +class INET_API IIeee80211ModeSetCoordinator +{ + public: + enum Phase { MAC_STATE, DERIVED_STATE }; + virtual ~IIeee80211ModeSetCoordinator() = default; + // Register during initialization; repeated registration in the same phase is idempotent. + virtual void registerModeSetConsumer(cModule *consumer, Phase phase) = 0; + // Explicitly detach before deleting a consumer. Membership cannot change during a transaction. + virtual void unregisterModeSetConsumer(cModule *consumer) = 0; + virtual void beginModeSetChange(const Ieee80211ModeSet *modeSet) = 0; + virtual void completeModeSetChange(const Ieee80211ModeSet *modeSet) = 0; +}; + +} // namespace physicallayer +} // namespace inet + +#endif diff --git a/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.ned b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.ned new file mode 100644 index 00000000000..2b0a6e2e26b --- /dev/null +++ b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.ned @@ -0,0 +1,14 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +package inet.physicallayer.wireless.ieee80211.contract.packetlevel; + +// Coordinates registered MAC/PHY state consumers before publishing a mode-set fact. +moduleinterface IIeee80211ModeSetCoordinator +{ + parameters: + @signal[modesetChanged](type=inet::physicallayer::Ieee80211ModeSet); +} 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..420b58cea0c --- /dev/null +++ b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.h @@ -0,0 +1,35 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +#ifndef __INET_IIEEE80211MODESETLISTENER_H +#define __INET_IIEEE80211MODESETLISTENER_H + +#include "inet/common/INETDefs.h" + +namespace inet { +namespace physicallayer { + +class Ieee80211ModeSet; + +/** + * Explicitly registered consumer of an interface's mode-set changes. The + * coordinator applies consumers before publishing modesetChanged. Implementing + * this contract or subscribing to that signal does not register a consumer. + * Applying a change must not notify observers. Failures are fatal simulation + * errors; partially applied changes are not rolled back. + */ +class INET_API IIeee80211ModeSetListener +{ + public: + virtual ~IIeee80211ModeSetListener() = default; + virtual const Ieee80211ModeSet *getModeSet() const = 0; + virtual void applyModeSet(const Ieee80211ModeSet *modeSet) = 0; +}; + +} // namespace physicallayer +} // namespace inet + +#endif diff --git a/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211Radio.h b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211Radio.h new file mode 100644 index 00000000000..f938d841ce3 --- /dev/null +++ b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211Radio.h @@ -0,0 +1,32 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +#ifndef __INET_IIEEE80211RADIO_H +#define __INET_IIEEE80211RADIO_H + +#include "inet/common/INETDefs.h" +#include "inet/common/Units.h" + +namespace inet { +namespace physicallayer { + +class Ieee80211Channel; + +/** Read-only IEEE 802.11 PHY capabilities, in addition to the IRadio role. */ +class INET_API IIeee80211Radio +{ + public: + virtual ~IIeee80211Radio() = default; + // True only when both the operational transmitter and receiver support this width. + virtual bool isHtChannelWidthSupported(units::values::Hz channelWidth) const = 0; + // Borrowed channel, or nullptr when no channel is configured. + virtual const Ieee80211Channel *getChannel() const = 0; +}; + +} // namespace physicallayer +} // namespace inet + +#endif diff --git a/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211Radio.ned b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211Radio.ned new file mode 100644 index 00000000000..42fe7ef481e --- /dev/null +++ b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211Radio.ned @@ -0,0 +1,14 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +package inet.physicallayer.wireless.ieee80211.contract.packetlevel; + +import inet.physicallayer.wireless.common.contract.packetlevel.IRadio; + +// IEEE 802.11 radio with operational HT width and channel queries. +moduleinterface IIeee80211Radio extends IRadio +{ +} diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/IIeee80211Mode.h b/src/inet/physicallayer/wireless/ieee80211/mode/IIeee80211Mode.h index aa615b7d19e..0011d588bad 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 = 0; virtual const IModulation *getModulation() const = 0; virtual int getNumberOfSpatialStreams() const = 0; }; @@ -68,6 +73,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; diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssMode.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssMode.h index 7b81268c9b7..af511f9a772 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssMode.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssMode.h @@ -64,6 +64,7 @@ class INET_API Ieee80211DsssDataMode : public Ieee80211DsssChunkMode, public IIe public: Ieee80211DsssDataMode(const DpskModulationBase *modulation); + virtual const simtime_t getGuardInterval() const override { return -1; } virtual Hz getBandwidth() const override { return MHz(22); } virtual bps getNetBitrate() const override { return Mbps(1) * modulation->getConstellationSize() / 2; } virtual bps getGrossBitrate() const override { return getNetBitrate(); } diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211FhssMode.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211FhssMode.h index 39295cf2bd5..2b29fe15539 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211FhssMode.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211FhssMode.h @@ -58,6 +58,7 @@ class INET_API Ieee80211FhssDataMode : public IIeee80211DataMode public: Ieee80211FhssDataMode(const GfskModulationBase *modulation); + virtual const simtime_t getGuardInterval() const override { return -1; } virtual Hz getBandwidth() const override { return Hz(NaN); } virtual bps getNetBitrate() const override { return Mbps(1) * modulation->getConstellationSize() / 2; } virtual bps getGrossBitrate() const override { return getNetBitrate(); } diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HrDsssMode.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HrDsssMode.h index 82324fe4a02..724daa40875 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HrDsssMode.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HrDsssMode.h @@ -74,6 +74,7 @@ class INET_API Ieee80211HrDsssDataMode : public IIeee80211DataMode public: Ieee80211HrDsssDataMode(bps bitrate); + virtual const simtime_t getGuardInterval() const override { return -1; } virtual Hz getBandwidth() const override { return MHz(22); } virtual bps getNetBitrate() const override { return bitrate; } virtual bps getGrossBitrate() const override { return bitrate; } diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.cc b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.cc index 319128cce20..d4498568496 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.insert(std::pair, const Ieee80211HtMode *>(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..5e5d43b6fa3 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(); } }; @@ -263,7 +265,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 +467,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/Ieee80211IrMode.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211IrMode.h index 86170cfc24b..4479d8e2365 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211IrMode.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211IrMode.h @@ -66,6 +66,7 @@ class INET_API Ieee80211IrDataMode : public IIeee80211DataMode public: Ieee80211IrDataMode(const PpmModulationBase *modulation); + virtual const simtime_t getGuardInterval() const override { return -1; } virtual Hz getBandwidth() const override { return Hz(NaN); } virtual bps getNetBitrate() const override { return Mbps(1) * modulation->getConstellationSize() / 2; } virtual bps getGrossBitrate() const override { return getNetBitrate(); } diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc index 5a53166e2cc..906910a00e2 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc @@ -578,6 +578,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 @@ -663,20 +665,49 @@ bool Ieee80211ModeSet::getIsMandatory(const IIeee80211Mode *mode) const return entries[getModeIndex(mode)].isMandatory; } -const IIeee80211Mode *Ieee80211ModeSet::findMode(bps bitrate, Hz bandwidth, int numSpatialStreams) const +const IIeee80211Mode *Ieee80211ModeSet::findCompatibleMode(const IIeee80211Mode *mode) const { - return findMode(bitrate - Mbps(0.05), bitrate + Mbps(0.05), bandwidth, numSpatialStreams); + 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(); + 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); + // Absence is part of the source tuple, not findMode()'s wildcard. + const bool guardIntervalMatches = (sourceGuardInterval < SIMTIME_ZERO && candidateGuardInterval < SIMTIME_ZERO) || + (sourceGuardInterval >= SIMTIME_ZERO && candidateGuardInterval == sourceGuardInterval); + if (candidateDataMode->getNetBitrate() == sourceBitrate && + bandwidthMatches && candidateDataMode->getNumberOfSpatialStreams() == sourceDataMode->getNumberOfSpatialStreams() && + guardIntervalMatches) + return entry.mode; + } + return nullptr; } -const IIeee80211Mode *Ieee80211ModeSet::findMode(bps minBitrate, bps maxBitrate, Hz bandwidth, int numSpatialStreams) const +const IIeee80211Mode *Ieee80211ModeSet::findMode(bps bitrate, Hz bandwidth, int numSpatialStreams, simtime_t guardInterval) const +{ + return findMode(bitrate - Mbps(0.05), bitrate + Mbps(0.05), bandwidth, numSpatialStreams, guardInterval); +} + +const IIeee80211Mode *Ieee80211ModeSet::findMode(bps minBitrate, bps maxBitrate, Hz bandwidth, int numSpatialStreams, simtime_t guardInterval) const { 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; } @@ -684,20 +715,22 @@ const IIeee80211Mode *Ieee80211ModeSet::findMode(bps minBitrate, bps maxBitrate, return nullptr; } -const IIeee80211Mode *Ieee80211ModeSet::getMode(bps bitrate, Hz bandwidth, int numSpatialStreams) const +const IIeee80211Mode *Ieee80211ModeSet::getMode(bps bitrate, Hz bandwidth, int numSpatialStreams, simtime_t guardInterval) 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); 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) const { - const IIeee80211Mode *mode = findMode(minBitrate, maxBitrate, bandwidth, numSpatialStreams); + const IIeee80211Mode *mode = findMode(minBitrate, maxBitrate, bandwidth, numSpatialStreams, guardInterval); 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 +748,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 +797,47 @@ 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 Ieee80211ModeSet *Ieee80211ModeSet::findModeSet(const char *mode) diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h index e14627dbcbe..0e1a3b6254f 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h @@ -88,17 +88,21 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject bool containsMode(const IIeee80211Mode *mode) const { return findModeIndex(mode) != -1; } bool getIsMandatory(const IIeee80211Mode *mode) const; - - const IIeee80211Mode *findMode(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; + + const IIeee80211Mode *findMode(bps bitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1, simtime_t guardInterval = -1) const; + const IIeee80211Mode *findMode(bps minBitrate, bps maxBitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1, simtime_t guardInterval = -1) const; + const IIeee80211Mode *getMode(bps bitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1, simtime_t guardInterval = -1) const; + const IIeee80211Mode *getMode(bps minBitrate, bps maxBitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1, simtime_t guardInterval = -1) 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; 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..ea12fd18697 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,9 +690,13 @@ 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 htModeId = std::make_tuple(mcsMode->getBandwidth(), mcsMode->getMcsIndex(), guardIntervalType, nss, centerFrequencyMode, preambleFormat); auto mode = singleton.modeCache.find(htModeId); if (mode == singleton.modeCache.end()) { const Ieee80211OfdmSignalMode *legacySignal = 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.insert(std::pair(htModeId, 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..2752f9c0ad4 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc @@ -7,8 +7,13 @@ #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" +#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" @@ -42,6 +47,9 @@ void Ieee80211Radio::initialize(int stage) FlatRadioBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { + modeSetCoordinator.reference(this, "modeSetCoordinatorModule", false); + if (modeSetCoordinator != nullptr && dynamic_cast(modeSetCoordinator.get()) != getParentModule()) + throw cRuntimeError("The mode-set coordinator must be the containing interface"); const char *fcsModeString = par("fcsMode"); fcsMode = parseFcsMode(fcsModeString, true); } @@ -58,13 +66,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 +94,51 @@ 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)); + if (modeSetCoordinator != nullptr) + modeSetCoordinator->beginModeSetChange(modeSet); + changingModeSet = true; + if (explicitMode) + transmitter->setModeSetAndMode(modeSet, mode); + else + transmitter->setModeSet(modeSet); + receiver->setModeSet(modeSet); receptionTimer = nullptr; + if (modeSetCoordinator != nullptr) + modeSetCoordinator->completeModeSetChange(modeSet); + else if (modeSet != nullptr) + emit(modesetChangedSignal, const_cast(modeSet)); emit(listeningChangedSignal, 0); + changingModeSet = false; + EV << "Changing radio mode set to " << modeSet << " and mode to " << transmitter->getMode() << endl; +} + +const Ieee80211Channel *Ieee80211Radio::getChannel() const +{ + return check_and_cast(transmitter)->getChannel(); +} + +bool Ieee80211Radio::isHtChannelWidthSupported(Hz channelWidth) const +{ + return check_and_cast(transmitter)->isHtChannelWidthSupported(channelWidth) && + check_and_cast(receiver)->isHtChannelWidthSupported(channelWidth); } void Ieee80211Radio::setMode(const IIeee80211Mode *mode) @@ -334,4 +384,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..586d1144f1c 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h @@ -9,6 +9,9 @@ #define __INET_IEEE80211RADIO_H #include "inet/physicallayer/wireless/common/base/packetlevel/FlatRadioBase.h" +#include "inet/common/ModuleRefByPar.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211Radio.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Band.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" @@ -18,7 +21,7 @@ namespace inet { namespace physicallayer { -class INET_API Ieee80211Radio : public FlatRadioBase +class INET_API Ieee80211Radio : public FlatRadioBase, public IIeee80211Radio { public: /** @@ -30,11 +33,15 @@ class INET_API Ieee80211Radio : public FlatRadioBase static const Ptr peekIeee80211PhyHeaderAtFront(const Packet *packet, b length = b(-1), int flags = 0); protected: + ModuleRefByPar modeSetCoordinator; + 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 +53,13 @@ class INET_API Ieee80211Radio : public FlatRadioBase public: Ieee80211Radio(); + // Update behavioral consumers before publishing the new mode set. + // Failures are fatal simulation errors; these setters do not roll back. + // Behavioral consumers implement IIeee80211ModeSetListener. + virtual const Ieee80211Channel *getChannel() const override; + virtual bool isHtChannelWidthSupported(Hz channelWidth) const override; 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 +70,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..c85451199c0 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.ned +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.ned @@ -8,6 +8,7 @@ package inet.physicallayer.wireless.ieee80211.packetlevel; import inet.physicallayer.wireless.common.base.packetlevel.FlatRadioBase; +import inet.physicallayer.wireless.ieee80211.contract.packetlevel.IIeee80211Radio; // // This radio model is part of the IEEE 802.11 physical layer model. It supports @@ -22,9 +23,11 @@ import inet.physicallayer.wireless.common.base.packetlevel.FlatRadioBase; // @see ~Ieee80211ScalarRadio, ~Ieee80211DimensionalRadio. // //# TODO check this Table 18-14—Receiver performance requirements -module Ieee80211Radio extends FlatRadioBase +module Ieee80211Radio extends FlatRadioBase like IIeee80211Radio { parameters: + string modeSetCoordinatorModule = default(""); // Optional interface transaction owner; empty for standalone PHY use + @signal[modesetChanged](type=inet::physicallayer::Ieee80211ModeSet); // Standalone radio only string opMode @enum("a", "b", "g(erp)", "g(mixed)", "n(mixed-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) 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/Ieee80211Transmitter.cc b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc index 6c76381a790..7011071f1d5 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc @@ -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,9 @@ 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())); 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 +176,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/tests/fingerprint/examples.csv b/tests/fingerprint/examples.csv index 696ba2cb33b..0c6b75202ac 100644 --- a/tests/fingerprint/examples.csv +++ b/tests/fingerprint/examples.csv @@ -377,8 +377,8 @@ /examples/manetrouting/gpsr/, -f omnetpp.ini -c DynamicIPv6 -r 0, 20s, 6299-3bf9/tplx;3617-890d/~tNl;2653-aa28/tyf, PASS, wireless adhoc /examples/manetrouting/gpsr/, -f omnetpp.ini -c DynamicGeneric -r 0, 20s, 732c-c576/tplx;03c2-f3ec/~tNl;fb81-3dac/tyf, PASS, wireless adhoc -/examples/manetrouting/multiradio/, -f omnetpp.ini -c MultiRadio -r 0, 20s, ec17-5cc2/tplx;55f5-0894/~tNl, PASS, wireless adhoc Ipv4 -/examples/manetrouting/multiradio/, -f omnetpp.ini -c SingleRadio -r 0, 20s, 85a0-51b8/tplx;c07a-44e1/~tNl;3aa0-49ed/tyf, PASS, wireless adhoc Ipv4 +/examples/manetrouting/multiradio/, -f omnetpp.ini -c MultiRadio -r 0, 20s, a83a-1ece/tplx;f1d9-19c4/~tNl, PASS, wireless adhoc Ipv4 +/examples/manetrouting/multiradio/, -f omnetpp.ini -c SingleRadio -r 0, 20s, c2bd-7372/tplx;fc98-08d6/~tNl;3aa0-49ed/tyf, PASS, wireless adhoc Ipv4 /examples/ipv6/mipv6/, -f omnetpp.ini -c Handover -r 0, 70s, 17ac-0898/tplx;8bbc-d083/~tNl;fdb7-4ab1/~tND;44ef-1a45/tyf, PASS, wireless EthernetMac /examples/ipv6/mipv6/, -f omnetpp.ini -c RouteOptimizationTwoCNs -r 0, 60s, 19e2-a165/tplx;d96c-5e41/~tNl;ee03-5334/~tND;ed3e-17fa/tyf, PASS, wireless EthernetMac @@ -596,7 +596,7 @@ /examples/wireless/lan80211/, -f omnetpp.ini -c Ping1 -r 0, 25s, 3785-bc39/tplx;18be-f36c/~tNl;c76d-5483/~tND, PASS, wireless Ipv4 # /examples/wireless/lan80211/, -f omnetpp.ini -c Ping2 -r 0, 100s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND;0000-0000/tyf, ERROR, wireless # [Config Ping2] # __interactive__ -/examples/wireless/lan80211ac/, -f omnetpp.ini -c Ping1 -r 0, 100s, bb14-f903/tplx;538b-6566/~tNl, PASS, Ipv4 +/examples/wireless/lan80211ac/, -f omnetpp.ini -c Ping1 -r 0, 100s, 8180-0d11/tplx;a5d5-2820/~tNl, PASS, Ipv4 # /examples/wireless/lan80211ac/, -f omnetpp.ini -c Ping2 -r 0, ---100s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND;0000-0000/tyf, PASS, # [Config Ping2] # __interactive__ /examples/wireless/layered80211/, -f omnetpp.ini -c LayeredCompliant80211Ping -r 0, 100s, 88dd-4b30/tplx;6264-75f5/~tNl;77ce-e367/~tND;7a22-c289/tyf, PASS, wireless Ipv4 diff --git a/tests/fingerprint/showcases.csv b/tests/fingerprint/showcases.csv index ef401f4edb1..a65ae8506ca 100644 --- a/tests/fingerprint/showcases.csv +++ b/tests/fingerprint/showcases.csv @@ -6,7 +6,7 @@ # /showcases/general/dynamic/, -f omnetpp.ini -c General -r 0, 100s, aa91-2c95/tplx;0000-0000/~tNl;0000-0000/~tND, PASS, -/showcases/general/pcaprecording/, -f omnetpp.ini -c PcapRecording -r 0, 10s, 40e0-9ac8/tplx;96d0-d13f/~tNl;e062-e9c4/~tND;588a-7dd9/tyf, PASS, wireless EthernetMac Ipv4 +/showcases/general/pcaprecording/, -f omnetpp.ini -c PcapRecording -r 0, 10s, a1b9-ff80/tplx;cb8f-443a/~tNl;63a8-4d3e/~tND;588a-7dd9/tyf, PASS, wireless EthernetMac Ipv4 /showcases/measurement/datarate/, -f omnetpp.ini -c General -r 0, 1s, 7af9-93b4/tplx;2d48-cf54/~tNl;ce00-3055/~tND;c848-3d2a/tyf, PASS, Ipv4 /showcases/measurement/endtoenddelay/, -f omnetpp.ini -c General -r 0, 1s, a6ec-9c0d/tplx;5632-17e6/~tNl;3b24-2f2d/~tND;1479-9b89/tyf, PASS, Ipv4 @@ -127,8 +127,8 @@ /showcases/visualizer/canvas/ieee80211/, -f omnetpp.ini -c VisualizingHandover -r 0, 250s, 0f01-f016/tplx;6ded-d407/~tNl;3268-55d7/~tND;07bb-3973/tyf, PASS, wireless /showcases/visualizer/canvas/ieee80211/, -f omnetpp.ini -c SignalLevels -r 0, 100s, 8782-6f1c/tplx;7a73-4da5/~tNl;483a-1ff7/~tND;c099-e5c5/tyf, PASS, Ipv4 -/showcases/visualizer/canvas/submoduleinfo/, -f omnetpp.ini -c PacketCounts -r 0, 5s, 271c-6821/tplx;3c0c-db82/~tNl;d4d4-a4d5/~tND;b145-6576/tyf, PASS, wireless Ipv4 # VisualizingSubmoduleInformation extended -/showcases/visualizer/canvas/submoduleinfo/, -f omnetpp.ini -c MACStates -r 0, 5s, 271c-6821/tplx;3c0c-db82/~tNl;d4d4-a4d5/~tND;4677-fc97/tyf, PASS, wireless Ipv4 +/showcases/visualizer/canvas/submoduleinfo/, -f omnetpp.ini -c PacketCounts -r 0, 5s, e700-27fb/tplx;f16c-28a3/~tNl;a630-063d/~tND;b145-6576/tyf, PASS, wireless Ipv4 # VisualizingSubmoduleInformation extended +/showcases/visualizer/canvas/submoduleinfo/, -f omnetpp.ini -c MACStates -r 0, 5s, e700-27fb/tplx;f16c-28a3/~tNl;a630-063d/~tND;4677-fc97/tyf, PASS, wireless Ipv4 /showcases/visualizer/canvas/instrumentfigures/, -f omnetpp.ini -c General -r 0, 3s, 6622-adb9/tplx;28ca-9f8c/~tNl;adfa-1bdd/~tND;4266-0d42/tyf, PASS, wireless Ipv4 @@ -177,7 +177,7 @@ /showcases/visualizer/canvas/spectrum/, -f omnetpp.ini -c PowerDensityMap -r 0, 1s, 2622-bc42/tplx;6db5-38b6/~tNl;45da-c896/~tND;88d9-8b3f/tyf, PASS, wireless Ipv4 /showcases/visualizer/canvas/statistic/, -f omnetpp.ini -c PingRtt -r 0, 500s, 1479-6398/tplx;9b09-5004/~tNl;c483-33cf/~tND;71d6-de16/tyf, PASS, wireless Ipv4 -/showcases/visualizer/canvas/statistic/, -f omnetpp.ini -c PacketErrorRate -r 0, 25s, eb0f-ff32/tplx;025d-a600/~tNl;d957-cacd/~tND;0b84-e1e1/tyf, PASS, wireless Ipv4 +/showcases/visualizer/canvas/statistic/, -f omnetpp.ini -c PacketErrorRate -r 0, 25s, 78cf-be58/tplx;a1d6-4bb6/~tNl;c537-1526/~tND;0b84-e1e1/tyf, PASS, wireless Ipv4 /showcases/visualizer/canvas/styling/, -f omnetpp.ini -c Line -r 0, 25s, cb1d-a154/tplx;a565-1a98/~tNl;6950-9c50/~tND;7b44-2fb8/tyf, PASS, wireless Ipv4 /showcases/visualizer/canvas/styling/, -f omnetpp.ini -c Font -r 0, 25s, cb1d-a154/tplx;a565-1a98/~tNl;6950-9c50/~tND;5e90-6a92/tyf, PASS, wireless Ipv4 @@ -196,7 +196,7 @@ /showcases/wireless/aggregation/, -f omnetpp.ini -c VoicePriorityAggregation -r 0, 1s, 1dbe-6672/tplx;a253-dbe3/~tNl;d167-ba3e/~tND;bd88-5ba6/tyf, PASS, wireless Ipv4 /showcases/wireless/analogmodel/, -f omnetpp.ini -c Routing -r 0, 5s, 56b0-3510/tplx;baeb-0d2c/~tNl;378f-fb30/~tND;00a2-e4ee/tyf, PASS, wireless Ipv4 -/showcases/wireless/analogmodel/, -f omnetpp.ini -c Distance -r 0, 2.5s, 1e75-270e/tplx;6d7a-d84c/~tNl;b380-6cd5/~tND;5575-fd8f/tyf, PASS, wireless Ipv4 +/showcases/wireless/analogmodel/, -f omnetpp.ini -c Distance -r 0, 2.5s, 98d8-d5d1/tplx;ebe5-7cf5/~tNl;56b4-f09b/~tND;5575-fd8f/tyf, PASS, wireless Ipv4 /showcases/wireless/analogmodel/, -f omnetpp.ini -c Noise -r 0, 0.1s, dd08-a63c/tplx;e167-9c84/~tNl;643d-41a6/~tND;0d1f-df73/tyf, PASS, wireless Ipv4 /showcases/wireless/blockack/, -f omnetpp.ini -c NoFragmentation -r 0, 1s, aa2d-5d35/tplx;2094-1f2a/~tNl;1470-1e1b/~tND, PASS, wireless Ipv4 @@ -311,10 +311,10 @@ /showcases/wireless/pathloss/, -f omnetpp.ini -c General -r 0, 100s, 171a-e6eb/tplx;3466-822d/~tNl;5909-6e87/~tND;929d-03d5/tyf, PASS, wireless Ipv4 -/showcases/wireless/power/, -f omnetpp.ini -c General -r 0, 100s, 498f-b665/tplx;6f50-5caf/~tNl;0ad4-1089/~tND, PASS, wireless Ipv4 +/showcases/wireless/power/, -f omnetpp.ini -c General -r 0, 100s, 21cb-ec85/tplx;30b0-ee3e/~tNl;f19f-589f/~tND, PASS, wireless Ipv4 -/showcases/wireless/qos/, -f omnetpp.ini -c NonQos -r 0, 10s, 37fd-5401/tplx;92f7-198c/~tNl;955c-ee96/~tND;093e-1ca4/tyf, PASS, wireless Ipv4 -/showcases/wireless/qos/, -f omnetpp.ini -c Qos -r 0, 10s, 1a49-72b3/tplx;e605-d79d/~tNl;10fc-7bb0/~tND;bda9-15d1/tyf, PASS, wireless Ipv4 +/showcases/wireless/qos/, -f omnetpp.ini -c NonQos -r 0, 10s, 21a9-2c4f/tplx;7734-7809/~tNl;8a89-483d/~tND;093e-1ca4/tyf, PASS, wireless Ipv4 +/showcases/wireless/qos/, -f omnetpp.ini -c Qos -r 0, 10s, e96b-3834/tplx;b711-4657/~tNl;a1dc-bbc5/~tND;bda9-15d1/tyf, PASS, wireless Ipv4 /showcases/wireless/ratecontrol/, -f omnetpp.ini -c NoRateControl -r 0, 14s, 7ee9-503a/tplx;0816-e58f/~tNl;648e-6e84/~tND;dad3-7f89/tyf, PASS, wireless Ipv4 /showcases/wireless/ratecontrol/, -f omnetpp.ini -c AarfRateControl -r 0, 12s, a7bc-05bb/tplx;9de0-4dd3/~tNl;1209-101b/~tND;7539-d32d/tyf, PASS, wireless Ipv4 diff --git a/tests/fingerprint/store.json b/tests/fingerprint/store.json index 321b6b5c23a..11d89adc409 100644 --- a/tests/fingerprint/store.json +++ b/tests/fingerprint/store.json @@ -16771,7 +16771,7 @@ "sim_time_limit": "20s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "ec17-5cc2", + "fingerprint": "a83a-1ece", "timestamp": 1681992800.3349502, "itervars": "$repetition==0" }, @@ -16783,7 +16783,7 @@ "sim_time_limit": "20s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "55f5-0894", + "fingerprint": "f1d9-19c4", "timestamp": 1681992800.3352785, "itervars": "$repetition==0" }, @@ -16795,7 +16795,7 @@ "sim_time_limit": "20s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "85a0-51b8", + "fingerprint": "c2bd-7372", "timestamp": 1681992800.3365238, "itervars": "$repetition==0" }, @@ -16819,7 +16819,7 @@ "sim_time_limit": "20s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "c07a-44e1", + "fingerprint": "fc98-08d6", "timestamp": 1681992800.3368874, "itervars": "$repetition==0" }, @@ -35623,7 +35623,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "bb14-f903", + "fingerprint": "8180-0d11", "timestamp": 1681992800.58698, "itervars": "$repetition==0" }, @@ -35635,7 +35635,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "1108-9845", + "fingerprint": "a5d5-2820", "timestamp": 1681992800.5873275, "itervars": "$repetition==0" }, @@ -38179,7 +38179,7 @@ "sim_time_limit": "10s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "40e0-9ac8", + "fingerprint": "a1b9-ff80", "timestamp": 1681992799.492428, "itervars": "$repetition==0" }, @@ -38203,7 +38203,7 @@ "sim_time_limit": "10s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "87f0-5b5f", + "fingerprint": "63a8-4d3e", "timestamp": 1681992799.4924438, "itervars": "$repetition==0" }, @@ -38215,7 +38215,7 @@ "sim_time_limit": "10s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "96d0-d13f", + "fingerprint": "cb8f-443a", "timestamp": 1681992799.4924366, "itervars": "$repetition==0" }, @@ -43147,7 +43147,7 @@ "sim_time_limit": "25s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "eb0f-ff32", + "fingerprint": "78cf-be58", "timestamp": 1681992799.6480415, "itervars": "$repetition==0" }, @@ -43171,7 +43171,7 @@ "sim_time_limit": "25s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "8a0d-832f", + "fingerprint": "c537-1526", "timestamp": 1681992799.6481428, "itervars": "$repetition==0" }, @@ -43183,7 +43183,7 @@ "sim_time_limit": "25s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "025d-a600", + "fingerprint": "a1d6-4bb6", "timestamp": 1681992799.6480927, "itervars": "$repetition==0" }, @@ -43435,7 +43435,7 @@ "sim_time_limit": "5s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "271c-6821", + "fingerprint": "e700-27fb", "timestamp": 1681992799.630568, "itervars": "$repetition==0" }, @@ -43459,7 +43459,7 @@ "sim_time_limit": "5s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "32ff-eec4", + "fingerprint": "a630-063d", "timestamp": 1681992799.6306372, "itervars": "$repetition==0" }, @@ -43471,7 +43471,7 @@ "sim_time_limit": "5s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "3c0c-db82", + "fingerprint": "f16c-28a3", "timestamp": 1681992799.6306028, "itervars": "$repetition==0" }, @@ -43483,7 +43483,7 @@ "sim_time_limit": "5s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "271c-6821", + "fingerprint": "e700-27fb", "timestamp": 1681992799.6302016, "itervars": "$repetition==0" }, @@ -43507,7 +43507,7 @@ "sim_time_limit": "5s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "32ff-eec4", + "fingerprint": "a630-063d", "timestamp": 1681992799.6302688, "itervars": "$repetition==0" }, @@ -43519,7 +43519,7 @@ "sim_time_limit": "5s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "3c0c-db82", + "fingerprint": "f16c-28a3", "timestamp": 1681992799.6302357, "itervars": "$repetition==0" }, @@ -44251,7 +44251,7 @@ "sim_time_limit": "2.5s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "1e75-270e", + "fingerprint": "98d8-d5d1", "timestamp": 1681992799.6558988, "itervars": "$repetition==0" }, @@ -44275,7 +44275,7 @@ "sim_time_limit": "2.5s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "9ccb-a57b", + "fingerprint": "56b4-f09b", "timestamp": 1681992799.6560135, "itervars": "$repetition==0" }, @@ -44287,7 +44287,7 @@ "sim_time_limit": "2.5s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "6d7a-d84c", + "fingerprint": "ebe5-7cf5", "timestamp": 1681992799.6559565, "itervars": "$repetition==0" }, @@ -46267,7 +46267,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "498f-b665", + "fingerprint": "21cb-ec85", "timestamp": 1681992799.6967368, "itervars": "$repetition==0" }, @@ -46279,7 +46279,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "fb79-0fa3", + "fingerprint": "f19f-589f", "timestamp": 1681992799.6968684, "itervars": "$repetition==0" }, @@ -46291,7 +46291,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "6f50-5caf", + "fingerprint": "30b0-ee3e", "timestamp": 1681992799.696803, "itervars": "$repetition==0" }, @@ -46303,7 +46303,7 @@ "sim_time_limit": "10s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "37fd-5401", + "fingerprint": "21a9-2c4f", "timestamp": 1681992799.6971657, "itervars": "$repetition==0" }, @@ -46327,7 +46327,7 @@ "sim_time_limit": "10s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "eb84-1083", + "fingerprint": "8a89-483d", "timestamp": 1681992799.697302, "itervars": "$repetition==0" }, @@ -46339,7 +46339,7 @@ "sim_time_limit": "10s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "1061-2f07", + "fingerprint": "7734-7809", "timestamp": 1681992799.6972342, "itervars": "$repetition==0" }, @@ -46351,7 +46351,7 @@ "sim_time_limit": "10s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "1a49-72b3", + "fingerprint": "e96b-3834", "timestamp": 1681992799.6976697, "itervars": "$repetition==0" }, @@ -46375,7 +46375,7 @@ "sim_time_limit": "10s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "83cc-bdf2", + "fingerprint": "a1dc-bbc5", "timestamp": 1681992799.697813, "itervars": "$repetition==0" }, @@ -46387,7 +46387,7 @@ "sim_time_limit": "10s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "9760-e96a", + "fingerprint": "b711-4657", "timestamp": 1681992799.6977413, "itervars": "$repetition==0" }, @@ -50035,7 +50035,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "71f5-b341", + "fingerprint": "e443-0781", "timestamp": 1681992799.813049, "itervars": "$repetition==0" }, @@ -50059,7 +50059,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "7612-50df", + "fingerprint": "0ea7-62fe", "timestamp": 1681992799.8132813, "itervars": "$repetition==0" }, @@ -50071,7 +50071,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "4a61-20ba", + "fingerprint": "cc4d-d519", "timestamp": 1681992799.8131676, "itervars": "$repetition==0" }, @@ -50803,7 +50803,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "7f72-228e", + "fingerprint": "09ce-6649", "timestamp": 1681992799.808365, "itervars": "$repetition==0" }, @@ -50827,7 +50827,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "c423-d956", + "fingerprint": "1f64-df13", "timestamp": 1681992799.8087497, "itervars": "$repetition==0" }, @@ -50839,7 +50839,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "1374-346a", + "fingerprint": "77af-bd15", "timestamp": 1681992799.8085785, "itervars": "$repetition==0" }, diff --git a/tests/fingerprint/tutorials.csv b/tests/fingerprint/tutorials.csv index 1a1b09eb160..4c9e16ffa2c 100644 --- a/tests/fingerprint/tutorials.csv +++ b/tests/fingerprint/tutorials.csv @@ -12,10 +12,10 @@ /tutorials/configurator/, -f omnetpp.ini -c Step7C -r 0, 500s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND;0000-0000/tyf, PASS, /tutorials/configurator/, -f omnetpp.ini -c Step8A -r 0, 100s, f99c-9e43/tplx;e810-af2b/~tNl;8f87-ee38/~tND;d3f3-ed4c/tyf, PASS, wireless EthernetMac Ipv4 # Step8A extended /tutorials/configurator/, -f omnetpp.ini -c Step8B -r 0, 100s, 6daa-17f7/tplx;e810-af2b/~tNl;e839-e562/~tND;af16-1150/tyf, PASS, wireless EthernetMac Ipv4 -/tutorials/configurator/, -f omnetpp.ini -c Step9 -r 0, 100s, 7f72-228e/tplx;44d7-fbf3/~tNl;48a0-6adb/~tND;c38c-98ef/tyf, PASS, wireless EthernetMac Ipv4 +/tutorials/configurator/, -f omnetpp.ini -c Step9 -r 0, 100s, 09ce-6649/tplx;77af-bd15/~tNl;1f64-df13/~tND;c38c-98ef/tyf, PASS, wireless EthernetMac Ipv4 /tutorials/configurator/, -f omnetpp.ini -c Step10A -r 0, 100s, 4b6f-7cd9/tplx;0000-0000/~tNl;0000-0000/~tND;095d-75bd/tyf, PASS, wireless # Step10A extended /tutorials/configurator/, -f omnetpp.ini -c Step10B -r 0, 100s, 4b6f-7cd9/tplx;0000-0000/~tNl;0000-0000/~tND;4d60-fcf9/tyf, PASS, wireless # Step10B extended -/tutorials/configurator/, -f omnetpp.ini -c Step10C -r 0, 100s, 71f5-b341/tplx;4a61-20ba/~tNl;c614-3445/~tND;f078-56fe/tyf, PASS, wireless Ipv4 +/tutorials/configurator/, -f omnetpp.ini -c Step10C -r 0, 100s, e443-0781/tplx;cc4d-d519/~tNl;0ea7-62fe/~tND;f078-56fe/tyf, PASS, wireless Ipv4 /tutorials/configurator/, -f omnetpp.ini -c Step11A -r 0, 500s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND;0000-0000/tyf, PASS, /tutorials/configurator/, -f omnetpp.ini -c Step11B -r 0, 500s, 49cf-d241/tplx;725c-f075/~tNl;15a3-3f79/~tND;7082-ff49/tyf, PASS, EthernetMac Ipv4 /tutorials/configurator/, -f omnetpp.ini -c Step12 -r 0, 100s, a186-bbc1/tplx;7370-3135/~tNl;1438-e824/~tND;4b03-e5a1/tyf, PASS, wireless EthernetMac Ipv4 diff --git a/tests/module/Ieee80211ContentionModeSet_1.test b/tests/module/Ieee80211ContentionModeSet_1.test new file mode 100644 index 00000000000..7a80b0c3140 --- /dev/null +++ b/tests/module/Ieee80211ContentionModeSet_1.test @@ -0,0 +1,210 @@ +%description: +Runtime radio mode-set changes refresh active DCF/EDCA contention, retaining +whole remaining slots without another random draw. Cover IFS, backoff, DEFER, +EIFS and unchanged timing, and observe the actual channel grant time. + +%file: ContentionModeSet.cc +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211OfdmMode.h" +#include "inet/linklayer/ieee80211/mac/contention/Contention.h" +#include "inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h" +#include "inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h" +#include "inet/linklayer/ieee80211/mac/contract/IChannelAccess.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" + +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +class BoundedCwMode : public Ieee80211OfdmMode +{ + int upper; + public: + BoundedCwMode(int upper) : Ieee80211OfdmMode(*check_and_cast(Ieee80211ModeSet::getModeSet("a")->getMode(Mbps(6)))), upper(upper) {} + virtual int getLegacyCwMin() const override { return 127; } + virtual int getLegacyCwMax() const override { return upper; } +}; + +class TimingProbeContention : public Contention +{ + public: + int slots() const { return backoffSlots; } + State state() const { return static_cast(fsm.getState()); } + simtime_t slot() const { return slotTime; } + simtime_t interframe() const { return ifs; } + simtime_t extendedInterframe() const { return eifs; } + simtime_t grantTime() const { return startTxEvent->getArrivalTime(); } +}; +Define_Module(TimingProbeContention); + +class ContentionModeSetTest : public cSimpleModule, public IChannelAccess::ICallback, public cListener +{ + TimingProbeContention *contention = nullptr; + IChannelAccess *access = nullptr; + cModule *accessModule = nullptr; + + int currentCw() { return par("qos").boolValue() ? check_and_cast(accessModule)->getCw() : check_and_cast(accessModule)->getCw(); } + void incrementCw() + { + if (par("qos").boolValue()) + check_and_cast(accessModule)->incrementCw(); + else + check_and_cast(accessModule)->incrementCw(); + } + Ieee80211Radio *radio = nullptr; + simtime_t expectedGrant; + simtime_t previousGrant; + int initialSlots = 0; + int draws = 0; + bool switched = false; + int modeNotifications = 0; + std::string phase; + + protected: + virtual void initialize() override { scheduleAt(SimTime(1, SIMTIME_US), new cMessage("start")); } + virtual void receiveSignal(cComponent *, simsignal_t, intval_t, cObject *) override { draws++; } + virtual void receiveSignal(cComponent *source, simsignal_t, cObject *value, cObject *) override + { + Enter_Method_Silent(); + ASSERT(source == radio->getParentModule()); + auto target = check_and_cast(value); + ASSERT(contention->slot() == target->getSlotTime()); + ASSERT(draws == 1); + if (contention->state() == Contention::IFS_AND_BACKOFF) { + auto interval = phase == "eifs" ? contention->extendedInterframe() : contention->interframe(); + auto expected = phase == "unchanged" ? previousGrant : simTime() + interval + contention->slots() * target->getSlotTime(); + ASSERT(contention->grantTime() == expected); + } + modeNotifications++; + } + virtual void handleMessage(cMessage *message) override + { + delete message; + if (contention == nullptr) { + phase = par("phase").stdstringValue(); + auto nic = getParentModule()->getSubmodule("ap")->getSubmodule("wlan", 0); + radio = check_and_cast(nic->getSubmodule("radio")); + nic->subscribe(modesetChangedSignal, this); + accessModule = nic->getSubmodule("mac")->getModuleByPath(par("qos").boolValue() ? ".hcf.edca.edcaf[1]" : ".dcf.channelAccess"); + access = check_and_cast(accessModule); + contention = check_and_cast(accessModule->getSubmodule("contention")); + contention->subscribe(IContention::backoffPeriodGeneratedSignal, this); + contention->mediumStateChanged(phase != "defer"); + ASSERT(currentCw() == 31); + incrementCw(); + ASSERT(currentCw() == 63); + access->requestChannel(this); + initialSlots = contention->slots(); + ASSERT(initialSlots >= 2); + ASSERT(draws == 1); + if (phase == "eifs") + contention->corruptedFrameReceived(); + previousGrant = contention->grantTime(); + simtime_t delay = SimTime(1, SIMTIME_US); + if (phase == "backoff") + delay = contention->interframe() + contention->slot() * 1.5; + scheduleAfter(delay, new cMessage("switch")); + } + else { + ASSERT(!switched); + ASSERT(contention->state() == (phase == "defer" ? Contention::DEFER : Contention::IFS_AND_BACKOFF)); + const auto *target = Ieee80211ModeSet::getModeSet(phase == "unchanged" ? "g(mixed)" : "g(erp)"); + radio->setModeSetAndMode(target, target->getMode(Mbps(24))); + ASSERT(currentCw() == 63); + ASSERT(modeNotifications == 1); + ASSERT(contention->slot() == target->getSlotTime()); + ASSERT(contention->interframe() == target->getSifsTime() + (par("qos").boolValue() ? 3 : 2) * target->getSlotTime()); + ASSERT(contention->slots() == initialSlots - (phase == "backoff" ? 1 : 0)); + ASSERT(draws == 1); + if (phase == "defer") + contention->mediumStateChanged(true); + auto interval = phase == "eifs" ? contention->extendedInterframe() : contention->interframe(); + expectedGrant = phase == "unchanged" ? previousGrant : simTime() + interval + contention->slots() * target->getSlotTime(); + ASSERT(contention->grantTime() == expectedGrant); + switched = true; + } + } + virtual void channelGranted(IChannelAccess *channelAccess) override + { + Enter_Method_Silent(); + ASSERT(switched); + ASSERT(channelAccess == access); + ASSERT(simTime() == expectedGrant); + ASSERT(draws == 1); + access->releaseChannel(this); + contention->unsubscribe(IContention::backoffPeriodGeneratedSignal, this); + radio->getParentModule()->unsubscribe(modesetChangedSignal, this); + // Exercise both clamp boundaries after retry-window growth. + auto participant = check_and_cast(accessModule); + static BoundedCwMode lowerMode(1023), upperMode(127); + static Ieee80211ModeSet lowerSet("lower bound", {{true, &lowerMode, true}}, &lowerMode, Ieee80211ModeSet::getModeSet("a")->getPhyType()); + static Ieee80211ModeSet upperSet("upper bound", {{true, &upperMode, true}}, &upperMode, Ieee80211ModeSet::getModeSet("a")->getPhyType()); + participant->applyModeSet(&lowerSet); + ASSERT(currentCw() == 127); + incrementCw(); + ASSERT(currentCw() == 255); + participant->applyModeSet(&upperSet); + ASSERT(currentCw() == 127); + std::cout << "Verified contention phase=" << phase << " qos=" << par("qos").boolValue() << "\n"; + endSimulation(); + } +}; +Define_Module(ContentionModeSetTest); + +%file: test.ned +import inet.linklayer.ieee80211.mac.contention.Contention; +import inet.node.wireless.AccessPoint; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; +simple TimingProbeContention extends Contention +{ + @class(::TimingProbeContention); +} +simple ContentionModeSetTest +{ + parameters: + string phase; + bool qos; + @class(::ContentionModeSetTest); +} +network ContentionModeSetNetwork +{ + submodules: + radioMedium: Ieee80211ScalarRadioMedium; + ap: AccessPoint; + test: ContentionModeSetTest; +} + +%inifile: omnetpp.ini +[General] +network = ContentionModeSetNetwork +ned-path = .;../../../../src;../../lib +sim-time-limit = 10ms +seed-set = 0 +cmdenv-express-mode = false +record-vector-results = false +record-scalar-results = false +*.test.phase = ${"ifs","backoff","defer","eifs","unchanged"} +*.test.qos = ${qos=false,true} +**.mobility.initFromDisplayString = false +**.mobility.initialX = 10m +**.mobility.initialY = 10m +**.wlan[*].opMode = "g(mixed)" +**.wlan[*].bitrate = 24Mbps +**.wlan[*].radio.bandName = "2.4 GHz" +**.wlan[*].radio.channelNumber = 0 +**.wlan[*].radio.transmitter.power = 100mW +**.wlan[*].mac.qosStation = ${qos} +**.mgmt.ssid = "contention" +**.mgmt.beaconInterval = 1s +**.contention.typename = "TimingProbeContention" +**.contention.backoffOptimization = false +**.cwMin = -1 +**.cwMax = -1 + +%file: check_results.py +from pathlib import Path +text = Path("test.out").read_text() +for phase in ("ifs", "backoff", "defer", "eifs", "unchanged"): + for qos in (0, 1): + assert text.count(f"Verified contention phase={phase} qos={qos}") == 1, (phase, qos) + +%postrun-command: python3 check_results.py diff --git a/tests/module/Ieee80211MgmtApChannelChange_1.test b/tests/module/Ieee80211MgmtApChannelChange_1.test index da3f7bd1429..021fb4f81bb 100644 --- a/tests/module/Ieee80211MgmtApChannelChange_1.test +++ b/tests/module/Ieee80211MgmtApChannelChange_1.test @@ -239,6 +239,19 @@ class Ieee80211MgmtApChannelChangeTest : public cSimpleModule ASSERT(selectedMode->getDataMode()->getBandwidth() == MHz(20)); std::cout << "Dynamic channel change to 11 downgraded to 20 MHz and updated peer state.\n"; + // Reapplying HT must not undo the band-derived fallback, including in + // existing peer state. Exercise the real coordinated radio setter. + auto generation = peerState->generation; + radio->setModeSetAndMode(modeSet, modeSet->getMode(Mbps(24))); + ASSERT(mib->getHtOperation().secondaryChannelOffset == 0); + ASSERT(mib->getHtOperation().operatingChannelWidth == MHz(20)); + peerState = mib->findPeerHtState(staAddress); + ASSERT(peerState->generation == generation + 1); + ASSERT(peerState->negotiatedCapabilities.operation.operatingChannelWidth == MHz(20)); + mgmt->emitBeaconNow(); + ASSERT(!mgmt->sentBeacons.back()->getHtOperation().staChannelWidth40Mhz); + std::cout << "HT mode refresh preserves edge-channel fallback and peer state.\n"; + // 4. Dynamic runtime channel change back to channel index 0 (std channel 1) radio->setChannelNumber(0); @@ -337,6 +350,21 @@ class Ieee80211MgmtApChannelChangeTest : public cSimpleModule std::cout << "Dynamic band change revalidated against new active band.\n"; + // Legacy-to-HT on an edge channel must also apply the configured + // secondary-channel policy before advertising HT operation again. + auto legacy = physicallayer::Ieee80211ModeSet::getModeSet("g(mixed)"); + radio->setModeSetAndMode(legacy, legacy->getMode(Mbps(24))); + radio->setChannelNumber(2); // last channel in the three-channel test band + radio->setModeSetAndMode(modeSet, modeSet->getMode(Mbps(24))); + ASSERT(mib->getHtOperation().primaryChannel == 2); + ASSERT(mib->getHtOperation().secondaryChannelOffset == 0); + ASSERT(mib->getHtOperation().operatingChannelWidth == MHz(20)); + mgmt->emitBeaconNow(); + ASSERT(mgmt->sentBeacons.back()->getHtOperation().primaryChannel == 44); + ASSERT(!mgmt->sentBeacons.back()->getHtOperation().staChannelWidth40Mhz); + radio->setChannelNumber(0); + std::cout << "Legacy-to-HT switch validates the active band before advertising.\n"; + // 7. Band notifications may arrive during initialization before the MAC // publishes whether HT operation is supported. A legacy/non-HT AP must // retain the radio's channel without applying HT-only band validation. @@ -424,7 +452,7 @@ record-scalar-results = false *.ap.wlan[0].radio.transmitter.typename = "TestHt40Transmitter" *.ap.wlan[0].radio.receiver.typename = "TestHt40Receiver" **.wlan[*].opMode = "n(mixed-2.4Ghz)" -**.wlan[*].bitrate = 65Mbps +**.wlan[*].bitrate = 24Mbps **.wlan[*].radio.bandName = "2.4 GHz" **.wlan[*].radio.centerFrequency = 2.4GHz **.mobility.initFromDisplayString = false @@ -457,3 +485,9 @@ Dynamic band change revalidated against new active band. %contains: stdout Non-HT band notification deferred HT-only validation. + +%contains: stdout +HT mode refresh preserves edge-channel fallback and peer state. + +%contains: stdout +Legacy-to-HT switch validates the active band before advertising. diff --git a/tests/module/Ieee80211MgmtApUnavailableChannel_1.test b/tests/module/Ieee80211MgmtApUnavailableChannel_1.test index 5db13b43fb1..e0782ca279f 100644 --- a/tests/module/Ieee80211MgmtApUnavailableChannel_1.test +++ b/tests/module/Ieee80211MgmtApUnavailableChannel_1.test @@ -1,8 +1,8 @@ %description: An HT-capable AP must not serialize a management frame before its radio has published a valid primary channel. A radio channel of -1 suppresses the -initial channel signal, so the first beacon deterministically reports the -missing MIB-owned channel instead of serializing an invalid value. +initial channel signal, so initial mode-set application reports the missing +MIB-owned channel before a notification or Beacon can expose invalid state. %file: test.ned 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/Ieee80211ModeSetFailure_1.test b/tests/module/Ieee80211ModeSetFailure_1.test new file mode 100644 index 00000000000..ba5be7dc502 --- /dev/null +++ b/tests/module/Ieee80211ModeSetFailure_1.test @@ -0,0 +1,126 @@ +%description: +Invalid fixed rates in either selector and exceptions in either radio notification +terminate the simulation. Both radio setters are exercised in independent runs; +no run catches an error or continues using partially updated state. A band +without standard channel mapping also fails through the real radio setter. Reentrant +mode-set changes are rejected while the new state is being published. + +%file: ModeSetFailure.cc +#include "inet/common/Simsignals.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Band.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" + +using namespace inet; +using namespace inet::physicallayer; + +class ModeSetFailure : public cSimpleModule, public cListener +{ + protected: + virtual void initialize() override { scheduleAt(SimTime(1, SIMTIME_US), new cMessage("switch")); } + virtual void receiveSignal(cComponent *source, simsignal_t signal, cObject *value, cObject *details) override + { + Enter_Method_Silent(); + if (par("failure").stdstringValue() == "detach") + check_and_cast(source)->unregisterModeSetConsumer(check_and_cast(source)->getSubmodule("mac")); + if (par("failure").stdstringValue() == "reentrant") + check_and_cast(check_and_cast(source)->getSubmodule("radio"))->setModeSet(check_and_cast(value)); + throw cRuntimeError("test observer rejects mode set"); + } + virtual void receiveSignal(cComponent *source, simsignal_t signal, intval_t value, cObject *details) override + { + Enter_Method_Silent(); + throw cRuntimeError("test observer rejects listening change"); + } + virtual void handleMessage(cMessage *message) override + { + delete message; + auto radio = check_and_cast(getParentModule()->getSubmodule("ap")->getSubmodule("wlan", 0)->getSubmodule("radio")); + auto ht = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"); + auto legacy = Ieee80211ModeSet::getModeSet("g(mixed)"); + radio->setMode(ht->getMode(Mbps(24))); + auto failure = par("failure").stdstringValue(); + if (failure == "modeset" || failure == "reentrant" || failure == "detach") + radio->getParentModule()->subscribe(modesetChangedSignal, this); + else if (failure == "listening") + radio->subscribe(IRadio::listeningChangedSignal, this); + std::cout << "Attempting " << failure << " explicit=" << par("explicitMode").boolValue() << "\n"; + if (failure == "band") { + radio->setBand(&Ieee80211CompliantBands::band5GHz); + throw cRuntimeError("UNEXPECTED successful band change"); + } + if (par("explicitMode")) + radio->setModeSetAndMode(legacy, legacy->getMode(Mbps(24))); + else + radio->setModeSet(legacy); + throw cRuntimeError("UNEXPECTED successful mode-set change"); + } +}; +Define_Module(ModeSetFailure); + +%file: test.ned +import inet.node.wireless.AccessPoint; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; + +simple ModeSetFailure +{ + parameters: + @class(::ModeSetFailure); + string failure; + bool explicitMode; +} +network ModeSetFailureNetwork +{ + submodules: + radioMedium: Ieee80211ScalarRadioMedium; + ap: AccessPoint; + test: ModeSetFailure; +} + +%inifile: omnetpp.ini +[General] +network = ModeSetFailureNetwork +ned-path = .;../../../../src;../../lib +sim-time-limit = 1ms +seed-set = 0 +cmdenv-express-mode = false +cmdenv-stop-batch-on-error = false +record-vector-results = false +record-scalar-results = false +*.test.failure = ${failure="dcf","hcf","modeset","listening","reentrant","band","detach"} +*.test.explicitMode = ${false,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 +**.mgmt.ssid = "failure" +**.mgmt.beaconInterval = 1s +**.mac.dcf.rateSelection.dataFrameBitrate = ${failure} == "dcf" ? 65Mbps : -1bps +**.mac.hcf.rateSelection.dataFrameBitrate = ${failure} == "hcf" ? 65Mbps : -1bps +**.rateSelection.dataFrameBandwidth = 20MHz +**.rateSelection.dataFrameNumSpatialStreams = 1 +**.rateSelection.dataFrameGuardInterval = 800ns + +%exitcode: 1 + +%file: check_errors.py +from pathlib import Path +text = Path("test.out").read_text() + Path("test.err").read_text() +for failure in ("dcf", "hcf", "modeset", "listening", "reentrant", "band", "detach"): + for explicit in (0, 1): + assert text.count(f"Attempting {failure} explicit={explicit}") == 1 +errors = [line for line in text.splitlines() if " Error:" in line] +assert len(errors) == 14, errors +assert sum("Unknown mode for bitrate" in line for line in errors) == 4, errors +assert sum("test observer rejects mode set" in line for line in errors) == 2, errors +assert sum("test observer rejects listening change" in line for line in errors) == 2, errors +assert sum("Reentrant radio mode-set change" in line for line in errors) == 2, errors +assert sum("Invalid primary channel" in line for line in errors) == 2, errors +assert sum("Cannot detach a mode-set consumer during a transition" in line for line in errors) == 2, errors +assert "UNEXPECTED successful" not in text + +%postrun-command: python3 check_errors.py diff --git a/tests/module/Ieee80211ModeSetRegistration_1.test b/tests/module/Ieee80211ModeSetRegistration_1.test new file mode 100644 index 00000000000..9b278e18f1f --- /dev/null +++ b/tests/module/Ieee80211ModeSetRegistration_1.test @@ -0,0 +1,232 @@ +%description: +Explicit mode-set registration is independent of observer subscriptions and registration +order. The interface publishes once after initialization and after each runtime change, +with MAC state already applied. Cover interface isolation, duplicate registration, +unregistration/deletion, and rejection of late and cross-interface registration. + +%file: Registration.cc +#include "inet/physicallayer/wireless/generic/GenericRadio.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Band.h" +#include "inet/common/ModuleAccess.h" +#include "inet/networklayer/common/NetworkInterface.h" +#include "inet/common/Simsignals.h" +#include "inet/linklayer/ieee80211/mib/Ieee80211Mib.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.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; + +// A capability provider with generic PHY roles proves that MAC/management use +// the radio contract, not concrete IEEE transmitter/receiver implementations. +class CapabilityRadio : public GenericRadio, public IIeee80211Radio +{ + Ieee80211Channel channel{&Ieee80211CompliantBands::band2_4GHz, 10}; + public: + virtual bool isHtChannelWidthSupported(Hz width) const override { return width == MHz(20) || width == MHz(40); } + virtual const Ieee80211Channel *getChannel() const override { return &channel; } + protected: + virtual void initialize(int stage) override + { + GenericRadio::initialize(stage); + if (stage == INITSTAGE_PHYSICAL_LAYER) + emit(cComponent::registerSignal("radioChannelChanged"), 10L); + } +}; +Define_Module(CapabilityRadio); + +class RegisteredConsumer : public cSimpleModule, public IIeee80211ModeSetListener +{ + const Ieee80211ModeSet *modeSet = nullptr; + public: + int applications = 0; + virtual const Ieee80211ModeSet *getModeSet() const override { return modeSet; } + virtual void applyModeSet(const Ieee80211ModeSet *value) override + { + Enter_Method_Silent(); + auto nic = getContainingNicModule(this); + ASSERT(check_and_cast(nic->getSubmodule("mac"))->getModeSet() == value); + ASSERT(check_and_cast(nic->getSubmodule("mib"))->isHtOperationSupported() == value->isHtOperationSupported()); + modeSet = value; + applications++; + } + protected: + virtual int numInitStages() const override { return NUM_INIT_STAGES; } + virtual void initialize(int stage) override + { + bool late = (getIndex() == 0) == par("reverse").boolValue(); + if (stage == (late ? INITSTAGE_PHYSICAL_LAYER : INITSTAGE_LOCAL)) { + auto coordinator = check_and_cast(getContainingNicModule(this)); + bool rejected = false; + try { coordinator->registerModeSetConsumer(this, static_cast(99)); } + catch (const cRuntimeError&) { rejected = true; } + ASSERT(rejected); + coordinator->registerModeSetConsumer(this, IIeee80211ModeSetCoordinator::DERIVED_STATE); + coordinator->registerModeSetConsumer(this, IIeee80211ModeSetCoordinator::DERIVED_STATE); + } + } +}; +Define_Module(RegisteredConsumer); + +// Merely implementing the consumer interface and subscribing must never enlist it. +class UnregisteredObserver : public cListener, public IIeee80211ModeSetListener +{ + public: + int notifications = 0; + virtual const Ieee80211ModeSet *getModeSet() const override { return nullptr; } + virtual void applyModeSet(const Ieee80211ModeSet *) override { throw cRuntimeError("Observer enlisted as participant"); } + virtual void receiveSignal(cComponent *, simsignal_t, cObject *, cObject *) override { notifications++; } +}; + +class RegistrationTest : public cSimpleModule, public cListener +{ + UnregisteredObserver observer; + int notifications = 0; + RegisteredConsumer *probe(cModule *nic, int index) { return check_and_cast(nic->getSubmodule("probe", index)); } + cModule *nic(int index) { return getParentModule()->getSubmodule("ap", index)->getSubmodule("wlan", 0); } + protected: + virtual int numInitStages() const override { return NUM_INIT_STAGES; } + virtual void initialize(int stage) override + { + if (stage == INITSTAGE_LOCAL) { + getParentModule()->subscribe(modesetChangedSignal, this); + getParentModule()->subscribe(modesetChangedSignal, &observer); + } + else if (stage == INITSTAGE_LAST) + scheduleAfter(SimTime(1, SIMTIME_US), new cMessage("change")); + } + virtual void receiveSignal(cComponent *source, simsignal_t, cObject *value, cObject *) override + { + Enter_Method_Silent(); + ASSERT(source == nic(0) || source == nic(1)); + auto module = check_and_cast(source); + auto target = check_and_cast(value); + ASSERT(probe(module, 0)->getModeSet() == target); + ASSERT(probe(module, 1)->getModeSet() == target); + if (module == nic(1)) { + auto mib = check_and_cast(module->getSubmodule("mib")); + ASSERT(mib->localHtCapabilities.supportedChannelWidths.count(MHz(40)) == 1); + ASSERT(mib->requirePrimaryChannel() == 10); + ASSERT(mib->getHtOperation().operatingChannelWidth == MHz(20)); + ASSERT(mib->getHtOperation().secondaryChannelOffset == 0); + } + notifications++; + } + virtual void handleMessage(cMessage *message) override + { + delete message; + ASSERT(notifications == 2 && observer.notifications == 2); + auto first = nic(0); + auto second = nic(1); + auto radio = check_and_cast(first->getSubmodule("radio")); + auto coordinator = check_and_cast(first); + auto legacy = Ieee80211ModeSet::getModeSet("g(mixed)"); + auto ht = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"); + radio->setModeSetAndMode(legacy, legacy->getMode(Mbps(24))); + ASSERT(notifications == 3 && observer.notifications == 3); + ASSERT(probe(first, 0)->applications == 2 && probe(first, 1)->applications == 2); + ASSERT(probe(second, 0)->applications == 1 && probe(second, 1)->applications == 1); + getParentModule()->unsubscribe(modesetChangedSignal, this); + getParentModule()->unsubscribe(modesetChangedSignal, &observer); + radio->setModeSet(ht); + ASSERT(probe(first, 0)->applications == 3 && probe(first, 1)->applications == 3); + ASSERT(notifications == 3 && observer.notifications == 3); + bool rejected = false; + try { coordinator->registerModeSetConsumer(probe(first, 0), IIeee80211ModeSetCoordinator::DERIVED_STATE); } + catch (const cRuntimeError&) { rejected = true; } + ASSERT(rejected); + rejected = false; + try { coordinator->unregisterModeSetConsumer(probe(second, 0)); } + catch (const cRuntimeError&) { rejected = true; } + ASSERT(rejected); + coordinator->unregisterModeSetConsumer(probe(first, 1)); + probe(first, 1)->deleteModule(); + radio->setModeSetAndMode(legacy, legacy->getMode(Mbps(24))); + ASSERT(probe(first, 0)->applications == 4); + ASSERT(probe(second, 0)->applications == 1); + if (par("deleteRegistered").boolValue()) + probe(first, 0)->deleteModule(); // Deliberately omit unregistering. + else + coordinator->unregisterModeSetConsumer(first->getSubmodule("mgmt")); + rejected = false; + try { radio->setModeSetAndMode(ht, ht->getMode(Mbps(24))); } + catch (const cRuntimeError&) { rejected = true; } + ASSERT(rejected); + // Missing membership is rejected before the PHY is mutated. + ASSERT(check_and_cast(radio->getTransmitter())->getModeSet() == legacy); + std::cout << "Explicit registration, publication, observer neutrality, isolation and teardown verified.\n"; + endSimulation(); + } +}; +Define_Module(RegistrationTest); + +%file: test.ned +import inet.node.wireless.AccessPoint; +import inet.linklayer.ieee80211.Ieee80211Interface; +import inet.physicallayer.wireless.common.medium.UnitDiskRadioMedium; +import inet.physicallayer.wireless.generic.GenericUnitDiskRadio; +import inet.physicallayer.wireless.ieee80211.contract.packetlevel.IIeee80211Radio; +module CapabilityRadio extends GenericUnitDiskRadio like IIeee80211Radio +{ + parameters: + @class(::CapabilityRadio); +} +simple RegisteredConsumer +{ + parameters: + bool reverse; + @class(::RegisteredConsumer); +} +module RegisteredInterface extends Ieee80211Interface +{ + submodules: + probe[2]: RegisteredConsumer; +} +simple RegistrationTest +{ + parameters: + bool deleteRegistered; + @class(::RegistrationTest); +} +network RegistrationNetwork +{ + submodules: + radioMedium: UnitDiskRadioMedium; + test: RegistrationTest; + ap[2]: AccessPoint; +} + +%inifile: omnetpp.ini +[General] +network = RegistrationNetwork +ned-path = .;../../../../src;../../lib +sim-time-limit = 1ms +seed-set = 0 +cmdenv-express-mode = false +record-vector-results = false +record-scalar-results = false +*.ap[*].wlan[*].typename = "RegisteredInterface" +**.probe[*].reverse = ${false,true} +*.test.deleteRegistered = ${false,true} +**.mobility.initFromDisplayString = false +**.mobility.initialX = 10m +**.mobility.initialY = 10m +*.ap[0].wlan[*].radio.typename = "Ieee80211UnitDiskRadio" +*.ap[1].wlan[*].radio.typename = "CapabilityRadio" +*.ap[1].wlan[*].mib.htSecondaryChannelOffset = 1 +**.wlan[*].opMode = "n(mixed-2.4Ghz)" +**.wlan[*].bitrate = 24Mbps +**.wlan[*].radio.bandName = "2.4 GHz" +**.wlan[*].radio.channelNumber = 0 +**.wlan[*].radio.transmitter.power = 100mW +**.radio.transmitter.analogModel.communicationRange = 100m +**.radio.transmitter.analogModel.interferenceRange = 100m +**.radio.transmitter.analogModel.detectionRange = 100m +**.mgmt.ssid = "registration" +**.mgmt.beaconInterval = 1s + +%contains: stdout +Explicit registration, publication, observer neutrality, isolation and teardown verified. diff --git a/tests/module/Ieee80211ModeSetTransition_1.test b/tests/module/Ieee80211ModeSetTransition_1.test new file mode 100644 index 00000000000..26a64bc573a --- /dev/null +++ b/tests/module/Ieee80211ModeSetTransition_1.test @@ -0,0 +1,283 @@ +%description: +Exercise both radio mode-set setters with DCF and HCF selectors. Successful +HT/legacy transitions refresh PHY-limited capabilities, actual Beacon bodies, +peer state and adaptive rates before observers run, and notify the radio medium. + +%file: Ieee80211ModeSetTransition.cc + +#include "inet/common/Simsignals.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" +#include "inet/physicallayer/wireless/common/medium/RadioMedium.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Mac.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 +{ + int notifications = 0; + const Ieee80211ModeSet *observedModeSet = nullptr; + CachingModeSetObserver cachingObserver; + MacAddress peer = MacAddress("02:00:00:00:01:ff"); + + std::vector participants(cModule *module) + { + std::vector result; + if (auto participant = dynamic_cast(module)) + result.push_back(participant); + for (cModule::SubmoduleIterator it(module); !it.end(); ++it) { + auto nested = participants(*it); + result.insert(result.end(), nested.begin(), nested.end()); + } + 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(nic)) + 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"); + // A retry or protection-prepared frame may still carry an old HT mode. + auto staleMode = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)")->getMode(Mbps(65), MHz(20), 1, 800e-9); + packet.addTag()->setMode(staleMode); + 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, cObject *value, cObject *details) override + { + Enter_Method_Silent(); + notifications++; + auto nic = check_and_cast(source); + ASSERT(nic->getSubmodule("mac") != nullptr); + auto target = check_and_cast(value); + observedModeSet = target; + // Publication must follow *all* internal updates, irrespective of where + // this observer appears in the subscriber list. + for (auto participant : participants(nic)) + ASSERT(participant->getModeSet() == target); + ASSERT(check_and_cast(nic->getSubmodule("mib"))->isHtOperationSupported() == target->isHtOperationSupported()); + check_and_cast(nic->getSubmodule("mgmt"))->checkAdvertisement(); + } + + virtual void handleMessage(cMessage *message) override + { + delete message; + auto nic = getParentModule()->getSubmodule("ap", 0)->getSubmodule("wlan", 0); + auto radio = check_and_cast(nic->getSubmodule("radio")); + auto mib = check_and_cast(nic->getSubmodule("mib")); + auto medium = check_and_cast(getParentModule()->getSubmodule("radioMedium")); + auto ht = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"); + auto legacy = Ieee80211ModeSet::getModeSet("g(mixed)"); + radio->setMode(ht->getMode(Mbps(24))); + cachingObserver.modeSet = ht; + nic->subscribe(modesetChangedSignal, &cachingObserver); + nic->subscribe(modesetChangedSignal, this); + mib->setPeerHtCapabilities(peer, mib->localHtCapabilities, mib->getHtOperation()); + check(nic, ht, true); + auto mac = nic->getSubmodule("mac"); + auto arf = check_and_cast(mac->getSubmodule("dcf")->getSubmodule("rateControl")); + auto onoe = check_and_cast(mac->getSubmodule("hcf")->getSubmodule("rateControl")); + arf->getRate(peer); + onoe->getRate(peer); + int oldListening = medium->listeningNotifications; + radio->setModeSet(legacy); + check(nic, legacy, false); + ASSERT(legacy->containsMode(arf->getRate(peer))); + ASSERT(legacy->containsMode(onoe->getRate(peer))); + radio->setModeSetAndMode(ht, ht->getMode(Mbps(65), MHz(20), 1, 800e-9)); + check(nic, ht, false); + ASSERT(ht->containsMode(arf->getRate(peer))); + ASSERT(ht->containsMode(onoe->getRate(peer))); + mib->setPeerHtCapabilities(peer, mib->localHtCapabilities, mib->getHtOperation()); + check(nic, ht, true); + ASSERT(notifications == 2); + ASSERT(medium->listeningNotifications == oldListening + 2); + ASSERT(medium->lastListeningSource == radio); + ASSERT(cachingObserver.modeSet == ht); + ASSERT(observedModeSet == ht); + nic->unsubscribe(modesetChangedSignal, this); + nic->unsubscribe(modesetChangedSignal, &cachingObserver); + std::cout << "Mode-set switching, advertisements, peer state, both selectors, and notifications 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[1]: 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" + +%contains: stdout +Mode-set switching, advertisements, peer state, both selectors, and notifications verified. diff --git a/tests/unit/Ieee80211HtGuardInterval_1.test b/tests/unit/Ieee80211HtGuardInterval_1.test new file mode 100644 index 00000000000..8c5cec83e78 --- /dev/null +++ b/tests/unit/Ieee80211HtGuardInterval_1.test @@ -0,0 +1,575 @@ +%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; + +// Same OFDM tuple except for an absent GI, to isolate compatibility semantics. +class AbsentGiDataMode : public Ieee80211OfdmDataMode +{ + public: + AbsentGiDataMode(const Ieee80211OfdmDataMode& source) : Ieee80211OfdmDataMode(source) {} + virtual const simtime_t getGuardInterval() const override { return -1; } +}; + +class AbsentGiMode : public Ieee80211OfdmMode +{ + AbsentGiDataMode data; + public: + AbsentGiMode(const Ieee80211OfdmMode& source) : Ieee80211OfdmMode(source), data(*source.getDataMode()) {} + virtual const Ieee80211OfdmDataMode *getDataMode() const override { return &data; } +}; + +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; + } +}; + +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) { + rs->applyModeSet(ms); + } +}; + +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) { + rs->applyModeSet(ms); + } +}; + +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) + { + applyModeSet(modeSet); + } + + 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); +AbsentGiMode absentGiMode(*check_and_cast(aMode)); +Ieee80211ModeSet absentGiSet("absent GI", {{true, &absentGiMode, true}}, &absentGiMode, legacyOfdmModeSet->getPhyType()); +ASSERT(legacyOfdmModeSet->findCompatibleMode(&absentGiMode) == nullptr); +ASSERT(absentGiSet.findCompatibleMode(aMode) == nullptr); +ASSERT(absentGiSet.findCompatibleMode(&absentGiMode) == &absentGiMode); +// Public lookup retains its unspecified-GI wildcard. +ASSERT(legacyOfdmModeSet->findMode(Mbps(6), MHz(20), 1, -1) == aMode); + +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(6e6); +rateSelection->finalizeParameters(); +rateSelection->buildInside(); + +TestRateSelectionAccessor::notifyModeSet(rateSelection, erpModeSet); +ASSERT(TestRateSelectionAccessor::readModeSet(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(erpModeSet->containsMode(TestRateSelectionAccessor::getResponseCtsFrameMode(rateSelection))); +ASSERT(erpModeSet->containsMode(TestRateSelectionAccessor::getFastestMandatoryMode(rateSelection))); +ASSERT(TestRateSelectionAccessor::getFastestMandatoryMode(rateSelection) == erpModeSet->getMode(Mbps(24))); + +// Dynamically switch rateSelection to HT mode set +TestRateSelectionAccessor::notifyModeSet(rateSelection, modeSet); +ASSERT(TestRateSelectionAccessor::readModeSet(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(modeSet->containsMode(TestRateSelectionAccessor::getResponseCtsFrameMode(rateSelection))); +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(6e6); +rateSelectionHt->finalizeParameters(); +rateSelectionHt->buildInside(); + +TestRateSelectionAccessor::notifyModeSet(rateSelectionHt, modeSet); +ASSERT(TestRateSelectionAccessor::readModeSet(rateSelectionHt) == modeSet); +ASSERT(modeSet->containsMode(TestRateSelectionAccessor::getDataFrameMode(rateSelectionHt))); +ASSERT(TestRateSelectionAccessor::getDataFrameMode(rateSelectionHt) == short65); + +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(6e6); +qosRateSelection->finalizeParameters(); +qosRateSelection->buildInside(); + +TestQosRateSelectionAccessor::notifyModeSet(qosRateSelection, erpModeSet); +ASSERT(TestQosRateSelectionAccessor::readModeSet(qosRateSelection) == erpModeSet); +ASSERT(erpModeSet->containsMode(TestQosRateSelectionAccessor::getDataFrameMode(qosRateSelection))); +ASSERT(erpModeSet->containsMode(TestQosRateSelectionAccessor::getResponseBlockAckFrameMode(qosRateSelection))); +ASSERT(erpModeSet->containsMode(TestQosRateSelectionAccessor::getFastestMandatoryMode(qosRateSelection))); + +TestQosRateSelectionAccessor::notifyModeSet(qosRateSelection, modeSet); +ASSERT(TestQosRateSelectionAccessor::readModeSet(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..f225dcee91c 100644 --- a/tests/unit/Ieee80211HtModeSet_1.test +++ b/tests/unit/Ieee80211HtModeSet_1.test @@ -38,6 +38,15 @@ static const IIeee80211Mode *findHtMode(const Ieee80211ModeSet *modeSet, int mcs return nullptr; } +// Synthetic data rates isolate exact tuple matching from public lookup tolerance. +class CustomBitrateDataMode : public Ieee80211HtDataMode +{ + bps bitrate; + public: + CustomBitrateDataMode(const Ieee80211HtDataMode& base, bps bitrate) : Ieee80211HtDataMode(base), bitrate(bitrate) {} + virtual bps getNetBitrate() const override { return bitrate; } +}; + %activity: const auto *htModeSet = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"); @@ -69,6 +78,18 @@ for (int mcsIndex = 0; mcsIndex < 32; mcsIndex++) { } } } +// No preamble is needed: these synthetic modes exercise only tuple lookup. +const auto *baseData = check_and_cast( + findHtMode(htModeSet, 0, MHz(20), Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG)->getDataMode()); +Ieee80211HtMode source("source", nullptr, new CustomBitrateDataMode(*baseData, Mbps(6)), Ieee80211HtMode::BAND_2_4GHZ); +Ieee80211HtMode nearby("nearby", nullptr, new CustomBitrateDataMode(*baseData, Mbps(6.04)), Ieee80211HtMode::BAND_2_4GHZ); +Ieee80211HtMode equal("equal", nullptr, new CustomBitrateDataMode(*baseData, Mbps(6)), Ieee80211HtMode::BAND_2_4GHZ); +SparseModeSet nearbySet({{true, &nearby}, {true, htModeSet->getMode(Mbps(1)), true}}, &nearby, false); +ASSERT(nearbySet.findCompatibleMode(&source) == nullptr); +ASSERT(nearbySet.findMode(Mbps(6), MHz(20), 1, 800e-9) == &nearby); +SparseModeSet equalSet({{true, &nearby}, {true, &equal}, {true, htModeSet->getMode(Mbps(1)), true}}, &equal, false); +ASSERT(equalSet.findCompatibleMode(&source) == &equal); +ASSERT(equalSet.findCompatibleMode(nullptr) == nullptr); ASSERT(htModeSet->getHtSupportedChannelWidths().size() == 2); ASSERT(htModeSet->isHtShortGuardIntervalSupported(MHz(20))); ASSERT(htModeSet->isHtShortGuardIntervalSupported(MHz(40))); 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..6d2b7fbe770 100644 --- a/tests/unit/Ieee80211PeerModeSelection_1.test +++ b/tests/unit/Ieee80211PeerModeSelection_1.test @@ -134,6 +134,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); 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