diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211BssidReq.msg b/src/inet/linklayer/ieee80211/mac/Ieee80211BssidReq.msg new file mode 100644 index 00000000000..ee4094c13ee --- /dev/null +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211BssidReq.msg @@ -0,0 +1,16 @@ +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +import inet.common.INETDefs; +import inet.common.TagBase; +import inet.linklayer.common.MacAddress; + +namespace inet; + +// The management procedure's BSSID for Address3 of an outgoing management frame. +// It is independent of the receiver address and survives queuing/state changes. +class Ieee80211BssidReq extends TagBase +{ + MacAddress bssid; +} diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc index d87f581231f..ec288f5587a 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc @@ -6,6 +6,7 @@ #include "inet/linklayer/ieee80211/mac/Ieee80211Mac.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211BssidReq_m.h" #include @@ -46,8 +47,8 @@ Ieee80211Mac::Ieee80211Mac() Ieee80211Mac::~Ieee80211Mac() { - if (pendingRadioConfigMsg) - delete pendingRadioConfigMsg; + cancelAndDelete(radioConfigRetry); + delete pendingRadioConfigMsg; } void Ieee80211Mac::initialize(int stage) @@ -156,15 +157,46 @@ void Ieee80211Mac::handleMessageWhenUp(cMessage *message) void Ieee80211Mac::handleSelfMessage(cMessage *msg) { - ASSERT(false); + if (msg == radioConfigRetry) { + // Retry after delivered receptions and exchange callbacks have reserved any SIFS response. + if (pendingRadioConfigMsg && rx->isMediumFree() && !tx->isTransmissionPending() && + !(mib->qos ? hcf->isFrameSequenceRunning() : dcf->isFrameSequenceRunning())) + sendDownPendingRadioConfigMsg(); + } + else + ASSERT(false); +} + +void Ieee80211Mac::scheduleRadioConfigRetry() +{ + Enter_Method("scheduleRadioConfigRetry"); + if (pendingRadioConfigMsg) { + if (!radioConfigRetry) + radioConfigRetry = new cMessage("radioConfigRetry"); + if (!radioConfigRetry->isScheduled()) + scheduleAt(simTime(), radioConfigRetry); + } } + +void Ieee80211Mac::clearPendingRadioConfig() +{ + if (radioConfigRetry) + cancelEvent(radioConfigRetry); + delete pendingRadioConfigMsg; + pendingRadioConfigMsg = nullptr; +} + + void Ieee80211Mac::handleMgmtPacket(Packet *packet) { const auto& header = makeShared(); header->setType((Ieee80211FrameType)packet->getTag()->getSubtype()); header->setReceiverAddress(packet->getTag()->getDestAddress()); - if (mib->mode == Ieee80211Mib::INFRASTRUCTURE && mib->bssStationData.stationType == Ieee80211Mib::ACCESS_POINT) + // IEEE Std 802.11-2024, 9.3.3.1: management supplies its intended BSSID. + if (auto bssid = packet->findTag()) + header->setAddress3(bssid->getBssid()); + else if (mib->mode == Ieee80211Mib::INFRASTRUCTURE && mib->bssStationData.stationType == Ieee80211Mib::ACCESS_POINT) header->setAddress3(mib->bssData.bssid); packet->insertAtFront(header); packet->insertAtBack(makeShared()); @@ -239,9 +271,9 @@ void Ieee80211Mac::handleUpperCommand(cMessage *msg) sendDown(msg); } else { - // TODO waiting potentially indefinitely?! wtf?! EV_DEBUG << "Delaying " << msg->getName() << " until next IDLE or DEFER state\n"; pendingRadioConfigMsg = msg; + scheduleRadioConfigRetry(); } } else { @@ -340,6 +372,8 @@ void Ieee80211Mac::receiveSignal(cComponent *source, simsignal_t signalID, intva configureRadioMode(IRadio::RADIO_MODE_RECEIVER); // FIXME this is in a very wrong place!!! should be done explicitly from coordination function! } rx->transmissionStateChanged(transmissionState); + if (transmissionFinished) + scheduleRadioConfigRetry(); } else if (signalID == IRadio::receivedSignalPartChangedSignal) { rx->receivedSignalPartChanged(static_cast(value)); @@ -428,11 +462,13 @@ void Ieee80211Mac::handleStartOperation(LifecycleOperation *operation) // FIXME void Ieee80211Mac::handleStopOperation(LifecycleOperation *operation) { + clearPendingRadioConfig(); } // FIXME void Ieee80211Mac::handleCrashOperation(LifecycleOperation *operation) { + clearPendingRadioConfig(); } } // namespace ieee80211 diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h index 1114deaed00..39958f6494c 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h @@ -60,6 +60,7 @@ class INET_API Ieee80211Mac : public MacProtocolBase // The last change channel message received and not yet sent to the physical layer, or nullptr. cMessage *pendingRadioConfigMsg = nullptr; + cMessage *radioConfigRetry = nullptr; protected: virtual int numInitStages() const override { return NUM_INIT_STAGES; } @@ -79,6 +80,7 @@ class INET_API Ieee80211Mac : public MacProtocolBase /** @brief Handle timer self messages */ virtual void handleSelfMessage(cMessage *msg) override; + void clearPendingRadioConfig(); /** @brief Handle packets from management */ virtual void handleMgmtPacket(Packet *packet); @@ -106,6 +108,7 @@ class INET_API Ieee80211Mac : public MacProtocolBase virtual void sendUpFrame(Packet *frame); virtual void sendDownFrame(Packet *frame); virtual void sendDownPendingRadioConfigMsg(); + virtual void scheduleRadioConfigRetry(); virtual void processUpperFrame(Packet *packet, const Ptr& header); virtual void processLowerFrame(Packet *packet, const Ptr& header); diff --git a/src/inet/linklayer/ieee80211/mac/Tx.h b/src/inet/linklayer/ieee80211/mac/Tx.h index ae4c37308b6..a5f1cf0a063 100644 --- a/src/inet/linklayer/ieee80211/mac/Tx.h +++ b/src/inet/linklayer/ieee80211/mac/Tx.h @@ -42,6 +42,7 @@ class INET_API Tx : public SimpleModule, public ITx virtual void transmitFrame(Packet *packet, const Ptr& header, ITx::ICallback *txCallback) override; virtual void transmitFrame(Packet *packet, const Ptr& header, simtime_t ifs, ITx::ICallback *txCallback) override; virtual void radioTransmissionFinished() override; + virtual bool isTransmissionPending() const override { return frame != nullptr; } }; } // namespace ieee80211 diff --git a/src/inet/linklayer/ieee80211/mac/contention/Contention.cc b/src/inet/linklayer/ieee80211/mac/contention/Contention.cc index 4a1471e28f1..e205e2118db 100644 --- a/src/inet/linklayer/ieee80211/mac/contention/Contention.cc +++ b/src/inet/linklayer/ieee80211/mac/contention/Contention.cc @@ -160,6 +160,9 @@ void Contention::mediumStateChanged(bool mediumFree) this->mediumFree = mediumFree; lastChannelBusyTime = simTime(); handleWithFSM(MEDIUM_STATE_CHANGED); + // An idle contender otherwise has no transition that applies a deferred channel change. + if (mediumFree) + mac->scheduleRadioConfigRetry(); } void Contention::handleMessage(cMessage *msg) diff --git a/src/inet/linklayer/ieee80211/mac/contract/ITx.h b/src/inet/linklayer/ieee80211/mac/contract/ITx.h index 090969da65c..d0adb83061b 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/ITx.h +++ b/src/inet/linklayer/ieee80211/mac/contract/ITx.h @@ -34,6 +34,9 @@ class INET_API ITx virtual void transmitFrame(Packet *packet, const Ptr& header, ICallback *callback) = 0; virtual void transmitFrame(Packet *packet, const Ptr& header, simtime_t ifs, ICallback *callback) = 0; virtual void radioTransmissionFinished() = 0; + + // Includes a frame waiting for its inter-frame space, as well as on-air transmission. + virtual bool isTransmissionPending() const = 0; }; } // namespace ieee80211 diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.h b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.h index 51ce7172954..6d7de852cb8 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.h +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.h @@ -123,6 +123,7 @@ class INET_API Dcf : public ICoordinationFunction, public IFrameSequenceHandler: public: virtual ~Dcf(); + virtual bool isFrameSequenceRunning() const { return frameSequenceHandler->isSequenceRunning(); } // ICoordinationFunction virtual void processUpperFrame(Packet *packet, const Ptr& header) override; diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h index 72f7af70fc4..f34ea1277f0 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h @@ -164,6 +164,7 @@ class INET_API Hcf : public ICoordinationFunction, public IFrameSequenceHandler: public: virtual ~Hcf(); + virtual bool isFrameSequenceRunning() const { return frameSequenceHandler->isSequenceRunning(); } // ICoordinationFunction virtual void processUpperFrame(Packet *packet, const Ptr& header) override; diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.cc b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.cc index 859a5c108ea..1f1b4bf4d65 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.cc +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.cc @@ -14,6 +14,10 @@ #include "inet/common/packet/Message.h" #include "inet/linklayer/common/MacAddressTag_m.h" #include "inet/linklayer/ieee80211/mac/Ieee80211SubtypeTag_m.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Mac.h" +#include "inet/linklayer/ieee80211/mac/contract/FrameTransmissionDetails_m.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtTransactionTag_m.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211BssidReq_m.h" #include "inet/networklayer/common/NetworkInterface.h" #include "inet/physicallayer/wireless/common/contract/packetlevel/IRadioMedium.h" #include "inet/physicallayer/wireless/common/contract/packetlevel/RadioControlInfo_m.h" @@ -36,6 +40,7 @@ Define_Module(Ieee80211MgmtSta); Register_Class(Ieee80211MgmtSta::HtNegotiationFailure); simsignal_t Ieee80211MgmtSta::htNegotiationFailedSignal = cComponent::registerSignal("htNegotiationFailed"); +static const simsignal_t scanRadioChannelChangedSignal = cComponent::registerSignal("radioChannelChanged"); // message kind values for timers #define MK_AUTH_TIMEOUT 1 @@ -44,6 +49,7 @@ simsignal_t Ieee80211MgmtSta::htNegotiationFailedSignal = cComponent::registerSi #define MK_SCAN_MINCHANNELTIME 4 #define MK_SCAN_MAXCHANNELTIME 5 #define MK_BEACON_TIMEOUT 6 +#define MK_SCAN_NEXTCHANNEL 7 #define MAX_BEACONS_MISSED 3.5 // beacon lost timeout, in beacon intervals (doesn't need to be integer) @@ -159,7 +165,7 @@ void Ieee80211MgmtSta::initialize(int stage) assocTimeoutMsg = nullptr; numChannels = par("numChannels"); - host = getContainingNode(this); + scanningRadio = getModuleFromPar(par("radioModule"), this); WATCH(isScanning); @@ -198,7 +204,7 @@ void Ieee80211MgmtSta::handleTimer(cMessage *msg) else sendAssociationConfirm(ap, PRC_TIMEOUT); } - else if (msg->getKind() == MK_SCAN_MAXCHANNELTIME) { + else if (msg->getKind() == MK_SCAN_MAXCHANNELTIME || msg->getKind() == MK_SCAN_NEXTCHANNEL) { ASSERT(msg == scanTimer); scanTimer = nullptr; // go to next channel during scanning @@ -212,10 +218,11 @@ void Ieee80211MgmtSta::handleTimer(cMessage *msg) scanTimer = nullptr; // Active Scan: send a probe request, then wait for minChannelTime (11.1.3.2.2) delete msg; - sendProbeRequest(); - ASSERT(scanTimer == nullptr); - scanTimer = new cMessage("minChannelTime", MK_SCAN_MINCHANNELTIME); - scheduleAfter(scanning.minChannelTime, scanTimer); // TODO actually, we should start waiting after ProbeReq actually got transmitted + scanPhase = SCAN_WAIT_PROBE; + if (++nextProbeTransactionId == 0) + throw cRuntimeError("Probe transaction identifier exhausted"); + pendingProbeTransactionId = nextProbeTransactionId; + sendProbeRequest(); // Dwell begins on completion of this probe's final fragment. } else if (msg->getKind() == MK_SCAN_MINCHANNELTIME) { ASSERT(msg == scanTimer); @@ -225,6 +232,7 @@ void Ieee80211MgmtSta::handleTimer(cMessage *msg) if (scanning.busyChannelDetected) { EV << "Busy channel detected during minChannelTime, continuing listening until maxChannelTime elapses\n"; ASSERT(scanTimer == nullptr); + scanPhase = SCAN_MAX_DWELL; scanTimer = new cMessage("maxChannelTime", MK_SCAN_MAXCHANNELTIME); scheduleAfter(scanning.maxChannelTime - scanning.minChannelTime, scanTimer); } @@ -319,7 +327,11 @@ void Ieee80211MgmtSta::sendManagementFrame(const char *name, const PtraddTag()->setDestAddress(address); + // IEEE Std 802.11-2024, 9.3.3.1: use the target AP, including before association. + packet->addTag()->setBssid(address); packet->addTag()->setSubtype(subtype); + if (subtype == ST_PROBEREQUEST && isScanning && scanPhase == SCAN_WAIT_PROBE) + packet->addTag()->setTransactionId(pendingProbeTransactionId); packet->insertAtBack(body); sendDown(packet); } @@ -399,17 +411,89 @@ void Ieee80211MgmtSta::startReassociation(ApInfo *ap, simtime_t timeout) void Ieee80211MgmtSta::receiveSignal(cComponent *source, simsignal_t signalID, intval_t value, cObject *details) { Enter_Method("%s", cComponent::getSignalName(signalID)); - - // Note that we are only subscribed during scanning! - if (signalID == IRadio::receptionStateChangedSignal) { - IRadio::ReceptionState newReceptionState = static_cast(value); - if (newReceptionState != IRadio::RECEPTION_STATE_UNDEFINED && newReceptionState != IRadio::RECEPTION_STATE_IDLE) { - EV << "busy radio channel detected during scanning\n"; + if (!isScanning || source != scanningRadio) + return; + if (signalID == scanRadioChannelChangedSignal && scanPhase == SCAN_WAIT_CHANNEL && + value == scanning.channelList[scanning.currentChannelIndex]) { + ASSERT(scanTimer == nullptr); + if (scanning.activeScan) { + scanPhase = SCAN_PROBE_DELAY; + scanTimer = new cMessage("sendProbe", MK_SCAN_SENDPROBE); + scheduleAfter(scanning.probeDelay, scanTimer); + } + else { + scanPhase = SCAN_MAX_DWELL; + scanTimer = new cMessage("maxChannelTime", MK_SCAN_MAXCHANNELTIME); + scheduleAfter(scanning.maxChannelTime, scanTimer); + } + } + else if (signalID == IRadio::receptionStateChangedSignal && scanPhase == SCAN_MIN_DWELL) { + auto state = static_cast(value); + if (state != IRadio::RECEPTION_STATE_UNDEFINED && state != IRadio::RECEPTION_STATE_IDLE) scanning.busyChannelDetected = true; + } +} + +void Ieee80211MgmtSta::startScanDwell() +{ + // IEEE Std 802.11-2024, 11.1.4.3.2: send the probe before starting ActiveScanningTimer. + // Matching transmission completion implements that ordering after contention. + ASSERT(scanTimer == nullptr); + pendingProbeTransactionId = 0; + scanPhase = SCAN_MIN_DWELL; + auto state = check_and_cast(scanningRadio.get())->getReceptionState(); + scanning.busyChannelDetected = state != IRadio::RECEPTION_STATE_UNDEFINED && state != IRadio::RECEPTION_STATE_IDLE; + scanTimer = new cMessage("minChannelTime", MK_SCAN_MINCHANNELTIME); + scheduleAfter(scanning.minChannelTime, scanTimer); +} + +void Ieee80211MgmtSta::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) +{ + if (signalID != packetSentToPeerSignal && signalID != Ieee80211Mac::frameTransmissionOutcomeSignal) { + Ieee80211MgmtBase::receiveSignal(source, signalID, obj, details); + return; + } + Enter_Method("%s", cComponent::getSignalName(signalID)); + if (!isScanning || scanPhase != SCAN_WAIT_PROBE) + return; + auto packet = dynamic_cast(obj); + if (!packet) + return; + auto tag = packet->findTag(); + if (!tag || tag->getTransactionId() != pendingProbeTransactionId) + return; + auto header = dynamicPtrCast(packet->peekAtFront(b(-1), Chunk::PF_ALLOW_NULLPTR)); + if (!header || header->getType() != ST_PROBEREQUEST) + return; + if (signalID == packetSentToPeerSignal) { + if (!header->getMoreFragments()) + startScanDwell(); + } + else { + auto outcome = check_and_cast(details); + if (outcome->getStatus() == FRAME_TRANSMISSION_STATUS_DROPPED_BEFORE_TRANSMISSION || + outcome->getStatus() == FRAME_TRANSMISSION_STATUS_RETRY_LIMIT_REACHED) { + pendingProbeTransactionId = 0; + scanPhase = SCAN_IDLE; + scanTimer = new cMessage("nextScanChannel", MK_SCAN_NEXTCHANNEL); + scheduleAt(simTime(), scanTimer); } } } +void Ieee80211MgmtSta::stopScanListening() +{ + if (isScanning) { + scanningRadio->unsubscribe(IRadio::receptionStateChangedSignal, this); + scanningRadio->unsubscribe(scanRadioChannelChangedSignal, this); + myIface->unsubscribe(packetSentToPeerSignal, this); + myIface->unsubscribe(Ieee80211Mac::frameTransmissionOutcomeSignal, this); + } + isScanning = false; + scanPhase = SCAN_IDLE; + pendingProbeTransactionId = 0; +} + void Ieee80211MgmtSta::processScanCommand(Ieee80211Prim_ScanRequest *ctrl) { EV << "Received Scan Request from agent, clearing AP list and starting scanning...\n"; @@ -441,8 +525,10 @@ void Ieee80211MgmtSta::processScanCommand(Ieee80211Prim_ScanRequest *ctrl) scanning.channelList.push_back(i); // start scanning - if (scanning.activeScan) - host->subscribe(IRadio::receptionStateChangedSignal, this); + scanningRadio->subscribe(IRadio::receptionStateChangedSignal, this); + scanningRadio->subscribe(scanRadioChannelChangedSignal, this); + myIface->subscribe(packetSentToPeerSignal, this); + myIface->subscribe(Ieee80211Mac::frameTransmissionOutcomeSignal, this); scanning.currentChannelIndex = -1; // so we'll start with index==0 isScanning = true; scanNextChannel(); @@ -453,29 +539,17 @@ bool Ieee80211MgmtSta::scanNextChannel() // if we're already at the last channel, we're through if (scanning.currentChannelIndex == (int)scanning.channelList.size() - 1) { EV << "Finished scanning last channel\n"; - if (scanning.activeScan) - host->unsubscribe(IRadio::receptionStateChangedSignal, this); - isScanning = false; + stopScanListening(); return true; // we're done } - // tune to next channel + // Start timing only after the own radio confirms that the requested channel is applied. int newChannel = scanning.channelList[++scanning.currentChannelIndex]; - changeChannel(newChannel); scanning.busyChannelDetected = false; - + pendingProbeTransactionId = 0; + scanPhase = SCAN_WAIT_CHANNEL; ASSERT(scanTimer == nullptr); - if (scanning.activeScan) { - // Active Scan: first wait probeDelay, then send a probe. Listening - // for minChannelTime or maxChannelTime takes place after that. (11.1.3.2) - scanTimer = new cMessage("sendProbe", MK_SCAN_SENDPROBE); - scheduleAfter(scanning.probeDelay, scanTimer); - } - else { - // Passive Scan: spend maxChannelTime on the channel (11.1.3.1) - scanTimer = new cMessage("maxChannelTime", MK_SCAN_MAXCHANNELTIME); - scheduleAfter(scanning.maxChannelTime, scanTimer); - } + changeChannel(newChannel); return false; } @@ -657,9 +731,7 @@ bool Ieee80211MgmtSta::terminateCurrentAssociationFromPeer(const MacAddress& add void Ieee80211MgmtSta::stop() { - if (host != nullptr && isScanning && scanning.activeScan) - host->unsubscribe(IRadio::receptionStateChangedSignal, this); - isScanning = false; + stopScanListening(); cancelScanTimer(); scanning = ScanningInfo(); diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.h b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.h index 326c7638644..eb05b5d9217 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.h +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.h @@ -109,7 +109,13 @@ class INET_API Ieee80211MgmtSta : public Ieee80211MgmtBase }; protected: - cModule *host; + opp_component_ptr scanningRadio; + enum ScanPhase { SCAN_IDLE, SCAN_WAIT_CHANNEL, SCAN_PROBE_DELAY, SCAN_WAIT_PROBE, SCAN_MIN_DWELL, SCAN_MAX_DWELL }; + ScanPhase scanPhase = SCAN_IDLE; + uint64_t nextProbeTransactionId = 0; + uint64_t pendingProbeTransactionId = 0; + void stopScanListening(); + void startScanDwell(); // number of channels in RadioMedium -- used if we're told to scan "all" channels int numChannels; @@ -130,7 +136,7 @@ class INET_API Ieee80211MgmtSta : public Ieee80211MgmtBase AssociatedApInfo assocAP; public: - Ieee80211MgmtSta() : host(nullptr), numChannels(-1), isScanning(false), scanTimer(nullptr), assocTimeoutMsg(nullptr) {} + Ieee80211MgmtSta() : numChannels(-1), isScanning(false), scanTimer(nullptr), assocTimeoutMsg(nullptr) {} virtual ~Ieee80211MgmtSta(); virtual const ApInfo *getAssociatedAp() { return &assocAP; } @@ -227,6 +233,7 @@ class INET_API Ieee80211MgmtSta : public Ieee80211MgmtBase /** Called by the signal handler whenever a change occurs we're interested in */ virtual void receiveSignal(cComponent *source, simsignal_t signalID, intval_t value, cObject *details) override; + virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) override; /** lifecycle support */ virtual void stop() override; diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.ned b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.ned index 8ca5f6e70d5..f5b5f289aa2 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.ned +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.ned @@ -31,6 +31,7 @@ simple Ieee80211MgmtSta extends SimpleModule like IIeee80211Mgmt string interfaceTableModule; // The path to the InterfaceTable module int numChannels = default(1); // Number of channels to scan string mibModule; + string radioModule = default("^.radio"); // Own radio for scan channel completion and busy detection string macModule; // The path to the MAC module @display("i=block/cogwheel"); @signal[l2Associated](type=inet::NetworkInterface); diff --git a/tests/module/Ieee80211ScanCompletionQos_1.test b/tests/module/Ieee80211ScanCompletionQos_1.test new file mode 100644 index 00000000000..8c9e97369f7 --- /dev/null +++ b/tests/module/Ieee80211ScanCompletionQos_1.test @@ -0,0 +1,240 @@ +%description: +Verify that active scan minimum dwell begins after the Probe Request has completed +transmission, and that passive scan dwell confirmation is preceded by application +of the requested radio channel when the scan starts while the radio is busy. + +%file: Ieee80211ScanCompletionQos.cc + +#include + +#include "inet/common/InitStages.h" +#include "inet/common/Simsignals.h" +#include "inet/common/packet/Packet.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211Primitives_m.h" +#include "inet/physicallayer/wireless/common/contract/packetlevel/IRadio.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" + +namespace inet { +namespace ieee80211 { + +class ScanCompletionQosObserver : public cSimpleModule, public cListener +{ + protected: + cMessage *trigger = nullptr; + cModule *radio = nullptr; + bool activeScan = true; + bool waitForBusyReception = false; + int targetChannel = 1; + bool scanRequested = false; + int scanConfirms = 0; + bool probeTransmissionCompleted = false; + simtime_t probeTransmissionCompletionTime = SIMTIME_ZERO; + bool requestedChannelApplied = false; + simtime_t requestedChannelApplicationTime = SIMTIME_ZERO; + simtime_t scanConfirmTime = SIMTIME_ZERO; + + public: + int getScanConfirms() const { return scanConfirms; } + bool hasProbeTransmissionCompleted() const { return probeTransmissionCompleted; } + simtime_t getProbeTransmissionCompletionTime() const { return probeTransmissionCompletionTime; } + bool hasRequestedChannelApplied() const { return requestedChannelApplied; } + simtime_t getRequestedChannelApplicationTime() const { return requestedChannelApplicationTime; } + simtime_t getScanConfirmTime() const { return scanConfirmTime; } + + protected: + virtual int numInitStages() const override { return NUM_INIT_STAGES; } + + virtual void initialize(int stage) override + { + if (stage == INITSTAGE_LOCAL) { + activeScan = par("active"); + waitForBusyReception = par("waitBusy"); + targetChannel = par("targetChannel"); + trigger = new cMessage("scanTrigger"); + radio = getParentModule()->getSubmodule("radio"); + radio->subscribe(physicallayer::IRadio::receptionStateChangedSignal, this); + radio->subscribe(physicallayer::Ieee80211Radio::radioChannelChangedSignal, this); + getParentModule()->subscribe(packetSentToPeerSignal, this); + } + if (stage == INITSTAGE_APPLICATION_LAYER && !waitForBusyReception) + scheduleAfter(SimTime(1, SIMTIME_MS), trigger); + } + + virtual void handleMessage(cMessage *message) override + { + if (message == trigger) { + ASSERT(!scanRequested); + scanRequested = true; + auto request = new Ieee80211Prim_ScanRequest(); + request->setBSSType(BSSTYPE_INFRASTRUCTURE); + request->setActiveScan(activeScan); + request->setProbeDelay(SIMTIME_ZERO); + request->setMinChannelTime(SimTime(1, SIMTIME_US)); + request->setMaxChannelTime(SimTime(2, SIMTIME_MS)); + request->setChannelListArraySize(1); + request->setChannelList(0, targetChannel); + auto command = new cMessage("scanRequest"); + command->setControlInfo(request); + send(command, "mgmtOut"); + } + else { + auto confirm = dynamic_cast(message->getControlInfo()); + ASSERT(confirm != nullptr); + ASSERT(confirm->getResultCode() == PRC_SUCCESS); + scanConfirms++; + scanConfirmTime = simTime(); + delete message; + } + } + + virtual void receiveSignal(cComponent *, simsignal_t signalID, intval_t value, cObject *) override + { + if (signalID == physicallayer::IRadio::receptionStateChangedSignal && waitForBusyReception && !scanRequested && + !trigger->isScheduled() && value == physicallayer::IRadio::RECEPTION_STATE_RECEIVING) + scheduleAfter(SIMTIME_ZERO, trigger); + else if (signalID == physicallayer::Ieee80211Radio::radioChannelChangedSignal && scanRequested && value == targetChannel) { + requestedChannelApplied = true; + requestedChannelApplicationTime = simTime(); + } + } + + virtual void receiveSignal(cComponent *, simsignal_t signalID, cObject *object, cObject *) override + { + if (signalID != packetSentToPeerSignal || !scanRequested || object == nullptr) + return; + auto packet = dynamic_cast(object); + if (packet == nullptr) + return; + const auto& header = packet->peekAtFront(); + if (header->getType() == ST_PROBEREQUEST) { + ASSERT(!probeTransmissionCompleted); + ASSERT(requestedChannelApplied && requestedChannelApplicationTime <= simTime()); + probeTransmissionCompleted = true; + probeTransmissionCompletionTime = simTime(); + } + } + + using cListener::finish; + + virtual void finish() override + { + if (radio != nullptr) { + radio->unsubscribe(physicallayer::IRadio::receptionStateChangedSignal, this); + radio->unsubscribe(physicallayer::Ieee80211Radio::radioChannelChangedSignal, this); + } + if (getParentModule() != nullptr) + getParentModule()->unsubscribe(packetSentToPeerSignal, this); + } + + public: + virtual ~ScanCompletionQosObserver() + { + cancelAndDelete(trigger); + } +}; + +Define_Module(ScanCompletionQosObserver); + +class Ieee80211ScanCompletionQosTest : public cSimpleModule +{ + protected: + virtual void finish() override + { + auto active = check_and_cast(getModuleByPath("^.activeSta.wlan[0].agent")); + auto passive = check_and_cast(getModuleByPath("^.passiveSta.wlan[0].agent")); + + ASSERT(active->getScanConfirms() == 1); + ASSERT(active->hasProbeTransmissionCompleted()); + ASSERT(active->getScanConfirmTime() >= active->getProbeTransmissionCompletionTime() + SimTime(1, SIMTIME_US)); + + ASSERT(passive->getScanConfirms() == 1); + ASSERT(passive->hasRequestedChannelApplied()); + ASSERT(passive->getRequestedChannelApplicationTime() + SimTime(2, SIMTIME_MS) == passive->getScanConfirmTime()); + + std::cout << "IEEE 802.11 scan dwell and retune completion ordering verified.\n"; + } +}; + +Define_Module(Ieee80211ScanCompletionQosTest); + +} // namespace ieee80211 +} // namespace inet + +%file: test.ned + +import inet.common.SimpleModule; +import inet.linklayer.ieee80211.mgmt.IIeee80211Agent; +import inet.node.inet.WirelessHost; +import inet.node.wireless.AccessPoint; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; + +simple ScanCompletionQosObserver like IIeee80211Agent +{ + parameters: + bool active = default(true); + bool waitBusy = default(false); + int targetChannel = default(1); + @class(::inet::ieee80211::ScanCompletionQosObserver); + gates: + input mgmtIn; + output mgmtOut; +} + +simple Ieee80211ScanCompletionQosTest extends SimpleModule +{ + parameters: + @class(::inet::ieee80211::Ieee80211ScanCompletionQosTest); +} + +network Ieee80211ScanCompletionQosTestNetwork +{ + submodules: + radioMedium: Ieee80211ScalarRadioMedium; + ap: AccessPoint; + activeSta: WirelessHost { + parameters: + wlan[*].agent.typename = "ScanCompletionQosObserver"; + wlan[*].agent.active = true; + wlan[*].agent.waitBusy = false; + } + passiveSta: WirelessHost { + parameters: + wlan[*].agent.typename = "ScanCompletionQosObserver"; + wlan[*].agent.active = false; + wlan[*].agent.waitBusy = true; + } + test: Ieee80211ScanCompletionQosTest; +} + +%inifile: omnetpp.ini + +[General] +network = Ieee80211ScanCompletionQosTestNetwork +ned-path = .;../../../../src;../../lib +seed-set = 0 +**.wlan[*].mac.qosStation = true +sim-time-limit = 5ms +cmdenv-express-mode = false +record-vector-results = false +record-scalar-results = false +**.analogModel.ignorePartialInterference = true +**.mobility.typename = "StationaryMobility" +**.mobility.initFromDisplayString = false +**.mobility.initialX = 0m +**.mobility.initialY = 0m +**.mobility.initialZ = 0m +**.wlan[*].opMode = "g(mixed)" +**.wlan[*].bitrate = 2Mbps +**.wlan[*].radio.channelNumber = 0 +**.wlan[*].radio.transmitter.power = 100mW +**.wlan[*].radio.receiver.sensitivity = -85dBm +**.wlan[*].radio.receiver.snirThreshold = 4dB +*.ap.wlan[*].mgmt.beaconInterval = 1024us +*.activeSta.wlan[*].mgmt.numChannels = 2 +*.passiveSta.wlan[*].mgmt.numChannels = 2 +*.activeSta.wlan[*].mac.dcf.channelAccess.cwMin = 0 +*.passiveSta.wlan[*].mac.dcf.channelAccess.cwMin = 0 + +%contains: stdout +IEEE 802.11 scan dwell and retune completion ordering verified. diff --git a/tests/module/Ieee80211ScanCompletion_1.test b/tests/module/Ieee80211ScanCompletion_1.test new file mode 100644 index 00000000000..c7334513bb3 --- /dev/null +++ b/tests/module/Ieee80211ScanCompletion_1.test @@ -0,0 +1,239 @@ +%description: +Verify that active scan minimum dwell begins after the Probe Request has completed +transmission, and that passive scan dwell confirmation is preceded by application +of the requested radio channel when the scan starts while the radio is busy. + +%file: Ieee80211ScanCompletion.cc + +#include + +#include "inet/common/InitStages.h" +#include "inet/common/Simsignals.h" +#include "inet/common/packet/Packet.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211Primitives_m.h" +#include "inet/physicallayer/wireless/common/contract/packetlevel/IRadio.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" + +namespace inet { +namespace ieee80211 { + +class ScanCompletionObserver : public cSimpleModule, public cListener +{ + protected: + cMessage *trigger = nullptr; + cModule *radio = nullptr; + bool activeScan = true; + bool waitForBusyReception = false; + int targetChannel = 1; + bool scanRequested = false; + int scanConfirms = 0; + bool probeTransmissionCompleted = false; + simtime_t probeTransmissionCompletionTime = SIMTIME_ZERO; + bool requestedChannelApplied = false; + simtime_t requestedChannelApplicationTime = SIMTIME_ZERO; + simtime_t scanConfirmTime = SIMTIME_ZERO; + + public: + int getScanConfirms() const { return scanConfirms; } + bool hasProbeTransmissionCompleted() const { return probeTransmissionCompleted; } + simtime_t getProbeTransmissionCompletionTime() const { return probeTransmissionCompletionTime; } + bool hasRequestedChannelApplied() const { return requestedChannelApplied; } + simtime_t getRequestedChannelApplicationTime() const { return requestedChannelApplicationTime; } + simtime_t getScanConfirmTime() const { return scanConfirmTime; } + + protected: + virtual int numInitStages() const override { return NUM_INIT_STAGES; } + + virtual void initialize(int stage) override + { + if (stage == INITSTAGE_LOCAL) { + activeScan = par("active"); + waitForBusyReception = par("waitBusy"); + targetChannel = par("targetChannel"); + trigger = new cMessage("scanTrigger"); + radio = getParentModule()->getSubmodule("radio"); + radio->subscribe(physicallayer::IRadio::receptionStateChangedSignal, this); + radio->subscribe(physicallayer::Ieee80211Radio::radioChannelChangedSignal, this); + getParentModule()->subscribe(packetSentToPeerSignal, this); + } + if (stage == INITSTAGE_APPLICATION_LAYER && !waitForBusyReception) + scheduleAfter(SimTime(1, SIMTIME_MS), trigger); + } + + virtual void handleMessage(cMessage *message) override + { + if (message == trigger) { + ASSERT(!scanRequested); + scanRequested = true; + auto request = new Ieee80211Prim_ScanRequest(); + request->setBSSType(BSSTYPE_INFRASTRUCTURE); + request->setActiveScan(activeScan); + request->setProbeDelay(SIMTIME_ZERO); + request->setMinChannelTime(SimTime(1, SIMTIME_US)); + request->setMaxChannelTime(SimTime(2, SIMTIME_MS)); + request->setChannelListArraySize(1); + request->setChannelList(0, targetChannel); + auto command = new cMessage("scanRequest"); + command->setControlInfo(request); + send(command, "mgmtOut"); + } + else { + auto confirm = dynamic_cast(message->getControlInfo()); + ASSERT(confirm != nullptr); + ASSERT(confirm->getResultCode() == PRC_SUCCESS); + scanConfirms++; + scanConfirmTime = simTime(); + delete message; + } + } + + virtual void receiveSignal(cComponent *, simsignal_t signalID, intval_t value, cObject *) override + { + if (signalID == physicallayer::IRadio::receptionStateChangedSignal && waitForBusyReception && !scanRequested && + !trigger->isScheduled() && value == physicallayer::IRadio::RECEPTION_STATE_RECEIVING) + scheduleAfter(SIMTIME_ZERO, trigger); + else if (signalID == physicallayer::Ieee80211Radio::radioChannelChangedSignal && scanRequested && value == targetChannel) { + requestedChannelApplied = true; + requestedChannelApplicationTime = simTime(); + } + } + + virtual void receiveSignal(cComponent *, simsignal_t signalID, cObject *object, cObject *) override + { + if (signalID != packetSentToPeerSignal || !scanRequested || object == nullptr) + return; + auto packet = dynamic_cast(object); + if (packet == nullptr) + return; + const auto& header = packet->peekAtFront(); + if (header->getType() == ST_PROBEREQUEST) { + ASSERT(!probeTransmissionCompleted); + ASSERT(requestedChannelApplied && requestedChannelApplicationTime <= simTime()); + probeTransmissionCompleted = true; + probeTransmissionCompletionTime = simTime(); + } + } + + using cListener::finish; + + virtual void finish() override + { + if (radio != nullptr) { + radio->unsubscribe(physicallayer::IRadio::receptionStateChangedSignal, this); + radio->unsubscribe(physicallayer::Ieee80211Radio::radioChannelChangedSignal, this); + } + if (getParentModule() != nullptr) + getParentModule()->unsubscribe(packetSentToPeerSignal, this); + } + + public: + virtual ~ScanCompletionObserver() + { + cancelAndDelete(trigger); + } +}; + +Define_Module(ScanCompletionObserver); + +class Ieee80211ScanCompletionTest : public cSimpleModule +{ + protected: + virtual void finish() override + { + auto active = check_and_cast(getModuleByPath("^.activeSta.wlan[0].agent")); + auto passive = check_and_cast(getModuleByPath("^.passiveSta.wlan[0].agent")); + + ASSERT(active->getScanConfirms() == 1); + ASSERT(active->hasProbeTransmissionCompleted()); + ASSERT(active->getScanConfirmTime() >= active->getProbeTransmissionCompletionTime() + SimTime(1, SIMTIME_US)); + + ASSERT(passive->getScanConfirms() == 1); + ASSERT(passive->hasRequestedChannelApplied()); + ASSERT(passive->getRequestedChannelApplicationTime() + SimTime(2, SIMTIME_MS) == passive->getScanConfirmTime()); + + std::cout << "IEEE 802.11 scan dwell and retune completion ordering verified.\n"; + } +}; + +Define_Module(Ieee80211ScanCompletionTest); + +} // namespace ieee80211 +} // namespace inet + +%file: test.ned + +import inet.common.SimpleModule; +import inet.linklayer.ieee80211.mgmt.IIeee80211Agent; +import inet.node.inet.WirelessHost; +import inet.node.wireless.AccessPoint; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; + +simple ScanCompletionObserver like IIeee80211Agent +{ + parameters: + bool active = default(true); + bool waitBusy = default(false); + int targetChannel = default(1); + @class(::inet::ieee80211::ScanCompletionObserver); + gates: + input mgmtIn; + output mgmtOut; +} + +simple Ieee80211ScanCompletionTest extends SimpleModule +{ + parameters: + @class(::inet::ieee80211::Ieee80211ScanCompletionTest); +} + +network Ieee80211ScanCompletionTestNetwork +{ + submodules: + radioMedium: Ieee80211ScalarRadioMedium; + ap: AccessPoint; + activeSta: WirelessHost { + parameters: + wlan[*].agent.typename = "ScanCompletionObserver"; + wlan[*].agent.active = true; + wlan[*].agent.waitBusy = false; + } + passiveSta: WirelessHost { + parameters: + wlan[*].agent.typename = "ScanCompletionObserver"; + wlan[*].agent.active = false; + wlan[*].agent.waitBusy = true; + } + test: Ieee80211ScanCompletionTest; +} + +%inifile: omnetpp.ini + +[General] +network = Ieee80211ScanCompletionTestNetwork +ned-path = .;../../../../src;../../lib +seed-set = 0 +sim-time-limit = 5ms +cmdenv-express-mode = false +record-vector-results = false +record-scalar-results = false +**.analogModel.ignorePartialInterference = true +**.mobility.typename = "StationaryMobility" +**.mobility.initFromDisplayString = false +**.mobility.initialX = 0m +**.mobility.initialY = 0m +**.mobility.initialZ = 0m +**.wlan[*].opMode = "g(mixed)" +**.wlan[*].bitrate = 2Mbps +**.wlan[*].radio.channelNumber = 0 +**.wlan[*].radio.transmitter.power = 100mW +**.wlan[*].radio.receiver.sensitivity = -85dBm +**.wlan[*].radio.receiver.snirThreshold = 4dB +*.ap.wlan[*].mgmt.beaconInterval = 1024us +*.activeSta.wlan[*].mgmt.numChannels = 2 +*.passiveSta.wlan[*].mgmt.numChannels = 2 +*.activeSta.wlan[*].mac.dcf.channelAccess.cwMin = 0 +*.passiveSta.wlan[*].mac.dcf.channelAccess.cwMin = 0 + +%contains: stdout +IEEE 802.11 scan dwell and retune completion ordering verified. diff --git a/tests/module/Ieee80211StaMgmtBssid_1.test b/tests/module/Ieee80211StaMgmtBssid_1.test new file mode 100644 index 00000000000..0b9e418cb1c --- /dev/null +++ b/tests/module/Ieee80211StaMgmtBssid_1.test @@ -0,0 +1,69 @@ +%description: +Infrastructure STA management packets retain the target BSSID at the real MAC +header construction boundary, including wildcard scans before association. + +%file: TestStaMgmtBssid.cc +#include +#include "inet/linklayer/ieee80211/mac/Ieee80211Mac.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame_m.h" +namespace inet { namespace ieee80211 { +class TestStaMgmtBssid : public Ieee80211Mac +{ + protected: + int probes = 0, authentications = 0, associations = 0; + virtual void processUpperFrame(Packet *packet, const Ptr& header) override { + if (dynamicPtrCast(header)) { + ASSERT(header->getAddress3() == header->getReceiverAddress()); + if (header->getType() == ST_PROBEREQUEST) probes++; + if (header->getType() == ST_AUTHENTICATION) authentications++; + if (header->getType() == ST_ASSOCIATIONREQUEST) associations++; + } + Ieee80211Mac::processUpperFrame(packet, header); + } + virtual void finish() override { + ASSERT(probes > 0 && authentications > 0 && associations > 0); + std::cout << "STA scan, authentication and association headers carry their target BSSID.\n"; + } +}; +Define_Module(TestStaMgmtBssid); +}} + +%file: test.ned +import inet.linklayer.ieee80211.mac.Ieee80211Mac; +import inet.node.wireless.AccessPoint; +import inet.node.inet.WirelessHost; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; +module TestStaMgmtBssid extends Ieee80211Mac { + parameters: + @class(::inet::ieee80211::TestStaMgmtBssid); +} +network StaMgmtBssidNetwork { + submodules: + radioMedium: Ieee80211ScalarRadioMedium; + ap: AccessPoint; + sta: WirelessHost; +} + +%inifile: omnetpp.ini +[General] +network = StaMgmtBssidNetwork +ned-path = .;../../../../src;../../lib +sim-time-limit = 1s +seed-set = 0 +cmdenv-express-mode = true +record-vector-results = false +record-scalar-results = false +*.sta.wlan[0].mac.typename = "TestStaMgmtBssid" +*.ap.wlan[0].mgmt.ssid = "test" +*.sta.wlan[0].agent.defaultSsid = "test" +*.sta.wlan[0].agent.channelsToScan = "0" +**.opMode = "g(erp)" +**.bitrate = 6Mbps +**.mobility.initFromDisplayString = false +*.ap.mobility.initialX = 0m +*.sta.mobility.initialX = 10m +**.mobility.initialY = 0m +**.mobility.initialZ = 0m + +%contains: stdout +STA scan, authentication and association headers carry their target BSSID. diff --git a/tests/unit/Ieee80211ScanEventScope_1.test b/tests/unit/Ieee80211ScanEventScope_1.test new file mode 100644 index 00000000000..a9d3d613474 --- /dev/null +++ b/tests/unit/Ieee80211ScanEventScope_1.test @@ -0,0 +1,47 @@ +%description: +Scan busy detection accepts only the scanning radio's indication, and a completed +probe from an older transaction cannot advance a new scan's state machine. + +%includes: +#include "inet/common/Simsignals.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtTransactionTag_m.h" +#include "inet/physicallayer/wireless/common/contract/packetlevel/IRadio.h" +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +%global: +class ScopedScanStation : public Ieee80211MgmtSta { + public: + void checkScope(cModule *own, cModule *other) { + ASSERT(own != other); + isScanning = true; + scanPhase = SCAN_MIN_DWELL; + scanningRadio = own; + scanning.busyChannelDetected = false; + receiveSignal(other, IRadio::receptionStateChangedSignal, intval_t(IRadio::RECEPTION_STATE_RECEIVING), nullptr); + ASSERT(!scanning.busyChannelDetected); + receiveSignal(own, IRadio::receptionStateChangedSignal, intval_t(IRadio::RECEPTION_STATE_RECEIVING), nullptr); + ASSERT(scanning.busyChannelDetected); + scanPhase = SCAN_WAIT_PROBE; + pendingProbeTransactionId = 8; + auto header = makeShared(); + header->setType(ST_PROBEREQUEST); + Packet stale("old-probe", header); + stale.addTag()->setTransactionId(7); + receiveSignal(own, packetSentToPeerSignal, &stale, nullptr); + ASSERT(scanPhase == SCAN_WAIT_PROBE && pendingProbeTransactionId == 8 && scanTimer == nullptr); + // This harness calls the handler directly; it never installs subscriptions. + isScanning = false; + scanningRadio = nullptr; + } +}; + +%activity: +ScopedScanStation station; +station.checkScope(getSimulation()->getSystemModule(), &station); +EV << "Scan events are scoped to the radio and probe transaction.\n"; + +%contains: stdout +Scan events are scoped to the radio and probe transaction.