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/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..20130b4a353 --- /dev/null +++ b/examples/wireless/channelwidths/omnetpp.ini @@ -0,0 +1,72 @@ +[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 +**.radioMedium.sameTransmissionStartTimeCheck = "ignore" + +# 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..8ac8f2e943d 100644 --- a/src/inet/linklayer/ieee80211/mac/Rx.cc +++ b/src/inet/linklayer/ieee80211/mac/Rx.cc @@ -159,13 +159,36 @@ 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 = (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) 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 || secondaryCcaBusy) + secondaryCcaIdleSince = -1; + else if (!wasHt40Cca || wasSecondaryBusy) + secondaryCcaIdleSince = simTime(); + 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..d9ea4a307e0 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 && (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 + // 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..b5f8dc74bd9 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 && (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 + // 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/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/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..dd0265d5322 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,26 @@ 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): + // 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) { Enter_Method("channelGranted"); @@ -200,13 +221,18 @@ void Hcf::channelGranted(IChannelAccess *channelAccess) if (edcaf) { AccessCategory ac = edcaf->getAccessCategory(); EV_DETAIL << "Channel access granted to the " << printAccessCategory(ac) << " queue" << std::endl; - 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 required interval before channel access for HT40 transmission, restarting backoff.\n"; + edcaf->restartChannelAccess(this); + return; + } + edcaf->getTxopProcedure()->startTxop(ac); startFrameSequence(ac); } else 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..eeac3b2abe5 100644 --- a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned +++ b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned @@ -20,6 +20,7 @@ simple Ieee80211Mib extends SimpleModule { parameters: @class(Ieee80211Mib); + string htSecondaryChannelOffset @enum("none", "above", "below") = default("none"); displayStringTextFormat = default("Address: {address}{ssidStr}\n{modeStr}{stationTypeStr}{qosStr}{associatedStr}"); @display("i=block/table"); } 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/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 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/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/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..6e9cc062304 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,60 @@ 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); + 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 Ieee80211Channel *channel = configureCommand->getChannel(); - if (channel != nullptr) - setChannel(channel); + const IIeee80211Band *bandParam = configureCommand->getBand(); + 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; 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(); + const IIeee80211Mode *mode = configureCommand->getMode(); + 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)") && + ((targetBandwidth == 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 || + *requestedOpMode; + + 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 (publishModeSet && targetModeSet != nullptr) + setModeSet(targetModeSet); + if (resolvedMode != nullptr) + setMode(resolvedMode); } } FlatRadioBase::handleUpperCommand(message); @@ -82,6 +189,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 +210,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 +225,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 +238,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..e3db132d4de 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.cc +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.cc @@ -7,6 +7,15 @@ #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" +#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 +35,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 +71,123 @@ 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(); + 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; + + 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 +206,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/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 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. +. 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/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. +. 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 +. diff --git a/tests/unit/Ieee80211RadioReconfiguration_1.test b/tests/unit/Ieee80211RadioReconfiguration_1.test new file mode 100644 index 00000000000..c1ce2cd76cc --- /dev/null +++ b/tests/unit/Ieee80211RadioReconfiguration_1.test @@ -0,0 +1,82 @@ +%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"; + + // 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"; + +%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. +ConfigureRadioCommand with channel object verified. +. +