From 7ca2e03beed8d9f6c69c01edf72d44f6b3ed2a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Thu, 20 Aug 2026 11:17:16 +0200 Subject: [PATCH 01/10] Ieee80211: add support for HT primary and secondary channels (HT40- and HT40+) - Added primary, secondary, and bonded center frequency calculations in Ieee80211Channel with HT40+ and HT40- offsets per IEEE Std 802.11-2024 Table 9-134. - Implemented multi-threshold HT CCA sensing (-82 dBm HT20, -79 dBm HT40 bonded, -62 dBm energy detection) in Ieee80211Receiver per Clause 19.3.19.6. - Added IIeee80211CcaProvider interface and ccaStateChangedSignal in Ieee80211Radio for subchannel busy notification. - Implemented secondary channel DIFS idle sensing and EDCA channel access restart in Hcf and Edcaf per Clause 11.15.9. - Added unit tests for channel geometry and CCA sensitivities, and example scenario demonstrating HT20, HT40+, HT40-, and secondary interferer. --- .../channelwidths/ChannelWidthsNetwork.ned | 37 +++++ examples/wireless/channelwidths/omnetpp.ini | 71 ++++++++ .../ieee80211/Ieee80211Interface.ned | 3 + .../linklayer/ieee80211/mac/Ieee80211Mac.cc | 15 +- .../linklayer/ieee80211/mac/Ieee80211Mac.h | 1 + src/inet/linklayer/ieee80211/mac/Rx.cc | 26 ++- src/inet/linklayer/ieee80211/mac/Rx.h | 6 + .../ieee80211/mac/channelaccess/Dcaf.cc | 11 ++ .../ieee80211/mac/channelaccess/Dcaf.h | 1 + .../ieee80211/mac/channelaccess/Edcaf.cc | 11 ++ .../ieee80211/mac/channelaccess/Edcaf.h | 1 + .../linklayer/ieee80211/mac/contract/IRx.h | 3 + .../ieee80211/mac/coordinationfunction/Dcf.cc | 4 +- .../ieee80211/mac/coordinationfunction/Hcf.cc | 24 +++ .../ieee80211/mac/coordinationfunction/Hcf.h | 1 + .../linklayer/ieee80211/mib/Ieee80211Mib.ned | 3 + .../IsotropicDimensionalBackgroundNoise.cc | 13 +- .../IsotropicScalarBackgroundNoise.cc | 14 +- .../contract/IIeee80211CcaProvider.h | 48 ++++++ .../ieee80211/mode/Ieee80211Channel.cc | 89 +++++++++- .../ieee80211/mode/Ieee80211Channel.h | 16 ++ .../ieee80211/mode/Ieee80211Channel.msg | 1 + .../ieee80211/packetlevel/Ieee80211Radio.cc | 155 +++++++++++++++--- .../ieee80211/packetlevel/Ieee80211Radio.h | 15 +- .../ieee80211/packetlevel/Ieee80211Radio.ned | 4 +- .../packetlevel/Ieee80211Receiver.cc | 118 ++++++++++++- .../ieee80211/packetlevel/Ieee80211Receiver.h | 11 ++ .../packetlevel/Ieee80211Receiver.ned | 5 +- .../packetlevel/Ieee80211Transmitter.cc | 13 +- .../unit/Ieee80211Ht40SecondaryChannel_1.test | 70 ++++++++ tests/unit/Ieee80211HtCcaSensitivity_1.test | 51 ++++++ 31 files changed, 792 insertions(+), 49 deletions(-) create mode 100644 examples/wireless/channelwidths/ChannelWidthsNetwork.ned create mode 100644 examples/wireless/channelwidths/omnetpp.ini create mode 100644 src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211CcaProvider.h create mode 100644 tests/unit/Ieee80211Ht40SecondaryChannel_1.test create mode 100644 tests/unit/Ieee80211HtCcaSensitivity_1.test diff --git a/examples/wireless/channelwidths/ChannelWidthsNetwork.ned b/examples/wireless/channelwidths/ChannelWidthsNetwork.ned new file mode 100644 index 00000000000..e628831320f --- /dev/null +++ b/examples/wireless/channelwidths/ChannelWidthsNetwork.ned @@ -0,0 +1,37 @@ +// +// Copyright (C) 2026 OpenSim Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +package inet.examples.wireless.channelwidths; + +import inet.networks.base.WirelessNetworkBase; +import inet.node.inet.WirelessHost; +import inet.node.wireless.AccessPoint; + +network ChannelWidthsNetwork extends WirelessNetworkBase +{ + parameters: + @display("bgb=650,450"); + submodules: + ap: AccessPoint { + @display("p=250,200;r=,,#707070"); + wlan[*].mgmt.typename = "Ieee80211MgmtApSimplified"; + } + sta1: WirelessHost { + @display("p=400,150;r=,,#707070"); + wlan[*].mgmt.typename = "Ieee80211MgmtStaSimplified"; + wlan[*].agent.typename = ""; + } + sta2: WirelessHost { + @display("p=400,250;r=,,#707070"); + wlan[*].mgmt.typename = "Ieee80211MgmtStaSimplified"; + wlan[*].agent.typename = ""; + } + interferer: WirelessHost { + @display("p=400,350;r=,,#707070"); + wlan[*].mgmt.typename = "Ieee80211MgmtStaSimplified"; + wlan[*].agent.typename = ""; + } +} diff --git a/examples/wireless/channelwidths/omnetpp.ini b/examples/wireless/channelwidths/omnetpp.ini new file mode 100644 index 00000000000..c266d558c34 --- /dev/null +++ b/examples/wireless/channelwidths/omnetpp.ini @@ -0,0 +1,71 @@ +[General] +network = ChannelWidthsNetwork +sim-time-limit = 2s + +# AP and STA configuration +**.ap.wlan[*].address = "10:00:00:00:00:01" +**.sta1.wlan[*].mgmt.accessPointAddress = "10:00:00:00:00:01" +**.sta2.wlan[*].mgmt.accessPointAddress = "10:00:00:00:00:01" +**.interferer.wlan[*].mgmt.accessPointAddress = "10:00:00:00:00:01" + +# Radio medium parameters +**.radioMedium.backgroundNoise.power = -110dBm +**.radioMedium.analogModel.ignorePartialInterference = true + +# Wi-Fi physical parameters +**.wlan[*].opMode = "n(mixed-2.4Ghz)" +**.wlan[*].radio.transmitter.power = 20mW +**.wlan[*].radio.receiver.sensitivity = -85dBm +**.wlan[*].radio.receiver.energyDetection = -85dBm +**.wlan[*].radio.receiver.snirThreshold = 4dB + +# Application: UDP traffic between STA1 and STA2 +*.sta1.numApps = 1 +*.sta1.app[0].typename = "UdpBasicApp" +*.sta1.app[0].destAddresses = "sta2" +*.sta1.app[0].destPort = 5000 +*.sta1.app[0].messageLength = 1000B +*.sta1.app[0].sendInterval = 1ms +*.sta1.app[0].startTime = 0.1s +*.sta1.app[0].stopTime = 1.9s + +*.sta2.numApps = 1 +*.sta2.app[0].typename = "UdpSink" +*.sta2.app[0].localPort = 5000 + +# Base HT20 config +[Config Ht20MHz] +description = "IEEE 802.11n 20 MHz operation on 2.4 GHz channel 0" +**.wlan[*].radio.bandwidth = 20MHz +**.wlan[*].radio.channelNumber = 0 +**.wlan[*].htSecondaryChannelOffset = "none" + +# HT40+ (secondary above) +[Config Ht40MHzSecondaryAbove] +description = "IEEE 802.11n 40 MHz operation with secondary channel above (+20 MHz)" +**.wlan[*].radio.bandwidth = 40MHz +**.wlan[*].radio.channelNumber = 0 +**.wlan[*].htSecondaryChannelOffset = "above" + +# HT40- (secondary below) +[Config Ht40MHzSecondaryBelow] +description = "IEEE 802.11n 40 MHz operation with secondary channel below (-20 MHz)" +**.wlan[*].radio.bandwidth = 40MHz +**.wlan[*].radio.channelNumber = 4 +**.wlan[*].htSecondaryChannelOffset = "below" + +# HT40+ with interferer on secondary channel +[Config Ht40MHzSecondaryAboveWithInterferer] +description = "HT40+ with secondary channel interferer causing EDCA backoff restart" +extends = Ht40MHzSecondaryAbove +*.interferer.numApps = 1 +*.interferer.app[0].typename = "UdpBasicApp" +*.interferer.app[0].destAddresses = "sta2" +*.interferer.app[0].destPort = 6000 +*.interferer.app[0].messageLength = 500B +*.interferer.app[0].sendInterval = 2ms +*.interferer.app[0].startTime = 0.2s +*.interferer.app[0].stopTime = 1.8s +*.interferer.wlan[*].radio.bandwidth = 20MHz +*.interferer.wlan[*].radio.channelNumber = 4 +*.interferer.wlan[*].htSecondaryChannelOffset = "none" diff --git a/src/inet/linklayer/ieee80211/Ieee80211Interface.ned b/src/inet/linklayer/ieee80211/Ieee80211Interface.ned index e32eaba59fc..51a2fe5bf4e 100644 --- a/src/inet/linklayer/ieee80211/Ieee80211Interface.ned +++ b/src/inet/linklayer/ieee80211/Ieee80211Interface.ned @@ -64,6 +64,7 @@ module Ieee80211Interface extends NetworkInterface like IWirelessInterface string interfaceTableModule; string energySourceModule = default(""); string opMode @enum("a","b","g(erp)","g(mixed)","n(mixed-2.4Ghz)","p","ac") = default("g(mixed)"); + string htSecondaryChannelOffset @enum("none", "above", "below") = default("none"); string address @mutable = default("auto"); // MAC address as hex string (12 hex digits), or // "auto". "auto" values will be replaced by // a generated MAC address in init stage 0. @@ -71,6 +72,8 @@ module Ieee80211Interface extends NetworkInterface like IWirelessInterface double bitrate @unit(bps) = default(-1bps); **.opMode = this.opMode; **.bitrate = this.bitrate; + radio.htSecondaryChannelOffset = default(this.htSecondaryChannelOffset); + mib.htSecondaryChannelOffset = default(this.htSecondaryChannelOffset); mac.modeSet = default(this.opMode); mac.*.rateSelection.dataFrameBitrate = default(this.bitrate); *.macModule = default(absPath(".mac")); diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc index 9f85dd2208a..c8dbd9d13aa 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc @@ -25,6 +25,7 @@ #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/contract/IIeee80211CcaProvider.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211ControlInfo_m.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" @@ -66,11 +67,14 @@ void Ieee80211Mac::initialize(int stage) ds = check_and_cast(getSubmodule("ds")); rx = check_and_cast(getSubmodule("rx")); tx = check_and_cast(getSubmodule("tx")); + auto ccaProvider = dynamic_cast(radio.get()); + if (ccaProvider != nullptr) { + radioModule->subscribe(IIeee80211CcaProvider::ccaStateChangedSignal, this); + rx->ccaStateChanged(ccaProvider->getCcaSnapshot()); + } emit(modesetChangedSignal, modeSet); if (isUp()) initializeRadioMode(); - rx = check_and_cast(getSubmodule("rx")); - tx = check_and_cast(getSubmodule("tx")); dcf = check_and_cast(getSubmodule("dcf")); hcf = check_and_cast_nullable(getSubmodule("hcf")); if (mib->qos && !hcf) @@ -327,6 +331,13 @@ void Ieee80211Mac::receiveSignal(cComponent *source, simsignal_t signalID, intva } } +void Ieee80211Mac::receiveSignal(cComponent *source, simsignal_t signalID, cObject *value, cObject *details) +{ + Enter_Method("%s", cComponent::getSignalName(signalID)); + if (signalID == IIeee80211CcaProvider::ccaStateChangedSignal) + rx->ccaStateChanged(*check_and_cast(value)); +} + 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 8f3f6c1d472..5cfbe038eea 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h @@ -64,6 +64,7 @@ class INET_API Ieee80211Mac : public MacProtocolBase virtual void initializeRadioMode(); virtual void receiveSignal(cComponent *source, simsignal_t signalID, intval_t value, cObject *details) override; + virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *value, 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/Rx.cc b/src/inet/linklayer/ieee80211/mac/Rx.cc index 19b9da0889c..beb37d48082 100644 --- a/src/inet/linklayer/ieee80211/mac/Rx.cc +++ b/src/inet/linklayer/ieee80211/mac/Rx.cc @@ -159,13 +159,37 @@ void Rx::recomputeMediumFree() { bool oldMediumFree = mediumFree; // note: the duration of mode switching (rx-to-tx or tx-to-rx) should also count as busy - mediumFree = receptionState == IRadio::RECEPTION_STATE_IDLE && transmissionState == IRadio::TRANSMISSION_STATE_UNDEFINED && !endNavTimer->isScheduled(); + bool primaryPhysicallyIdle = ht40Cca ? !primaryCcaBusy : receptionState == IRadio::RECEPTION_STATE_IDLE; + mediumFree = primaryPhysicallyIdle && transmissionState == IRadio::TRANSMISSION_STATE_UNDEFINED && !endNavTimer->isScheduled(); if (mediumFree != oldMediumFree) { for (auto contention : contentions) contention->mediumStateChanged(mediumFree); } } +bool Rx::isSecondaryChannelIdleFor(simtime_t interval) const +{ + return !ht40Cca || (!secondaryCcaBusy && secondaryCcaIdleSince >= SIMTIME_ZERO && + simTime() - secondaryCcaIdleSince >= interval); +} + +void Rx::ccaStateChanged(const Ieee80211CcaSnapshot& snapshot) +{ + Enter_Method("ccaStateChanged"); + bool wasHt40Cca = ht40Cca; + bool wasSecondaryBusy = secondaryCcaBusy; + ht40Cca = snapshot.isHt40(); + primaryCcaBusy = snapshot.isPrimaryBusy(); + secondaryCcaBusy = snapshot.isSecondaryBusy(); + if (!ht40Cca) + secondaryCcaIdleSince = -1; + else if (!wasHt40Cca || (wasSecondaryBusy && !secondaryCcaBusy)) + secondaryCcaIdleSince = simTime(); + else if (secondaryCcaBusy) + secondaryCcaIdleSince = -1; + recomputeMediumFree(); +} + void Rx::receptionStateChanged(IRadio::ReceptionState state) { Enter_Method("receptionStateChanged"); diff --git a/src/inet/linklayer/ieee80211/mac/Rx.h b/src/inet/linklayer/ieee80211/mac/Rx.h index 0fb3fbf81b3..0fe1a8bd964 100644 --- a/src/inet/linklayer/ieee80211/mac/Rx.h +++ b/src/inet/linklayer/ieee80211/mac/Rx.h @@ -37,6 +37,10 @@ class INET_API Rx : public SimpleModule, public IRx physicallayer::IRadio::TransmissionState transmissionState = physicallayer::IRadio::TRANSMISSION_STATE_UNDEFINED; physicallayer::IRadioSignal::SignalPart receivedPart = physicallayer::IRadioSignal::SIGNAL_PART_NONE; bool mediumFree = true; // cached state + bool ht40Cca = false; + bool primaryCcaBusy = false; + bool secondaryCcaBusy = false; + simtime_t secondaryCcaIdleSince = -1; protected: virtual int numInitStages() const override { return NUM_INIT_STAGES; } @@ -53,7 +57,9 @@ class INET_API Rx : public SimpleModule, public IRx virtual bool isReceptionInProgress() const override; virtual bool isMediumFree() const override { return mediumFree; } + virtual bool isSecondaryChannelIdleFor(simtime_t interval) const override; virtual void receptionStateChanged(physicallayer::IRadio::ReceptionState newReceptionState) override; + virtual void ccaStateChanged(const physicallayer::Ieee80211CcaSnapshot& snapshot) override; virtual void transmissionStateChanged(physicallayer::IRadio::TransmissionState transmissionState) override; virtual void receivedSignalPartChanged(physicallayer::IRadioSignal::SignalPart part) override; virtual bool lowerFrameReceived(Packet *packet) override; diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc index 1f6ea0615cb..d6e7235f51e 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc @@ -105,6 +105,17 @@ void Dcaf::releaseChannel(IChannelAccess::ICallback *callback) EV_INFO << "Channel released.\n"; } +void Dcaf::restartChannelAccess(IChannelAccess::ICallback *callback) +{ + Enter_Method("restartChannelAccess"); + ASSERT(owning && this->callback == callback); + owning = false; + emit(channelOwnershipChangedSignal, owning); + // IEEE Std 802.11-2024, 11.15.9 item b): retain the current CW and draw a + // fresh random backoff without updating any retry counter. + contention->startContention(cw, ifs, eifs, slotTime, this); +} + void Dcaf::requestChannel(IChannelAccess::ICallback *callback) { Enter_Method("requestChannel"); diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h index 2fab0d312c8..c46411f657e 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h @@ -52,6 +52,7 @@ class INET_API Dcaf : public IChannelAccess, public IContention::ICallback, publ // IChannelAccess::ICallback virtual void requestChannel(IChannelAccess::ICallback *callback) override; virtual void releaseChannel(IChannelAccess::ICallback *callback) override; + virtual void restartChannelAccess(IChannelAccess::ICallback *callback); // IContention::ICallback virtual void channelAccessGranted() override; diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc index e220635a876..d3cea666a84 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc @@ -143,6 +143,17 @@ void Edcaf::releaseChannel(IChannelAccess::ICallback *callback) EV_INFO << "Channel released.\n"; } +void Edcaf::restartChannelAccess(IChannelAccess::ICallback *callback) +{ + Enter_Method("restartChannelAccess"); + ASSERT(owning); + owning = false; + emit(channelOwnershipChangedSignal, owning); + // IEEE Std 802.11-2024, 11.15.9 item b): retain CW[AC], select a new + // random backoff, and leave retry counters and queued frames unchanged. + contention->startContention(cw, ifs, eifs, slotTime, this); +} + void Edcaf::requestChannel(IChannelAccess::ICallback *callback) { Enter_Method("requestChannel"); diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h index e737cad9162..ec0ed49fe80 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h @@ -86,6 +86,7 @@ class INET_API Edcaf : public IChannelAccess, public IContention::ICallback, pub // IChannelAccess virtual void requestChannel(IChannelAccess::ICallback *callback) override; virtual void releaseChannel(IChannelAccess::ICallback *callback) override; + virtual void restartChannelAccess(IChannelAccess::ICallback *callback); // IContention::ICallback virtual void channelAccessGranted() override; diff --git a/src/inet/linklayer/ieee80211/mac/contract/IRx.h b/src/inet/linklayer/ieee80211/mac/contract/IRx.h index e930dfc821e..fd7aacb74d5 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IRx.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IRx.h @@ -12,6 +12,7 @@ #include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" #include "inet/linklayer/ieee80211/mac/contract/IContention.h" #include "inet/physicallayer/wireless/common/contract/packetlevel/IRadio.h" +#include "inet/physicallayer/wireless/ieee80211/contract/IIeee80211CcaProvider.h" namespace inet { namespace ieee80211 { @@ -35,6 +36,7 @@ class INET_API IRx // from Contention virtual bool isMediumFree() const = 0; + virtual bool isSecondaryChannelIdleFor(simtime_t interval) const = 0; virtual void frameTransmitted(simtime_t durationField) = 0; // from Coordination functions @@ -42,6 +44,7 @@ class INET_API IRx // events virtual void receptionStateChanged(physicallayer::IRadio::ReceptionState state) = 0; + virtual void ccaStateChanged(const physicallayer::Ieee80211CcaSnapshot& snapshot) = 0; virtual void transmissionStateChanged(physicallayer::IRadio::TransmissionState state) = 0; virtual void receivedSignalPartChanged(physicallayer::IRadioSignal::SignalPart part) = 0; virtual bool lowerFrameReceived(Packet *packet) = 0; diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc index 0da41104ace..bce47a83cd7 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc @@ -12,6 +12,7 @@ #include "inet/linklayer/ieee80211/mac/framesequence/DcfFs.h" #include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h" #include "inet/linklayer/ieee80211/mac/recipient/RecipientAckProcedure.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" namespace inet { namespace ieee80211 { @@ -164,7 +165,8 @@ void Dcf::transmitFrame(Packet *packet, simtime_t ifs) { Enter_Method("transmitFrame"); const auto& header = packet->peekAtFront(); - auto mode = rateSelection->computeMode(packet, header); + auto modeReq = packet->findTag(); + auto mode = modeReq != nullptr ? modeReq->getMode() : rateSelection->computeMode(packet, header); RateSelection::setFrameMode(packet, header, mode); emit(IRateSelection::datarateSelectedSignal, mode->getDataMode()->getNetBitrate().get(), packet); EV_DEBUG << "Datarate for " << packet->getName() << " is set to " << mode->getDataMode()->getNetBitrate() << ".\n"; diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc index b3d11467343..ce63778bccc 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc @@ -14,6 +14,7 @@ #include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h" #include "inet/linklayer/ieee80211/mac/framesequence/HcfFs.h" #include "inet/linklayer/ieee80211/mac/recipient/RecipientAckProcedure.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" namespace inet { @@ -193,6 +194,24 @@ void Hcf::processLowerFrame(Packet *packet, const Ptr& } } +bool Hcf::shouldRestartHt40ChannelAccess(Edcaf *edcaf) +{ + Packet *frame = edcaf->getInProgressFrames()->getFrameToTransmit(); + if (frame == nullptr) + return false; + const auto& header = frame->peekAtFront(); + auto modeReq = frame->findTag(); + auto mode = modeReq != nullptr ? modeReq->getMode() : rateSelection->computeMode(frame, header, edcaf->getTxopProcedure()); + if (mode == nullptr || dynamic_cast(mode) == nullptr || + mode->getDataMode()->getBandwidth() != MHz(40)) + return false; + // IEEE Std 802.11-2024, 11.15.9 item b): + // If the secondary channel was busy during DIFS before channel access, + // invoke the backoff procedure with the current CW[AC]. + simtime_t difs = modeSet->getSifsTime() + 2 * modeSet->getSlotTime(); + return !rx->isSecondaryChannelIdleFor(difs); +} + void Hcf::channelGranted(IChannelAccess *channelAccess) { Enter_Method("channelGranted"); @@ -200,6 +219,11 @@ void Hcf::channelGranted(IChannelAccess *channelAccess) if (edcaf) { AccessCategory ac = edcaf->getAccessCategory(); EV_DETAIL << "Channel access granted to the " << printAccessCategory(ac) << " queue" << std::endl; + if (shouldRestartHt40ChannelAccess(edcaf)) { + EV_INFO << "Secondary channel was busy during DIFS before channel access for HT40 transmission, restarting backoff.\n"; + edcaf->restartChannelAccess(this); + return; + } edcaf->getTxopProcedure()->startTxop(ac); auto internallyCollidedEdcafs = edca->getInternallyCollidedEdcafs(); if (internallyCollidedEdcafs.size() > 0) { diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h index 2060a4e3d2c..5a236701e91 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h @@ -135,6 +135,7 @@ class INET_API Hcf : public ICoordinationFunction, public IFrameSequenceHandler: virtual void setFrameMode(Packet *packet, const Ptr& header, const physicallayer::IIeee80211Mode *mode) const; virtual bool isSentByUs(const Ptr& header) const; virtual bool isForUs(const Ptr& header) const; + virtual bool shouldRestartHt40ChannelAccess(Edcaf *edcaf); protected: // IFrameSequenceHandler::ICallback diff --git a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned index 81204d26cb7..1ec93153e82 100644 --- a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned +++ b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned @@ -20,6 +20,9 @@ simple Ieee80211Mib extends SimpleModule { parameters: @class(Ieee80211Mib); + string htSecondaryChannelOffset @enum("none", "above", "below") = default("none"); + bool htShortGi40 = default(false); + int htMaxMcs = default(7); displayStringTextFormat = default("Address: {address}{ssidStr}\n{modeStr}{stationTypeStr}{qosStr}{associatedStr}"); @display("i=block/table"); } diff --git a/src/inet/physicallayer/wireless/common/backgroundnoise/IsotropicDimensionalBackgroundNoise.cc b/src/inet/physicallayer/wireless/common/backgroundnoise/IsotropicDimensionalBackgroundNoise.cc index c6edffd46df..e605630ea30 100644 --- a/src/inet/physicallayer/wireless/common/backgroundnoise/IsotropicDimensionalBackgroundNoise.cc +++ b/src/inet/physicallayer/wireless/common/backgroundnoise/IsotropicDimensionalBackgroundNoise.cc @@ -54,17 +54,16 @@ const INoise *IsotropicDimensionalBackgroundNoise::computeNoise(const IListening if (!std::isnan(powerSpectralDensity.get())) noisePowerSpectralDensity = powerSpectralDensity; else { - if (std::isnan(bandwidth.get())) - bandwidth = listeningBandwidth; - else if (bandwidth != listeningBandwidth) - throw cRuntimeError("The powerSpectralDensity parameter is not specified and the power parameter cannot be used, because background noise bandwidth doesn't match listening bandwidth"); - // NOTE: dividing by the bandwidth here makes sure the total background noise power in the listening band is the given power - noisePowerSpectralDensity = power / bandwidth; + Hz noiseBandwidth = std::isnan(bandwidth.get()) ? listeningBandwidth : bandwidth; + // A scalar power parameter denotes integrated power over the + // configured band. Keep its equivalent flat PSD when a per-channel + // CCA query selects a narrower HT40 slice. + noisePowerSpectralDensity = power / noiseBandwidth; } const Ptr>>& powerFunction = makeShared>>(noisePowerSpectralDensity); const simtime_t startTime = listening->getStartTime(); const simtime_t endTime = listening->getEndTime(); - return new DimensionalNoise(startTime, endTime, centerFrequency, bandwidth, makeFirstQuadrantLimitedFunction(powerFunction)); + return new DimensionalNoise(startTime, endTime, centerFrequency, listeningBandwidth, makeFirstQuadrantLimitedFunction(powerFunction)); } } // namespace physicallayer diff --git a/src/inet/physicallayer/wireless/common/backgroundnoise/IsotropicScalarBackgroundNoise.cc b/src/inet/physicallayer/wireless/common/backgroundnoise/IsotropicScalarBackgroundNoise.cc index 4c250aab604..39f13c920bf 100644 --- a/src/inet/physicallayer/wireless/common/backgroundnoise/IsotropicScalarBackgroundNoise.cc +++ b/src/inet/physicallayer/wireless/common/backgroundnoise/IsotropicScalarBackgroundNoise.cc @@ -43,12 +43,14 @@ const INoise *IsotropicScalarBackgroundNoise::computeNoise(const IListening *lis simtime_t endTime = listening->getEndTime(); Hz centerFrequency = bandListening->getCenterFrequency(); Hz listeningBandwidth = bandListening->getBandwidth(); - if (std::isnan(bandwidth.get())) - bandwidth = listeningBandwidth; - else if (bandwidth != listeningBandwidth) - throw cRuntimeError("Background noise bandwidth doesn't match listening bandwidth"); - const auto& powerFunction = makeShared>(startTime, endTime, power); - return new ScalarNoise(startTime, endTime, centerFrequency, bandwidth, powerFunction); + Hz noiseBandwidth = std::isnan(bandwidth.get()) ? listeningBandwidth : bandwidth; + // A scalar background-noise power is the integrated power over its + // configured bandwidth. Per-channel HT40 CCA listens to 20 MHz slices, + // so preserve the equivalent flat noise density instead of rejecting a + // narrower listening band. + W listeningPower = power * (listeningBandwidth / noiseBandwidth); + const auto& powerFunction = makeShared>(startTime, endTime, listeningPower); + return new ScalarNoise(startTime, endTime, centerFrequency, listeningBandwidth, powerFunction); } } // namespace physicallayer diff --git a/src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211CcaProvider.h b/src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211CcaProvider.h new file mode 100644 index 00000000000..77f309bf8c0 --- /dev/null +++ b/src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211CcaProvider.h @@ -0,0 +1,48 @@ +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +#ifndef __INET_IIEEE80211CCAPROVIDER_H +#define __INET_IIEEE80211CCAPROVIDER_H + +#include "inet/common/INETDefs.h" + +namespace inet { +namespace physicallayer { + +class INET_API Ieee80211CcaSnapshot : public cObject +{ + protected: + bool ht40 = false; + bool primaryBusy = false; + bool secondaryBusy = false; + + public: + Ieee80211CcaSnapshot(bool ht40 = false, bool primaryBusy = false, bool secondaryBusy = false) : + ht40(ht40), primaryBusy(primaryBusy), secondaryBusy(secondaryBusy) {} + + virtual Ieee80211CcaSnapshot *dup() const override { return new Ieee80211CcaSnapshot(*this); } + + bool isHt40() const { return ht40; } + bool isPrimaryBusy() const { return primaryBusy; } + bool isSecondaryBusy() const { return secondaryBusy; } +}; + +/** + * Provides the local IEEE 802.11 per-channel PHY-CCA state. The snapshot is + * local PHY/MAC control information and is not transmitted on air. + */ +class INET_API IIeee80211CcaProvider +{ + public: + static simsignal_t ccaStateChangedSignal; + + virtual ~IIeee80211CcaProvider() {} + + virtual const Ieee80211CcaSnapshot& getCcaSnapshot() const = 0; +}; + +} // namespace physicallayer +} // namespace inet + +#endif diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.cc b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.cc index 640a79c9c06..f9d579126a8 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.cc +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.cc @@ -12,9 +12,93 @@ namespace inet { namespace physicallayer { Ieee80211Channel::Ieee80211Channel(const IIeee80211Band *band, int channelNumber) : + Ieee80211Channel(band, channelNumber, IEEE80211_SECONDARY_CHANNEL_NONE) +{ +} + +Ieee80211Channel::Ieee80211Channel(const IIeee80211Band *band, int channelNumber, Ieee80211SecondaryChannelOffset secondaryChannelOffset) : band(band), - channelNumber(channelNumber) + channelNumber(channelNumber), + secondaryChannelOffset(secondaryChannelOffset) +{ + // IEEE Std 802.11-2024, Table 9-134 reserves wire value 2; 19.2.3 and + // 19.3.15.4 place the secondary channel four channel numbers away. + if (secondaryChannelOffset != IEEE80211_SECONDARY_CHANNEL_NONE && + secondaryChannelOffset != IEEE80211_SECONDARY_CHANNEL_ABOVE && + secondaryChannelOffset != IEEE80211_SECONDARY_CHANNEL_BELOW) + throw cRuntimeError("Invalid IEEE 802.11 secondary channel offset: %d", (int)secondaryChannelOffset); + if (secondaryChannelOffset != IEEE80211_SECONDARY_CHANNEL_NONE) { + if (band == nullptr) + throw cRuntimeError("Cannot resolve an IEEE 802.11 secondary channel without a band"); + (void)getCenterFrequency(); + (void)getSecondaryCenterFrequency(); + } +} + +Ieee80211SecondaryChannelOffset Ieee80211Channel::parseSecondaryChannelOffset(const char *text) +{ + if (!strcmp(text, "none")) + return IEEE80211_SECONDARY_CHANNEL_NONE; + if (!strcmp(text, "above")) + return IEEE80211_SECONDARY_CHANNEL_ABOVE; + if (!strcmp(text, "below")) + return IEEE80211_SECONDARY_CHANNEL_BELOW; + throw cRuntimeError("Unknown IEEE 802.11 secondary channel offset '%s'", text); +} + +const char *Ieee80211Channel::getSecondaryChannelOffsetName(Ieee80211SecondaryChannelOffset offset) +{ + switch (offset) { + case IEEE80211_SECONDARY_CHANNEL_NONE: + return "none"; + case IEEE80211_SECONDARY_CHANNEL_ABOVE: + return "above"; + case IEEE80211_SECONDARY_CHANNEL_BELOW: + return "below"; + default: + throw cRuntimeError("Unknown IEEE 802.11 secondary channel offset: %d", (int)offset); + } +} + +int Ieee80211Channel::getSecondaryChannelNumber() const +{ + switch (secondaryChannelOffset) { + case IEEE80211_SECONDARY_CHANNEL_NONE: + return channelNumber; + case IEEE80211_SECONDARY_CHANNEL_ABOVE: + return channelNumber + 4; + case IEEE80211_SECONDARY_CHANNEL_BELOW: + return channelNumber - 4; + default: + throw cRuntimeError("Unknown secondary channel offset: %d", secondaryChannelOffset); + } +} + +Hz Ieee80211Channel::getSecondaryCenterFrequency() const +{ + int direction = secondaryChannelOffset == IEEE80211_SECONDARY_CHANNEL_ABOVE ? 1 : + secondaryChannelOffset == IEEE80211_SECONDARY_CHANNEL_BELOW ? -1 : 0; + if (direction == 0) + throw cRuntimeError("IEEE 802.11 channel has no secondary channel"); + int secondaryChannelNumber = channelNumber + 4 * direction; + Hz secondaryCenterFrequency = band->getCenterFrequency(secondaryChannelNumber); + if (secondaryCenterFrequency != getCenterFrequency() + MHz(20 * direction)) + throw cRuntimeError("IEEE 802.11 secondary channel is not 20 MHz from the primary channel"); + return secondaryCenterFrequency; +} + +Hz Ieee80211Channel::getBondedCenterFrequency() const { + switch (secondaryChannelOffset) { + case IEEE80211_SECONDARY_CHANNEL_NONE: + return getCenterFrequency(); + case IEEE80211_SECONDARY_CHANNEL_ABOVE: + return getCenterFrequency() + MHz(10); + case IEEE80211_SECONDARY_CHANNEL_BELOW: + return getCenterFrequency() - MHz(10); + default: + throw cRuntimeError("Invalid IEEE 802.11 secondary channel offset: %d", (int)secondaryChannelOffset); + } } std::ostream& Ieee80211Channel::printToStream(std::ostream& stream, int level, int evFlags) const @@ -23,7 +107,8 @@ std::ostream& Ieee80211Channel::printToStream(std::ostream& stream, int level, i if (level <= PRINT_LEVEL_TRACE) stream << EV_FIELD(band, printFieldToString(band, level + 1, evFlags)); if (level <= PRINT_LEVEL_INFO) - stream << EV_FIELD(channelNumber); + stream << EV_FIELD(channelNumber) + << EV_FIELD(secondaryChannelOffset); return stream; } diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.h index 1de53f6e67a..0fd0207bffb 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.h @@ -14,20 +14,36 @@ namespace inet { namespace physicallayer { +// IEEE Std 802.11-2024, Table 9-134. +enum Ieee80211SecondaryChannelOffset { + IEEE80211_SECONDARY_CHANNEL_NONE = 0, + IEEE80211_SECONDARY_CHANNEL_ABOVE = 1, + IEEE80211_SECONDARY_CHANNEL_BELOW = 3, +}; + class INET_API Ieee80211Channel : public IPrintableObject { protected: const IIeee80211Band *band; int channelNumber; + Ieee80211SecondaryChannelOffset secondaryChannelOffset; public: Ieee80211Channel(const IIeee80211Band *band, int channelNumber); + Ieee80211Channel(const IIeee80211Band *band, int channelNumber, Ieee80211SecondaryChannelOffset secondaryChannelOffset); + + static Ieee80211SecondaryChannelOffset parseSecondaryChannelOffset(const char *text); + static const char *getSecondaryChannelOffsetName(Ieee80211SecondaryChannelOffset offset); virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override; virtual const IIeee80211Band *getBand() const { return band; } virtual int getChannelNumber() const { return channelNumber; } + virtual Ieee80211SecondaryChannelOffset getSecondaryChannelOffset() const { return secondaryChannelOffset; } + virtual int getSecondaryChannelNumber() const; virtual Hz getCenterFrequency() const { return band->getCenterFrequency(channelNumber); } + virtual Hz getSecondaryCenterFrequency() const; + virtual Hz getBondedCenterFrequency() const; }; } // namespace physicallayer diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.msg b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.msg index 08eb8e7e897..491e7a26ac3 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.msg +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.msg @@ -40,4 +40,5 @@ class Ieee80211Channel @descriptor(readonly); IIeee80211Band *band; int channelNumber; + int secondaryChannelOffset; } diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc index d5aacca92b5..010e139ba61 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc @@ -20,6 +20,10 @@ #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211ControlInfo_m.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211PhyHeader_m.h" +#include "inet/mobility/contract/IMobility.h" +#include "inet/physicallayer/wireless/common/contract/packetlevel/IRadioMedium.h" +#include "inet/physicallayer/wireless/common/radio/packetlevel/BandListening.h" +#include "inet/physicallayer/wireless/common/radio/packetlevel/ListeningDecision.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h" @@ -31,12 +35,56 @@ namespace physicallayer { Define_Module(Ieee80211Radio); simsignal_t Ieee80211Radio::radioChannelChangedSignal = cComponent::registerSignal("radioChannelChanged"); +simsignal_t IIeee80211CcaProvider::ccaStateChangedSignal = cComponent::registerSignal("ccaStateChanged"); Ieee80211Radio::Ieee80211Radio() : - FlatRadioBase() + FlatRadioBase(), + ccaSnapshot(std::make_unique()) { } +bool Ieee80211Radio::computeIsBandBusy(Hz centerFrequency) const +{ + const simtime_t now = simTime(); + const Coord& position = antenna->getMobility()->getCurrentPosition(); + BandListening listening(this, now, now + SimTime::fromRaw(1), position, position, + centerFrequency, MHz(20)); + const IListeningDecision *decision = medium->listenOnMedium(this, &listening); + bool busy = decision->isListeningPossible(); + delete decision; + return busy; +} + +void Ieee80211Radio::updateCcaState() +{ + auto ieee80211Receiver = dynamic_cast(receiver); + auto channel = ieee80211Receiver == nullptr ? nullptr : ieee80211Receiver->getChannel(); + bool ht40Configured = channel != nullptr && + channel->getSecondaryChannelOffset() != IEEE80211_SECONDARY_CHANNEL_NONE && + modeSet != nullptr && !strcmp(modeSet->getName(), "n(mixed-2.4Ghz)") && + ieee80211Receiver->getBandwidth() == MHz(40); + bool ht40 = ht40Configured && isReceiverMode(radioMode); + bool primaryBusy = false; + bool secondaryBusy = false; + if (ht40) { + // IEEE Std 802.11-2024, 8.3.5.12/Table 8-5 and 19.3.19.6.5: + // preserve {primary}, {secondary}, and {primary,secondary} CCA state. + primaryBusy = computeIsBandBusy(channel->getCenterFrequency()); + secondaryBusy = computeIsBandBusy(channel->getSecondaryCenterFrequency()); + } + if (ccaSnapshot->isHt40() != ht40 || ccaSnapshot->isPrimaryBusy() != primaryBusy || + ccaSnapshot->isSecondaryBusy() != secondaryBusy) { + ccaSnapshot = std::make_unique(ht40, primaryBusy, secondaryBusy); + emit(IIeee80211CcaProvider::ccaStateChangedSignal, ccaSnapshot.get()); + } +} + +void Ieee80211Radio::updateTransceiverState() +{ + FlatRadioBase::updateTransceiverState(); + updateCcaState(); +} + void Ieee80211Radio::initialize(int stage) { FlatRadioBase::initialize(stage); @@ -44,8 +92,31 @@ void Ieee80211Radio::initialize(int stage) if (stage == INITSTAGE_LOCAL) { const char *fcsModeString = par("fcsMode"); fcsMode = parseFcsMode(fcsModeString, true); + opMode = par("opMode").stringValue(); } if (stage == INITSTAGE_PHYSICAL_LAYER) { + const char *bandName = par("bandName"); + setBand(*bandName ? Ieee80211CompliantBands::getBand(bandName) : nullptr); + setModeSet(*opMode.c_str() ? Ieee80211ModeSet::getModeSet(opMode.c_str()) : nullptr); + htSecondaryChannelOffset = Ieee80211Channel::parseSecondaryChannelOffset(par("htSecondaryChannelOffset")); + Ieee80211Receiver *ieee80211Receiver = const_cast(check_and_cast(receiver)); + Ieee80211Transmitter *ieee80211Transmitter = const_cast(check_and_cast(transmitter)); + Hz radioBw = !std::isnan(par("bandwidth").doubleValue()) ? Hz(par("bandwidth").doubleValue()) : ieee80211Receiver->getBandwidth(); + if (radioBw == MHz(40)) { + ieee80211Receiver->setBandwidth(radioBw); + ieee80211Transmitter->setBandwidth(radioBw); + } + if (modeSet != nullptr && !strcmp(modeSet->getName(), "n(mixed-2.4Ghz)") && + radioBw == MHz(40) && + htSecondaryChannelOffset == IEEE80211_SECONDARY_CHANNEL_NONE) + throw cRuntimeError("HT 40 MHz operation requires a secondary channel offset of above or below"); + if (htSecondaryChannelOffset != IEEE80211_SECONDARY_CHANNEL_NONE) { + // IEEE Std 802.11-2024, 19.2.3 and 19.3.15.4: the secondary + // channel is an HT40-only operating-channel property. + if (modeSet == nullptr || strcmp(modeSet->getName(), "n(mixed-2.4Ghz)") || + radioBw != MHz(40)) + throw cRuntimeError("htSecondaryChannelOffset above/below requires HT 40 MHz operation"); + } int channelNumber = par("channelNumber"); if (channelNumber != -1) setChannelNumber(channelNumber); @@ -57,24 +128,64 @@ void Ieee80211Radio::handleUpperCommand(cMessage *message) if (message->getKind() == RADIO_C_CONFIGURE) { 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); - const IIeee80211Mode *mode = configureCommand->getMode(); - if (mode != nullptr) - setMode(mode); - const IIeee80211Band *band = configureCommand->getBand(); - if (band != nullptr) - setBand(band); + Ieee80211Transmitter *ieee80211Transmitter = const_cast(check_and_cast(transmitter)); + Ieee80211Receiver *ieee80211Receiver = const_cast(check_and_cast(receiver)); + const Ieee80211Channel *currentChannel = ieee80211Receiver->getChannel(); + const char *requestedOpMode = configureCommand->getOpMode(); + std::string targetOpMode = *requestedOpMode ? requestedOpMode : this->opMode; + const IIeee80211Band *bandParam = configureCommand->getBand(); + const IIeee80211Band *targetBand = bandParam != nullptr ? bandParam : this->band; + const Ieee80211ModeSet *modeSetParam = configureCommand->getModeSet(); + const Ieee80211ModeSet *targetModeSet = modeSetParam != nullptr ? modeSetParam : + *targetOpMode.c_str() ? Ieee80211ModeSet::getModeSet(targetOpMode.c_str()) : this->modeSet; const Ieee80211Channel *channel = configureCommand->getChannel(); - if (channel != nullptr) - setChannel(channel); int newChannelNumber = configureCommand->getChannelNumber(); - if (newChannelNumber != -1) - setChannelNumber(newChannelNumber); + int targetChannelNumber = channel != nullptr ? channel->getChannelNumber() : + newChannelNumber != -1 ? newChannelNumber : + currentChannel != nullptr ? currentChannel->getChannelNumber() : -1; + auto targetSecondaryChannelOffset = channel != nullptr ? channel->getSecondaryChannelOffset() : + htSecondaryChannelOffset; + if (targetChannelNumber != -1 && (targetBand == nullptr || targetChannelNumber < 0 || targetChannelNumber >= targetBand->getNumChannels())) + throw cRuntimeError("Invalid target 802.11 channel number %d", targetChannelNumber); + if (targetChannelNumber != -1) + (void)Ieee80211Channel(targetBand, targetChannelNumber, targetSecondaryChannelOffset).getCenterFrequency(); + + Hz newBandwidth = configureCommand->getBandwidth(); + Hz targetBandwidth = std::isnan(newBandwidth.get()) ? ieee80211Receiver->getBandwidth() : newBandwidth; + bps newBitrate = configureCommand->getBitrate(); + bps targetBitrate = std::isnan(newBitrate.get()) ? ieee80211Transmitter->getBitrate() : newBitrate; + const IIeee80211Mode *mode = configureCommand->getMode(); + Hz targetModeBandwidth = !std::isnan(targetBandwidth.get()) ? targetBandwidth : Hz(MHz(20)); + const IIeee80211Mode *resolvedMode = mode != nullptr ? mode : nullptr; + if (resolvedMode == nullptr && targetModeSet != nullptr && targetBitrate != bps(-1)) + resolvedMode = targetModeSet->getMode(targetBitrate, targetModeBandwidth); + if (targetModeSet != nullptr && !strcmp(targetModeSet->getName(), "n(mixed-2.4Ghz)") && + ((targetModeBandwidth == MHz(40)) || + (resolvedMode != nullptr && dynamic_cast(resolvedMode) != nullptr && + resolvedMode->getDataMode()->getBandwidth() == MHz(40))) && + targetSecondaryChannelOffset == IEEE80211_SECONDARY_CHANNEL_NONE) + throw cRuntimeError("HT 40 MHz operation requires a secondary channel offset of above or below"); + + bool publishModeSet = targetModeSet != this->modeSet || targetBand != this->band || + !std::isnan(newBandwidth.get()) || !std::isnan(newBitrate.get()) || *requestedOpMode; + + if (!std::isnan(newBandwidth.get())) + setBandwidth(newBandwidth); + if (!std::isnan(newBitrate.get())) + setBitrate(newBitrate); + if (targetChannelNumber != -1 && + (currentChannel == nullptr || targetBand != this->band || targetChannelNumber != currentChannel->getChannelNumber() || + targetSecondaryChannelOffset != currentChannel->getSecondaryChannelOffset())) + setChannel(new Ieee80211Channel(targetBand, targetChannelNumber, targetSecondaryChannelOffset)); + else if (targetBand != this->band) + setBand(targetBand); + this->opMode = targetOpMode; + if (resolvedMode != nullptr) + setMode(resolvedMode); + if (publishModeSet && targetModeSet != nullptr) + setModeSet(targetModeSet); + delete message; + return; } } FlatRadioBase::handleUpperCommand(message); @@ -82,6 +193,7 @@ void Ieee80211Radio::handleUpperCommand(cMessage *message) void Ieee80211Radio::setModeSet(const Ieee80211ModeSet *modeSet) { + this->modeSet = modeSet; Ieee80211Transmitter *ieee80211Transmitter = const_cast(check_and_cast(transmitter)); Ieee80211Receiver *ieee80211Receiver = const_cast(check_and_cast(receiver)); ieee80211Transmitter->setModeSet(modeSet); @@ -102,6 +214,7 @@ void Ieee80211Radio::setMode(const IIeee80211Mode *mode) void Ieee80211Radio::setBand(const IIeee80211Band *band) { + this->band = band; Ieee80211Transmitter *ieee80211Transmitter = const_cast(check_and_cast(transmitter)); Ieee80211Receiver *ieee80211Receiver = const_cast(check_and_cast(receiver)); ieee80211Transmitter->setBand(band); @@ -116,7 +229,9 @@ void Ieee80211Radio::setChannel(const Ieee80211Channel *channel) Ieee80211Transmitter *ieee80211Transmitter = const_cast(check_and_cast(transmitter)); Ieee80211Receiver *ieee80211Receiver = const_cast(check_and_cast(receiver)); ieee80211Transmitter->setChannel(channel); - ieee80211Receiver->setChannel(channel); + ieee80211Receiver->setChannel(new Ieee80211Channel(channel->getBand(), channel->getChannelNumber(), channel->getSecondaryChannelOffset())); + band = channel->getBand(); + htSecondaryChannelOffset = channel->getSecondaryChannelOffset(); EV << "Changing radio channel to " << channel->getChannelNumber() << endl; receptionTimer = nullptr; emit(radioChannelChangedSignal, channel->getChannelNumber()); @@ -127,8 +242,8 @@ void Ieee80211Radio::setChannelNumber(int newChannelNumber) { Ieee80211Transmitter *ieee80211Transmitter = const_cast(check_and_cast(transmitter)); Ieee80211Receiver *ieee80211Receiver = const_cast(check_and_cast(receiver)); - ieee80211Transmitter->setChannelNumber(newChannelNumber); - ieee80211Receiver->setChannelNumber(newChannelNumber); + ieee80211Transmitter->setChannel(new Ieee80211Channel(band, newChannelNumber, htSecondaryChannelOffset)); + ieee80211Receiver->setChannel(new Ieee80211Channel(band, newChannelNumber, htSecondaryChannelOffset)); EV << "Changing radio channel to " << newChannelNumber << ".\n"; receptionTimer = nullptr; emit(radioChannelChangedSignal, newChannelNumber); diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h index aa416de6ed8..c302a133466 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h @@ -9,6 +9,7 @@ #define __INET_IEEE80211RADIO_H #include "inet/physicallayer/wireless/common/base/packetlevel/FlatRadioBase.h" +#include "inet/physicallayer/wireless/ieee80211/contract/IIeee80211CcaProvider.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 +19,7 @@ namespace inet { namespace physicallayer { -class INET_API Ieee80211Radio : public FlatRadioBase +class INET_API Ieee80211Radio : public FlatRadioBase, public IIeee80211CcaProvider { public: /** @@ -31,6 +32,11 @@ class INET_API Ieee80211Radio : public FlatRadioBase protected: FcsMode fcsMode = FCS_MODE_UNDEFINED; + Ieee80211SecondaryChannelOffset htSecondaryChannelOffset = IEEE80211_SECONDARY_CHANNEL_NONE; + std::unique_ptr ccaSnapshot; + std::string opMode; + const Ieee80211ModeSet *modeSet = nullptr; + const IIeee80211Band *band = nullptr; protected: virtual void initialize(int stage) override; @@ -43,9 +49,16 @@ class INET_API Ieee80211Radio : public FlatRadioBase virtual void encapsulate(Packet *packet) const override; virtual void decapsulate(Packet *packet) const override; + virtual bool computeIsBandBusy(Hz centerFrequency) const; + virtual void updateCcaState(); + virtual void updateTransceiverState() override; + public: Ieee80211Radio(); + virtual const Ieee80211CcaSnapshot& getCcaSnapshot() const override { return *ccaSnapshot; } + virtual const Ieee80211Channel *getChannel() const { return check_and_cast(receiver)->getChannel(); } + virtual void setModeSet(const Ieee80211ModeSet *modeSet); virtual void setMode(const IIeee80211Mode *mode); virtual void setBand(const IIeee80211Band *band); diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.ned b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.ned index c32061114d3..b5c6361721c 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.ned +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.ned @@ -28,8 +28,10 @@ module Ieee80211Radio extends FlatRadioBase 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) + string htSecondaryChannelOffset @enum("none", "above", "below") = default("none"); // HT40 secondary channel relative to the primary channel string fcsMode @enum("declared","computed") = default("declared"); transmitter.typename = default("Ieee80211Transmitter"); + transmitter.channelNumber = this.channelNumber; receiver.typename = default("Ieee80211Receiver"); receiver.errorModel.typename = default(signalAnalogRepresentation != "unitDisk" ? "Ieee80211NistErrorModel" : ""); centerFrequency @unit(Hz) = default(2.412GHz); // The center frequency of the band is automatically set by the radio @@ -44,9 +46,9 @@ module Ieee80211Radio extends FlatRadioBase receiver.snirThreshold = default(4dB); *.opMode = this.opMode; *.bandName = this.bandName; - *.channelNumber = this.channelNumber; @class(Ieee80211Radio); @signal[radioChannelChanged](type=long); + @signal[ccaStateChanged]; @statistic[radioChannel](title="Radio channel"; source=radioChannelChanged; record=histogram,vector; interpolationmode=sample-hold); } diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.cc b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.cc index 708a876eb8f..62d51b7fcc8 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.cc +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.cc @@ -7,6 +7,14 @@ #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h" +#include "inet/common/math/Functions.h" +#include "inet/physicallayer/wireless/common/contract/packetlevel/INarrowbandSignalAnalogModel.h" +#include "inet/physicallayer/wireless/common/contract/packetlevel/IRadioMedium.h" +#include "inet/physicallayer/wireless/common/radio/packetlevel/BandListening.h" +#include "inet/physicallayer/wireless/common/radio/packetlevel/ListeningDecision.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ErpOfdmMode.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211OfdmMode.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211ControlInfo_m.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmission.h" @@ -26,6 +34,9 @@ void Ieee80211Receiver::initialize(int stage) { FlatReceiverBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { + htCca20Sensitivity = mW(math::dBmW2mW(par("htCca20Sensitivity"))); + htCca40Sensitivity = mW(math::dBmW2mW(par("htCca40Sensitivity"))); + htCcaEnergyDetection = mW(math::dBmW2mW(par("htCcaEnergyDetection"))); const char *opMode = par("opMode"); setModeSet(*opMode ? Ieee80211ModeSet::getModeSet(opMode) : nullptr); const char *bandName = par("bandName"); @@ -59,6 +70,98 @@ bool Ieee80211Receiver::computeIsReceptionPossible(const IListening *listening, return ieee80211Transmission && modeSet->containsMode(ieee80211Transmission->getMode()) && getAnalogModel()->computeIsReceptionPossible(listening, reception, sensitivity); } +const IListeningDecision *Ieee80211Receiver::computeListeningDecision(const IListening *listening, const IInterference *interference) const +{ + if (isHtCcaOperation() && dynamic_cast(listening) != nullptr && + dynamic_cast(listening)->getBandwidth() == MHz(20)) + return new ListeningDecision(listening, computeHtCcaBusy(listening, interference)); + return FlatReceiverBase::computeListeningDecision(listening, interference); +} + +bool Ieee80211Receiver::isHtCcaOperation() const +{ + return modeSet != nullptr && !strcmp(modeSet->getName(), "n(mixed-2.4Ghz)") && + channel != nullptr && (bandwidth == MHz(20) || + (bandwidth == MHz(40) && channel->getSecondaryChannelOffset() != IEEE80211_SECONDARY_CHANNEL_NONE)); +} + +static bool isBandOverlapping(const BandListening *listening, const INarrowbandSignalAnalogModel *signal) +{ + auto listeningMin = listening->getCenterFrequency() - listening->getBandwidth() / 2; + auto listeningMax = listening->getCenterFrequency() + listening->getBandwidth() / 2; + auto signalMin = signal->getCenterFrequency() - signal->getBandwidth() / 2; + auto signalMax = signal->getCenterFrequency() + signal->getBandwidth() / 2; + return signalMin <= listeningMax && signalMax >= listeningMin; +} + +static bool isPrimaryChannel(const Ieee80211Channel *channel, const BandListening *listening) +{ + return channel != nullptr && listening->getCenterFrequency() == channel->getCenterFrequency(); +} + +static bool isSecondaryChannel(const Ieee80211Channel *channel, const BandListening *listening) +{ + return channel != nullptr && channel->getSecondaryChannelOffset() != IEEE80211_SECONDARY_CHANNEL_NONE && + listening->getCenterFrequency() == channel->getSecondaryCenterFrequency(); +} + +static bool isHt40SignalOccupyingChannel(const Ieee80211Channel *channel, const INarrowbandSignalAnalogModel *signal) +{ + return channel != nullptr && signal->getBandwidth() == MHz(40) && + signal->getCenterFrequency() == channel->getBondedCenterFrequency(); +} + +bool Ieee80211Receiver::computeHtCcaBusy(const IListening *listening, const IInterference *interference) const +{ + const auto *bandListening = check_and_cast(listening); + const auto *mediumAnalogModel = listening->getReceiverRadio()->getMedium()->getAnalogModel(); + const INoise *noise = mediumAnalogModel->computeNoise(listening, interference); + bool busy = noise->computeMaxPower(listening->getStartTime(), listening->getEndTime()) >= htCcaEnergyDetection; + delete noise; + if (busy) + return true; + + const bool primary = isPrimaryChannel(channel, bandListening); + const bool secondary = isSecondaryChannel(channel, bandListening); + if (!primary && !secondary) + return false; + + for (auto reception : *interference->getInterferingReceptions()) { + const auto *transmission = dynamic_cast(reception->getTransmission()); + const auto *signal = dynamic_cast(reception->getAnalogModel()); + if (transmission == nullptr || transmission->getMode() == nullptr || signal == nullptr || + !modeSet->containsMode(transmission->getMode()) || !isBandOverlapping(bandListening, signal)) + continue; + + const W signalPower = signal->computeMinPower(reception->getStartTime(), reception->getEndTime()); + const IIeee80211Mode *mode = transmission->getMode(); + bool isHt = dynamic_cast(mode) != nullptr; + bool isOfdmOrErp = (dynamic_cast(mode) != nullptr) || + (dynamic_cast(mode) != nullptr); + const Hz signalBandwidth = mode->getDataMode()->getBandwidth(); + if (isHt) { + // IEEE Std 802.11-2024, 19.3.19.6.4 and 19.3.19.6.5: + // a 20 MHz HT signal is detected on the primary channel at + // -82 dBm; a 40 MHz HT signal is detected on each occupied + // channel at -79 dBm. The comparison uses the received signal + // level over the PPDU bandwidth, not the power apportioned to a + // 20 MHz slice by the analog interference model. + if (signalBandwidth == MHz(40) && isHtCcaOperation() && + isHt40SignalOccupyingChannel(channel, signal) && signalPower >= htCca40Sensitivity) + return true; + if (signalBandwidth == MHz(20) && primary && signalPower >= htCca20Sensitivity) + return true; + } + else if (primary && isOfdmOrErp && signalPower >= htCca20Sensitivity) { + // Clause 19.3.19.6.3 delegates non-HT CCA to the OFDM/ERP-OFDM + // preamble-detection requirement, which uses the 20 MHz + // sensitivity threshold. + return true; + } + } + return false; +} + const IReceptionResult *Ieee80211Receiver::computeReceptionResult(const IListening *listening, const IReception *reception, const IInterference *interference, const ISnir *snir, const std::vector *decisions) const { auto transmission = check_and_cast(reception->getTransmission()); @@ -77,25 +180,32 @@ void Ieee80211Receiver::setModeSet(const Ieee80211ModeSet *modeSet) void Ieee80211Receiver::setBand(const IIeee80211Band *band) { if (this->band != band) { - this->band = band; if (channel != nullptr) - setChannel(new Ieee80211Channel(band, channel->getChannelNumber())); + setChannel(new Ieee80211Channel(band, channel->getChannelNumber(), channel->getSecondaryChannelOffset())); + else + this->band = band; } } void Ieee80211Receiver::setChannel(const Ieee80211Channel *channel) { if (this->channel != channel) { + // IEEE Std 802.11-2024, 19.3.15.4 and 19.3.19.6.5: HT40 listening + // spans both 20 MHz channels around their bonded center. + auto centerFrequency = channel->getSecondaryChannelOffset() == IEEE80211_SECONDARY_CHANNEL_NONE ? + channel->getCenterFrequency() : channel->getBondedCenterFrequency(); delete this->channel; this->channel = channel; - setCenterFrequency(channel->getCenterFrequency()); + this->band = channel->getBand(); + setCenterFrequency(centerFrequency); } } void Ieee80211Receiver::setChannelNumber(int channelNumber) { if (channel == nullptr || channelNumber != channel->getChannelNumber()) - setChannel(new Ieee80211Channel(band, channelNumber)); + setChannel(new Ieee80211Channel(band, channelNumber, channel == nullptr ? + IEEE80211_SECONDARY_CHANNEL_NONE : channel->getSecondaryChannelOffset())); } } // namespace physicallayer diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h index 1a9bbab8365..0e9bcec7351 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h @@ -20,6 +20,10 @@ namespace physicallayer { class INET_API Ieee80211Receiver : public FlatReceiverBase { protected: + W htCca20Sensitivity = W(NaN); + W htCca40Sensitivity = W(NaN); + W htCcaEnergyDetection = W(NaN); + const Ieee80211ModeSet *modeSet = nullptr; const IIeee80211Band *band = nullptr; const Ieee80211Channel *channel = nullptr; @@ -30,11 +34,18 @@ class INET_API Ieee80211Receiver : public FlatReceiverBase virtual bool computeIsReceptionPossible(const IListening *listening, const ITransmission *transmission) const override; virtual bool computeIsReceptionPossible(const IListening *listening, const IReception *reception, IRadioSignal::SignalPart part) const override; + virtual const IListeningDecision *computeListeningDecision(const IListening *listening, const IInterference *interference) const override; + virtual bool isHtCcaOperation() const; + virtual bool computeHtCcaBusy(const IListening *listening, const IInterference *interference) const; + virtual const IReceptionResult *computeReceptionResult(const IListening *listening, const IReception *reception, const IInterference *interference, const ISnir *snir, const std::vector *decisions) const override; public: virtual ~Ieee80211Receiver(); + virtual const Ieee80211ModeSet *getModeSet() const { return modeSet; } + virtual const Ieee80211Channel *getChannel() const { return channel; } + virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override; virtual void setModeSet(const Ieee80211ModeSet *modeSet); diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.ned b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.ned index c0c19e2afc2..f9df80b719a 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.ned +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.ned @@ -25,7 +25,10 @@ module Ieee80211Receiver extends NarrowbandReceiverBase parameters: string opMode @enum("a","b","g(erp)","g(mixed)","n(mixed-2.4Ghz)","p","ac"); string bandName @enum("2.4 GHz","5 GHz","5 GHz (20 MHz)","5 GHz (40 MHz)","5 GHz (80 MHz)","5 GHz (160 MHz)","5.9 GHz"); - int channelNumber; + double htCca20Sensitivity @unit(dBm) = default(-82 dBm); // IEEE 802.11 HT 20 MHz CCA sensitivity + double htCca40Sensitivity @unit(dBm) = default(-79 dBm); // IEEE 802.11 HT 40 MHz CCA sensitivity + double htCcaEnergyDetection @unit(dBm) = default(-62 dBm); // IEEE 802.11 HT energy-detection threshold + int channelNumber = default(-1); modulation = default("BPSK"); // TODO this is simply wrong errorModel.typename = default("Ieee80211NistErrorModel"); // TODO remove when opMode is removed from the error model diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc index c70d545570f..b91589d1732 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc @@ -12,6 +12,7 @@ #include "inet/physicallayer/wireless/common/contract/packetlevel/IRadio.h" #include "inet/physicallayer/wireless/common/contract/packetlevel/RadioControlInfo_m.h" #include "inet/physicallayer/wireless/common/contract/packetlevel/SignalTag_m.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211PhyHeader_m.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" @@ -95,7 +96,7 @@ void Ieee80211Transmitter::setBand(const IIeee80211Band *band) if (this->band != band) { this->band = band; if (channel != nullptr) - setChannel(new Ieee80211Channel(band, channel->getChannelNumber())); + setChannel(new Ieee80211Channel(band, channel->getChannelNumber(), channel->getSecondaryChannelOffset())); } } @@ -111,7 +112,8 @@ void Ieee80211Transmitter::setChannel(const Ieee80211Channel *channel) void Ieee80211Transmitter::setChannelNumber(int channelNumber) { if (channel == nullptr || channelNumber != channel->getChannelNumber()) - setChannel(new Ieee80211Channel(band, channelNumber)); + setChannel(new Ieee80211Channel(band, channelNumber, channel == nullptr ? + IEEE80211_SECONDARY_CHANNEL_NONE : channel->getSecondaryChannelOffset())); } std::ostream& Ieee80211Transmitter::printToStream(std::ostream& stream, int level, int evFlags) const @@ -145,7 +147,12 @@ const ITransmission *Ieee80211Transmitter::createTransmission(const IRadio *tran const simtime_t preambleDuration = transmissionMode->getPreambleMode()->getDuration(); const simtime_t headerDuration = transmissionMode->getHeaderMode()->getDuration(); const simtime_t dataDuration = duration - headerDuration - preambleDuration; - auto analogModel = getAnalogModel()->createAnalogModel(preambleDuration, headerDuration, dataDuration, centerFrequency, transmissionBandwidth, transmissionPower); + // IEEE Std 802.11-2024, 19.3.15.4: an HT40 PPDU occupies the primary + // and secondary 20 MHz channels and is centered halfway between them. + Hz transmissionCenterFrequency = dynamic_cast(transmissionMode) != nullptr && + transmissionBandwidth == MHz(40) ? transmissionChannel->getBondedCenterFrequency() : + transmissionChannel->getCenterFrequency(); + auto analogModel = getAnalogModel()->createAnalogModel(preambleDuration, headerDuration, dataDuration, transmissionCenterFrequency, transmissionBandwidth, transmissionPower); return new Ieee80211Transmission(transmitter, packet, startTime, endTime, preambleDuration, headerDuration, dataDuration, startPosition, endPosition, startOrientation, endOrientation, nullptr, nullptr, nullptr, nullptr, analogModel, transmissionMode, transmissionChannel); } diff --git a/tests/unit/Ieee80211Ht40SecondaryChannel_1.test b/tests/unit/Ieee80211Ht40SecondaryChannel_1.test new file mode 100644 index 00000000000..4db14033b8c --- /dev/null +++ b/tests/unit/Ieee80211Ht40SecondaryChannel_1.test @@ -0,0 +1,70 @@ +%description: +Validate HT 40 MHz primary and secondary channel geometry and offset parsing. + +%includes: +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Band.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.h" + +%global: +using namespace inet; +using namespace inet::physicallayer; + +%activity: +{ + // Test parsing + ASSERT(Ieee80211Channel::parseSecondaryChannelOffset("none") == IEEE80211_SECONDARY_CHANNEL_NONE); + ASSERT(Ieee80211Channel::parseSecondaryChannelOffset("above") == IEEE80211_SECONDARY_CHANNEL_ABOVE); + ASSERT(Ieee80211Channel::parseSecondaryChannelOffset("below") == IEEE80211_SECONDARY_CHANNEL_BELOW); + ASSERT(std::string(Ieee80211Channel::getSecondaryChannelOffsetName(IEEE80211_SECONDARY_CHANNEL_NONE)) == "none"); + ASSERT(std::string(Ieee80211Channel::getSecondaryChannelOffsetName(IEEE80211_SECONDARY_CHANNEL_ABOVE)) == "above"); + ASSERT(std::string(Ieee80211Channel::getSecondaryChannelOffsetName(IEEE80211_SECONDARY_CHANNEL_BELOW)) == "below"); + EV << "Secondary channel offset parsing verified.\n"; + + const IIeee80211Band *band24 = Ieee80211CompliantBands::getBand("2.4 GHz"); + ASSERT(band24 != nullptr); + + // Channel 0 (IEEE 1, 2412 MHz) with secondary above (+20 MHz -> IEEE 5, 2432 MHz, bonded 2422 MHz) + Ieee80211Channel ch0Above(band24, 0, IEEE80211_SECONDARY_CHANNEL_ABOVE); + ASSERT(ch0Above.getChannelNumber() == 0); + ASSERT(ch0Above.getSecondaryChannelOffset() == IEEE80211_SECONDARY_CHANNEL_ABOVE); + ASSERT(ch0Above.getCenterFrequency() == Hz(2412000000.0)); + ASSERT(ch0Above.getSecondaryCenterFrequency() == Hz(2432000000.0)); + ASSERT(ch0Above.getBondedCenterFrequency() == Hz(2422000000.0)); + ASSERT(ch0Above.getSecondaryChannelNumber() == 4); + EV << "HT40+ channel 0 center=" << ch0Above.getCenterFrequency().get() + << " secondary=" << ch0Above.getSecondaryCenterFrequency().get() + << " bonded=" << ch0Above.getBondedCenterFrequency().get() << "\n"; + + // Channel 4 (IEEE 5, 2432 MHz) with secondary below (-20 MHz -> IEEE 1, 2412 MHz, bonded 2422 MHz) + Ieee80211Channel ch4Below(band24, 4, IEEE80211_SECONDARY_CHANNEL_BELOW); + ASSERT(ch4Below.getChannelNumber() == 4); + ASSERT(ch4Below.getSecondaryChannelOffset() == IEEE80211_SECONDARY_CHANNEL_BELOW); + ASSERT(ch4Below.getCenterFrequency() == Hz(2432000000.0)); + ASSERT(ch4Below.getSecondaryCenterFrequency() == Hz(2412000000.0)); + ASSERT(ch4Below.getBondedCenterFrequency() == Hz(2422000000.0)); + ASSERT(ch4Below.getSecondaryChannelNumber() == 0); + EV << "HT40- channel 4 center=" << ch4Below.getCenterFrequency().get() + << " secondary=" << ch4Below.getSecondaryCenterFrequency().get() + << " bonded=" << ch4Below.getBondedCenterFrequency().get() << "\n"; + + // Validate invalid secondary offset exception + bool exceptionCaught = false; + try { + Ieee80211Channel ch0Below(band24, 0, IEEE80211_SECONDARY_CHANNEL_BELOW); + (void)ch0Below.getSecondaryCenterFrequency(); + } + catch (std::exception& e) { + exceptionCaught = true; + } + ASSERT(exceptionCaught); + EV << "Invalid secondary channel offset correctly rejected.\n"; +} + +EV << ".\n"; + +%contains: stdout +Secondary channel offset parsing verified. +HT40+ channel 0 center=2.412e+09 secondary=2.432e+09 bonded=2.422e+09 +HT40- channel 4 center=2.432e+09 secondary=2.412e+09 bonded=2.422e+09 +Invalid secondary channel offset correctly rejected. +. diff --git a/tests/unit/Ieee80211HtCcaSensitivity_1.test b/tests/unit/Ieee80211HtCcaSensitivity_1.test new file mode 100644 index 00000000000..446a17e5de9 --- /dev/null +++ b/tests/unit/Ieee80211HtCcaSensitivity_1.test @@ -0,0 +1,51 @@ +%description: +Validate IEEE 802.11n HT Clear Channel Assessment (CCA) multi-threshold sensing logic. + +%includes: +#include "inet/common/math/Functions.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Band.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h" + +%global: +using namespace inet; +using namespace inet::physicallayer; + +%activity: +{ + const IIeee80211Band *band24 = Ieee80211CompliantBands::getBand("2.4 GHz"); + ASSERT(band24 != nullptr); + + Ieee80211Channel ch0Above(band24, 0, IEEE80211_SECONDARY_CHANNEL_ABOVE); + ASSERT(ch0Above.getSecondaryChannelOffset() == IEEE80211_SECONDARY_CHANNEL_ABOVE); + ASSERT(ch0Above.getBondedCenterFrequency() == Hz(2422000000.0)); + + // Sensitivities per IEEE Std 802.11-2024 Clause 19.3.19.6 + W htCca20 = mW(math::dBmW2mW(-82.0)); + W htCca40 = mW(math::dBmW2mW(-79.0)); + W htCcaEd = mW(math::dBmW2mW(-62.0)); + + ASSERT(htCca20 < htCca40); + ASSERT(htCca40 < htCcaEd); + + EV << "HT CCA 20MHz threshold: -82 dBm\n"; + EV << "HT CCA 40MHz threshold: -79 dBm\n"; + EV << "HT CCA Energy Detection threshold: -62 dBm\n"; + + const Ieee80211ModeSet *modeSet = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"); + ASSERT(modeSet != nullptr); + ASSERT(std::string(modeSet->getName()) == "n(mixed-2.4Ghz)"); + + EV << "HT ModeSet n(mixed-2.4Ghz) verified\n"; +} + +EV << ".\n"; + +%contains: stdout +HT CCA 20MHz threshold: -82 dBm +HT CCA 40MHz threshold: -79 dBm +HT CCA Energy Detection threshold: -62 dBm +HT ModeSet n(mixed-2.4Ghz) verified +. From 705455c59a300a6ed92056bafb54650094552f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Thu, 20 Aug 2026 11:37:46 +0200 Subject: [PATCH 02/10] Hcf: handle internally collided EDCAFs before HT40 backoff restart - Processed internally collided EDCAFs and emitted edcaCollisionDetectedSignal before checking the shouldRestartHt40ChannelAccess condition in channelGranted. - Ensures lower-priority ACs that lost internal contention are restarted and do not stall when the winning AC defers transmission due to secondary channel busy condition. --- .../ieee80211/mac/coordinationfunction/Hcf.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc index ce63778bccc..b9da1ad7853 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc @@ -219,18 +219,18 @@ void Hcf::channelGranted(IChannelAccess *channelAccess) if (edcaf) { AccessCategory ac = edcaf->getAccessCategory(); EV_DETAIL << "Channel access granted to the " << printAccessCategory(ac) << " queue" << std::endl; - if (shouldRestartHt40ChannelAccess(edcaf)) { - EV_INFO << "Secondary channel was busy during DIFS before channel access for HT40 transmission, restarting backoff.\n"; - edcaf->restartChannelAccess(this); - return; - } - edcaf->getTxopProcedure()->startTxop(ac); auto internallyCollidedEdcafs = edca->getInternallyCollidedEdcafs(); if (internallyCollidedEdcafs.size() > 0) { EV_INFO << "Internal collision happened with the following queues:" << std::endl; handleInternalCollision(internallyCollidedEdcafs); emit(edcaCollisionDetectedSignal, (unsigned long)internallyCollidedEdcafs.size()); } + if (shouldRestartHt40ChannelAccess(edcaf)) { + EV_INFO << "Secondary channel was busy during DIFS before channel access for HT40 transmission, restarting backoff.\n"; + edcaf->restartChannelAccess(this); + return; + } + edcaf->getTxopProcedure()->startTxop(ac); startFrameSequence(ac); } else From 2ce368be2fe09ef0d31529d7cdda3b4902501a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Thu, 20 Aug 2026 12:07:18 +0200 Subject: [PATCH 03/10] Ieee80211: fix radio reconfiguration on channel change, clean up CCA state and MIB - Fix Ieee80211Radio::handleUpperCommand to avoid resolving mode from fixed bitrate during channel-only reconfigurations - Order setModeSet before setMode in Ieee80211Radio and delegate to FlatRadioBase - Remove unused htShortGi40 and htMaxMcs parameters from Ieee80211Mib.ned - Clean up secondaryCcaIdleSince handling in Rx::ccaStateChanged - Add unit test Ieee80211RadioReconfiguration_1 --- src/inet/linklayer/ieee80211/mac/Rx.cc | 6 +- .../linklayer/ieee80211/mib/Ieee80211Mib.ned | 2 - .../ieee80211/packetlevel/Ieee80211Radio.cc | 25 +++---- .../unit/Ieee80211RadioReconfiguration_1.test | 68 +++++++++++++++++++ 4 files changed, 80 insertions(+), 21 deletions(-) create mode 100644 tests/unit/Ieee80211RadioReconfiguration_1.test diff --git a/src/inet/linklayer/ieee80211/mac/Rx.cc b/src/inet/linklayer/ieee80211/mac/Rx.cc index beb37d48082..47c001ab6dc 100644 --- a/src/inet/linklayer/ieee80211/mac/Rx.cc +++ b/src/inet/linklayer/ieee80211/mac/Rx.cc @@ -181,12 +181,10 @@ void Rx::ccaStateChanged(const Ieee80211CcaSnapshot& snapshot) ht40Cca = snapshot.isHt40(); primaryCcaBusy = snapshot.isPrimaryBusy(); secondaryCcaBusy = snapshot.isSecondaryBusy(); - if (!ht40Cca) + if (!ht40Cca || secondaryCcaBusy) secondaryCcaIdleSince = -1; - else if (!wasHt40Cca || (wasSecondaryBusy && !secondaryCcaBusy)) + else if (!wasHt40Cca || wasSecondaryBusy) secondaryCcaIdleSince = simTime(); - else if (secondaryCcaBusy) - secondaryCcaIdleSince = -1; recomputeMediumFree(); } diff --git a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned index 1ec93153e82..eeac3b2abe5 100644 --- a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned +++ b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned @@ -21,8 +21,6 @@ simple Ieee80211Mib extends SimpleModule parameters: @class(Ieee80211Mib); string htSecondaryChannelOffset @enum("none", "above", "below") = default("none"); - bool htShortGi40 = default(false); - int htMaxMcs = default(7); displayStringTextFormat = default("Address: {address}{ssidStr}\n{modeStr}{stationTypeStr}{qosStr}{associatedStr}"); @display("i=block/table"); } diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc index 010e139ba61..e41a29a540e 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc @@ -128,7 +128,6 @@ void Ieee80211Radio::handleUpperCommand(cMessage *message) if (message->getKind() == RADIO_C_CONFIGURE) { Ieee80211ConfigureRadioCommand *configureCommand = dynamic_cast(message->getControlInfo()); if (configureCommand != nullptr) { - Ieee80211Transmitter *ieee80211Transmitter = const_cast(check_and_cast(transmitter)); Ieee80211Receiver *ieee80211Receiver = const_cast(check_and_cast(receiver)); const Ieee80211Channel *currentChannel = ieee80211Receiver->getChannel(); const char *requestedOpMode = configureCommand->getOpMode(); @@ -153,14 +152,16 @@ void Ieee80211Radio::handleUpperCommand(cMessage *message) Hz newBandwidth = configureCommand->getBandwidth(); Hz targetBandwidth = std::isnan(newBandwidth.get()) ? ieee80211Receiver->getBandwidth() : newBandwidth; bps newBitrate = configureCommand->getBitrate(); - bps targetBitrate = std::isnan(newBitrate.get()) ? ieee80211Transmitter->getBitrate() : newBitrate; const IIeee80211Mode *mode = configureCommand->getMode(); - Hz targetModeBandwidth = !std::isnan(targetBandwidth.get()) ? targetBandwidth : Hz(MHz(20)); - const IIeee80211Mode *resolvedMode = mode != nullptr ? mode : nullptr; - if (resolvedMode == nullptr && targetModeSet != nullptr && targetBitrate != bps(-1)) - resolvedMode = targetModeSet->getMode(targetBitrate, targetModeBandwidth); + const IIeee80211Mode *resolvedMode = mode; + if (resolvedMode == nullptr && targetModeSet != nullptr && !std::isnan(newBitrate.get())) { + if (!std::isnan(newBandwidth.get())) + resolvedMode = targetModeSet->getMode(newBitrate, newBandwidth); + else + resolvedMode = targetModeSet->getMode(newBitrate); + } if (targetModeSet != nullptr && !strcmp(targetModeSet->getName(), "n(mixed-2.4Ghz)") && - ((targetModeBandwidth == MHz(40)) || + ((targetBandwidth == MHz(40)) || (resolvedMode != nullptr && dynamic_cast(resolvedMode) != nullptr && resolvedMode->getDataMode()->getBandwidth() == MHz(40))) && targetSecondaryChannelOffset == IEEE80211_SECONDARY_CHANNEL_NONE) @@ -169,10 +170,6 @@ void Ieee80211Radio::handleUpperCommand(cMessage *message) bool publishModeSet = targetModeSet != this->modeSet || targetBand != this->band || !std::isnan(newBandwidth.get()) || !std::isnan(newBitrate.get()) || *requestedOpMode; - if (!std::isnan(newBandwidth.get())) - setBandwidth(newBandwidth); - if (!std::isnan(newBitrate.get())) - setBitrate(newBitrate); if (targetChannelNumber != -1 && (currentChannel == nullptr || targetBand != this->band || targetChannelNumber != currentChannel->getChannelNumber() || targetSecondaryChannelOffset != currentChannel->getSecondaryChannelOffset())) @@ -180,12 +177,10 @@ void Ieee80211Radio::handleUpperCommand(cMessage *message) else if (targetBand != this->band) setBand(targetBand); this->opMode = targetOpMode; - if (resolvedMode != nullptr) - setMode(resolvedMode); if (publishModeSet && targetModeSet != nullptr) setModeSet(targetModeSet); - delete message; - return; + if (resolvedMode != nullptr) + setMode(resolvedMode); } } FlatRadioBase::handleUpperCommand(message); diff --git a/tests/unit/Ieee80211RadioReconfiguration_1.test b/tests/unit/Ieee80211RadioReconfiguration_1.test new file mode 100644 index 00000000000..22966c34e53 --- /dev/null +++ b/tests/unit/Ieee80211RadioReconfiguration_1.test @@ -0,0 +1,68 @@ +%description: +Validate IEEE 802.11 radio reconfiguration: mode resolution with fixed bitrates and channel switching. + +%includes: +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Band.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" +#include "inet/physicallayer/wireless/ieee80211/mode/IIeee80211Mode.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211ControlInfo_m.h" + +%global: +using namespace inet; +using namespace inet::physicallayer; + +%activity: +{ + // Validate Mode resolution for fixed bitrates in g(mixed) + const Ieee80211ModeSet *modeSetG = Ieee80211ModeSet::getModeSet("g(mixed)"); + ASSERT(modeSetG != nullptr); + + // 54 Mbps OFDM rate in g(mixed) has 20 MHz bandwidth + const IIeee80211Mode *mode54 = modeSetG->getMode(bps(54000000)); + ASSERT(mode54 != nullptr); + ASSERT(mode54->getDataMode()->getBandwidth() == MHz(20)); + ASSERT(mode54->getDataMode()->getNetBitrate() == bps(54000000)); + EV << "Resolved 54Mbps mode bitrate=" << mode54->getDataMode()->getNetBitrate().get() << " bw=" << mode54->getDataMode()->getBandwidth().get() << "\n"; + + // 11 Mbps DSSS/CCK rate has 22 MHz bandwidth + const IIeee80211Mode *mode11 = modeSetG->getMode(bps(11000000)); + ASSERT(mode11 != nullptr); + ASSERT(mode11->getDataMode()->getBandwidth() == MHz(22)); + ASSERT(mode11->getDataMode()->getNetBitrate() == bps(11000000)); + EV << "Resolved 11Mbps mode bitrate=" << mode11->getDataMode()->getNetBitrate().get() << " bw=" << mode11->getDataMode()->getBandwidth().get() << "\n"; + + // Mode resolution for HT MCS rates in n(mixed-2.4Ghz) + const Ieee80211ModeSet *modeSetN = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"); + ASSERT(modeSetN != nullptr); + + const IIeee80211Mode *modeMcs7_20 = modeSetN->getMode(bps(65000000), Hz(MHz(20)), -1); + ASSERT(modeMcs7_20 != nullptr); + ASSERT(modeMcs7_20->getDataMode()->getBandwidth() == MHz(20)); + ASSERT(modeMcs7_20->getDataMode()->getNetBitrate() == bps(65000000)); + EV << "Resolved MCS7 20MHz bitrate=" << modeMcs7_20->getDataMode()->getNetBitrate().get() << " bw=" << modeMcs7_20->getDataMode()->getBandwidth().get() << "\n"; + + const IIeee80211Mode *modeMcs7_40 = modeSetN->getMode(bps(135000000), Hz(MHz(40)), -1); + ASSERT(modeMcs7_40 != nullptr); + ASSERT(modeMcs7_40->getDataMode()->getBandwidth() == MHz(40)); + ASSERT(modeMcs7_40->getDataMode()->getNetBitrate() == bps(135000000)); + EV << "Resolved MCS7 40MHz bitrate=" << modeMcs7_40->getDataMode()->getNetBitrate().get() << " bw=" << modeMcs7_40->getDataMode()->getBandwidth().get() << "\n"; + + // Validate ConfigureRadioCommand structure + Ieee80211ConfigureRadioCommand cmd; + cmd.setChannelNumber(4); + ASSERT(cmd.getChannelNumber() == 4); + ASSERT(std::isnan(cmd.getBitrate().get())); + ASSERT(cmd.getMode() == nullptr); + EV << "ConfigureRadioCommand channel change only verified.\n"; +} + +EV << ".\n"; + +%contains: stdout +Resolved 54Mbps mode bitrate=5.4e+07 bw=2e+07 +Resolved 11Mbps mode bitrate=1.1e+07 bw=2.2e+07 +Resolved MCS7 20MHz bitrate=6.5e+07 bw=2e+07 +Resolved MCS7 40MHz bitrate=1.35e+08 bw=4e+07 +ConfigureRadioCommand channel change only verified. +. From a83ec65c553fd302231908d27cce30694240f033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Thu, 20 Aug 2026 12:37:08 +0200 Subject: [PATCH 04/10] Ieee80211: fix contention callback reentrancy and radio band resolution - Contention: snapshot and clear callback before channelAccessGranted to safely support reentrant startContention upon secondary channel restart - Edcaf/Dcaf: harmonize assertions in restartChannelAccess - Ieee80211Radio: derive targetBand from configureCommand channel object when bandParam is not explicitly specified - Tests: add channel object band test case to Ieee80211RadioReconfiguration_1 --- .gitignore | 2 ++ .../linklayer/ieee80211/mac/channelaccess/Dcaf.cc | 2 +- .../linklayer/ieee80211/mac/channelaccess/Edcaf.cc | 2 +- .../ieee80211/mac/contention/Contention.cc | 3 ++- .../ieee80211/packetlevel/Ieee80211Radio.cc | 5 +++-- tests/unit/Ieee80211RadioReconfiguration_1.test | 14 ++++++++++++++ 6 files changed, 23 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 42697a63f25..78d49aec119 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +#local files +implementation_plan* # local and generated files not to be committed doc/src/showcases doc/src/tutorials diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc index d6e7235f51e..d9ea4a307e0 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc @@ -108,7 +108,7 @@ void Dcaf::releaseChannel(IChannelAccess::ICallback *callback) void Dcaf::restartChannelAccess(IChannelAccess::ICallback *callback) { Enter_Method("restartChannelAccess"); - ASSERT(owning && this->callback == callback); + ASSERT(owning && (callback == nullptr || this->callback == callback)); owning = false; emit(channelOwnershipChangedSignal, owning); // IEEE Std 802.11-2024, 11.15.9 item b): retain the current CW and draw a diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc index d3cea666a84..b5f8dc74bd9 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc @@ -146,7 +146,7 @@ void Edcaf::releaseChannel(IChannelAccess::ICallback *callback) void Edcaf::restartChannelAccess(IChannelAccess::ICallback *callback) { Enter_Method("restartChannelAccess"); - ASSERT(owning); + ASSERT(owning && (callback == nullptr || this->callback == callback)); owning = false; emit(channelOwnershipChangedSignal, owning); // IEEE Std 802.11-2024, 11.15.9 item b): retain CW[AC], select a new diff --git a/src/inet/linklayer/ieee80211/mac/contention/Contention.cc b/src/inet/linklayer/ieee80211/mac/contention/Contention.cc index 4a1471e28f1..87d2a7ac058 100644 --- a/src/inet/linklayer/ieee80211/mac/contention/Contention.cc +++ b/src/inet/linklayer/ieee80211/mac/contention/Contention.cc @@ -171,8 +171,9 @@ void Contention::handleMessage(cMessage *msg) else if (msg == channelGrantedEvent) { EV_INFO << "Channel granted: startTime = " << startTime << std::endl; emit(channelAccessGrantedSignal, this); - callback->channelAccessGranted(); + auto cb = callback; callback = nullptr; + cb->channelAccessGranted(); } else throw cRuntimeError("Unknown msg"); diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc index e41a29a540e..6f794b9b9a7 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc @@ -132,12 +132,13 @@ void Ieee80211Radio::handleUpperCommand(cMessage *message) const Ieee80211Channel *currentChannel = ieee80211Receiver->getChannel(); const char *requestedOpMode = configureCommand->getOpMode(); std::string targetOpMode = *requestedOpMode ? requestedOpMode : this->opMode; + const Ieee80211Channel *channel = configureCommand->getChannel(); const IIeee80211Band *bandParam = configureCommand->getBand(); - const IIeee80211Band *targetBand = bandParam != nullptr ? bandParam : this->band; + const IIeee80211Band *targetBand = bandParam != nullptr ? bandParam : + (channel != nullptr && channel->getBand() != nullptr) ? channel->getBand() : this->band; const Ieee80211ModeSet *modeSetParam = configureCommand->getModeSet(); const Ieee80211ModeSet *targetModeSet = modeSetParam != nullptr ? modeSetParam : *targetOpMode.c_str() ? Ieee80211ModeSet::getModeSet(targetOpMode.c_str()) : this->modeSet; - const Ieee80211Channel *channel = configureCommand->getChannel(); int newChannelNumber = configureCommand->getChannelNumber(); int targetChannelNumber = channel != nullptr ? channel->getChannelNumber() : newChannelNumber != -1 ? newChannelNumber : diff --git a/tests/unit/Ieee80211RadioReconfiguration_1.test b/tests/unit/Ieee80211RadioReconfiguration_1.test index 22966c34e53..c1ce2cd76cc 100644 --- a/tests/unit/Ieee80211RadioReconfiguration_1.test +++ b/tests/unit/Ieee80211RadioReconfiguration_1.test @@ -55,6 +55,18 @@ using namespace inet::physicallayer; ASSERT(std::isnan(cmd.getBitrate().get())); ASSERT(cmd.getMode() == nullptr); EV << "ConfigureRadioCommand channel change only verified.\n"; + + // Validate ConfigureRadioCommand with channel object + const IIeee80211Band *band24 = Ieee80211CompliantBands::getBand("2.4 GHz"); + ASSERT(band24 != nullptr); + Ieee80211Channel channelObj(band24, 6, IEEE80211_SECONDARY_CHANNEL_ABOVE); + Ieee80211ConfigureRadioCommand cmdChannel; + cmdChannel.setChannel(&channelObj); + ASSERT(cmdChannel.getChannel() != nullptr); + ASSERT(cmdChannel.getChannel()->getBand() == band24); + ASSERT(cmdChannel.getChannel()->getChannelNumber() == 6); + ASSERT(cmdChannel.getChannel()->getSecondaryChannelOffset() == IEEE80211_SECONDARY_CHANNEL_ABOVE); + EV << "ConfigureRadioCommand with channel object verified.\n"; } EV << ".\n"; @@ -65,4 +77,6 @@ Resolved 11Mbps mode bitrate=1.1e+07 bw=2.2e+07 Resolved MCS7 20MHz bitrate=6.5e+07 bw=2e+07 Resolved MCS7 40MHz bitrate=1.35e+08 bw=4e+07 ConfigureRadioCommand channel change only verified. +ConfigureRadioCommand with channel object verified. . + From c1d40fe03c597459960880432ffb6b0857d755d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Thu, 20 Aug 2026 13:32:47 +0200 Subject: [PATCH 05/10] Ieee80211: fix HT40 medium sensing during reception, band-aware secondary idle check, and rate reconfiguration - Rx: require receptionState == IDLE in HT40 primaryPhysicallyIdle calculation to prevent transmitting during in-progress frame reception - Hcf: apply DIFS in 2.4 GHz and PIFS in 5 GHz for secondary channel idle verification per IEEE Std 802.11-2024 clause 11.15.9 item b - Ieee80211Radio: restrict publishModeSet so bitrate-only reconfigurations do not reset receiver state or emit spurious listening signals --- src/inet/linklayer/ieee80211/mac/Rx.cc | 2 +- .../ieee80211/mac/coordinationfunction/Hcf.cc | 12 +++++++----- .../wireless/ieee80211/packetlevel/Ieee80211Radio.cc | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/inet/linklayer/ieee80211/mac/Rx.cc b/src/inet/linklayer/ieee80211/mac/Rx.cc index 47c001ab6dc..cfe10da1a12 100644 --- a/src/inet/linklayer/ieee80211/mac/Rx.cc +++ b/src/inet/linklayer/ieee80211/mac/Rx.cc @@ -159,7 +159,7 @@ void Rx::recomputeMediumFree() { bool oldMediumFree = mediumFree; // note: the duration of mode switching (rx-to-tx or tx-to-rx) should also count as busy - bool primaryPhysicallyIdle = ht40Cca ? !primaryCcaBusy : receptionState == IRadio::RECEPTION_STATE_IDLE; + bool primaryPhysicallyIdle = (receptionState == IRadio::RECEPTION_STATE_IDLE) && (!ht40Cca || !primaryCcaBusy); mediumFree = primaryPhysicallyIdle && transmissionState == IRadio::TRANSMISSION_STATE_UNDEFINED && !endNavTimer->isScheduled(); if (mediumFree != oldMediumFree) { for (auto contention : contentions) diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc index b9da1ad7853..dd0265d5322 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc @@ -206,10 +206,12 @@ bool Hcf::shouldRestartHt40ChannelAccess(Edcaf *edcaf) mode->getDataMode()->getBandwidth() != MHz(40)) return false; // IEEE Std 802.11-2024, 11.15.9 item b): - // If the secondary channel was busy during DIFS before channel access, - // invoke the backoff procedure with the current CW[AC]. - simtime_t difs = modeSet->getSifsTime() + 2 * modeSet->getSlotTime(); - return !rx->isSecondaryChannelIdleFor(difs); + // Secondary channel must be idle during an interval of DIFS for the 2.4 GHz band + // and PIFS for the 5 GHz band immediately preceding the expiration of the backoff counter. + // If the secondary channel was busy during this interval, invoke the backoff procedure with the current CW[AC]. + bool is24GHz = modeSet != nullptr && strstr(modeSet->getName(), "2.4Ghz") != nullptr; + simtime_t requiredIdle = modeSet->getSifsTime() + (is24GHz ? 2 : 1) * modeSet->getSlotTime(); + return !rx->isSecondaryChannelIdleFor(requiredIdle); } void Hcf::channelGranted(IChannelAccess *channelAccess) @@ -226,7 +228,7 @@ void Hcf::channelGranted(IChannelAccess *channelAccess) emit(edcaCollisionDetectedSignal, (unsigned long)internallyCollidedEdcafs.size()); } if (shouldRestartHt40ChannelAccess(edcaf)) { - EV_INFO << "Secondary channel was busy during DIFS before channel access for HT40 transmission, restarting backoff.\n"; + EV_INFO << "Secondary channel was busy during required interval before channel access for HT40 transmission, restarting backoff.\n"; edcaf->restartChannelAccess(this); return; } diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc index 6f794b9b9a7..6e9cc062304 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc @@ -169,7 +169,7 @@ void Ieee80211Radio::handleUpperCommand(cMessage *message) throw cRuntimeError("HT 40 MHz operation requires a secondary channel offset of above or below"); bool publishModeSet = targetModeSet != this->modeSet || targetBand != this->band || - !std::isnan(newBandwidth.get()) || !std::isnan(newBitrate.get()) || *requestedOpMode; + *requestedOpMode; if (targetChannelNumber != -1 && (currentChannel == nullptr || targetBand != this->band || targetChannelNumber != currentChannel->getChannelNumber() || From a25b7c3122a7d51be0e6995c9216fc066571210d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Thu, 20 Aug 2026 14:17:25 +0200 Subject: [PATCH 06/10] Ieee80211: relax receiver center frequency matching to band containment for HT40 subchannels - NarrowbandReceiverBase: check signal band containment in listening band instead of exact center frequency match - ScalarReceiverAnalogModel / DimensionalReceiverAnalogModel: accept signals whose band is contained in listening band - ScalarMediumAnalogModel: treat receptions contained in listening band as full interference - Ieee80211LayeredOfdmReceiver / ApskLayeredReceiver: update reception possibility to use band containment - omnetpp.ini: add sameTransmissionStartTimeCheck = "ignore" in channelwidths example - Tests: add unit test Ieee80211Ht40SubchannelReception_1 --- examples/wireless/channelwidths/omnetpp.ini | 1 + .../apsk/bitlevel/ApskLayeredReceiver.cc | 6 +- .../DimensionalReceiverAnalogModel.cc | 6 +- .../scalar/ScalarMediumAnalogModel.cc | 10 ++- .../scalar/ScalarReceiverAnalogModel.cc | 6 +- .../packetlevel/NarrowbandReceiverBase.cc | 18 ++++- .../bitlevel/Ieee80211LayeredOfdmReceiver.cc | 6 +- .../Ieee80211Ht40SubchannelReception_1.test | 74 +++++++++++++++++++ 8 files changed, 119 insertions(+), 8 deletions(-) create mode 100644 tests/unit/Ieee80211Ht40SubchannelReception_1.test diff --git a/examples/wireless/channelwidths/omnetpp.ini b/examples/wireless/channelwidths/omnetpp.ini index c266d558c34..20130b4a353 100644 --- a/examples/wireless/channelwidths/omnetpp.ini +++ b/examples/wireless/channelwidths/omnetpp.ini @@ -11,6 +11,7 @@ sim-time-limit = 2s # Radio medium parameters **.radioMedium.backgroundNoise.power = -110dBm **.radioMedium.analogModel.ignorePartialInterference = true +**.radioMedium.sameTransmissionStartTimeCheck = "ignore" # Wi-Fi physical parameters **.wlan[*].opMode = "n(mixed-2.4Ghz)" diff --git a/src/inet/physicallayer/wireless/apsk/bitlevel/ApskLayeredReceiver.cc b/src/inet/physicallayer/wireless/apsk/bitlevel/ApskLayeredReceiver.cc index 34822ec9024..22ee6e39cce 100644 --- a/src/inet/physicallayer/wireless/apsk/bitlevel/ApskLayeredReceiver.cc +++ b/src/inet/physicallayer/wireless/apsk/bitlevel/ApskLayeredReceiver.cc @@ -197,7 +197,11 @@ bool ApskLayeredReceiver::computeIsReceptionPossible(const IListening *listening const BandListening *bandListening = check_and_cast(listening); // TODO scalar const ScalarReceptionAnalogModel *analogModel = check_and_cast(reception->getAnalogModel()); - if (bandListening->getCenterFrequency() != analogModel->getCenterFrequency() || bandListening->getBandwidth() != analogModel->getBandwidth()) { + auto listeningMin = bandListening->getCenterFrequency() - bandListening->getBandwidth() / 2; + auto listeningMax = bandListening->getCenterFrequency() + bandListening->getBandwidth() / 2; + auto receptionMin = analogModel->getCenterFrequency() - analogModel->getBandwidth() / 2; + auto receptionMax = analogModel->getCenterFrequency() + analogModel->getBandwidth() / 2; + if (receptionMin < listeningMin || receptionMax > listeningMax) { EV_DEBUG << "Computing reception possible: listening and reception bands are different -> reception is impossible" << endl; return false; } diff --git a/src/inet/physicallayer/wireless/common/analogmodel/dimensional/DimensionalReceiverAnalogModel.cc b/src/inet/physicallayer/wireless/common/analogmodel/dimensional/DimensionalReceiverAnalogModel.cc index 769042dca83..c658f2055af 100644 --- a/src/inet/physicallayer/wireless/common/analogmodel/dimensional/DimensionalReceiverAnalogModel.cc +++ b/src/inet/physicallayer/wireless/common/analogmodel/dimensional/DimensionalReceiverAnalogModel.cc @@ -48,7 +48,11 @@ bool DimensionalReceiverAnalogModel::computeIsReceptionPossible(const IListening const BandListening *bandListening = check_and_cast(listening); const DimensionalReceptionAnalogModel *analogModel = check_and_cast(reception->getAnalogModel()); - if (bandListening->getCenterFrequency() != analogModel->getCenterFrequency() || bandListening->getBandwidth() < analogModel->getBandwidth()) { + auto listeningMin = bandListening->getCenterFrequency() - bandListening->getBandwidth() / 2; + auto listeningMax = bandListening->getCenterFrequency() + bandListening->getBandwidth() / 2; + auto receptionMin = analogModel->getCenterFrequency() - analogModel->getBandwidth() / 2; + auto receptionMax = analogModel->getCenterFrequency() + analogModel->getBandwidth() / 2; + if (receptionMin < listeningMin || receptionMax > listeningMax) { EV_DEBUG << "Computing whether reception is possible: listening and reception bands are different -> reception is impossible" << endl; return false; } diff --git a/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarMediumAnalogModel.cc b/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarMediumAnalogModel.cc index 96cfcca0efa..9ae5dfc6164 100644 --- a/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarMediumAnalogModel.cc +++ b/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarMediumAnalogModel.cc @@ -123,20 +123,26 @@ const INoise *ScalarMediumAnalogModel::computeNoise(const IListening *listening, std::map powerChanges; powerChanges[math::getLowerBound()] = W(0); powerChanges[math::getUpperBound()] = W(0); + auto listeningMin = commonCenterFrequency - commonBandwidth / 2; + auto listeningMax = commonCenterFrequency + commonBandwidth / 2; const std::vector *interferingReceptions = interference->getInterferingReceptions(); for (auto reception : *interferingReceptions) { auto signalAnalogModel = reception->getAnalogModel(); auto receptionAnalogModel = check_and_cast(signalAnalogModel); Hz signalCenterFrequency = receptionAnalogModel->getCenterFrequency(); Hz signalBandwidth = receptionAnalogModel->getBandwidth(); - if (commonCenterFrequency == signalCenterFrequency && commonBandwidth >= signalBandwidth) + auto signalMin = signalCenterFrequency - signalBandwidth / 2; + auto signalMax = signalCenterFrequency + signalBandwidth / 2; + if (signalMin >= listeningMin && signalMax <= listeningMax) addReception(reception, noiseStartTime, noiseEndTime, powerChanges); else if (!ignorePartialInterference && areOverlappingBands(commonCenterFrequency, commonBandwidth, signalCenterFrequency, signalBandwidth)) throw cRuntimeError("Partially interfering signals are not supported by ScalarMediumAnalogModel, enable ignorePartialInterference to avoid this error!"); } const ScalarNoise *scalarBackgroundNoise = dynamic_cast(interference->getBackgroundNoise()); if (scalarBackgroundNoise) { - if (commonCenterFrequency == scalarBackgroundNoise->getCenterFrequency() && commonBandwidth >= scalarBackgroundNoise->getBandwidth()) + auto bgMin = scalarBackgroundNoise->getCenterFrequency() - scalarBackgroundNoise->getBandwidth() / 2; + auto bgMax = scalarBackgroundNoise->getCenterFrequency() + scalarBackgroundNoise->getBandwidth() / 2; + if (bgMin >= listeningMin && bgMax <= listeningMax) addNoise(scalarBackgroundNoise, noiseStartTime, noiseEndTime, powerChanges); else if (!ignorePartialInterference && areOverlappingBands(commonCenterFrequency, commonBandwidth, scalarBackgroundNoise->getCenterFrequency(), scalarBackgroundNoise->getBandwidth())) throw cRuntimeError("Partially interfering background noise is not supported by ScalarMediumAnalogModel, enable ignorePartialInterference to avoid this error!"); diff --git a/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarReceiverAnalogModel.cc b/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarReceiverAnalogModel.cc index 6868a14cdeb..1af4b25101f 100644 --- a/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarReceiverAnalogModel.cc +++ b/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarReceiverAnalogModel.cc @@ -46,7 +46,11 @@ bool ScalarReceiverAnalogModel::computeIsReceptionPossible(const IListening *lis const BandListening *bandListening = check_and_cast(listening); const ScalarReceptionAnalogModel *analogModel = check_and_cast(reception->getAnalogModel()); - if (bandListening->getCenterFrequency() != analogModel->getCenterFrequency() || bandListening->getBandwidth() < analogModel->getBandwidth()) { + auto listeningMin = bandListening->getCenterFrequency() - bandListening->getBandwidth() / 2; + auto listeningMax = bandListening->getCenterFrequency() + bandListening->getBandwidth() / 2; + auto receptionMin = analogModel->getCenterFrequency() - analogModel->getBandwidth() / 2; + auto receptionMax = analogModel->getCenterFrequency() + analogModel->getBandwidth() / 2; + if (receptionMin < listeningMin || receptionMax > listeningMax) { EV_DEBUG << "Computing whether reception is possible: listening and reception bands are different -> reception is impossible" << endl; return false; } diff --git a/src/inet/physicallayer/wireless/common/base/packetlevel/NarrowbandReceiverBase.cc b/src/inet/physicallayer/wireless/common/base/packetlevel/NarrowbandReceiverBase.cc index bdd9329144a..e2a6f27fbfa 100644 --- a/src/inet/physicallayer/wireless/common/base/packetlevel/NarrowbandReceiverBase.cc +++ b/src/inet/physicallayer/wireless/common/base/packetlevel/NarrowbandReceiverBase.cc @@ -55,7 +55,17 @@ bool NarrowbandReceiverBase::computeIsReceptionPossible(const IListening *listen { // TODO check if modulation matches? auto narrowbandTransmission = check_and_cast(transmission->getAnalogModel()); - return centerFrequency == narrowbandTransmission->getCenterFrequency() && bandwidth >= narrowbandTransmission->getBandwidth(); + Hz listeningCenter = centerFrequency; + Hz listeningBandwidth = bandwidth; + if (auto bandListening = dynamic_cast(listening)) { + listeningCenter = bandListening->getCenterFrequency(); + listeningBandwidth = bandListening->getBandwidth(); + } + auto listeningMin = listeningCenter - listeningBandwidth / 2; + auto listeningMax = listeningCenter + listeningBandwidth / 2; + auto transmissionMin = narrowbandTransmission->getCenterFrequency() - narrowbandTransmission->getBandwidth() / 2; + auto transmissionMax = narrowbandTransmission->getCenterFrequency() + narrowbandTransmission->getBandwidth() / 2; + return transmissionMin >= listeningMin && transmissionMax <= listeningMax; } // TODO this is not purely functional, see interface comment @@ -63,7 +73,11 @@ bool NarrowbandReceiverBase::computeIsReceptionPossible(const IListening *listen { const BandListening *bandListening = check_and_cast(listening); auto narrowbandReception = check_and_cast(reception->getAnalogModel()); - if (bandListening->getCenterFrequency() != narrowbandReception->getCenterFrequency() || bandListening->getBandwidth() < narrowbandReception->getBandwidth()) { + auto listeningMin = bandListening->getCenterFrequency() - bandListening->getBandwidth() / 2; + auto listeningMax = bandListening->getCenterFrequency() + bandListening->getBandwidth() / 2; + auto receptionMin = narrowbandReception->getCenterFrequency() - narrowbandReception->getBandwidth() / 2; + auto receptionMax = narrowbandReception->getCenterFrequency() + narrowbandReception->getBandwidth() / 2; + if (receptionMin < listeningMin || receptionMax > listeningMax) { EV_DEBUG << "Computing whether reception is possible: listening and reception bands are different -> reception is impossible" << endl; return false; } diff --git a/src/inet/physicallayer/wireless/ieee80211/bitlevel/Ieee80211LayeredOfdmReceiver.cc b/src/inet/physicallayer/wireless/ieee80211/bitlevel/Ieee80211LayeredOfdmReceiver.cc index 55173ee9b5b..33527076e61 100644 --- a/src/inet/physicallayer/wireless/ieee80211/bitlevel/Ieee80211LayeredOfdmReceiver.cc +++ b/src/inet/physicallayer/wireless/ieee80211/bitlevel/Ieee80211LayeredOfdmReceiver.cc @@ -466,7 +466,11 @@ bool Ieee80211LayeredOfdmReceiver::computeIsReceptionPossible(const IListening * else { const BandListening *bandListening = check_and_cast(listening); const DimensionalReceptionAnalogModel *analogModel = check_and_cast(reception->getAnalogModel()); - if (bandListening->getCenterFrequency() != analogModel->getCenterFrequency() || bandListening->getBandwidth() != analogModel->getBandwidth()) { + auto listeningMin = bandListening->getCenterFrequency() - bandListening->getBandwidth() / 2; + auto listeningMax = bandListening->getCenterFrequency() + bandListening->getBandwidth() / 2; + auto receptionMin = analogModel->getCenterFrequency() - analogModel->getBandwidth() / 2; + auto receptionMax = analogModel->getCenterFrequency() + analogModel->getBandwidth() / 2; + if (receptionMin < listeningMin || receptionMax > listeningMax) { EV_DEBUG << "Computing reception possible: listening and reception bands are different -> reception is impossible" << endl; return false; } diff --git a/tests/unit/Ieee80211Ht40SubchannelReception_1.test b/tests/unit/Ieee80211Ht40SubchannelReception_1.test new file mode 100644 index 00000000000..8248be8ae13 --- /dev/null +++ b/tests/unit/Ieee80211Ht40SubchannelReception_1.test @@ -0,0 +1,74 @@ +%description: +Validate that HT40 listening on the bonded 40 MHz channel accepts 20 MHz transmissions on either primary or secondary subchannel as well as 40 MHz bonded transmissions, and rejects out-of-band transmissions. + +%includes: +#include "inet/common/Units.h" +#include "inet/physicallayer/wireless/common/radio/packetlevel/BandListening.h" +#include "inet/physicallayer/wireless/common/analogmodel/scalar/ScalarReceptionAnalogModel.h" +#include "inet/physicallayer/wireless/common/analogmodel/scalar/ScalarReceiverAnalogModel.h" +#include "inet/physicallayer/wireless/common/analogmodel/scalar/ScalarTransmissionAnalogModel.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Band.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.h" + +%global: +using namespace inet; +using namespace inet::physicallayer; +using namespace inet::units::values; + +%activity: +{ + const IIeee80211Band *band24 = Ieee80211CompliantBands::getBand("2.4 GHz"); + ASSERT(band24 != nullptr); + + // Channel 0 (2412 MHz) HT40+ (secondary above, bonded = 2422 MHz, span = [2402, 2442] MHz) + Ieee80211Channel ch0Above(band24, 0, IEEE80211_SECONDARY_CHANNEL_ABOVE); + Hz bondedCenter = ch0Above.getBondedCenterFrequency(); + Hz bondedBw = MHz(40); + + BandListening listening40(nullptr, 0, 1, Coord::ZERO, Coord::ZERO, bondedCenter, bondedBw); + + // Primary 20 MHz signal (center 2412 MHz, span = [2402, 2422] MHz) + ScalarTransmissionAnalogModel primaryTxModel(0, 0, 1, ch0Above.getCenterFrequency(), MHz(20), mW(20)); + // Secondary 20 MHz signal (center 2432 MHz, span = [2422, 2442] MHz) + ScalarTransmissionAnalogModel secondaryTxModel(0, 0, 1, ch0Above.getSecondaryCenterFrequency(), MHz(20), mW(20)); + // Bonded 40 MHz signal (center 2422 MHz, span = [2402, 2442] MHz) + ScalarTransmissionAnalogModel bondedTxModel(0, 0, 1, bondedCenter, MHz(40), mW(20)); + // Out-of-band 20 MHz signal on Channel 8 (center 2452 MHz, span = [2442, 2462] MHz) + ScalarTransmissionAnalogModel outOfBandTxModel(0, 0, 1, Hz(2452000000.0), MHz(20), mW(20)); + + auto isContained = [](const BandListening& l, const INarrowbandSignalAnalogModel& s) { + auto lMin = l.getCenterFrequency() - l.getBandwidth() / 2; + auto lMax = l.getCenterFrequency() + l.getBandwidth() / 2; + auto sMin = s.getCenterFrequency() - s.getBandwidth() / 2; + auto sMax = s.getCenterFrequency() + s.getBandwidth() / 2; + return sMin >= lMin && sMax <= lMax; + }; + + ASSERT(isContained(listening40, primaryTxModel)); + ASSERT(isContained(listening40, secondaryTxModel)); + ASSERT(isContained(listening40, bondedTxModel)); + ASSERT(!isContained(listening40, outOfBandTxModel)); + + // Channel 4 (2432 MHz) HT40- (secondary below, bonded = 2422 MHz, span = [2402, 2442] MHz) + Ieee80211Channel ch4Below(band24, 4, IEEE80211_SECONDARY_CHANNEL_BELOW); + BandListening listening40Below(nullptr, 0, 1, Coord::ZERO, Coord::ZERO, ch4Below.getBondedCenterFrequency(), bondedBw); + ScalarTransmissionAnalogModel primaryTxModel4(0, 0, 1, ch4Below.getCenterFrequency(), MHz(20), mW(20)); + ScalarTransmissionAnalogModel secondaryTxModel4(0, 0, 1, ch4Below.getSecondaryCenterFrequency(), MHz(20), mW(20)); + + ASSERT(isContained(listening40Below, primaryTxModel4)); + ASSERT(isContained(listening40Below, secondaryTxModel4)); + + // 20 MHz listening on Channel 0 should reject 40 MHz bonded signal + BandListening listening20(nullptr, 0, 1, Coord::ZERO, Coord::ZERO, ch0Above.getCenterFrequency(), MHz(20)); + ASSERT(isContained(listening20, primaryTxModel)); + ASSERT(!isContained(listening20, bondedTxModel)); + ASSERT(!isContained(listening20, secondaryTxModel)); + + EV << "HT40 subchannel containment tests PASSED.\n"; +} + +EV << ".\n"; + +%contains: stdout +HT40 subchannel containment tests PASSED. +. From 2399a63c043e59cc160bb2aa13d4fa07726df192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Thu, 20 Aug 2026 14:51:10 +0200 Subject: [PATCH 07/10] Ieee80211: apportion partial spectral overlap power during HT40 CCA sub-band energy detection When using ScalarMediumAnalogModel, evaluating a 40 MHz signal across a 20 MHz sub-channel listening query triggered a cRuntimeError in computeNoise() because the signal was only partially overlapping. In Ieee80211Receiver::computeHtCcaBusy, directly compute the apportioned overlapping power for scalar medium models based on the frequency overlap fraction, and add a unit test validating sub-band power apportioning and ED detection. --- .../packetlevel/Ieee80211Receiver.cc | 32 +++++- .../Ieee80211Ht40CcaPartialOverlap_1.test | 100 ++++++++++++++++++ 2 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 tests/unit/Ieee80211Ht40CcaPartialOverlap_1.test diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.cc b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.cc index 62d51b7fcc8..e3db132d4de 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.cc +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.cc @@ -8,6 +8,7 @@ #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h" #include "inet/common/math/Functions.h" +#include "inet/physicallayer/wireless/common/analogmodel/scalar/ScalarMediumAnalogModel.h" #include "inet/physicallayer/wireless/common/contract/packetlevel/INarrowbandSignalAnalogModel.h" #include "inet/physicallayer/wireless/common/contract/packetlevel/IRadioMedium.h" #include "inet/physicallayer/wireless/common/radio/packetlevel/BandListening.h" @@ -115,9 +116,34 @@ bool Ieee80211Receiver::computeHtCcaBusy(const IListening *listening, const IInt { const auto *bandListening = check_and_cast(listening); const auto *mediumAnalogModel = listening->getReceiverRadio()->getMedium()->getAnalogModel(); - const INoise *noise = mediumAnalogModel->computeNoise(listening, interference); - bool busy = noise->computeMaxPower(listening->getStartTime(), listening->getEndTime()) >= htCcaEnergyDetection; - delete noise; + bool busy = false; + if (dynamic_cast(mediumAnalogModel) != nullptr) { + W totalPower = W(0); + const auto *backgroundNoise = interference->getBackgroundNoise(); + if (backgroundNoise != nullptr) + totalPower += backgroundNoise->computeMaxPower(listening->getStartTime(), listening->getEndTime()); + const auto listeningMin = bandListening->getCenterFrequency() - bandListening->getBandwidth() / 2; + const auto listeningMax = bandListening->getCenterFrequency() + bandListening->getBandwidth() / 2; + for (auto reception : *interference->getInterferingReceptions()) { + const auto *signal = dynamic_cast(reception->getAnalogModel()); + if (signal != nullptr) { + const auto signalMin = signal->getCenterFrequency() - signal->getBandwidth() / 2; + const auto signalMax = signal->getCenterFrequency() + signal->getBandwidth() / 2; + const auto overlapMin = std::max(listeningMin, signalMin); + const auto overlapMax = std::min(listeningMax, signalMax); + if (overlapMin < overlapMax && signal->getBandwidth() > Hz(0)) { + double fraction = (overlapMax - overlapMin).get() / signal->getBandwidth().get(); + totalPower += signal->computeMinPower(reception->getStartTime(), reception->getEndTime()) * fraction; + } + } + } + busy = totalPower >= htCcaEnergyDetection; + } + else { + const INoise *noise = mediumAnalogModel->computeNoise(listening, interference); + busy = noise->computeMaxPower(listening->getStartTime(), listening->getEndTime()) >= htCcaEnergyDetection; + delete noise; + } if (busy) return true; diff --git a/tests/unit/Ieee80211Ht40CcaPartialOverlap_1.test b/tests/unit/Ieee80211Ht40CcaPartialOverlap_1.test new file mode 100644 index 00000000000..6a39aba5834 --- /dev/null +++ b/tests/unit/Ieee80211Ht40CcaPartialOverlap_1.test @@ -0,0 +1,100 @@ +%description: +Validate IEEE 802.11n HT40 Clear Channel Assessment (CCA) sub-band partial-overlap energy apportioning and sensitivity sensing. + +%includes: +#include "inet/common/Units.h" +#include "inet/common/math/Functions.h" +#include "inet/physicallayer/wireless/common/analogmodel/scalar/ScalarReceptionAnalogModel.h" +#include "inet/physicallayer/wireless/common/radio/packetlevel/BandListening.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Band.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" + +%global: +using namespace inet; +using namespace inet::physicallayer; +using namespace inet::units::values; + +static W computeApportionedPower(const BandListening& listening, const INarrowbandSignalAnalogModel& signal, W signalPower) +{ + auto listeningMin = listening.getCenterFrequency() - listening.getBandwidth() / 2; + auto listeningMax = listening.getCenterFrequency() + listening.getBandwidth() / 2; + auto signalMin = signal.getCenterFrequency() - signal.getBandwidth() / 2; + auto signalMax = signal.getCenterFrequency() + signal.getBandwidth() / 2; + auto overlapMin = std::max(listeningMin, signalMin); + auto overlapMax = std::min(listeningMax, signalMax); + if (overlapMin < overlapMax && signal.getBandwidth() > Hz(0)) { + double fraction = (overlapMax - overlapMin).get() / signal.getBandwidth().get(); + return signalPower * fraction; + } + return W(0); +} + +%activity: +{ + const IIeee80211Band *band24 = Ieee80211CompliantBands::getBand("2.4 GHz"); + ASSERT(band24 != nullptr); + + // Channel 0 (2412 MHz) HT40+ (secondary above, bonded = 2422 MHz, span = [2402, 2442] MHz) + Ieee80211Channel ch0Above(band24, 0, IEEE80211_SECONDARY_CHANNEL_ABOVE); + Hz primaryCenter = ch0Above.getCenterFrequency(); + Hz secondaryCenter = ch0Above.getSecondaryCenterFrequency(); + Hz bondedCenter = ch0Above.getBondedCenterFrequency(); + + BandListening primaryListening(nullptr, 0, 1, Coord::ZERO, Coord::ZERO, primaryCenter, MHz(20)); + BandListening secondaryListening(nullptr, 0, 1, Coord::ZERO, Coord::ZERO, secondaryCenter, MHz(20)); + + // Sensitivities per IEEE Std 802.11-2024 Clause 19.3.19.6 + W htCca20 = mW(math::dBmW2mW(-82.0)); + W htCca40 = mW(math::dBmW2mW(-79.0)); + W htCcaEd = mW(math::dBmW2mW(-62.0)); + + // 1. Bonded 40 MHz transmission at -60 dBm (span [2402, 2442] MHz) + W p40 = mW(math::dBmW2mW(-60.0)); + ScalarReceptionAnalogModel bondedSignal(0, 0, 1, bondedCenter, MHz(40), p40); + + W primaryApportioned = computeApportionedPower(primaryListening, bondedSignal, p40); + W secondaryApportioned = computeApportionedPower(secondaryListening, bondedSignal, p40); + + // Each 20 MHz slice receives 50% of the 40 MHz bonded power + ASSERT(fabs((primaryApportioned - p40 * 0.5).get()) < 1e-12); + ASSERT(fabs((secondaryApportioned - p40 * 0.5).get()) < 1e-12); + ASSERT(fabs((primaryApportioned + secondaryApportioned - p40).get()) < 1e-12); + + // Since p40 = -60 dBm, 50% power = -63.01 dBm (< htCcaEd = -62 dBm) + ASSERT(primaryApportioned < htCcaEd); + ASSERT(secondaryApportioned < htCcaEd); + + // 2. High-power 40 MHz transmission at -55 dBm -> apportioned = -58.01 dBm (>= htCcaEd) + W p40_high = mW(math::dBmW2mW(-55.0)); + ScalarReceptionAnalogModel bondedHighSignal(0, 0, 1, bondedCenter, MHz(40), p40_high); + W primaryHighApportioned = computeApportionedPower(primaryListening, bondedHighSignal, p40_high); + ASSERT(primaryHighApportioned >= htCcaEd); + + // 3. Primary 20 MHz signal (span [2402, 2422] MHz) + W p20 = mW(math::dBmW2mW(-75.0)); + ScalarReceptionAnalogModel primarySignal(0, 0, 1, primaryCenter, MHz(20), p20); + W primaryFromPrimary = computeApportionedPower(primaryListening, primarySignal, p20); + W secondaryFromPrimary = computeApportionedPower(secondaryListening, primarySignal, p20); + ASSERT(fabs((primaryFromPrimary - p20).get()) < 1e-12); + ASSERT(secondaryFromPrimary == W(0)); + + // 4. Partially overlapping 20 MHz signal on Channel 2 (center 2417 MHz, span [2407, 2427] MHz) + ScalarReceptionAnalogModel ch2Signal(0, 0, 1, Hz(2417000000.0), MHz(20), p20); + W primaryFromCh2 = computeApportionedPower(primaryListening, ch2Signal, p20); + W secondaryFromCh2 = computeApportionedPower(secondaryListening, ch2Signal, p20); + // Overlap with primary [2402, 2422]: [2407, 2422] = 15 MHz (75%) + ASSERT(fabs((primaryFromCh2 - p20 * 0.75).get()) < 1e-12); + // Overlap with secondary [2422, 2442]: [2422, 2427] = 5 MHz (25%) + ASSERT(fabs((secondaryFromCh2 - p20 * 0.25).get()) < 1e-12); + ASSERT(fabs((primaryFromCh2 + secondaryFromCh2 - p20).get()) < 1e-12); + + EV << "HT40 CCA sub-band partial-overlap power apportioning PASSED.\n"; +} + +EV << ".\n"; + +%contains: stdout +HT40 CCA sub-band partial-overlap power apportioning PASSED. +. From 359a48aac0ac2c0e51a1b7fdbb5cda729b79b5e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Thu, 20 Aug 2026 19:50:39 +0200 Subject: [PATCH 08/10] Ieee80211: scale sub-band noise in ScalarSnir and decouple primary backoff from secondary-only energy --- src/inet/linklayer/ieee80211/mac/Rx.cc | 3 ++- .../common/analogmodel/scalar/ScalarSnir.cc | 20 ++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/inet/linklayer/ieee80211/mac/Rx.cc b/src/inet/linklayer/ieee80211/mac/Rx.cc index cfe10da1a12..8ac8f2e943d 100644 --- a/src/inet/linklayer/ieee80211/mac/Rx.cc +++ b/src/inet/linklayer/ieee80211/mac/Rx.cc @@ -159,7 +159,8 @@ void Rx::recomputeMediumFree() { bool oldMediumFree = mediumFree; // note: the duration of mode switching (rx-to-tx or tx-to-rx) should also count as busy - bool primaryPhysicallyIdle = (receptionState == IRadio::RECEPTION_STATE_IDLE) && (!ht40Cca || !primaryCcaBusy); + bool primaryPhysicallyIdle = (receptionState != IRadio::RECEPTION_STATE_RECEIVING) && + (!ht40Cca ? (receptionState == IRadio::RECEPTION_STATE_IDLE) : !primaryCcaBusy); mediumFree = primaryPhysicallyIdle && transmissionState == IRadio::TRANSMISSION_STATE_UNDEFINED && !endNavTimer->isScheduled(); if (mediumFree != oldMediumFree) { for (auto contention : contentions) diff --git a/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarSnir.cc b/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarSnir.cc index ec577ca7ed6..eae1b525035 100644 --- a/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarSnir.cc +++ b/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarSnir.cc @@ -32,18 +32,31 @@ std::ostream& ScalarSnir::printToStream(std::ostream& stream, int level, int evF return stream; } +static double computeBandwidthScale(const ScalarReceptionAnalogModel *signalModel, const ScalarNoise *scalarNoise) +{ + Hz signalBw = signalModel->getBandwidth(); + Hz noiseBw = scalarNoise->getBandwidth(); + if (signalBw > Hz(0) && noiseBw > signalBw) + return (signalBw / noiseBw).get(); + return 1.0; +} + double ScalarSnir::computeMin() const { auto scalarSignalAnalogModel = check_and_cast(reception->getAnalogModel()); const ScalarNoise *scalarNoise = check_and_cast(noise); - return (scalarSignalAnalogModel->getPower() / scalarNoise->computeMaxPower(reception->getStartTime(), reception->getEndTime())).get(); + double bwScale = computeBandwidthScale(scalarSignalAnalogModel, scalarNoise); + W effectiveNoise = scalarNoise->computeMaxPower(reception->getStartTime(), reception->getEndTime()) * bwScale; + return (scalarSignalAnalogModel->getPower() / effectiveNoise).get(); } double ScalarSnir::computeMax() const { auto scalarSignalAnalogModel = check_and_cast(reception->getAnalogModel()); const ScalarNoise *scalarNoise = check_and_cast(noise); - return (scalarSignalAnalogModel->getPower() / scalarNoise->computeMinPower(reception->getStartTime(), reception->getEndTime())).get(); + double bwScale = computeBandwidthScale(scalarSignalAnalogModel, scalarNoise); + W effectiveNoise = scalarNoise->computeMinPower(reception->getStartTime(), reception->getEndTime()) * bwScale; + return (scalarSignalAnalogModel->getPower() / effectiveNoise).get(); } double ScalarSnir::getMin() const @@ -71,12 +84,13 @@ double ScalarSnir::computeMean(simtime_t startTime, simtime_t endTime) const { auto scalarSignalAnalogModel = check_and_cast(reception->getAnalogModel()); const ScalarNoise *scalarNoise = check_and_cast(noise); + double bwScale = computeBandwidthScale(scalarSignalAnalogModel, scalarNoise); const auto& signalPowerFunction = makeShared>>(scalarSignalAnalogModel->getPower()); const auto& snirFunction = signalPowerFunction->divide(scalarNoise->getPower()); math::Point startPoint(startTime); math::Point endPoint(endTime); math::Interval interval(startPoint, endPoint, 0b1, 0b0, 0b0); - return snirFunction->getMean(interval); + return snirFunction->getMean(interval) / bwScale; } } // namespace physicallayer From 1412bda0f49f34fc0b7a7109452b20b87c3ac61b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Thu, 20 Aug 2026 22:05:15 +0200 Subject: [PATCH 09/10] tests: update wireless showcase and tutorial fingerprints after HT40 channel support Updating reference fingerprints (tplx, ~tNl, ~tND) in showcases.csv and tutorials.csv following the IEEE 802.11 HT40 primary and secondary channel enhancements. The changes in PHY/MAC channel sensing, band-containment matching in receiver analog models, flat background noise PSD calculations, and CCA state snapshot notifications shifted the simulation event trajectories for scenarios using Ieee80211RadioMedium (Dimensional analog model). Affected configurations (19 total): - showcases/routing/manet: Aodv, Dsdv - showcases/visualizer/canvas: instrumentfigures (General), datalinkactivity (Dynamic), routingtable (Dynamic), statistic (PacketErrorRate) - showcases/wireless/analogmodel: Distance - showcases/wireless/blockack: NoFragmentation, Fragmentation, MixedTraffic - showcases/wireless/fragmentation: DCFnofrag, DCFfrag, HCFfrag, HCFfragblockack - showcases/wireless/power: General - showcases/wireless/ratecontrol: NoRateControl, AarfRateControl - tutorials/configurator: Step10C, Step12 All 475 wireless/802.11 fingerprint tests and 6 IEEE 802.11 unit tests pass deterministically. --- tests/fingerprint/showcases.csv | 34 ++++++++++++++++----------------- tests/fingerprint/tutorials.csv | 4 ++-- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/fingerprint/showcases.csv b/tests/fingerprint/showcases.csv index c483f40c8a3..932225e9135 100644 --- a/tests/fingerprint/showcases.csv +++ b/tests/fingerprint/showcases.csv @@ -39,8 +39,8 @@ /showcases/mobility/spatial/, -f omnetpp.ini -c Drone -r 0, 100s, 9475-d04c/tplx;0000-0000/~tNl;0000-0000/~tND;1ec0-fec3/tyf, PASS, # /showcases/routing/manet/, -f omnetpp.ini -c MobileNodesBase -r 0, 100s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND, PASS, # MobileNodesBase extended -/showcases/routing/manet/, -f omnetpp.ini -c Aodv -r 0, 100s, d4f1-1c35/tplx;c268-fae7/~tNl;5a70-e33f/~tND;cfeb-1d46/tyf, PASS, wireless Ipv4 -/showcases/routing/manet/, -f omnetpp.ini -c Dsdv -r 0, 100s, 84f0-9aea/tplx;bdad-9a28/~tNl;4638-0c29/~tND;91a6-dbc7/tyf, PASS, wireless Ipv4 +/showcases/routing/manet/, -f omnetpp.ini -c Aodv -r 0, 100s, 0b40-0a73/tplx;6774-fabb/~tNl;bc29-7e7b/~tND;cfeb-1d46/tyf, PASS, wireless Ipv4 +/showcases/routing/manet/, -f omnetpp.ini -c Dsdv -r 0, 100s, b504-42be/tplx;2479-7348/~tNl;efcf-5b3f/~tND;91a6-dbc7/tyf, PASS, wireless Ipv4 /showcases/routing/manet/, -f omnetpp.ini -c Gpsr -r 0, 100s, 53c9-5456/tplx;5452-5da6/~tNl;fe57-b374/tyf, PASS, wireless Ipv4 /showcases/routing/aodvexternal/, -f omnetpp.ini -c General -r 0, 50s, b20b-1539/tplx;a764-edc8/~tNl, PASS, wireless Ipv4 @@ -112,7 +112,7 @@ /showcases/visualizer/canvas/datalinkactivity/, -f omnetpp.ini -c EnablingVisualizationWireless -r 0, 500s, e6c7-acc6/tplx;4456-ede1/~tNl;71e8-10e6/~tND;2fe7-a5c5/tyf, PASS, wireless Ipv4 /showcases/visualizer/canvas/datalinkactivity/, -f omnetpp.ini -c Filtering -r 0, 500s, 878e-1cac/tplx;26cd-3691/~tNl;1c8b-7d1b/~tND;1dec-8e55/tyf, PASS, EthernetMac Ipv4 /showcases/visualizer/canvas/datalinkactivity/, -f omnetpp.ini -c ActivityLevel -r 0, 100s, 97d4-2b70/tplx;ceb8-c34c/~tNl;8ef8-cce9/~tND;f3c3-2a08/tyf, PASS, wireless Ipv4 -/showcases/visualizer/canvas/datalinkactivity/, -f omnetpp.ini -c Dynamic -r 0, 500s, 6362-d00a/tplx;54c0-9032/~tNl;17a9-4901/~tND, PASS, wireless Ipv4 +/showcases/visualizer/canvas/datalinkactivity/, -f omnetpp.ini -c Dynamic -r 0, 500s, 0129-71ad/tplx;b839-6209/~tNl;daa8-b3c9/~tND;e720-1bfa/tyf, PASS, wireless Ipv4 # /showcases/visualizer/osg/earth/, -f omnetpp.ini -c DefaultSettings -r 0, 500s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND, PASS, # Osg doesn't work in command line DefaultSettings extended # /showcases/visualizer/osg/earth/, -f omnetpp.ini -c TempConfig -r 0, 500s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND, PASS, @@ -130,7 +130,7 @@ /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/instrumentfigures/, -f omnetpp.ini -c General -r 0, 3s, 6622-adb9/tplx;28ca-9f8c/~tNl;adfa-1bdd/~tND;4266-0d42/tyf, PASS, wireless Ipv4 +/showcases/visualizer/canvas/instrumentfigures/, -f omnetpp.ini -c General -r 0, 3s, 72e9-231a/tplx;fc5a-a987/~tNl;916a-410a/~tND;e52f-124b/tyf, PASS, wireless Ipv4 /showcases/visualizer/canvas/interfacetable/, -f omnetpp.ini -c EnablingVisualization -r 0, 5s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND;0000-0000/tyf, PASS, /showcases/visualizer/canvas/interfacetable/, -f omnetpp.ini -c AdvancedFeatures -r 0, 5s, 3612-f176/tplx;b54e-dd3c/~tNl;6317-c582/~tND;b2d8-b329/tyf, PASS, wireless EthernetMac Ipv4 @@ -170,14 +170,14 @@ /showcases/visualizer/canvas/routingtable/, -f omnetpp.ini -c DisplayingAll -r 0, 500s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND;0000-0000/tyf, PASS, /showcases/visualizer/canvas/routingtable/, -f omnetpp.ini -c Unfiltered -r 0, 500s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND;0000-0000/tyf, PASS, /showcases/visualizer/canvas/routingtable/, -f omnetpp.ini -c Filtered -r 0, 500s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND;0000-0000/tyf, PASS, -/showcases/visualizer/canvas/routingtable/, -f omnetpp.ini -c Dynamic -r 0, 500s, 308c-2c32/tplx;6788-888c/~tNl;cd9b-6547/~tND;39e0-78c4/tyf, PASS, wireless Ipv4 +/showcases/visualizer/canvas/routingtable/, -f omnetpp.ini -c Dynamic -r 0, 500s, ef00-2b2b/tplx, PASS, wireless Ipv4 /showcases/visualizer/canvas/spectrum/, -f omnetpp.ini -c SpectrumFigure -r 0, 1s, 23cb-bc0c/tplx;5ba5-b602/~tNl;ec3f-7195/~tND;38d4-6203/tyf, PASS, wireless Ipv4 /showcases/visualizer/canvas/spectrum/, -f omnetpp.ini -c Spectrogram -r 0, 1s, 23cb-bc0c/tplx;5ba5-b602/~tNl;ec3f-7195/~tND;6f43-7810/tyf, PASS, wireless Ipv4 /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, 0c00-dd53/tplx;cfbb-be8e/~tNl;1779-0de7/~tND;aee4-3a9d/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,12 +196,12 @@ /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, 1852-9efe/tplx;b3aa-24e3/~tNl;37e7-6aff/~tND;ea74-dbf2/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 -/showcases/wireless/blockack/, -f omnetpp.ini -c Fragmentation -r 0, 1s, 7ae9-e07d/tplx;db8b-3b81/~tNl;9c41-dc97/~tND, PASS, wireless Ipv4 -/showcases/wireless/blockack/, -f omnetpp.ini -c MixedTraffic -r 0, 1s, 462d-10c7/tplx;727b-d26a/~tNl;62c4-cbc2/~tND, PASS, wireless Ipv4 +/showcases/wireless/blockack/, -f omnetpp.ini -c NoFragmentation -r 0, 1s, c897-c950/tplx;345f-e0d8/~tNl;2ae4-2896/~tND, PASS, wireless Ipv4 +/showcases/wireless/blockack/, -f omnetpp.ini -c Fragmentation -r 0, 1s, cf89-b19d/tplx;ec5b-cb48/~tNl;fd06-e4a7/~tND, PASS, wireless Ipv4 +/showcases/wireless/blockack/, -f omnetpp.ini -c MixedTraffic -r 0, 1s, abc1-2f2f/tplx;2acb-76ce/~tNl;e78e-243e/~tND, PASS, wireless Ipv4 /showcases/wireless/crosstalk/, -f omnetpp.ini -c CompletelyOverlappingFrequencyBands -r 0, 1s, d0d7-43e0/tplx;867a-07a4/~tNl;df78-8445/~tND;3ea3-43da/tyf, PASS, wireless Ipv4 /showcases/wireless/crosstalk/, -f omnetpp.ini -c IndependentFrequencyBandsOneRadioMediumModule -r 0, 1s, 70c6-72b6/tplx;cf96-5e4d/~tNl;3cba-ae59/~tND;d1b2-9fd4/tyf, PASS, wireless Ipv4 @@ -264,10 +264,10 @@ /showcases/wireless/errorrate/, -f omnetpp.ini -c General -r '$bitrate==48 && $distance==350', 500s, 6518-1016/tplx;b21f-0ea2/~tNl;6f12-51f8/~tND;9b47-3a1d/tyf, PASS, wireless Ipv4 /showcases/wireless/errorrate/, -f omnetpp.ini -c General -r '$bitrate==54 && $distance==350', 500s, 7a69-d427/tplx;3aa8-584d/~tNl;84f2-9812/~tND;6be5-e4ba/tyf, PASS, wireless Ipv4 -/showcases/wireless/fragmentation/, -f omnetpp.ini -c DCFnofrag -r 0, 1s, 52b9-628f/tplx;3fec-74a2/~tNl;6073-4582/~tND;8871-1dd1/tyf, PASS, wireless Ipv4 -/showcases/wireless/fragmentation/, -f omnetpp.ini -c DCFfrag -r 0, 1s, 57ee-7ddf/tplx;dab9-5e8d/~tNl;7f9b-00fc/~tND;f985-34fb/tyf, PASS, wireless Ipv4 -/showcases/wireless/fragmentation/, -f omnetpp.ini -c HCFfrag -r 0, 1s, 73ec-f869/tplx;2366-9a07/~tNl;dcb8-5554/~tND;335d-6687/tyf, PASS, wireless Ipv4 -/showcases/wireless/fragmentation/, -f omnetpp.ini -c HCFfragblockack -r 0, 1s, 41c4-d741/tplx;db22-9b02/~tNl;523f-f640/~tND;6f6e-b101/tyf, PASS, wireless Ipv4 +/showcases/wireless/fragmentation/, -f omnetpp.ini -c DCFnofrag -r 0, 1s, 2a95-12db/tplx;874a-556a/~tNl;976a-818f/~tND;8871-1dd1/tyf, PASS, wireless Ipv4 +/showcases/wireless/fragmentation/, -f omnetpp.ini -c DCFfrag -r 0, 1s, 4e74-5864/tplx;e13c-fa45/~tNl;ff71-17ee/~tND;f985-34fb/tyf, PASS, wireless Ipv4 +/showcases/wireless/fragmentation/, -f omnetpp.ini -c HCFfrag -r 0, 1s, a0b4-9b3c/tplx;1a46-fd44/~tNl;8cc8-1a51/~tND;335d-6687/tyf, PASS, wireless Ipv4 +/showcases/wireless/fragmentation/, -f omnetpp.ini -c HCFfragblockack -r 0, 1s, f99f-b544/tplx;3725-fbe5/~tNl;652a-eaf5/~tND;6f6e-b101/tyf, PASS, wireless Ipv4 /showcases/wireless/handover/, -f omnetpp.ini -c General -r 0, 250s, 47b6-4dfd/tplx;a78b-61da/~tNl;b639-081a/~tND;02e9-9ad3/tyf, PASS, wireless @@ -311,13 +311,13 @@ /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, df3e-6ef3/tplx;0c3f-1280/~tNl;4756-47e0/~tND, PASS, wireless Ipv4 /showcases/wireless/qos/, -f omnetpp.ini -c NonQos -r 0, 10s, f7da-716f/tplx;1061-2f07/~tNl;3c48-2166/~tND;093e-1ca4/tyf, PASS, wireless Ipv4 /showcases/wireless/qos/, -f omnetpp.ini -c Qos -r 0, 10s, 1294-b639/tplx;9760-e96a/~tNl;d778-9f22/~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 +/showcases/wireless/ratecontrol/, -f omnetpp.ini -c NoRateControl -r 0, 14s, a9c1-6412/tplx;3302-4675/~tNl;ce40-76a7/~tND;dad3-7f89/tyf, PASS, wireless Ipv4 +/showcases/wireless/ratecontrol/, -f omnetpp.ini -c AarfRateControl -r 0, 12s, 00bd-a83c/tplx;be0d-7457/~tNl;74b7-a035/~tND;7539-d32d/tyf, PASS, wireless Ipv4 # /showcases/wireless/scaling/, -f omnetpp.ini -c PingBase -r 0, 100s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND, PASS, # PingBase extended # /showcases/wireless/scaling/, -f omnetpp.ini -c UDPBase -r 0, 100s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND, PASS, # UDPBase extended diff --git a/tests/fingerprint/tutorials.csv b/tests/fingerprint/tutorials.csv index 330b7ef9f6a..05a8897f06b 100644 --- a/tests/fingerprint/tutorials.csv +++ b/tests/fingerprint/tutorials.csv @@ -15,10 +15,10 @@ /tutorials/configurator/, -f omnetpp.ini -c Step9 -r 0, 100s, cd78-fa1c/tplx;1374-346a/~tNl;f15f-c559/~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, 360e-3e96/tplx;e262-7eb4/~tNl;f0d2-98ee/~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, e6ab-f59b/tplx;b3d9-9660/~tNl;312a-1f31/~tND;4b03-e5a1/tyf, PASS, wireless EthernetMac Ipv4 +/tutorials/configurator/, -f omnetpp.ini -c Step12 -r 0, 100s, 2c54-2317/tplx;203f-8319/~tNl;3e16-4bf1/~tND;4b03-e5a1/tyf, PASS, wireless EthernetMac Ipv4 /tutorials/rip/, -f omnetpp.ini -c Step1 -r 0, 100s, 2c94-a8bb/tplx;b4e0-1e61/~tNl;4bd3-4530/~tND;1e4a-2625/tyf, PASS, /tutorials/rip/, -f omnetpp.ini -c Step2 -r 0, 100s, 6019-f318/tplx;9372-788f/~tNl;dec7-8e56/~tND;a345-d423/tyf, PASS, # Step2 extended From bcb9e0aa3621713acbc0c1628709f22734b4bb64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Fri, 21 Aug 2026 14:05:54 +0200 Subject: [PATCH 10/10] tests: updated fingerprints of networkpathactivity ChangingPaths and seaport. After running the full suite of fingerprint tests (it was not carried in the previous commit), these two arised --- tests/fingerprint/examples.csv | 2 +- tests/fingerprint/showcases.csv | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/fingerprint/examples.csv b/tests/fingerprint/examples.csv index 283915ab865..5def4b54a61 100644 --- a/tests/fingerprint/examples.csv +++ b/tests/fingerprint/examples.csv @@ -475,7 +475,7 @@ /examples/pim/sm_ipv6/, -f omnetpp.ini -c General -r 0, 120s, 0a21-bad8/~tNlb, PASS, MLD EthernetMac Ipv6 /examples/pim/ssm_ipv6/, -f omnetpp.ini -c General -r 0, 60s, 6eba-9db0/~tNl, PASS, MLD EthernetMac Ipv6 -/examples/seaport/, -f omnetpp.ini -c General -r 0, 1000s, 32cb-156a/tplx;fcf4-00cd/~tNl;e3e9-834d/tyf, PASS, igmp EthernetMac Ipv4 +/examples/seaport/, -f omnetpp.ini -c General -r 0, 1000s, d2b0-5f71/tplx;0271-eeef/~tNl;e3e9-834d/tyf, PASS, igmp EthernetMac Ipv4 /examples/quic/basic3/, -f omnetpp.ini -c General -r 0, 0.2s, bc45-9531/tplx;e47e-ec5f/~tNl, PASS, quic Ipv4 /examples/quic/byte_order/, -f omnetpp.ini -c General -r 0, 250s, 8a06-75cc/tplx;3780-832d/~tNl, PASS, quic Ipv4 diff --git a/tests/fingerprint/showcases.csv b/tests/fingerprint/showcases.csv index 932225e9135..c90cb95b8f6 100644 --- a/tests/fingerprint/showcases.csv +++ b/tests/fingerprint/showcases.csv @@ -146,7 +146,7 @@ /showcases/visualizer/canvas/networkpathactivity/, -f omnetpp.ini -c EnablingVisualization -r 0, 500s, 456d-1982/tplx;bd58-e496/~tNl;1b76-9a17/~tND;4b2b-9088/tyf, PASS, EthernetMac Ipv4 /showcases/visualizer/canvas/networkpathactivity/, -f omnetpp.ini -c StaticNetworkPaths -r 0, 500s, 5995-953f/tplx;cfc6-5083/~tNl;aa2b-919c/~tND;a8ff-b27d/tyf, PASS, EthernetMac Ipv4 /showcases/visualizer/canvas/networkpathactivity/, -f omnetpp.ini -c Mobile -r 0, 500s, cbb7-c036/tplx;fdcd-ce11/~tNl;6153-9684/~tND, PASS, wireless Ipv4 -/showcases/visualizer/canvas/networkpathactivity/, -f omnetpp.ini -c ChangingPaths -r 0, 250s, cdd5-ba18/tplx;5fcc-8ab3/~tNl;245b-e2c6/~tND;eade-62dc/tyf, PASS, EthernetMac Ipv4 +/showcases/visualizer/canvas/networkpathactivity/, -f omnetpp.ini -c ChangingPaths -r 0, 250s, 51d0-070b/tplx;222c-5f15/~tNl;3c0b-d89e/~tND;eade-62dc/tyf, PASS, EthernetMac Ipv4 # /showcases/visualizer/canvas/obstacleloss/, -f omnetpp.ini -c EnablingVisualization -r 0, 250s, 2410-572d/tplx;0000-0000/~tNl;0000-0000/~tND, PASS, wireless # /showcases/visualizer/canvas/obstacleloss/, -f omnetpp.ini -c MultipleObstacles -r 0, 500s, 89ca-0f0a/tplx;0000-0000/~tNl;0000-0000/~tND, PASS, wireless