From 7431be3f5ca2850e0b5d5e69d55b682e69bd9400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Fri, 18 Sep 2026 20:25:07 +0200 Subject: [PATCH 1/3] common: change: prepare interface identity before link-layer users Link-layer initialization may resolve peer interfaces by their configured addresses. Declare network-interface configuration as a prerequisite so these queries see initialized identities regardless of module declaration order. The physical-layer prerequisite remains in place. This ordering supplies the shared readiness contract used by simplified wireless association before network configuration. No fingerprint or statistical baseline is changed. The selected regression contract covers MacNonQos and MacQos, run 0, in examples/adhoc/qos, plus simplified association and AP lifecycle module tests in debug mode. Plan: plan/done/80211htcapop-refactor-v3.md Change: src.common.InitStages | behavior.change | test | ieee80211-htcapop-v3 --- src/inet/common/InitStages.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/inet/common/InitStages.cc b/src/inet/common/InitStages.cc index ccd02afae27..e28cb982f6d 100644 --- a/src/inet/common/InitStages.cc +++ b/src/inet/common/InitStages.cc @@ -54,6 +54,7 @@ Define_InitStage_Dependency(QUEUEING, GATE_SCHEDULE_CONFIGURATION); Define_InitStage(LINK_LAYER); Define_InitStage_Dependency(LINK_LAYER, PHYSICAL_LAYER); +Define_InitStage_Dependency(LINK_LAYER, NETWORK_INTERFACE_CONFIGURATION); Define_InitStage(NETWORK_CONFIGURATION); Define_InitStage_Dependency(NETWORK_CONFIGURATION, LINK_LAYER); From aaa369f76a8ad28e711ae6535d4a95cf1d2ad96f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Fri, 18 Sep 2026 20:46:35 +0200 Subject: [PATCH 2/3] ieee80211: change: separate capability and BSS state ownership Capability preparation previously depended on initialization broadcasts and mixed local PHY support with active BSS operation. Make the MAC prepare an idempotent profile through typed PHY contributors, and let management own accepted BSS transitions and simplified peer installation. Keep directional peer capabilities immutable and evaluate operation and HT eligibility separately. Publish committed MIB changes after management bookkeeping, guard state mutation, and replace modesetChanged listeners with explicit configuration providers. Prepare simplified associations before network configuration, independently of node declaration order. Keep simplified legacy STAs associated without accepting HT operation from an HT AP. Cover both declaration orders and shutdown/crash restart, while retaining BSS identity and channel and removing AP-side peer resources on stop. Migrate dependent consumers and regression fixtures together with the contracts. Document public API migration and the notification lifetime. Validation scope: debug build; 3 focused unit and 9 module cases covering capability preparation, provider wiring, association, lifecycle, beacon and channel updates; MacNonQos and MacQos run 0 fingerprints at 10s with unchanged tplx expectations. No fingerprint or statistical baseline changes. Plan: plan/done/80211htcapop-refactor-v3.md Change: src.ieee80211 | behavior.change | test expected whatsnew migration | ieee80211-htcapop-v3 --- WHATSNEW | 16 + .../design/ieee80211-model-architecture.md | 25 + doc/src/migration-guide/index.rst | 42 ++ .../done/80211htcapop-refactor-v3-evidence.md | 266 +++++++++ plan/done/80211htcapop-refactor-v3.md | 519 ++++++++++++++++++ src/inet/common/Simsignals.cc | 1 - src/inet/common/Simsignals.h | 1 - src/inet/linklayer/ieee80211/__TODO | 1 - src/inet/linklayer/ieee80211/mac/Ds.cc | 12 +- .../linklayer/ieee80211/mac/Ieee80211Mac.cc | 104 +++- .../linklayer/ieee80211/mac/Ieee80211Mac.h | 6 +- .../linklayer/ieee80211/mac/Ieee80211Mac.ned | 6 +- .../OriginatorBlockAckAgreementPolicy.cc | 2 +- .../OriginatorBlockAckAgreementPolicy.h | 4 +- .../OriginatorBlockAckAgreementPolicy.ned | 1 + .../ieee80211/mac/channelaccess/Dcaf.cc | 16 +- .../ieee80211/mac/channelaccess/Dcaf.h | 6 +- .../ieee80211/mac/channelaccess/Dcaf.ned | 1 + .../ieee80211/mac/channelaccess/Edcaf.cc | 11 +- .../ieee80211/mac/channelaccess/Edcaf.h | 5 +- .../ieee80211/mac/channelaccess/Edcaf.ned | 1 + .../ieee80211/mac/common/ModeSetListener.cc | 33 -- .../ieee80211/mac/common/ModeSetListener.h | 32 -- .../ieee80211/mac/common/ModeSetModuleBase.cc | 20 + .../ieee80211/mac/common/ModeSetModuleBase.h | 26 + .../mac/contract/IIeee80211MacConfiguration.h | 27 + .../contract/IIeee80211MacConfiguration.ned | 12 + .../ieee80211/mac/coordinationfunction/Dcf.cc | 4 +- .../ieee80211/mac/coordinationfunction/Dcf.h | 4 +- .../mac/coordinationfunction/Dcf.ned | 1 + .../ieee80211/mac/coordinationfunction/Hcf.cc | 6 +- .../ieee80211/mac/coordinationfunction/Hcf.h | 4 +- .../mac/coordinationfunction/Hcf.ned | 1 + .../mac/framesequence/FrameSequenceContext.cc | 2 +- .../mac/framesequence/FrameSequenceContext.h | 4 +- .../mac/originator/OriginatorAckPolicy.cc | 2 +- .../mac/originator/OriginatorAckPolicy.h | 4 +- .../mac/originator/OriginatorAckPolicy.ned | 1 + .../mac/originator/OriginatorQosAckPolicy.cc | 2 +- .../mac/originator/OriginatorQosAckPolicy.h | 4 +- .../mac/originator/OriginatorQosAckPolicy.ned | 1 + .../ieee80211/mac/originator/QosRtsPolicy.cc | 2 +- .../ieee80211/mac/originator/QosRtsPolicy.h | 4 +- .../ieee80211/mac/originator/QosRtsPolicy.ned | 1 + .../ieee80211/mac/originator/RtsPolicy.cc | 2 +- .../ieee80211/mac/originator/RtsPolicy.h | 4 +- .../ieee80211/mac/originator/RtsPolicy.ned | 1 + .../ieee80211/mac/originator/TxopProcedure.cc | 2 +- .../ieee80211/mac/originator/TxopProcedure.h | 4 +- .../mac/originator/TxopProcedure.ned | 1 + .../OriginatorProtectionMechanism.cc | 2 +- .../OriginatorProtectionMechanism.h | 4 +- .../OriginatorProtectionMechanism.ned | 1 + .../SingleProtectionMechanism.cc | 2 +- .../SingleProtectionMechanism.h | 4 +- .../SingleProtectionMechanism.ned | 1 + .../mac/ratecontrol/AarfRateControl.ned | 1 + .../mac/ratecontrol/OnoeRateControl.ned | 1 + .../mac/ratecontrol/RateControlBase.cc | 13 +- .../mac/ratecontrol/RateControlBase.h | 5 +- .../Ieee80211PeerModeSelection.cc | 16 +- .../Ieee80211PeerModeSelection.h | 3 +- .../mac/rateselection/QosRateSelection.cc | 15 +- .../mac/rateselection/QosRateSelection.h | 6 +- .../mac/rateselection/QosRateSelection.ned | 1 + .../mac/rateselection/RateSelection.cc | 14 +- .../mac/rateselection/RateSelection.h | 6 +- .../mac/rateselection/RateSelection.ned | 1 + .../ieee80211/mac/recipient/CtsPolicy.cc | 2 +- .../ieee80211/mac/recipient/CtsPolicy.h | 4 +- .../ieee80211/mac/recipient/CtsPolicy.ned | 1 + .../ieee80211/mac/recipient/QosCtsPolicy.cc | 2 +- .../ieee80211/mac/recipient/QosCtsPolicy.h | 2 +- .../ieee80211/mac/recipient/QosCtsPolicy.ned | 1 + .../mac/recipient/RecipientAckPolicy.cc | 2 +- .../mac/recipient/RecipientAckPolicy.h | 4 +- .../mac/recipient/RecipientAckPolicy.ned | 1 + .../mac/recipient/RecipientQosAckPolicy.cc | 2 +- .../mac/recipient/RecipientQosAckPolicy.h | 4 +- .../mac/recipient/RecipientQosAckPolicy.ned | 1 + .../ieee80211/mgmt/Ieee80211AgentSta.cc | 2 +- .../ieee80211/mgmt/Ieee80211MgmtAdhoc.cc | 7 + .../ieee80211/mgmt/Ieee80211MgmtAdhoc.h | 1 + .../ieee80211/mgmt/Ieee80211MgmtAp.cc | 53 +- .../ieee80211/mgmt/Ieee80211MgmtAp.ned | 3 +- .../ieee80211/mgmt/Ieee80211MgmtApBase.cc | 74 ++- .../ieee80211/mgmt/Ieee80211MgmtApBase.h | 10 +- .../mgmt/Ieee80211MgmtApSimplified.ned | 3 +- .../ieee80211/mgmt/Ieee80211MgmtBase.cc | 111 ++-- .../ieee80211/mgmt/Ieee80211MgmtBase.h | 12 +- .../ieee80211/mgmt/Ieee80211MgmtSta.cc | 94 ++-- .../ieee80211/mgmt/Ieee80211MgmtSta.h | 1 + .../mgmt/Ieee80211MgmtStaSimplified.cc | 36 +- .../mgmt/contract/IIeee80211BssProvider.h | 23 + .../mgmt/contract/IIeee80211BssProvider.ned | 12 + .../ieee80211/mib/Ieee80211HtCapabilities.h | 20 +- .../linklayer/ieee80211/mib/Ieee80211Mib.cc | 291 +++++----- .../linklayer/ieee80211/mib/Ieee80211Mib.h | 48 +- .../linklayer/ieee80211/mib/Ieee80211Mib.ned | 1 + .../base/L3NetworkConfiguratorBase.cc | 2 +- .../contract/IIeee80211ReceiverCapabilities.h | 25 + .../IIeee80211ReceiverCapabilities.ned | 10 + .../IIeee80211TransmitterCapabilities.h | 24 + .../IIeee80211TransmitterCapabilities.ned | 10 + .../packetlevel/Ieee80211Receiver.cc | 5 + .../ieee80211/packetlevel/Ieee80211Receiver.h | 6 +- .../packetlevel/Ieee80211Receiver.ned | 4 +- .../packetlevel/Ieee80211Transmitter.h | 5 +- .../packetlevel/Ieee80211Transmitter.ned | 4 +- .../Ieee80211AgentStaReassociation_1.test | 14 +- .../Ieee80211ConfigurationContracts_1.test | 202 +++++++ .../Ieee80211HtAntennaRateControl_1.test | 24 +- tests/module/Ieee80211HtAssociation_1.test | 24 +- .../Ieee80211HtCapabilityPreparation_1.test | 157 ++++++ .../Ieee80211MgmtApChannelChange_1.test | 45 +- .../module/Ieee80211MgmtApGenericRadio_1.test | 2 +- .../module/Ieee80211MgmtApHcfQueueDrop_1.test | 8 +- .../Ieee80211MgmtApHcfRtsTimeout_1.test | 6 +- tests/module/Ieee80211MgmtApLifecycle_1.test | 22 +- .../Ieee80211MgmtApMalformedHtCap_1.test | 24 +- tests/module/Ieee80211MgmtApQueueDrop_1.test | 4 +- ...eee80211MgmtApReassociationSnapshot_1.test | 48 +- tests/module/Ieee80211MgmtApTimeout_1.test | 16 +- .../Ieee80211MgmtStaBeaconUpdate_1.test | 116 +++- .../Ieee80211MgmtStaDeauthentication_1.test | 22 +- .../Ieee80211MgmtStaDisassociation_1.test | 12 +- tests/module/Ieee80211MgmtStaDiscovery_1.test | 32 +- tests/module/Ieee80211MgmtStaLifecycle_1.test | 17 +- ...0211MgmtStaSimplifiedInitialization_1.test | 149 ++++- .../protocol/wifi/11n/WifiHtAssociation.test | 41 ++ tests/protocol/wifi/common/WifiDeauth.test | 4 +- tests/unit/Ieee80211HtCapabilities_1.test | 8 +- tests/unit/Ieee80211MibAssociationId_1.test | 12 +- tests/unit/Ieee80211PeerModeSelection_1.test | 63 ++- 134 files changed, 2586 insertions(+), 762 deletions(-) create mode 100644 plan/done/80211htcapop-refactor-v3-evidence.md create mode 100644 plan/done/80211htcapop-refactor-v3.md delete mode 100644 src/inet/linklayer/ieee80211/mac/common/ModeSetListener.cc delete mode 100644 src/inet/linklayer/ieee80211/mac/common/ModeSetListener.h create mode 100644 src/inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.cc create mode 100644 src/inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h create mode 100644 src/inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.h create mode 100644 src/inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.ned create mode 100644 src/inet/linklayer/ieee80211/mgmt/contract/IIeee80211BssProvider.h create mode 100644 src/inet/linklayer/ieee80211/mgmt/contract/IIeee80211BssProvider.ned create mode 100644 src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211ReceiverCapabilities.h create mode 100644 src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211ReceiverCapabilities.ned create mode 100644 src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211TransmitterCapabilities.h create mode 100644 src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211TransmitterCapabilities.ned create mode 100644 tests/module/Ieee80211ConfigurationContracts_1.test create mode 100644 tests/module/Ieee80211HtCapabilityPreparation_1.test create mode 100644 tests/protocol/wifi/11n/WifiHtAssociation.test diff --git a/WHATSNEW b/WHATSNEW index 1a61a1b6935..9e29f5c5c19 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -280,6 +280,22 @@ Notable backward incompatible changes are the following: arrives, so renewal timing does not move; traffic from the client starts one probeWait later. +16. IEEE 802.11 capability and BSS state ownership + + HT capabilities are prepared independently of the active BSS operation. + Simplified associations prepare both peers before network configuration, + regardless of node declaration order. Accepted BSS changes publish a + bssStateChanged notification after management completes its bookkeeping. + Legacy simplified stations retain BSS identity and channel information + without accepting the HT operation advertised by an HT access point. + + Custom WLAN modules must replace ModeSetListener and modesetChanged with + an explicit modeSetModule provider, and replace direct MIB BSS/profile + writes with the owner APIs. Custom HT PHY contributors use typed capability + interfaces. The migration guide describes these source and wiring changes. + The selected legacy QoS and non-QoS ad hoc fingerprints remain unchanged; + no recorded fingerprint baseline was updated. + Notable backward compatible changes are the following: 1. IEEE 802.11 per-station rate statistics diff --git a/doc/project/design/ieee80211-model-architecture.md b/doc/project/design/ieee80211-model-architecture.md index 1a8f75f9199..7df73468377 100644 --- a/doc/project/design/ieee80211-model-architecture.md +++ b/doc/project/design/ieee80211-model-architecture.md @@ -92,3 +92,28 @@ Concrete signals declare their source/scope, change condition, payload, and life [AR-COM-NOTIFY](../rule/architecture.md#ar-com-notify). An immutable borrowed snapshot is allowed; it creates no second writable authority. Commands, queries, and required coordination use typed calls or protocol messages, rather than notifications. + +**Implemented HT contracts.** `IIeee80211MacConfiguration` exposes the configured catalog after +`LOCAL` and an idempotent `prepareLocalCapabilities()` operation after PHY readiness. MAC consumers +resolve their `modeSetModule` dependency through `ModeSetModuleBase` at `LINK_LAYER`; the MAC NED +provides the default descendant path. Management uses `macModule`. `LINK_LAYER` depends explicitly +on both `PHYSICAL_LAYER` and `NETWORK_INTERFACE_CONFIGURATION`, so addresses and contribution inputs +are available before simplified association. Its AP preparation/install/removal calls use +`IIeee80211BssProvider`; no remote `LAST` callback is required. + +`Ieee80211Mib` provides const BSS/profile queries and guarded mutations. Management stages a complete +BSS/peer change, finishes its transaction and timer bookkeeping, then calls `publishStateChange()`. +The MIB's `bssStateChanged` signal covers meaningful BSS or peer changes. Its Boolean value reports +whether a BSS is active; observers query the current MIB for details. There is no borrowed signal +payload. Delivery is synchronous and observational: MIB mutation during delivery throws an error. +References into the MIB are valid only until the next corresponding mutation. A retained +`shared_ptr` keeps an immutable capability result alive, +but does not grant continued relationship eligibility. + +`hasPreparedLocalCapabilities()`, `isLocalHtCapable()`, `hasActiveBss()`, `hasHtOperation()`, and +`relationshipAllowsHt(peer)` distinguish readiness, implementation support, current BSS presence, +accepted operation presence, and permission to select HT. A legacy BSS may be active without HT +operation. The current ad hoc no-beacon abstraction likewise has no learned channel/HT operation or +accepted peer advertisements. Stop/crash clears operational relationships while retaining prepared +configuration. Physical AP channel context is retained by management for restart; STA operation is +learned from accepted management information, independently of scan tuning. diff --git a/doc/src/migration-guide/index.rst b/doc/src/migration-guide/index.rst index 668ccf48149..a8f16c4fee1 100644 --- a/doc/src/migration-guide/index.rst +++ b/doc/src/migration-guide/index.rst @@ -4,6 +4,48 @@ Migrating Code from INET 3.x ============================ Release: |release| +IEEE 802.11 Capability and BSS State Ownership +-------------------------------------------- + +Custom modules that used ``ModeSetListener`` or subscribed to ``modesetChanged`` +must obtain the configured catalog through ``IIeee80211MacConfiguration``. +For catalog consumers, derive from ``ModeSetModuleBase``, declare a +``modeSetModule`` NED parameter, and call the base initialization before using +``modeSet`` at ``INITSTAGE_LINK_LAYER``. Keep ``NUM_INIT_STAGES``. The built-in +MAC supplies the descendant parameter default; standalone consumers must point +it at a module implementing the C++ and NED configuration contracts. Move +algorithm initialization formerly performed by the signal callback to that +initialization stage. Ordinary catalog queries must not reset algorithm state. + +Custom transmitters and receivers that contribute HT capabilities implement +``IIeee80211TransmitterCapabilities`` and ``IIeee80211ReceiverCapabilities``, +respectively, in C++ and NED. Report implemented width support and, for receivers, +short-GI support independently of the current BSS operation. The MAC's +``prepareLocalCapabilities()`` assembles the local profile after PHY readiness; +repeated preparation preserves active protocol state. + +Replace direct access to MIB BSS/profile fields with ``getBssData()``, +``getBssStationData()``, ``getBssAccessPointData()`` and +``getLocalHtCapabilities()`` for reads. Management owners use ``commitBss()``, +``clearBss()`` and the association/peer methods for writes. Profile installation +uses ``installLocalHtCapabilities()``; replacing a changed profile requires an +inactive BSS with no peer relationships. Custom simplified AP management +implements ``IIeee80211BssProvider`` for explicit preparation and peer +installation/removal instead of allowing another module to modify its MIB. + +Subscribe to the MIB's Boolean ``bssStateChanged`` signal to observe accepted +state transitions. Query the MIB synchronously; the signal carries no borrowed +state object. Management calls ``publishStateChange()`` after completing the +transition's timer and transaction bookkeeping. Observers must not mutate the +MIB during publication or retain raw BSS/peer references across a mutation. +Use ``hasActiveBss()`` and ``hasHtOperation()`` explicitly, and supply operation +and HT eligibility separately from cached capabilities to peer mode selection. + +The old initialization signal and writable BSS/profile fields are removed +without compatibility adapters because they permit missed initialization or +state changes that bypass owner bookkeeping and notification. Existing custom +implementations must migrate together with their consumers. + IEEE 802.11 Beacon and Probe Response Fields ------------------------------------------ diff --git a/plan/done/80211htcapop-refactor-v3-evidence.md b/plan/done/80211htcapop-refactor-v3-evidence.md new file mode 100644 index 00000000000..9e30289619b --- /dev/null +++ b/plan/done/80211htcapop-refactor-v3-evidence.md @@ -0,0 +1,266 @@ +# HT capability/BSS refactor V3: implementation evidence + +Date: 2026-09-18. Baseline: `98117c3257b2e11661d2baf685c18911c8639b24`. +Tested implementation: the uncommitted working tree following that baseline. No commits, recorded +fingerprints, generated fingerprint expectations, or sealed packet-core sources were changed. +The proposal's relevant source/test paths match baseline `3f89b4b439c1dafd6b217ae9beb94b604f50c615`. + +## Implemented decisions + +- MAC assembles capabilities through `IIeee80211TransmitterCapabilities` and + `IIeee80211ReceiverCapabilities`, configured catalog and antenna limits. Preparation is idempotent; + MIB installation does not construct operation. A changed profile cannot replace an active BSS or + installed peer relationship. Ordinary runtime profile reconfiguration is outside this change. +- Management constructs operation and owns accepted transitions. MIB stores an explicit active BSS, + decoding band/channel context, optional operation, accepted capabilities and association state. + Its BSS/profile structures are private, exposed through const queries and guarded writes. +- The AP retains physical channel context while down, reconstructs operation on restart, and uses + its current operation after response completion. STA response/discovery provenance remains local + to management. Scan tuning is not used as accepted BSS state. +- `bssStateChanged` is a MIB Boolean signal with no borrowed payload. Management publishes after + required timer/transaction work. Observers query the committed MIB; nested MIB mutation is rejected. + Equality suppresses redundant state notification, not accepted-beacon liveness refresh. +- Directional capability results are immutable shared values. Equal capability inputs reuse them; + operation-only changes reevaluate eligibility without deriving again. Teardown and acknowledged + relationship replacement remove the old cache. A retained shared pointer proves reuse/lifetime in + tests without adding a public counter. Raw peer/BSS references must not survive their next mutation. +- Both selection modules pass accepted capabilities, explicit operation and HT eligibility to the + common compatibility filter. Management/control/group dispatch and legacy fallback remain intact. +- Static catalog broadcasts and `ModeSetListener` are removed. The replacement base queries a declared + provider at `LINK_LAYER`; the MAC supplies `**.modeSetModule` defaults to descendants in either + standard or external-upper composition. Management retains its existing `macModule` parameter. +- `LINK_LAYER` now explicitly depends on `NETWORK_INTERFACE_CONFIGURATION`, as well as PHY readiness. + Simplified management prepares the AP through `IIeee80211BssProvider`, then installs local and + remote state through owner methods. Identity and HT state are ready before network configuration. +- Ad hoc management explicitly activates/clears its no-beacon abstraction without inventing learned + channel, HT operation, or peer advertisements. Prepared local HT/legacy support survives lifecycle. + +The concrete notification and lifetime contracts are documented in +[the WLAN architecture](../../doc/project/design/ieee80211-model-architecture.md). + +## Consumer readiness worksheet + +For all catalog consumers below, `modeSetModule` identifies a module implementing +`IIeee80211MacConfiguration`. The default is the enclosing MAC's absolute path, set by MAC NED; +standalone compositions specify their own path. Catalog selection is ready after all `LOCAL` +callbacks. `ModeSetModuleBase::initialize(LINK_LAYER)` obtains it before the derived callback. +All retain `NUM_INIT_STAGES`. Catalog acquisition performs no algorithm reset on ordinary queries. + +| Consumer | Additional prerequisite / preparation at LINK_LAYER | First read / lifetime rule | +|---|---|---| +| `Dcaf` | Resolve queue/contention/Rx and calculate slot/SIFS/IFS/EIFS/CW defaults | Contention request; preserve subsequent CW changes | +| `Edcaf` | AC/parameter inputs initialized at LOCAL; register contention and calculate timing/CW | Contention request; preserve subsequent CW changes | +| `Dcf` | Existing coordination references | Protocol actions; retain unrelated drop listener | +| `Hcf` | Existing coordination references | Protocol actions; retain unrelated drop listener | +| `RateSelection` | Resolve control and configured frame modes; fastest mandatory mode | Frame selection; preserve transmitted-frame history | +| `QosRateSelection` | Resolve control and configured frame modes; fastest mandatory mode | Frame selection; preserve per-receiver/frame history | +| `AarfRateControl` | Concrete LOCAL parameters; base invokes `resetRateControl()` | Initial mode / first station estimate; no repeated preparation reset | +| `OnoeRateControl` | Concrete LOCAL parameters; base invokes `resetRateControl()` | Explicit first rate query at NETWORK_CONFIGURATION in the configuration-contract fixture; no repeated preparation reset | +| `OriginatorAckPolicy` | Catalog query | ACK-policy timing during exchange | +| `OriginatorQosAckPolicy` | Catalog query | QoS ACK-policy timing during exchange | +| `RtsPolicy` | Catalog query | RTS decision/timing | +| `QosRtsPolicy` | Catalog query | QoS RTS decision/timing | +| `RecipientAckPolicy` | Catalog query | Response timing | +| `RecipientQosAckPolicy` | Catalog query | QoS response timing | +| `CtsPolicy` | Catalog query | CTS timing | +| `QosCtsPolicy` | Catalog query | QoS CTS timing | +| `TxopProcedure` | Catalog query | TXOP timing; later TXOP state belongs to procedure | +| `SingleProtectionMechanism` | Catalog query | Protection timing | +| `OriginatorProtectionMechanism` | Catalog query | Originator protection timing | +| `OriginatorBlockAckAgreementPolicy` | Catalog query | Agreement policy; agreement lifecycle remains with existing owners | +| `Ieee80211MgmtBase` | Existing `macModule`; idempotent MAC profile preparation and supported-rate construction | First management frame/peer derivation; no repeated reset | +| AP management | PHY channel notification retained; profile + configured operation policy | Explicit `prepareBss()` or LINK_LAYER, before advertisement/peer installation | +| Simplified STA management | Both interface addresses after NETWORK_INTERFACE_CONFIGURATION; both profiles; explicit AP preparation | LINK_LAYER, before NETWORK_CONFIGURATION; no LAST retry | +| Ad hoc management | Prepared local profile | LINK_LAYER/start; no learned HT operation is fabricated | + +`FrameSequenceContext` keeps a const catalog pointer supplied by its owner. It does not subscribe to +an initialization event. `opMode`, `mac.modeSet`, radio contribution parameters, and `macModule` paths +retain their forwarding; the new descendant default also covers external-upper composition. + +## Acceptance evidence + +| Claim | Production path and regression assertion | +|---|---| +| Independent, replaceable contribution assembly | `Ieee80211HtCapabilityPreparation_1`: production MAC queries a non-built-in transmitter wrapper and receiver contribution; widths/GI differ as declared; repeated preparation and installation preserve operation | +| Built-in advertisement/antenna limits | `Ieee80211HtAssociation_1`, `Ieee80211HtAntennaRateControl_1`, serialization/unit cases: real 20 MHz packet PHY and existing advertisement assertions | +| Directional support and bounded selection | Five selected unit cases plus beacon fixture invoking both production selection modules | +| Cache reuse and HT recovery | Beacon fixture: width-only/equal inputs retain shared result; changed GI/MCS refresh; missing HT removes; unsupported Basic MCS retains knowledge; Basic-MCS-only recovery reuses cache | +| Liveness and atomic STA notification | Two synchronous listeners, reversed registration order, inspect identity/band/operation/timer; equal Beacon refreshes deadline without notification; malformed Beacon/probe does not publish authoritative changes; nested clear is rejected | +| AP current operation versus response history | Reassociation snapshot fixture changes radio channel while response is pending; response bytes retain their captured value and MIB uses current channel | +| AP completion/replacement boundary | Same fixture observes pending status at channel change and cleared transaction/AID/peer state at completion; second equal-capability reassociation replaces the cache while retaining the correct AID | +| Scoped cleanup and lifecycle | Detailed AP/STA lifecycle, deauth/disassoc, timeout, queue drop, HCF queue/RTS timeout, malformed HT, discovery and agent reassociation fixtures | +| Early readiness independent of declaration order | Simplified initialization fixture contains both AP-first and STA-first pairs, checks before NETWORK_CONFIGURATION, repeats preparation and checks cache preservation; stop/crash/restart and missing-AP cleanup retained | +| Declared providers/compositions | `Ieee80211ConfigurationContracts_1`: standalone replacement catalog provider, missing-provider diagnostic, HT/legacy ad hoc stop/restart, inherited production external-upper composition with only TAP endpoint replaced before child initialization | +| Real peer sequences | `WifiAssociation`, new `WifiHtAssociation`, `WifiReassociation`, and migrated `WifiDeauth`: protocol-category packet exchange expectations | +| Legacy trajectory preservation | Two selected QoS/non-QoS ad hoc fingerprints, run 0, all three selected ingredients match recorded expectations | + +The 40/20 MHz transition fixtures are synthetic management/selection evidence. They do not establish +real 40 MHz packet-PHY support. The external-upper fixture does not claim real TAP I/O. One pinned +run/seed is used for deterministic transitions; no throughput/performance or standards-expansion +claim is made. + +## Commands and results + +All commands run from the repository root, except fingerprint commands from `tests/fingerprint`. +The active toolchain is OMNeT++ 6.4.0aipre2 / clang with the checkout's enabled features. Debug is the +behavioral test mode. Module/protocol tests use their checked-in bounded configuration, run 0 and +seed 0/default; protocol base explicitly pins `seed-set = 0`. + +Exact selectors: + +```bash +UNIT='Ieee80211(HtCapabilities_1|HtMgmtElements_1|HtModeSet_1|PeerModeSelection_1|MibAssociationId_1)\.test' +MODULE='Ieee80211(HtAssociation_1|HtAntennaRateControl_1|HtCapabilityPreparation_1|ConfigurationContracts_1|MgmtStaBeaconUpdate_1|MgmtApReassociationSnapshot_1|MgmtStaSimplifiedInitialization_1|MgmtAp(Lifecycle|Timeout|QueueDrop|HcfQueueDrop|HcfRtsTimeout|ChannelChange|GenericRadio|UnavailableChannel|MalformedHtCap)_1|MgmtSta(Lifecycle|Deauthentication|Disassociation|Discovery)_1|AgentStaReassociation_1)\.test' +make -j$(nproc) MODE=debug +inet_run_unit_tests -m debug -f "$UNIT" +inet_run_module_tests -m debug -f "$MODULE" +inet_run_module_tests -m debug -f 'Ieee80211MgmtStaBeaconUpdate_1\.test' +inet_run_protocol_tests -p inet -m debug -w '^tests/protocol/wifi$' -f 'Wifi(HtAssociation|Association|Reassociation)\.test$' +inet_run_protocol_tests -p inet -m debug -w '^tests/protocol/wifi$' -f 'WifiDeauth\.test$' +make -j$(nproc) MODE=release +# cwd: tests/fingerprint; two configurations, 10s simulated time, run 0 +./fingerprinttest -d -m '/examples/adhoc/qos/ .* -c Mac(NonQos|Qos) -r 0 ' -f tplx -f '~tNl' -f '~tND' examples.csv +``` + +| Run | Executed | Outcome / log | +|---|---:|---| +| Baseline debug build | 1 | PASS, `/tmp/htcapop-baseline-build.log` | +| Baseline existing unit / core / lifecycle-module checks | 4 / 5 / 14 | PASS, `/tmp/htcapop-baseline-*` | +| P1/P2/P3 builds and focused migration checks | See implementation record | Corrected failures described below; `/tmp/htcapop-p1-*`, `p2-*`, `p3-*` | +| Final debug build | 1 | PASS, `/tmp/htcapop-final2-debug.log` | +| Final unit suite | 5 | PASS, `/tmp/htcapop-final2-unit.log` | +| Final module suite | 21 | PASS, `/tmp/htcapop-final2-module.log` | +| Added Basic-MCS-only recovery assertion | 1 existing module case | PASS, `/tmp/htcapop-basic-recovery.log` | +| Final association / HT association / reassociation protocol cases | 3 | PASS, `/tmp/htcapop-final2-protocol.log` | +| Migrated deauthentication protocol case | 1 | PASS, `/tmp/htcapop-deauth-protocol.log` | +| Release build | 1 + final incremental build | PASS (including final rebuilds); `/tmp/htcapop-final-release.log`, `/tmp/htcapop-final2-release.log` | +| Legacy fingerprints | 2 | PASS (initial and final-tree repeat), `/tmp/htcapop-final2-fingerprint.log` | + +Baseline unit/module evidence was captured before implementation. The protocol/fingerprint selectors +were finalized and executed later, a sequencing deviation from P0; fingerprint comparison uses the +unchanged repository expectations. An initial fingerprint regex anchored before trailing tags +selected zero cases (`NOT_RUN`); removing that anchor selected exactly the two intended cases. + +During development, tests exposed and drove corrections to missing-channel error precedence, AP +restart operation reconstruction, synthetic discovery-frame provenance, and lost fastest-mandatory +rate preparation after signal removal. These are resolved; their failed logs are retained rather +than presented as passes. Generated test build/work files were not used as source edits. + +## Mechanical gates and review boundaries + +- Scoped architecture gates for linklayer WLAN, PHY WLAN and external-upper WLAN pass. +- Full architecture gate reports 11 application/transport dependency candidates. An archived HEAD + comparison finds the identical 11; no added/removed candidate. +- Full interface gate reports 15 interface-purity violations. Archived HEAD has the identical 15; + all four new paired contracts satisfy the mechanical purity check. +- Scoped naming reports 35 declaration candidates in eight files. Each candidate-bearing file is + byte-identical to HEAD. The project-wide naming gate additionally reports existing resource names. + Its `--base HEAD` declaration scan selects zero committed changes, so scoped scans are the useful + evidence for this uncommitted implementation. +- Document seal/index gate passes. The source-seal `--base HEAD` gate selects no committed paths; + explicit working-tree/untracked-path inspection confirms no change under sealed `common/packet/`. +- Commit/classification checks are not applicable to an uncommitted working tree. The empty-range + commit checker emits a spurious empty-subject violation; the classification checker selects zero + commits. Neither is reported as source verification or a validated future commit series. +- C++ quality scan: stable scan completed on 36 changed translation units, with zero compiler errors. + Ten changed-line redundant-virtual diagnostics were corrected. The final rescan has zero + diagnostics on changed/new lines (4,079 unique diagnostics elsewhere in included legacy code). + The compilation database is generated from the + active debug build's exact compiler options at `/tmp/htcapop-compile-db/compile_commands.json`. +- Semantic self-review covers both selection callers, every old listener, full/simplified/ad hoc + lifecycle ownership, transaction matching and snapshot provenance. No independent reviewer was + commissioned, and repository-wide legacy gate violations are not silently waived or changed. + +## Semantic self-audit + +This is the author's final diff audit, not an independent code-review verdict. + +| General checklist item | Result and basis | +|---|---| +| AR-ORG-CONTRACTS | PASS — replacement contributors and standalone catalog provider exercise the declared query outcomes | +| PR-MSG-BODY / PR-MSG-WHY | N/A — no new commits authored | +| AR-ORG-CONTRACT-PURITY | PASS — four role interfaces contain pure operations and trivial destructors only | +| AR-ORG-VIS-SPLIT | PASS — model state remains in MIB; visualizer changes are const-reader migration | +| AR-ORG-KERNEL | PASS — adds an edge to the existing INET initialization-stage graph | +| AR-MOD-COMPOSITION | PASS — provider interfaces use existing modules; shared initialization base contains only dependency acquisition | +| AR-COM-SOCKETS | N/A — no application/transport integration | +| AR-COM-DIRECT | PASS — readiness uses direct typed calls; no zero-time handshake event | +| AR-COM-NOTIFY | PASS — committed state notification after timers/transactions, no required subscriber | +| AR-OBS-SIGNALS | PASS — synchronous observation and rejected nested state mutation tested in both registration orders | +| AR-OBS-NED-TRUTH | PASS — dependency defaults and signal declaration live in NED; documentation adds readiness/lifetime semantics | +| AR-OBS-INTROSPECTION | N/A — no new wire protocol or packet representation | +| AR-CFG-INFER / QR-DUP | PASS — remove copied operation and derive eligibility from accepted state | +| QR-OBJECT-OWNERSHIP | PASS — immutable shared cache, retained test snapshots, existing timer/packet cleanup paths | +| AR-CFG-PARAMS | PASS — new provider paths default empty and are mandatory dependencies, filled by composition | +| AR-EXT-NOCORE | N/A — no new protocol registration; common edits remove a signal and declare readiness | +| AR-EXT-MINIMAL-SURFACE | PASS — state private; public queries/preparation/commit operations serve production roles; old eligible-peer query retained as a const compatibility query | +| AR-EXT-VIRTUAL-IS-A-PROMISE | PASS — provider roles, framework hooks, and AP/ad hoc operation template step | +| AR-BUILD-DECLARATIVE | PASS — no machine paths or flags added to production build descriptors | +| RR-NUMERIC-STABLE | N/A — no enum/numeric wire code changed | +| AR-QUAL-NAMING | PASS — new role/type/parameter names follow role and camelCase conventions; existing findings separately identified | +| AR-QUAL-LOGGING | PASS — invalid preparation/dependency/mutation throws; no logged invariant failure proceeds | +| AR-QUAL-DETERMINISM | PASS — semantic equality and existing deterministic mode tie-breaks; pointer identity used only for test cache observation | +| AR-QUAL-TESTS | PASS — focused unit, module, protocol and legacy fingerprint evidence | +| AR-QUAL-TRACEABILITY | N/A — no recorded fingerprint/statistical baseline changed | +| AR-QUAL-DISPLAY | N/A — no new concrete production NED module; existing MIB inspection retained | + +| WLAN checklist item | Result and basis | +|---|---| +| AR-WLAN-STD-TRACE | PASS — existing normative references retained; no-air/ad hoc behavior explicitly identified as abstraction | +| AR-WLAN-STD-GATING | PASS — local profile, accepted capabilities, operation and eligibility remain separate gates | +| AR-WLAN-ARCH-BOUNDARIES | PASS — capability assembly uses role contracts; simplified STA calls AP owner operations | +| AR-WLAN-ARCH-OWNERSHIP | PASS — current BSS operation has one writer; discovery/pending snapshots retain their historical purpose | +| AR-WLAN-ARCH-VARIANTS | PASS — AP/ad hoc operation uses existing management roles | +| AR-WLAN-FRAME-REPRESENTATION | N/A — no new on-air fields/tags or serializer changes | +| AR-WLAN-PHY-AUTHORITY / AR-WLAN-PHY-TIMING | PASS — band/mode APIs remain authority for legality and timing | +| AR-WLAN-MAC-EXCHANGE | PASS — no new exchange matcher; management continues receiving existing transmission outcomes | +| AR-WLAN-MAC-SEQUENCE | N/A — no sequence/window arithmetic change | +| AR-WLAN-MAC-QOS | PASS — existing EDCA state and callback timing preparation retained | +| AR-WLAN-MAC-MULTIUSER | N/A — no MU feature work | +| AR-WLAN-OBS-EVENTS | PASS — equal information does not republish state; protocol outcomes retain their distinct signal | +| AR-WLAN-QUAL-TESTS | PASS — acceptance matrix above, including legacy fingerprints | + +REVIEW: 29 PASS, 10 N/A, 0 FLAG, 0 QUESTION. +Mechanical gate failures and test boundaries are recorded separately above. + +## Final review corrections + +- `prepareBss()` rejects calls while AP management is down; AP lifecycle coverage asserts that + preparation cannot resurrect cleared state. Both it and simplified initialization pass after the + guard (`/tmp/htcapop-inactive-ap.log`). +- New catalog-path NED parameters have explicit empty defaults; standard composition supplies the + mandatory path, and standalone missing-provider behavior remains an error. New contracts/base + follow the checker's namespace, guard, trivial-destructor and override conventions. A transient + namespace edit failed compilation and was fixed before the final builds. +- Fresh final debug/release builds pass (`/tmp/htcapop-final5-debug.log`, + `/tmp/htcapop-final5-release.log`). Three provider/contribution/composition cases pass afterward + (`/tmp/htcapop-provider-defaults.log`). Final-tree legacy fingerprints pass, two cases / three + ingredients each (`/tmp/htcapop-final2-fingerprint.log`). +- The long whole-WLAN C++ scan overlapped header edits and is invalid as final evidence. The final + stable-source scan uses the same compilation database/check configuration on every changed `.cc` + in six parallel processes, preserving one log per translation unit under `/tmp/htcapop-tidy-final`. + A focused new-provider/base scan reports no diagnostic in those new files + (`/tmp/htcapop-cpp-new-provider.log`; included legacy headers still generate suppressed warnings). + +## Completion record + +Final debug and release builds: PASS, `/tmp/htcapop-final-debug-build.log` and +`/tmp/htcapop-final-release-build.log`. Both Aarf and Onoe focused cases pass after the final +header/default cleanup (`/tmp/htcapop-final-ratecontrol.log`). The final stable C++ scan covers +36 changed translation units: zero tool/compiler errors, zero diagnostics on changed/new lines; +4,079 unique diagnostics remain elsewhere in included legacy code. These are not represented as a +clean repository-wide lint result. See `/tmp/htcapop-tidy-final-summary.log` and +`/tmp/htcapop-tidy-final-diff.log`. All new contracts/base files are included in the changed-line +filter regardless of line number. + +The full final 21-module / 5-unit / 4-protocol runs precede only the explicitly recorded inactive-AP +guard and declaration/default cleanup; affected lifecycle, composition, contribution, and rate-control +cases were rerun afterward. The guard does not enter the ad hoc fingerprint paths. Final fingerprint +and code-style changes do not modify recorded expectations. `git diff --check` is clean. + +Logs and the per-translation-unit C++ results are preserved locally under +`report/htcapop-v3/logs/` in addition to their original `/tmp` paths. This evidence directory follows +the checkout's existing ignored-report convention; the plan/evidence Markdown files are reviewable +new files under `plan/done/`. No commit or push was performed. + +Source/test manifest SHA-256: `7cd8705f802b9f599c76ad696cef7daffdc5b57e2a2a0a874c7c7d58ce129375` (130 paths, including deletions). diff --git a/plan/done/80211htcapop-refactor-v3.md b/plan/done/80211htcapop-refactor-v3.md new file mode 100644 index 00000000000..a1bf2cd6491 --- /dev/null +++ b/plan/done/80211htcapop-refactor-v3.md @@ -0,0 +1,519 @@ +# Implementation plan: IEEE 802.11 HT capability, BSS, and peer-state contracts + +> **Status:** complete · **Source:** [Proposal V3](../../report/80211htcapop-refactor-v3.md) · **Prepared:** 2026-09-18 + +Deliver the four contracts in V3 through four coherent implementation milestones: independent +capability preparation, committed management transitions, capability-only peer caching, and explicit +initialization dependencies. Preserve the existing HT and legacy behavior described by the proposal. +Implementation results and the consumer readiness worksheet are recorded in the +[execution evidence](80211htcapop-refactor-v3-evidence.md). The sections below preserve the original +work breakdown; the evidence records actual commands, outcomes, boundaries, and deviations. + +## 1. Baseline, scope, and deliverables + +The proposal references `3f89b4b439c1dafd6b217ae9beb94b604f50c615`. This plan was prepared against +`98117c3257b2e11661d2baf685c18911c8639b24`. A comparison of those commits found no differences in +`src/inet/linklayer/ieee80211`, `src/inet/physicallayer/wireless/ieee80211`, `tests/unit`, or +`tests/module`. Refresh this comparison before implementation, including newly added amendment +writers and consumers. The working tree was clean before this plan was added. + +At planning time, the source exhibited the migration points identified by V3: + +- `Ieee80211Mac::initialize()` selects the catalog at `LOCAL`, casts to concrete PHY contributors + at `LINK_LAYER`, prepares MIB capabilities, and emits `modesetChanged`. +- `Ieee80211Mib::updateLocalHtCapabilities()` also resets operation, derives its basic MCS set, + applies operation configuration, and rebuilds peer results. +- `Ieee80211Mib::setPrimaryChannel()` chooses width fallback and rebuilds peer results. +- `Ieee80211NegotiatedHtCapabilities` contains an operation copy read by the shared selector. +- `Ieee80211MgmtApBase` finalizes operation at `LAST`; simplified STA management calls + `configureAssociation()` at both `LINK_LAYER` and `LAST`. + +Implementation deliverables are: + +1. Typed, read-only catalog and component-contribution contracts with compatible NED wiring. +2. A prepared local capability profile, explicit active BSS state, and relationship-scoped peer state. +3. Management-owned transition operations with complete-state notification and lifecycle behavior. +4. A shared HT compatibility filter consuming cached capabilities, eligibility, and explicit operation. +5. A verified consumer preparation map and removal of initialization signal/listener plumbing. +6. Focused regression tests, an evidence manifest, and removal of temporary migration adapters. + +Use the [architecture](../../doc/project/rule/architecture.md), +[WLAN architecture](../../doc/project/design/ieee80211-model-architecture.md), +[WLAN rules](../../doc/project/domain/ieee80211.md), +[testing rules](../../doc/project/rule/testing.md), and +[contribution workflow](../../doc/project/guide/contribute-a-change.md) as the governing references. +Recheck the [seal registry](../../doc/project/audit/seal-list.md) before source edits. The currently +listed `common/packet/` seal is outside the intended change surface; no packet-core edit is planned. +Check the existing architecture and naming exception ledgers before reporting a deviation. + +### Compatibility boundaries + +Preserve parameter paths and precedence, built-in advertisements, association outcomes, supported-rate +elements, rate-selection behavior, frame-class dispatch, legacy operation, and existing radio command +handling. Keep protocol state inspectable in simple modules through WATCH/display facilities. + +Explicitly exclude new capability switches, new PHY features, complete basic-rate policy, runtime +interface-wide mode reconfiguration, Notify Channel Width, coexistence scheduling, operating-class +transitions, and VHT/HE/EHT capability models. Do not turn a serialized field into an implementation +claim. Do not claim a performance or standards-conformance improvement from this refactor alone. + +Retain the standards basis in V3. Its STA fallback/recovery, current-AP Probe Response treatment, +simplified no-air association, and immediate local channel updates are compatibility/model choices. +If implementation needs a new normative decision, verify the cited standard and scope that decision +separately rather than silently changing the expected behavior. + +## 2. Dependency order and review boundaries + +```text +P0: baseline, writer/reader inventory, API and readiness design + -> P1: typed contributions + capability-only preparation + replacement operation initializer + -> P2: committed BSS/relationship transitions + notification + lifecycle + -> P3: capability-only cache + explicit selection context + remove operation copies + -> P4: typed catalog dependency + migrate all preparation + remove initialization broadcast + -> P5: final integration evidence, cleanup, documentation, review +``` + +Each milestone must build and have its directly related tests passing before the next milestone +relies on it. Add tests alongside each behavior migration. Keep broad renames and formatting out of +semantic commits. A milestone can contain several commits, but every commit must leave a coherent, +buildable model. In particular, remove an old initializer only in a commit that supplies its +replacement, and migrate a selector signature together with its callers. + +| Milestone | Suggested commit concern | Temporary compatibility mechanism | Removal deadline | +|---|---|---|---| +| P0 | Characterization tests for currently supported behavior, if missing | Existing model | Before semantic changes | +| P1 | Query contracts, built-in contributors, separated local construction and operation preparation | Existing initialization signal; selector operation copies | P3/P4 | +| P2 | Shared transition contract, then detailed and simplified role migration | Old selector input adapted from the committed view | P3 | +| P3 | Cache/selection migration and obsolete state removal | Initialization signal only | P4 | +| P4 | Catalog provider and consumer migrations in buildable groups; signal removal last | Short-lived provider/signal coexistence | End of P4 | +| P5 | Remaining integration fixtures and contract documentation | None | Completion | + +## 3. P0 — establish the execution baseline + +**Entry:** implementation checkout and target revision are identified. + +- [x] Record HEAD, working-tree status, build environment, enabled INET features, and the proposal + baseline comparison. Preserve unrelated local changes. **Done:** baseline recorded; only this task + changed the initially clean source. +- [x] Inventory all readers/writers of local capabilities, `PeerHtState`, BSS identity, operation, + `isHtOperationSupported()`, `setPrimaryChannel()`, and `negotiateHtCapabilities()` across the tree. + Classify each as configuration, management policy, shared storage, transaction snapshot, or algorithm + state. Include tests, serializers, simplified/ad hoc paths, and any concurrent amendment additions. +- [x] Inventory every subscription, override, and inherited dependency on `ModeSetListener` and + `modesetChangedSignal`. Use the starting inventory in section 8, then search the whole source tree. +- [x] Record effective `opMode`/`modeSet` forwarding in both interface compositions and standalone + fixtures. Include default forwarding, an explicit MAC catalog override, radio/component parameters, + and initialization errors. Preserve these paths instead of consolidating them speculatively. +- [x] Complete the API decisions in section 4 and the readiness worksheet in section 8. Settle contract + placement before adding a dependency that would point PHY code back into MAC implementation types. +- [x] Build a fresh debug library and run the existing focused tests in section 10. Record pre-existing + failures separately. Capture exact advertisements, accepted state, selection results, and current + rejection behavior where later comparisons need an oracle. +- [x] Select the directly affected legacy fingerprint cases and peer-exchange protocol cases. Record + their exact selectors and executed counts before production changes; avoid choosing cases merely + because they pass. **Sequencing deviation:** protocol/fingerprint selection and execution followed + the initial source changes; unit/module baseline runs preceded them. See execution evidence. + +Useful inventory commands, run from the repository root: + +```bash +git rev-parse HEAD +git status --short +rg -n 'ModeSetListener|modesetChangedSignal|modesetChanged' src tests +rg -n 'updateLocalHtCapabilities|setPeerHtCapabilities|findPeerHtState|negotiateHtCapabilities' src tests +rg -n 'localHtCapabilities|bssData|bssStationData|bssAccessPointData|getHtOperation|setPrimaryChannel' src/inet/linklayer/ieee80211 +rg -n 'INITSTAGE_|Define_InitStage_Dependency' src/inet/common/InitStages.cc src/inet/linklayer/ieee80211 +``` + +**Exit:** a finite reader/writer list, exact baseline test selection, parameter-precedence record, and +resolved preparation design exist. New-contract tests may be planned here and implemented in their +own milestone; the baseline suite must describe the old supported behavior rather than require the +new API to exist. + +## 4. Contract decisions to settle before implementation + +The following are recommended shapes, not mandatory new class names. Prefer extending suitable +existing value types over creating one class or module per concept. + +| Contract | Required content and invariant | Decision owner | +|---|---|---| +| Configured catalog provider | Const query such as `getConfiguredModeSet()`; configuration lifetime; explicit readiness | MAC | +| Tx/Rx contribution | Typed queries for currently needed implemented abilities; identify direction, widths, stream/MCS limits, and per-width receive short GI | Implementing PHY component | +| Prepared local profile | Distinguish unprepared, prepared legacy, and prepared HT; stable across ordinary stop/restart | Assembly installs; MIB stores | +| Current BSS | Presence, identity, role-appropriate channel context, optional HT operation; legacy channel information usable independently | Management decides; MIB stores | +| Relationship | Explicit installed relationship, accepted peer information, validated HT eligibility; address equality alone is not identity | Management decides; MIB stores | +| Directional cache | Exact capability-derived results for local-Tx/peer-Rx and local-Rx/peer-Tx; immutable to consumers | MIB peer record | +| Transition/publication | Validate before mutation; commit complete shared state; finish owner bookkeeping; publish once | Management coordinates; MIB emits shared-state notification | + +Choose these details explicitly: + +- Keep known directional capabilities during temporary operational HT ineligibility, with all use + gated by the validated relationship query. An accepted missing/changed advertisement must still + update its presence/contents appropriately; retained historical information cannot become current + merely because eligibility later changes. Teardown discards relationship-scoped state. +- Treat local capability installation as initialization-only in ordinary operation. Repeated typed + preparation is idempotent; an unsupported attempt to replace a prepared profile must not leave + stale peer caches. Do not add runtime reconfiguration as part of this work. +- Compute semantic equality across every derivation input, preserving presence and unknown values. + Do not compare padded structures with `memcmp`, or compare only an MCS ceiling. +- Keep band plus internal channel index together, or provide equivalent unambiguous decoding context. + Preserve a generic legacy radio's supported absence of an IEEE band mapping. +- Distinguish `hasPreparedLocalCapabilities`, local HT support, active BSS presence, and applicable + HT operation in query semantics. Final names should follow repository naming guidance. Remove the + ambiguous MIB query once callers migrate; the similarly named mode-catalog query has a different role. +- Define const-reference/pointer lifetimes: a peer pointer may become invalid on teardown or replacement. + Selection should consume one consistent view without retaining it across callbacks. +- Keep pending responses and discovery snapshots immutable where their historical meaning requires it. + Eliminating duplicated current operation does not authorize deleting transaction history. + +## 5. P1 — separate capability construction and management operation + +**Primary files:** `mac/Ieee80211Mac.{h,cc}`, `mib/Ieee80211Mib.{h,cc,ned}`, +`mib/Ieee80211HtCapabilities.h`, `mgmt/Ieee80211MgmtApBase.{h,cc}`, relevant ad hoc management, +and PHY contributor declarations/implementations under +`src/inet/physicallayer/wireless/ieee80211/packetlevel/`. +Paths beginning `mac/`, `mib/`, or `mgmt/` here and below are relative to +`src/inet/linklayer/ieee80211/`. + +- [x] Add the smallest typed contribution contracts in the package owning the queried role. Keep + interface declarations pure; place implementation in concrete/base classes. Avoid making PHY + depend on a MAC-owned profile assembler or requiring one concrete radio class. +- [x] Implement the contracts in the built-in transmitter/receiver. Document each contribution's + source, direction, implementation limit, readiness, and consumer. Preserve built-in answers. +- [x] Replace the MAC's casts to concrete `Ieee80211Transmitter`/`Ieee80211Receiver` with role-contract + queries. Retain a meaningful error for HT configurations missing required contribution support. + Do not impose new HT-only dependencies on legacy configurations. +- [x] Extract local-profile assembly from MIB operation and peer mutation. Preserve catalog/Tx/Rx + width intersection, antenna/stream limits, exact MCS bitmap, receiver short-GI information, + and existing MAC advertisement inputs such as A-MPDU exponent. +- [x] Preserve undefined, equal, and unequal Tx-MCS knowledge and conversion semantics. Equal Tx/Rx + support uses the exact bitmap; summary/unknown peer Tx knowledge must not become exact empty support. +- [x] Add management's operation-preparation procedure in the same change. It derives Basic HT-MCS, + width/secondary offset, protection configuration, and channel context from prepared inputs. Keep + existing parameter paths even when the procedure interpreting them moves out of the MIB. +- [x] Move configured-width rejection and band-dependent fallback decisions into management. Use PHY + band/mode legality APIs; preserve the distinction between an incapable configured PHY and a channel + requiring fallback. The MIB checks structural consistency and stores the result. +- [x] Supply explicit AP and ad hoc preparation paths. STA local capability preparation must not + invent an active BSS. Preserve initial radio channel information received before profile readiness. +- [x] Adapt existing callers while retaining initialization signaling temporarily. Keep this adapter + incapable of resetting BSS operation when local-profile preparation is called again. + +**Evidence:** extend `Ieee80211HtCapabilities_1.test` and `Ieee80211HtMgmtElements_1.test`; add a focused +module fixture using a replacement contributor that is not a subclass of the built-in concrete +Tx/Rx classes. Change its declared contribution and observe the assembled profile through the +production MAC path. Include local assembly with an existing active operation and verify no mutation. +Run the real built-in association fixture to compare emitted advertisements and antenna limits. + +**Exit:** capability installation cannot select a channel, create a BSS, change operation, or install +peers. Management supplies every removed operation-initialization effect. Built-in serialized +advertisements remain equivalent; replacement contributors work without assembler edits. + +## 6. P2 — commit complete BSS and relationship transitions + +**Primary files:** `Ieee80211Mib`, `Ieee80211MgmtBase`, `Ieee80211MgmtApBase`, +`Ieee80211MgmtAp`, `Ieee80211MgmtSta`, `Ieee80211MgmtStaSimplified`, +`Ieee80211MgmtApSimplified`, `Ieee80211MgmtAdhoc`, and `Ieee80211HtMgmtElements.h`. + +- [x] Introduce explicit local readiness, active BSS presence, band/channel context, and relationship + validity. Keep display/configuration identity separate where compatibility retains it after stop. +- [x] Introduce transition inputs that can be validated before mutation. Route relevant direct writes + to shared BSS/peer state through the owner-controlled commit path. Avoid exposing mutable references + that bypass invariants; preserve compatible inspection queries. +- [x] Implement the field-source and transition matrix below in detailed management. Keep discovery, + pending transactions, and active state separate. Make HT eligibility a validated result of the + relationship and applicable requirements, rather than an independently writable Boolean. +- [x] Commit AP relationships only for the matching acknowledged successful response. Evaluate current + local operation at commit, without modifying the transmitted response snapshot. Preserve AID + reservation/commit/cancel semantics and transaction matching. +- [x] Commit the STA's accepted successful response with response capability/operation fields plus + the selected discovery record's Basic HT-MCS requirement and decoding context. Preserve the + existing successful-association/legacy-fallback outcome for unusable HT information. +- [x] Route simplified management through equivalent local transition contracts. Resolve the AP + management endpoint through an appropriate typed preparation/transition contract; do not make the + STA an independent policy writer of AP internals. Preserve the explicit no-air abstraction. +- [x] Complete stop, crash, restart, peer replacement, and scoped transaction cleanup for all affected + roles. Restart uses retained prepared configuration and reestablishes operational state. +- [x] Preserve agreement cleanup through its existing algorithm owners. Equal advertisements must + not suppress same-address relationship replacement or required cleanup. +- [x] Adapt old selection inputs from the committed view until P3, with exactly one writer and a + documented removal point. No old mutable adapter becomes a second authority. + +### Required transition matrix + +| Stimulus | State action | Observable acceptance condition | +|---|---|---| +| Candidate Beacon/Probe Response | Update validated discovery only | Existing active relationship unchanged | +| Matching successful Association/Reassociation Response at STA | Merge accepted response fields with discovery Basic HT-MCS and channel context; commit relationship | Correct association outcome and independently classified HT usability | +| Matching successful response ACK at AP | Commit pending peer using current local operation | AP may have channel index 11 while STA retains response index 6 | +| Current-AP accepted Beacon, width only | Commit operation and reevaluate eligibility | Subsequent selection obeys width; P3 proves no capability rederivation | +| Current-AP accepted Beacon, HT absent or unsupported basic MCS | Preserve association; mark HT unusable through validated state | Existing bounded legacy fallback | +| Valid HT information returns | Reevaluate independently of historical capability equality | HT restored with previously seen capability bytes | +| Current-AP Probe Response | Discovery update | Existing discovery-only authoritative-state policy preserved | +| Malformed Beacon | Reject before authoritative mutation | No partial BSS/peer commit; existing rejection/timer behavior preserved | +| AP radio channel update | Compute and commit band, channel, operation together | Callback sees consistent channel interpretation | +| Failed reassociation to another AP | Clear affected pending state | Old active relationship retained | +| Failed reassociation to current AP | Apply existing same-AP teardown | Correct relationship and agreement cleanup | +| Timeout, refusal, queue drop, cancellation, late completion | Match transaction and clean its scope | No unrelated peer/active relationship changed | +| Relationship replacement, including same BSSID | Apply identity and cleanup rules before installation | No stale eligibility, agreements, or cache inherited | +| Stop/crash | Clear operational/peer state and pending resources | Operational queries inactive; simplified AP peer removed where resolvable | +| Restart | Reprepare operation/relationship from stable configuration | No stale state; local profile retained | + +### Publication contract + +Use a combined transition or explicit commit-then-publish API. Define the shared-state signal on the +MIB boundary and declare it in NED. Specify meaningful change detection, affected BSS/peer scope, +payload ownership, and borrowed lifetime. Management completes required AID/transaction/relationship +bookkeeping before publication and preserves existing protocol-notification completion points. + +In particular, fix the current ordering where `Ieee80211MgmtApBase::receiveSignal()` assigns +`radioBand` after calling the MIB channel setter. A synchronous notification must expose the new +band with its channel/operation, not the old band. A listener must never finish the transition. + +Choose and document a reentrancy policy: synchronous read-only observation is supported; nested +mutations must either be rejected explicitly or safely handled by the transition implementation. +Do not add generation counters solely to make publication convenient. Do not retain map references +across callbacks that could invalidate them. + +**Evidence:** extend the existing beacon, association, reassociation snapshot, AP/STA lifecycle, +deauthentication/disassociation, timeout, and queue-drop fixtures. Add an observer fixture that reads +identity, band, channel, operation, eligibility, and required transaction status synchronously. Vary +listener registration order and use two peers to prove scoped cleanup. Verify that an equal accepted +Beacon still refreshes applicable liveness/signal observations without a spurious operation event. + +**Exit:** all affected management writers use consistent transition paths; no observer can see a +half-committed relationship; legacy/no-BSS/unusable-HT states are distinguishable. Detailed, +simplified, and ad hoc lifecycle paths have explicit ownership. + +## 7. P3 — separate capability caching from operation checks + +**Primary files:** `mib/Ieee80211HtCapabilities.h`, `Ieee80211Mib`, +`mac/rateselection/Ieee80211PeerModeSelection.{h,cc}`, `RateSelection`, `QosRateSelection`, and +management consumers of negotiated results. + +- [x] Make directional derivation depend only on the prepared local profile and accepted peer + capability inputs. Remove the operation argument from the core derivation function. +- [x] Install/reuse the cache on semantic input equality. A changed capability input refreshes before + use; an operation-only change reevaluates eligibility without rebuilding directional support. +- [x] Keep capability equality separate from liveness, relationship identity, transaction completion, + and eligibility. Reinstallation after teardown builds fresh relationship state even at the same MAC. +- [x] Change the shared selector to receive a consistent explicit context: directional capabilities, + validated HT eligibility, and applicable local BSS operation. Preserve old frame-class and group + dispatch; do not introduce an association requirement for every management/control transmission. +- [x] Migrate both QoS and non-QoS production call sites together. Keep rate control responsible for + proposing/ranking modes; the helper remains a compatibility filter. +- [x] Preserve non-HT pass-through, exact sparse MCS support, directional receiver short GI, width + checks, proposed-bitrate ceiling, deterministic tie breaking, and bounded legacy fallback/errors. +- [x] Remove `Ieee80211NegotiatedHtCapabilities::operation` after the last reader migrates. Preserve + accepted local BSS state and immutable response/discovery snapshots. +- [x] Remove operation-triggered derivation loops and the unused peer `generation` field after a + fresh whole-tree reader check. Do not replace it with another public counter without a consumer. + +**Evidence:** extend `Ieee80211PeerModeSelection_1.test` for explicit contexts, missing/ineligible +state, sparse MCS, both short-GI directions, bitrate boundaries, and ties. Exercise both production +selection modules in module fixtures. Use test-only derivation instrumentation to establish: + +| Action on a live relationship | Expected derivation behavior | +|---|---| +| First usable capability installation | Derive once | +| Repeated equal advertisement and repeated selection | Reuse | +| Width/protection/basic-MCS-only update | Reuse; reevaluate applicable constraints | +| Changed capability inputs | Refresh before consumption | +| HT ineligibility then valid information returns | Restore correctly; reuse only if cached inputs remain valid | +| Teardown then same-address installation | Fresh relationship/cache | + +The 40-to-20 MHz fixture is synthetic evidence for management/selection; retain separate real +built-in 20 MHz packet-PHY association/serialization coverage. Do not describe that fixture as a +real 40 MHz PHY exchange. + +**Exit:** operation-only changes change selection where appropriate without capability rederivation; +no production reader uses a peer copy of current BSS operation; recovery works after both missing HT +and unsupported Basic HT-MCS updates. + +## 8. P4 — replace initialization broadcasts with declared dependencies + +**Primary files:** `Ieee80211Mac`, `mac/common/ModeSetListener.*`, all consumers below, their NED +declarations, management preparation code, both interface compositions, and +`src/inet/common/Simsignals.{h,cc}`. Remove the NED signal declaration and stale TODO references too. + +### Readiness design prerequisite + +The implementation must fill one worksheet row per consumer, recording **input, provider path, +input-ready stage, preparation stage/call, first read, and idempotency rule**. The following constrains +that design; it is not a claim that sibling callbacks at the same stage are ordered. + +| Prepared value | Earliest established prerequisite | Required preparation/first-use contract | +|---|---|---| +| Configured catalog | MAC `LOCAL` completed | Consumers query after `LOCAL`; no dependency on HT readiness | +| Local profile | Catalog plus configured PHY contributions/antenna inputs | Typed idempotent preparation after PHY readiness; complete before advertisement or peer derivation | +| Simplified identity | Interface addresses and AP SSID ready | Separate identity preparation complete before network configurators group interfaces | +| AP operation | Local profile and valid radio channel context | Typed idempotent AP preparation before advertisement or simplified HT installation | +| Simplified HT relationship | Both profiles plus prepared AP operation | Establish before its first initialization reader and before protocol operation | +| Timing/rate/policy state | Catalog and each consumer's local parameters/dependencies | Prepare once before the first protocol action; no reliance on broadcast order | + +Use the actual graph in `src/inet/common/InitStages.cc`: `LINK_LAYER` depends on `PHYSICAL_LAYER`, +`NETWORK_CONFIGURATION` depends on `LINK_LAYER`, and `NETWORK_INTERFACE_CONFIGURATION` declares +only a dependency on `LOCAL`. Do not assume address initialization precedes link-layer work merely +from current numeric ordering. Trace the address provider as part of the worksheet. + +The preferred design is post-`LOCAL` catalog queries and typed, idempotent preparation calls that can +prepare the peer's required inputs regardless of AP/STA declaration order. Such a call must only use +inputs already ready; it must not manually call another module's `initialize()` or lifecycle callback. +If a required address/PHY deadline cannot be met through existing declared dependencies, add the +smallest justified stage/dependency change and test it. Record that decision before removing the +signal; leaving the concrete stage/call mapping unresolved fails this milestone. + +### Starting consumer inventory + +Search for direct callbacks as well as classes inheriting `ModeSetListener`. Some consumers only use +the cached pointer later; others perform work in overridden callbacks. + +| Consumer group | Existing work/input to preserve | Migration task | +|---|---|---| +| `Dcaf`, `Edcaf` | Slot/SIFS/IFS/EIFS, CW defaults, access-category parameters | Invoke existing timing/contention preparation after querying catalog | +| `Dcf`, `Hcf` | Mode-dependent coordination behavior; signal forwarding | Replace catalog dependency while preserving unrelated signal handling | +| `RateSelection`, `QosRateSelection` | Catalog and fastest mandatory mode cache | Prepare query/cache explicitly; retain selection-history behavior | +| `RateControlBase` and concrete algorithms | Catalog and `resetRateControl()` initialization | Run setup once after concrete prerequisites; preserve separate lifecycle reset semantics | +| `Ieee80211MgmtBase` | Supported/Extended Supported Rates construction | Prepare before first frame construction; preserve bytes/basic-rate flags | +| `OriginatorAckPolicy`, `OriginatorQosAckPolicy`, `RtsPolicy`, `QosRtsPolicy` | Mode-dependent policy queries | Declare provider and establish readiness before use | +| `RecipientAckPolicy`, `RecipientQosAckPolicy`, `CtsPolicy`, `QosCtsPolicy` | Response timing/mode queries | Same explicit provider preparation | +| `TxopProcedure`, `SingleProtectionMechanism`, `OriginatorProtectionMechanism` | TXOP/protection mode inputs | Preserve preparation and subsequent timing behavior | +| `OriginatorBlockAckAgreementPolicy` | Catalog-dependent agreement policy | Explicit preparation without changing agreement ownership/lifetime | +| Other inherited/concurrent consumers found by P0 | Any additional callback or deferred catalog use | Add individual worksheet rows and focused checks | + +### Implementation tasks + +- [x] Add the MAC's typed catalog provider, such as a pure `getConfiguredModeSet()` query, in the + appropriate MAC contract package. NED `IIeee80211Mac` alone does not supply a C++ query interface. +- [x] Declare each consumer dependency using module parameters/contracts and connect defaults in + `Ieee80211Interface.ned` and `ExtUpperIeee80211Interface.ned`. Preserve `opMode`/`modeSet` forwarding + and allow explicitly configured standalone compositions. Do not cast to a containing-interface class. +- [x] Migrate consumer groups using the completed worksheet. Preserve callback side effects as + explicit preparation, not merely a stored pointer. Retain unrelated signals/subscriptions. +- [x] Preserve `numInitStages()` behavior when replacing the listener base class; check overrides and + initialization chains so later preparation stages still execute. +- [x] Split simplified identity preparation from HT relationship installation. Replace the `LAST` + retry with a deterministic preparation path. Ensure AP operation is ready in either declaration order. +- [x] Repeated preparation must not reset contention, rate estimates, agreements, or transaction state. + Stop/restart retains profiles and catalog while using the role-specific operational reset contract. +- [x] Remove signal emission only when all consumers have migrated. Remove listener implementation, + inheritance/includes, subscriptions, signal definition/declaration, NED metadata, and stale comments. +- [x] Recheck runtime radio setters/command handling and their callers. Preserve their compatibility; + do not reinterpret signal removal as either supporting or forbidding interface-wide reconfiguration. + +**Evidence:** update `Ieee80211MgmtStaSimplifiedInitialization_1.test` to preserve early identity +assertions and verify HT readiness at the new documented first-read boundary. Run AP-before-STA and +STA-before-AP orders, repeated preparation, stop/restart, missing-AP cleanup, prepared legacy state, +ad hoc, and standard/external-upper/standalone compositions. Compare timing, supported rates, and +initial rate-control state with P0. Test missing-provider diagnostics. + +**Exit:** no initialization reader depends on a sibling's or remote module's `LAST` publication; +no consumer relies on `modesetChanged`; all worksheet rows identify an implemented and tested path. + +## 9. Acceptance coverage and evidence ownership + +Existing test names below are starting points, not assertions of complete current coverage. New +fixtures should be added only where the existing production path cannot express the required probe. + +| Evidence ID | Claim and test layer | Existing starting point / required addition | Milestone | +|---|---|---|---| +| E1 | Exact directional capability derivation and representation, unit | `Ieee80211HtCapabilities_1.test`, `Ieee80211HtMgmtElements_1.test`; unknown/unequal/equal/sparse/short-GI cases | P1/P3 | +| E2 | Replaceable contributors and independent construction, module | New focused production-MAC contributor fixture; operation unchanged by assembly | P1 | +| E3 | Built-in advertised limits, module and serialization | `Ieee80211HtAssociation_1.test`, `Ieee80211HtAntennaRateControl_1.test`; real packet PHY remains 20 MHz | P1/P5 | +| E4 | Selection invariants, unit plus production integration | `Ieee80211PeerModeSelection_1.test`; QoS and non-QoS module checks | P3 | +| E5 | Width-only cache reuse, liveness, HT fallback/recovery, malformed input, module | `Ieee80211MgmtStaBeaconUpdate_1.test`; test-only derivation observation | P2/P3 | +| E6 | Response provenance and distinct AP/STA knowledge, module | `Ieee80211HtAssociation_1.test`, `Ieee80211MgmtApReassociationSnapshot_1.test`; response Basic HT-MCS merge | P2 | +| E7 | Transaction cleanup and lifecycle scope, module | AP/STA lifecycle, AP timeout/queue-drop/HCF variants, STA deauthentication/disassociation, agent reassociation tests | P2 | +| E8 | Atomic observer contract, module | New focused commit observer; listener-order and supported reentrancy probes | P2 | +| E9 | Initialization and composition, module/network | `Ieee80211MgmtStaSimplifiedInitialization_1.test`; both orders, external-upper, standalone, legacy/ad hoc fixtures | P4 | +| E10 | Channel context/fallback and generic-radio compatibility, module | `Ieee80211MgmtApChannelChange_1.test`, `Ieee80211MgmtApGenericRadio_1.test`, `Ieee80211MgmtApUnavailableChannel_1.test` | P1/P2 | +| E11 | Preserved peer exchange sequence, protocol | Inspect `tests/protocol/wifi/common/WifiAssociation.test` and `WifiReassociation.test`; add a focused case if the changed path is absent | P2/P5 | +| E12 | Unintended legacy trajectory changes, fingerprint | Explicitly selected WLAN legacy cases/configurations from the repository fingerprint catalog | P4/P5 | + +For each claim, record the production entry point and the assertion that detects a regression. A +helper-only test is not production-path evidence. Module tests prove state/owner contracts; +cross-peer sequence claims need protocol-category evidence even when an existing module fixture +provides useful complementary observations. If a fixture bypasses the PHY, label that boundary. + +Use deterministic fixtures with a pinned configuration, run 0, explicit seed, relevant parameters, +and a bounded duration. Enumerate both declaration orders, QoS modes, roles, and lifecycle outcomes +where required rather than hoping random seeds exercise them. A single seed suffices for an exact +state-transition reproduction; use a finite additional seed/configuration campaign only where timing +or randomized contention is part of the claim. Do not rerun unexplained failures until green. + +## 10. Verification commands and reporting + +Commands below are the original execution plan. Actual build/test results and final selectors are +recorded in the linked execution evidence. Both `inet_run_unit_tests` and `inet_run_module_tests` exist in the inspected checkout. +Run from the repository root with the normal INET/OMNeT++ environment active. + +After compiled source or generated-code inputs change, rebuild the library the tests load: + +```bash +make -j$(nproc) MODE=debug +inet_run_unit_tests -m debug -f 'Ieee80211(HtCapabilities_1|HtMgmtElements_1|HtModeSet_1|PeerModeSelection_1)\.test' +inet_run_module_tests -m debug -f 'Ieee80211(HtAssociation_1|HtAntennaRateControl_1|MgmtStaBeaconUpdate_1|MgmtApReassociationSnapshot_1|MgmtStaSimplifiedInitialization_1)\.test' +inet_run_module_tests -m debug -f 'Ieee80211(MgmtAp(Lifecycle|Timeout|QueueDrop|HcfQueueDrop|HcfRtsTimeout|ChannelChange|GenericRadio|UnavailableChannel|MalformedHtCap)|MgmtSta(Lifecycle|Deauthentication|Disassociation|Discovery)|AgentStaReassociation)_1\.test' +``` + +Use the appropriate subset during each milestone, and add explicit filters for every new fixture. +Resolve exact protocol invocation/build requirements from +[protocol AUTHORING](../../tests/protocol/lib/AUTHORING.md#10-running-tests) and the Wi-Fi suite's +current runner; the case filenames above are candidate coverage, not a fabricated runnable selector. +Resolve fingerprint cases/configurations against the actual catalog and record the final regex before +execution. A placeholder or a zero-case selection is `NOT_RUN`. + +Run focused architecture checks for the changed paths: + +```bash +doc/project/enforcement/check-architecture.sh src/inet/linklayer/ieee80211 +doc/project/enforcement/check-architecture.sh src/inet/physicallayer/wireless/ieee80211 +doc/project/enforcement/check-architecture.sh src/inet/emulation/linklayer/ieee80211 +doc/project/enforcement/check-interfaces.sh +``` + +For final integration, follow the current [run-the-gates guide](../../doc/project/guide/run-the-gates.md): +fresh debug and release compilation, focused tests, project-wide architecture/naming/interface/seal +gates, commit/classification gates against the actual PR base, and the general/WLAN semantic +checklists. Include changed common signal/stage code in the gate scope. Broad C++ changes also need +the documented C++ checker with a compilation database. Distinguish pre-existing gate findings from +new defects and reconcile them with the ledgers rather than hiding them. + +Store an evidence manifest with one row per command: milestone, tested revision, working directory, +exact command/filter, build mode, configuration/run/seed, selected/executed count, exit status, +outcome classification, and log/artifact paths. Keep helper, module, protocol, and fingerprint claims +separate. Setup failures, expected failures, and missing coverage are not passes. + +If a fingerprint changes, diagnose the first relevant divergence against the pinned baseline. An +intentional stage move still needs a behavioral explanation and focused proof. Changes to recorded +baselines follow [change-a-baseline](../../doc/project/guide/change-a-baseline.md); do not regenerate +fingerprints simply to make this refactor pass. + +## 11. P5 — completion audit + +- [x] Every P0 reader/writer is migrated or explicitly justified as a historical transaction/discovery + snapshot. No remaining production writer bypasses the relevant transition invariant. +- [x] Search confirms no concrete built-in Tx/Rx cast in capability assembly, no obsolete initialization + signal/listener path, and no peer copy of current operation. Review legitimate similarly named PHY + catalog/runtime-command APIs instead of deleting them mechanically. +- [x] No operation-only path rebuilds capability intersections. No selection path rebuilds them on + each frame. Equality cannot suppress liveness, eligibility restoration, or relationship replacement. +- [x] No new public counter exists solely for tests. WATCH/display inspection reflects authoritative + state and does not imply active operation from retained display identity. +- [x] Both selection paths and every affected management role meet the acceptance matrix. The + AP-current/STA-accepted channel distinction and response Basic HT-MCS provenance remain explicit. +- [x] The preparation worksheet is complete, source-backed, and exercised in both declaration orders. + All required preparation finishes before first use without cross-module `LAST` dependence. +- [x] New provider/dependency contracts work in standard, external-upper, and explicit standalone + compositions, including legacy behavior and useful missing-dependency diagnostics. +- [x] All migration adapters listed in section 2 are removed. Stable parameters and radio command + semantics remain compatible; deferred feature work has not entered the patch series. +- [x] Final focused evidence, fresh debug/release build results, gate outcomes, and unresolved gaps are + recorded. A required behavioral coverage gap prevents marking the implementation complete. +- [x] Review the final change using the canonical code/PR guides and general/WLAN checklists. Update + canonical contract documentation only where the implementation adds concrete details; link this plan + rather than duplicating project policy. Move the plan to `plan/done/` only after its work is complete. + +Completion means the new contracts govern every changed production path and the focused evidence +supports the preserved behavior. This planning document does not authorize unrelated feature work, +baseline replacement, or modification of sealed source. diff --git a/src/inet/common/Simsignals.cc b/src/inet/common/Simsignals.cc index 126e6b6ea7e..e79367a3d83 100644 --- a/src/inet/common/Simsignals.cc +++ b/src/inet/common/Simsignals.cc @@ -21,7 +21,6 @@ simsignal_t l2ApDisassociatedSignal = cComponent::registerSignal("l2ApDisassocia simsignal_t linkBrokenSignal = cComponent::registerSignal("linkBroken"); -simsignal_t modesetChangedSignal = cComponent::registerSignal("modesetChanged"); simsignal_t interpacketGapStartedSignal = cComponent::registerSignal("interpacketGapStarted"); simsignal_t interpacketGapEndedSignal = cComponent::registerSignal("interpacketGapEnded"); diff --git a/src/inet/common/Simsignals.h b/src/inet/common/Simsignals.h index 69a1363fe52..3301001fe46 100644 --- a/src/inet/common/Simsignals.h +++ b/src/inet/common/Simsignals.h @@ -30,7 +30,6 @@ extern INET_API simsignal_t // admin linkBrokenSignal, // used for manet link layer feedback - modesetChangedSignal, interpacketGapStartedSignal, interpacketGapEndedSignal, diff --git a/src/inet/linklayer/ieee80211/__TODO b/src/inet/linklayer/ieee80211/__TODO index 6bf30247628..2053bf77fe1 100644 --- a/src/inet/linklayer/ieee80211/__TODO +++ b/src/inet/linklayer/ieee80211/__TODO @@ -14,7 +14,6 @@ @signal[packetSentToPeer](type=inet::Packet); @signal[packetDropped](type=inet::Packet); @signal[linkBroken](type=inet::Packet); - @signal[modesetChanged](type=inet::physicallayer::Ieee80211ModeSet); @statistic[packetDropIncorrectlyReceived](record=count); @statistic[packetDropNotAddressedToUs](record=count); @statistic[packetDropQueueOverflow](record=count); diff --git a/src/inet/linklayer/ieee80211/mac/Ds.cc b/src/inet/linklayer/ieee80211/mac/Ds.cc index ff26ccba776..fe93a5f219e 100644 --- a/src/inet/linklayer/ieee80211/mac/Ds.cc +++ b/src/inet/linklayer/ieee80211/mac/Ds.cc @@ -31,7 +31,7 @@ void Ds::processDataFrame(Packet *frame, const Ptr& h if (mib->mode == Ieee80211Mib::INDEPENDENT) mac->sendUp(frame); else if (mib->mode == Ieee80211Mib::INFRASTRUCTURE) { - if (mib->bssStationData.stationType == Ieee80211Mib::ACCESS_POINT) { + if (mib->getBssStationData().stationType == Ieee80211Mib::ACCESS_POINT) { // check toDS bit if (!header->getToDS()) { // looks like this is not for us - discard @@ -50,8 +50,8 @@ void Ds::processDataFrame(Packet *frame, const Ptr& h return; } // look up destination address in our STA list - auto it = mib->bssAccessPointData.stations.find(header->getAddress3()); - if (it == mib->bssAccessPointData.stations.end()) { + auto it = mib->getBssAccessPointData().stations.find(header->getAddress3()); + if (it == mib->getBssAccessPointData().stations.end()) { EV_WARN << "Frame's destination address is not in our STA list -- passing up\n"; mac->sendUp(frame); } @@ -70,15 +70,15 @@ void Ds::processDataFrame(Packet *frame, const Ptr& h } } } - else if (mib->bssStationData.stationType == Ieee80211Mib::STATION) { - if (!mib->bssStationData.isAssociated) { + else if (mib->getBssStationData().stationType == Ieee80211Mib::STATION) { + if (!mib->getBssStationData().isAssociated) { EV_WARN << "Rejecting data frame as STA is not associated with an AP" << endl; PacketDropDetails details; details.setReason(OTHER_PACKET_DROP); emit(packetDroppedSignal, frame, &details); delete frame; } - else if (mib->bssData.bssid != header->getTransmitterAddress()) { + else if (mib->getBssData().bssid != header->getTransmitterAddress()) { EV_WARN << "Rejecting data frame received from another AP" << endl; PacketDropDetails details; details.setReason(OTHER_PACKET_DROP); diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc index d87f581231f..da8dd51225e 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc @@ -27,9 +27,9 @@ #include "inet/linklayer/ieee80211/mac/contract/ITx.h" #include "inet/networklayer/contract/IInterfaceTable.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211ControlInfo_m.h" -#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h" +#include "inet/physicallayer/wireless/ieee80211/contract/IIeee80211ReceiverCapabilities.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" -#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h" +#include "inet/physicallayer/wireless/ieee80211/contract/IIeee80211TransmitterCapabilities.h" namespace inet { namespace ieee80211 { @@ -71,21 +71,7 @@ void Ieee80211Mac::initialize(int stage) ds = check_and_cast(getSubmodule("ds")); rx = check_and_cast(getSubmodule("rx")); tx = check_and_cast(getSubmodule("tx")); - int operationalHtSpatialStreamLimit = std::min(radio->getAntenna()->getNumAntennas(), - modeSet->getMaximumNumberOfSpatialStreams()); - std::set operationalChannelWidths; - if (modeSet->isHtOperationSupported()) { - const auto *transmitter = dynamic_cast(radio->getTransmitter()); - const auto *receiver = dynamic_cast(radio->getReceiver()); - if (transmitter == nullptr || receiver == nullptr) - throw cRuntimeError("HT operation requires Ieee80211Transmitter and Ieee80211Receiver"); - for (auto channelWidth : modeSet->getHtSupportedChannelWidths()) - if (transmitter->isHtChannelWidthSupported(channelWidth) && - receiver->isHtChannelWidthSupported(channelWidth)) - operationalChannelWidths.insert(channelWidth); - } - mib->updateLocalHtCapabilities(modeSet, operationalChannelWidths, operationalHtSpatialStreamLimit); - emit(modesetChangedSignal, modeSet); + prepareLocalCapabilities(); if (isUp()) initializeRadioMode(); rx = check_and_cast(getSubmodule("rx")); @@ -97,6 +83,68 @@ void Ieee80211Mac::initialize(int stage) } } +void Ieee80211Mac::prepareLocalCapabilities() +{ + Enter_Method("prepareLocalCapabilities"); + if (mib->hasPreparedLocalCapabilities()) + return; + if (!modeSet->isHtOperationSupported()) { + mib->installLocalHtCapabilities(Ieee80211HtCapabilities(), false); + return; + } + auto *configuredRadio = check_and_cast(gate("lowerLayerOut")->getNextGate()->getOwnerModule()); + const auto *transmitter = dynamic_cast(configuredRadio->getTransmitter()); + const auto *receiver = dynamic_cast(configuredRadio->getReceiver()); + if (transmitter == nullptr || receiver == nullptr) + throw cRuntimeError("HT operation requires transmitter and receiver capability providers"); + int operationalHtSpatialStreamLimit = std::min(configuredRadio->getAntenna()->getNumAntennas(), + modeSet->getMaximumNumberOfSpatialStreams()); + std::set operationalChannelWidths; + for (auto width : modeSet->getHtSupportedChannelWidths()) + if (transmitter->isHtChannelWidthSupported(width) && receiver->isHtChannelWidthSupported(width)) + operationalChannelWidths.insert(width); + Ieee80211HtCapabilities localHtCapabilities; + if (operationalHtSpatialStreamLimit <= 0) + throw cRuntimeError("HT operation requires a positive operational spatial-stream limit"); + + // IEEE Std 802.11-2024, 9.4.2.54.4 and 9.4.2.55: advertise exactly the + // HT modes from the authoritative mode set, while advertised channel + // widths are restricted to those the configured transmitter and receiver + // can actually operate. In particular, do not infer dense MCS blocks or HT + // widths from legacy/VHT modes that happen to share the set. + for (auto channelWidth : modeSet->getHtSupportedChannelWidths()) + if (operationalChannelWidths.count(channelWidth) != 0) + localHtCapabilities.supportedChannelWidths.insert(channelWidth); + localHtCapabilities.shortGi20 = localHtCapabilities.supportedChannelWidths.count(MHz(20)) != 0 && + modeSet->isHtShortGuardIntervalSupported(MHz(20)) && receiver->isHtShortGuardIntervalSupported(MHz(20)); + localHtCapabilities.shortGi40 = localHtCapabilities.supportedChannelWidths.count(MHz(40)) != 0 && + modeSet->isHtShortGuardIntervalSupported(MHz(40)) && receiver->isHtShortGuardIntervalSupported(MHz(40)); + for (int index = 0; index < modeSet->getNumModes(); index++) { + const auto *mode = modeSet->getMode(index); + int mcs = mode->getHtMcsIndex(); + if (mcs >= 0 && mcs < 77 && operationalChannelWidths.count(mode->getDataMode()->getBandwidth()) != 0 && + mode->getDataMode()->getNumberOfSpatialStreams() <= operationalHtSpatialStreamLimit) + localHtCapabilities.rxMcsSupported[mcs] = true; + } + // The equal-case Tx MCS set is represented by the maximum MCS index per + // spatial-stream group. Rebuild it from the filtered Rx bitmap; MCS 32 is + // not part of this map's MCS 0..31 NSS encoding. + localHtCapabilities.txMcsNss = Ieee80211HtMcsNssMap(); + for (int mcs = 0; mcs < 32; mcs++) { + if (localHtCapabilities.rxMcsSupported[mcs]) { + int nss = mcs / 8; + localHtCapabilities.txMcsNss.maxMcsPerNss[nss] = std::max(localHtCapabilities.txMcsNss.maxMcsPerNss[nss], mcs % 8); + } + } + if (localHtCapabilities.supportedChannelWidths.empty()) + throw cRuntimeError("HT operation mode set '%s' does not provide an HT channel width", modeSet->getName()); + localHtCapabilities.maxAmpduLengthExponent = mib->par("htMaxAmpduLengthExponent"); + if (localHtCapabilities.maxAmpduLengthExponent < 0 || localHtCapabilities.maxAmpduLengthExponent > 3) + throw cRuntimeError("htMaxAmpduLengthExponent must be between 0 and 3"); + + mib->installLocalHtCapabilities(localHtCapabilities, true); +} + void Ieee80211Mac::initializeRadioMode() { const char *initialRadioMode = par("initialRadioMode"); @@ -164,8 +212,8 @@ 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) - header->setAddress3(mib->bssData.bssid); + if (mib->mode == Ieee80211Mib::INFRASTRUCTURE && mib->getBssStationData().stationType == Ieee80211Mib::ACCESS_POINT) + header->setAddress3(mib->getBssData().bssid); packet->insertAtFront(header); packet->insertAtBack(makeShared()); processUpperFrame(packet, header); @@ -173,7 +221,7 @@ void Ieee80211Mac::handleMgmtPacket(Packet *packet) void Ieee80211Mac::handleUpperPacket(Packet *packet) { - if (mib->mode == Ieee80211Mib::INFRASTRUCTURE && mib->bssStationData.stationType == Ieee80211Mib::STATION && !mib->bssStationData.isAssociated) { + if (mib->mode == Ieee80211Mib::INFRASTRUCTURE && mib->getBssStationData().stationType == Ieee80211Mib::STATION && !mib->getBssStationData().isAssociated) { EV << "STA is not associated with an access point, discarding packet " << packet << "\n"; PacketDropDetails details; details.setReason(OTHER_PACKET_DROP); @@ -183,11 +231,11 @@ void Ieee80211Mac::handleUpperPacket(Packet *packet) } encapsulate(packet); const auto& header = packet->peekAtFront(); - if (mib->mode == Ieee80211Mib::INFRASTRUCTURE && mib->bssStationData.stationType == Ieee80211Mib::ACCESS_POINT) { + if (mib->mode == Ieee80211Mib::INFRASTRUCTURE && mib->getBssStationData().stationType == Ieee80211Mib::ACCESS_POINT) { auto receiverAddress = header->getReceiverAddress(); if (!receiverAddress.isMulticast()) { - auto it = mib->bssAccessPointData.stations.find(receiverAddress); - if (it == mib->bssAccessPointData.stations.end() || it->second != Ieee80211Mib::ASSOCIATED) { + auto it = mib->getBssAccessPointData().stations.find(receiverAddress); + if (it == mib->getBssAccessPointData().stations.end() || it->second != Ieee80211Mib::ASSOCIATED) { EV << "STA with MAC address " << receiverAddress << " not associated with this AP, dropping frame\n"; PacketDropDetails details; details.setReason(OTHER_PACKET_DROP); @@ -259,14 +307,14 @@ void Ieee80211Mac::encapsulate(Packet *packet) if (mib->mode == Ieee80211Mib::INDEPENDENT) header->setReceiverAddress(destAddress); else if (mib->mode == Ieee80211Mib::INFRASTRUCTURE) { - if (mib->bssStationData.stationType == Ieee80211Mib::ACCESS_POINT) { + if (mib->getBssStationData().stationType == Ieee80211Mib::ACCESS_POINT) { header->setFromDS(true); header->setAddress3(mib->address); header->setReceiverAddress(destAddress); } - else if (mib->bssStationData.stationType == Ieee80211Mib::STATION) { + else if (mib->getBssStationData().stationType == Ieee80211Mib::STATION) { header->setToDS(true); - header->setReceiverAddress(mib->bssData.bssid); + header->setReceiverAddress(mib->getBssData().bssid); header->setAddress3(destAddress); } else @@ -300,11 +348,11 @@ void Ieee80211Mac::decapsulate(Packet *packet) macAddressInd->setDestAddress(header->getReceiverAddress()); } else if (mib->mode == Ieee80211Mib::INFRASTRUCTURE) { - if (mib->bssStationData.stationType == Ieee80211Mib::ACCESS_POINT) { + if (mib->getBssStationData().stationType == Ieee80211Mib::ACCESS_POINT) { macAddressInd->setSrcAddress(header->getTransmitterAddress()); macAddressInd->setDestAddress(header->getAddress3()); } - else if (mib->bssStationData.stationType == Ieee80211Mib::STATION) { + else if (mib->getBssStationData().stationType == Ieee80211Mib::STATION) { macAddressInd->setSrcAddress(header->getAddress3()); macAddressInd->setDestAddress(header->getReceiverAddress()); } diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h index 1114deaed00..09b69439f3a 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h @@ -11,6 +11,7 @@ #include "inet/common/ModuleRefByPar.h" #include "inet/linklayer/base/MacProtocolBase.h" #include "inet/linklayer/ieee80211/mac/contract/IDs.h" +#include "inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.h" #include "inet/linklayer/ieee80211/mac/contract/IRateControl.h" #include "inet/linklayer/ieee80211/mac/contract/IRateSelection.h" #include "inet/linklayer/ieee80211/mac/contract/IRx.h" @@ -35,7 +36,7 @@ class Ieee80211MacHeader; * exact operation of the MAC depend on the plugged-in components (see IUpperMac, * IRx, ITx, IContention and other interface classes). */ -class INET_API Ieee80211Mac : public MacProtocolBase +class INET_API Ieee80211Mac : public MacProtocolBase, public IIeee80211MacConfiguration { public: static simsignal_t frameTransmissionOutcomeSignal; @@ -100,6 +101,9 @@ class INET_API Ieee80211Mac : public MacProtocolBase Ieee80211Mac(); virtual ~Ieee80211Mac(); + void prepareLocalCapabilities() override; + const physicallayer::Ieee80211ModeSet *getConfiguredModeSet() const override { return modeSet; } + virtual FcsMode getFcsMode() const { return fcsMode; } virtual const MacAddress& getAddress() const { return mib->address; } virtual void sendUp(cMessage *message) override; diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.ned b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.ned index 51f4198660a..bd2f9b93915 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.ned +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.ned @@ -7,6 +7,8 @@ package inet.linklayer.ieee80211.mac; +import inet.linklayer.ieee80211.mac.contract.IIeee80211MacConfiguration; + import inet.linklayer.base.MacProtocolBase; import inet.linklayer.ieee80211.IIeee80211Mac; import inet.linklayer.ieee80211.mac.contract.IDcf; @@ -74,7 +76,7 @@ import inet.linklayer.ieee80211.mac.contract.ITx; // which is related to power management, capability information // which is related to PCF and other non-modeled features). // -module Ieee80211Mac extends MacProtocolBase like IIeee80211Mac +module Ieee80211Mac extends MacProtocolBase like IIeee80211Mac, IIeee80211MacConfiguration { parameters: string mibModule; @@ -85,6 +87,7 @@ module Ieee80211Mac extends MacProtocolBase like IIeee80211Mac int mtu @unit(B) = default(2304B); bool qosStation = default(false); + **.modeSetModule = default(absPath(".")); *.mibModule = default(absPath(this.mibModule)); *.rxModule = "^.rx"; *.txModule = "^.tx"; @@ -92,7 +95,6 @@ module Ieee80211Mac extends MacProtocolBase like IIeee80211Mac @display("i=block/layer"); @class(Ieee80211Mac); @signal[linkBroken](type=inet::Packet); // TODO this signal is only present for the statistic to pass the signal check, to be removed - @signal[modesetChanged](type=inet::physicallayer::Ieee80211ModeSet); @signal[frameTransmissionOutcome](type=inet::Packet); @statistic[packetSentToUpper](title="packets sent to upper layer"; record=count,sum(packetBytes),vector(packetBytes); interpolationmode=none); @statistic[packetSentToLower](title="packets sent to lower layer"; record=count,sum(packetBytes),vector(packetBytes); interpolationmode=none); diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc index 44b4c0874fa..5a40aeb40cc 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc @@ -17,7 +17,7 @@ Define_Module(OriginatorBlockAckAgreementPolicy); void OriginatorBlockAckAgreementPolicy::initialize(int stage) { - ModeSetListener::initialize(stage); + ModeSetModuleBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { ackPolicy = check_and_cast(getModuleByPath(par("originatorAckPolicyModule"))); delayedAckPolicySupported = par("delayedAckPolicySupported"); diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.h index b762cc29828..88fadd48377 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.h @@ -9,13 +9,13 @@ #define __INET_ORIGINATORBLOCKACKAGREEMENTPOLICY_H #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h" -#include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" #include "inet/linklayer/ieee80211/mac/contract/IOriginatorQoSAckPolicy.h" namespace inet { namespace ieee80211 { -class INET_API OriginatorBlockAckAgreementPolicy : public ModeSetListener, public IOriginatorBlockAckAgreementPolicy +class INET_API OriginatorBlockAckAgreementPolicy : public ModeSetModuleBase, public IOriginatorBlockAckAgreementPolicy { protected: IOriginatorQoSAckPolicy *ackPolicy = nullptr; diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.ned b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.ned index bbb569b4b20..68ee746d154 100644 --- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.ned +++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.ned @@ -16,6 +16,7 @@ import inet.linklayer.ieee80211.mac.contract.IOriginatorBlockAckAgreementPolicy; simple OriginatorBlockAckAgreementPolicy extends SimpleModule like IOriginatorBlockAckAgreementPolicy { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider @class(OriginatorBlockAckAgreementPolicy); string originatorAckPolicyModule; diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc index 1f6ea0615cb..71b157c6e77 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc @@ -21,11 +21,8 @@ Define_Module(Dcaf); void Dcaf::initialize(int stage) { - if (stage == INITSTAGE_LOCAL) { - getContainingNicModule(this)->subscribe(modesetChangedSignal, this); - } - else if (stage == INITSTAGE_LINK_LAYER) { - // TODO calculateTimingParameters() + ModeSetModuleBase::initialize(stage); + if (stage == INITSTAGE_LINK_LAYER) { pendingQueue = check_and_cast(getSubmodule("pendingQueue")); inProgressFrames = check_and_cast(getSubmodule("inProgressFrames")); contention = check_and_cast(getSubmodule("contention")); @@ -122,15 +119,6 @@ void Dcaf::expectedChannelAccess(simtime_t time) // don't care } -void Dcaf::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) -{ - Enter_Method("%s", cComponent::getSignalName(signalID)); - - if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - calculateTimingParameters(); - } -} } /* namespace ieee80211 */ } /* namespace inet */ diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h index 2fab0d312c8..29fa80fb55e 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h @@ -8,7 +8,7 @@ #ifndef __INET_DCAF_H #define __INET_DCAF_H -#include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" #include "inet/linklayer/ieee80211/mac/contract/IChannelAccess.h" #include "inet/linklayer/ieee80211/mac/contract/IContention.h" #include "inet/linklayer/ieee80211/mac/contract/IRecoveryProcedure.h" @@ -17,10 +17,9 @@ namespace inet { namespace ieee80211 { -class INET_API Dcaf : public IChannelAccess, public IContention::ICallback, public IRecoveryProcedure::ICwCalculator, public ModeSetListener +class INET_API Dcaf : public IChannelAccess, public IContention::ICallback, public IRecoveryProcedure::ICwCalculator, public ModeSetModuleBase { protected: - physicallayer::Ieee80211ModeSet *modeSet = nullptr; IContention *contention = nullptr; IChannelAccess::ICallback *callback = nullptr; @@ -43,7 +42,6 @@ class INET_API Dcaf : public IChannelAccess, public IContention::ICallback, publ virtual void initialize(int stage) override; virtual void calculateTimingParameters(); - virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) override; public: virtual queueing::IPacketQueue *getPendingQueue() const { return pendingQueue; } diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.ned b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.ned index f6749e32cca..10a0aa43c39 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.ned +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.ned @@ -18,6 +18,7 @@ import inet.linklayer.ieee80211.mac.queue.InProgressFrames; module Dcaf extends Module { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider int difsn = default(-1); int cwMin = default(-1); int cwMax = default(-1); diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc index e220635a876..b0070f3c401 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc @@ -28,8 +28,8 @@ Edcaf::~Edcaf() void Edcaf::initialize(int stage) { + ModeSetModuleBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { - getContainingNicModule(this)->subscribe(modesetChangedSignal, this); ac = getAccessCategory(par("accessCategory")); contention = check_and_cast(getSubmodule("contention")); collisionController = check_and_cast(getModuleByPath(par("collisionControllerModule"))); @@ -186,15 +186,6 @@ int Edcaf::getCwMin(AccessCategory ac, int aCwMin) } } -void Edcaf::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) -{ - Enter_Method("%s", cComponent::getSignalName(signalID)); - - if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - calculateTimingParameters(); - } -} } // namespace ieee80211 } // namespace inet diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h index e737cad9162..4e984fb03a0 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h @@ -10,7 +10,7 @@ #include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" #include "inet/linklayer/ieee80211/mac/common/AccessCategory.h" -#include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" #include "inet/linklayer/ieee80211/mac/common/StationRetryCounters.h" #include "inet/linklayer/ieee80211/mac/contract/IChannelAccess.h" #include "inet/linklayer/ieee80211/mac/contract/IContention.h" @@ -28,7 +28,7 @@ namespace ieee80211 { /** * Implements IEEE 802.11 Enhanced Distributed Channel Access Function. */ -class INET_API Edcaf : public IChannelAccess, public IContention::ICallback, public IRecoveryProcedure::ICwCalculator, public ModeSetListener +class INET_API Edcaf : public IChannelAccess, public IContention::ICallback, public IRecoveryProcedure::ICwCalculator, public ModeSetModuleBase { protected: IContention *contention = nullptr; @@ -61,7 +61,6 @@ class INET_API Edcaf : public IChannelAccess, public IContention::ICallback, pub protected: virtual int numInitStages() const override { return NUM_INIT_STAGES; } virtual void initialize(int stage) override; - virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) override; virtual AccessCategory getAccessCategory(const char *ac); virtual int getAifsNumber(AccessCategory ac); diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.ned b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.ned index edacd445fe3..b67dadf2cd2 100644 --- a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.ned +++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.ned @@ -22,6 +22,7 @@ import inet.linklayer.ieee80211.mac.queue.InProgressFrames; module Edcaf extends Module { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider string rxModule; string collisionControllerModule; string originatorMacDataServiceModule; diff --git a/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.cc b/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.cc deleted file mode 100644 index 15dd3838eb5..00000000000 --- a/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.cc +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (C) 2016 OpenSim Ltd. -// -// SPDX-License-Identifier: LGPL-3.0-or-later -// - - -#include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" - -#include "inet/common/ModuleAccess.h" -#include "inet/common/Simsignals.h" -#include "inet/networklayer/common/NetworkInterface.h" - -namespace inet { -namespace ieee80211 { - -void ModeSetListener::initialize(int stage) -{ - if (stage == INITSTAGE_LOCAL) - getContainingNicModule(this)->subscribe(modesetChangedSignal, this); -} - -void ModeSetListener::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) -{ - Enter_Method("%s", cComponent::getSignalName(signalID)); - - if (signalID == modesetChangedSignal) - modeSet = check_and_cast(obj); -} - -} /* namespace ieee80211 */ -} /* namespace inet */ - diff --git a/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.h b/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.h deleted file mode 100644 index 05581613050..00000000000 --- a/src/inet/linklayer/ieee80211/mac/common/ModeSetListener.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// Copyright (C) 2016 OpenSim Ltd. -// -// SPDX-License-Identifier: LGPL-3.0-or-later -// - - -#ifndef __INET_MODESETLISTENER_H -#define __INET_MODESETLISTENER_H - -#include "inet/common/SimpleModule.h" -#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" - -namespace inet { -namespace ieee80211 { - -class INET_API ModeSetListener : public SimpleModule, public cListener -{ - protected: - physicallayer::Ieee80211ModeSet *modeSet = nullptr; - - protected: - virtual int numInitStages() const override { return NUM_INIT_STAGES; } - virtual void initialize(int stage) override; - virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) override; -}; - -} /* namespace ieee80211 */ -} /* namespace inet */ - -#endif - diff --git a/src/inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.cc b/src/inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.cc new file mode 100644 index 00000000000..77406a7179e --- /dev/null +++ b/src/inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.cc @@ -0,0 +1,20 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + + +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" +namespace inet::ieee80211 { +void ModeSetModuleBase::initialize(int stage) +{ + if (stage == INITSTAGE_LOCAL) + modeSetProvider.reference(this, "modeSetModule", true); + else if (stage == INITSTAGE_LINK_LAYER) { + modeSet = modeSetProvider->getConfiguredModeSet(); + if (modeSet == nullptr) + throw cRuntimeError("Configured IEEE 802.11 mode catalog is unavailable"); + } +} +} // namespace inet::ieee80211 diff --git a/src/inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h b/src/inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h new file mode 100644 index 00000000000..2092283634e --- /dev/null +++ b/src/inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h @@ -0,0 +1,26 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + + +#ifndef INET_MODESETMODULEBASE_H +#define INET_MODESETMODULEBASE_H + +#include "inet/common/SimpleModule.h" +#include "inet/common/ModuleRefByPar.h" +#include "inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" +namespace inet::ieee80211 { +/** Base for modules with a declared, configuration-lifetime catalog dependency. */ +class INET_API ModeSetModuleBase : public SimpleModule +{ + protected: + ModuleRefByPar modeSetProvider; + const physicallayer::Ieee80211ModeSet *modeSet = nullptr; + int numInitStages() const override { return NUM_INIT_STAGES; } + void initialize(int stage) override; +}; +} // namespace inet::ieee80211 +#endif diff --git a/src/inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.h b/src/inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.h new file mode 100644 index 00000000000..27b67801365 --- /dev/null +++ b/src/inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.h @@ -0,0 +1,27 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + + +#ifndef INET_IIEEE80211MACCONFIGURATION_H +#define INET_IIEEE80211MACCONFIGURATION_H + +#include "inet/common/INETDefs.h" + +namespace inet::physicallayer { class Ieee80211ModeSet; } + +namespace inet::ieee80211 { +/** Configured catalog is ready after LOCAL; explicit preparation requires PHY readiness. + * Repeated preparation preserves protocol and algorithm state. + */ +class INET_API IIeee80211MacConfiguration +{ + public: + virtual ~IIeee80211MacConfiguration() = default; + [[nodiscard]] virtual const physicallayer::Ieee80211ModeSet *getConfiguredModeSet() const = 0; + virtual void prepareLocalCapabilities() = 0; +}; +} // namespace inet::ieee80211 +#endif diff --git a/src/inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.ned b/src/inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.ned new file mode 100644 index 00000000000..ed517dddcee --- /dev/null +++ b/src/inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.ned @@ -0,0 +1,12 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + + +package inet.linklayer.ieee80211.mac.contract; +// Configured mode catalog and explicit capability preparation; see the C++ contract. +moduleinterface IIeee80211MacConfiguration +{ +} diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc index ddb1cf9aa65..e286df0df07 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc @@ -25,7 +25,7 @@ Define_Module(Dcf); void Dcf::initialize(int stage) { - ModeSetListener::initialize(stage); + ModeSetModuleBase::initialize(stage); if (stage == INITSTAGE_LINK_LAYER) { startRxTimer = new cMessage("startRxTimeout"); mac = check_and_cast(getContainingNicModule(this)->getSubmodule("mac")); @@ -132,8 +132,6 @@ void Dcf::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, emit(Ieee80211Mac::frameTransmissionOutcomeSignal, packet, &transmissionDetails); } } - else - ModeSetListener::receiveSignal(source, signalID, obj, details); } void Dcf::recipientProcessTransmittedControlResponseFrame(Packet *packet, const Ptr& header) diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.h b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.h index 51ce7172954..21908e37454 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.h +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.h @@ -9,7 +9,7 @@ #define __INET_DCF_H #include "inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h" -#include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" #include "inet/linklayer/ieee80211/mac/contract/ICoordinationFunction.h" #include "inet/linklayer/ieee80211/mac/contract/ICtsPolicy.h" #include "inet/linklayer/ieee80211/mac/contract/ICtsProcedure.h" @@ -37,7 +37,7 @@ class Ieee80211Mac; /** * Implements IEEE 802.11 Distributed Coordination Function. */ -class INET_API Dcf : public ICoordinationFunction, public IFrameSequenceHandler::ICallback, public IChannelAccess::ICallback, public ITx::ICallback, public IProcedureCallback, public ModeSetListener +class INET_API Dcf : public ICoordinationFunction, public IFrameSequenceHandler::ICallback, public IChannelAccess::ICallback, public ITx::ICallback, public IProcedureCallback, public ModeSetModuleBase, public cListener { protected: Ieee80211Mac *mac = nullptr; diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.ned b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.ned index df457a9149d..66364ef7a4e 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.ned +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.ned @@ -28,6 +28,7 @@ import inet.linklayer.ieee80211.mac.recipient.RecipientMacDataService; module Dcf extends Module like IDcf { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider string rxModule; string txModule; string mibModule = default("^.^.mib"); diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc index e64ce4db852..f4473f4b311 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc @@ -33,7 +33,7 @@ Define_Module(Hcf); void Hcf::initialize(int stage) { - ModeSetListener::initialize(stage); + ModeSetModuleBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { mac = check_and_cast(getContainingNicModule(this)->getSubmodule("mac")); startRxTimer = new cMessage("startRxTimeout"); @@ -113,7 +113,7 @@ void Hcf::handleMessage(cMessage *msg) void Hcf::refreshDisplay() const { - ModeSetListener::refreshDisplay(); + ModeSetModuleBase::refreshDisplay(); if (frameSequenceHandler->isSequenceRunning()) { auto history = frameSequenceHandler->getFrameSequence()->getHistory(); getDisplayString().setTagArg("tt", 0, ("Fs: " + history).c_str()); @@ -165,8 +165,6 @@ void Hcf::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, emit(Ieee80211Mac::frameTransmissionOutcomeSignal, packet, &transmissionDetails); } } - else - ModeSetListener::receiveSignal(source, signalID, obj, details); } void Hcf::scheduleStartRxTimer(simtime_t timeout) diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h index 72f7af70fc4..22398f73831 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h @@ -10,7 +10,7 @@ #include "inet/linklayer/ieee80211/mac/channelaccess/Edca.h" #include "inet/linklayer/ieee80211/mac/channelaccess/Hcca.h" -#include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" #include "inet/linklayer/ieee80211/mac/contract/IAckHandler.h" #include "inet/linklayer/ieee80211/mac/contract/IBlockAckAgreementHandlerCallback.h" #include "inet/linklayer/ieee80211/mac/contract/ICoordinationFunction.h" @@ -45,7 +45,7 @@ class Ieee80211Mac; /** * Implements IEEE 802.11 Hybrid Coordination Function. */ -class INET_API Hcf : public ICoordinationFunction, public IFrameSequenceHandler::ICallback, public IChannelAccess::ICallback, public ITx::ICallback, public IProcedureCallback, public IBlockAckAgreementHandlerCallback, public ModeSetListener +class INET_API Hcf : public ICoordinationFunction, public IFrameSequenceHandler::ICallback, public IChannelAccess::ICallback, public ITx::ICallback, public IProcedureCallback, public IBlockAckAgreementHandlerCallback, public ModeSetModuleBase, public cListener { public: static simsignal_t edcaCollisionDetectedSignal; diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.ned b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.ned index 31b8bfc7047..cac8079b706 100644 --- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.ned +++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.ned @@ -28,6 +28,7 @@ import inet.linklayer.ieee80211.mac.recipient.RecipientQosMacDataService; module Hcf extends Module like IHcf { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider bool isBlockAckSupported = default(false); string rxModule; diff --git a/src/inet/linklayer/ieee80211/mac/framesequence/FrameSequenceContext.cc b/src/inet/linklayer/ieee80211/mac/framesequence/FrameSequenceContext.cc index ae6ab2207c1..b0d8c41efd5 100644 --- a/src/inet/linklayer/ieee80211/mac/framesequence/FrameSequenceContext.cc +++ b/src/inet/linklayer/ieee80211/mac/framesequence/FrameSequenceContext.cc @@ -12,7 +12,7 @@ namespace ieee80211 { using namespace inet::physicallayer; -FrameSequenceContext::FrameSequenceContext(MacAddress address, Ieee80211ModeSet *modeSet, InProgressFrames *inProgressFrames, IRtsProcedure *rtsProcedure, IRtsPolicy *rtsPolicy, NonQoSContext *nonQoSContext, QoSContext *qosContext) : +FrameSequenceContext::FrameSequenceContext(MacAddress address, const Ieee80211ModeSet *modeSet, InProgressFrames *inProgressFrames, IRtsProcedure *rtsProcedure, IRtsPolicy *rtsPolicy, NonQoSContext *nonQoSContext, QoSContext *qosContext) : address(address), modeSet(modeSet), inProgressFrames(inProgressFrames), diff --git a/src/inet/linklayer/ieee80211/mac/framesequence/FrameSequenceContext.h b/src/inet/linklayer/ieee80211/mac/framesequence/FrameSequenceContext.h index bf2370a6b66..f6a200c0547 100644 --- a/src/inet/linklayer/ieee80211/mac/framesequence/FrameSequenceContext.h +++ b/src/inet/linklayer/ieee80211/mac/framesequence/FrameSequenceContext.h @@ -55,7 +55,7 @@ class INET_API FrameSequenceContext : public cObject protected: simtime_t startTime = simTime(); MacAddress address = MacAddress::UNSPECIFIED_ADDRESS; - physicallayer::Ieee80211ModeSet *modeSet = nullptr; + const physicallayer::Ieee80211ModeSet *modeSet = nullptr; InProgressFrames *inProgressFrames = nullptr; std::vector steps; @@ -66,7 +66,7 @@ class INET_API FrameSequenceContext : public cObject QoSContext *qosContext = nullptr; public: - FrameSequenceContext(MacAddress address, physicallayer::Ieee80211ModeSet *modeSet, InProgressFrames *inProgressFrames, IRtsProcedure *rtsProcedure, IRtsPolicy *rtsPolicy, NonQoSContext *nonQosContext, QoSContext *qosContext); + FrameSequenceContext(MacAddress address, const physicallayer::Ieee80211ModeSet *modeSet, InProgressFrames *inProgressFrames, IRtsProcedure *rtsProcedure, IRtsPolicy *rtsPolicy, NonQoSContext *nonQosContext, QoSContext *qosContext); virtual ~FrameSequenceContext(); virtual simtime_t getDuration() const { return simTime() - startTime; } diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorAckPolicy.cc b/src/inet/linklayer/ieee80211/mac/originator/OriginatorAckPolicy.cc index 699235a2000..12972a8076d 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorAckPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorAckPolicy.cc @@ -14,7 +14,7 @@ Define_Module(OriginatorAckPolicy); void OriginatorAckPolicy::initialize(int stage) { - ModeSetListener::initialize(stage); + ModeSetModuleBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { rateSelection = check_and_cast(getModuleByPath(par("rateSelectionModule"))); ackTimeout = par("ackTimeout"); diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorAckPolicy.h b/src/inet/linklayer/ieee80211/mac/originator/OriginatorAckPolicy.h index aa6d6528b56..250617c602f 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorAckPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorAckPolicy.h @@ -8,14 +8,14 @@ #ifndef __INET_ORIGINATORACKPOLICY_H #define __INET_ORIGINATORACKPOLICY_H -#include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" #include "inet/linklayer/ieee80211/mac/contract/IOriginatorAckPolicy.h" #include "inet/linklayer/ieee80211/mac/contract/IRateSelection.h" namespace inet { namespace ieee80211 { -class INET_API OriginatorAckPolicy : public ModeSetListener, public IOriginatorAckPolicy +class INET_API OriginatorAckPolicy : public ModeSetModuleBase, public IOriginatorAckPolicy { protected: IRateSelection *rateSelection = nullptr; diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorAckPolicy.ned b/src/inet/linklayer/ieee80211/mac/originator/OriginatorAckPolicy.ned index 49ed1be3d62..3e0dbb05577 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorAckPolicy.ned +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorAckPolicy.ned @@ -16,6 +16,7 @@ import inet.linklayer.ieee80211.mac.contract.IOriginatorAckPolicy; simple OriginatorAckPolicy extends SimpleModule like IOriginatorAckPolicy { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider @class(OriginatorAckPolicy); string rateSelectionModule; double ackTimeout @unit(s) = default(-1s); diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.cc b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.cc index 90cc7602199..206d9e8b73d 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.cc @@ -16,7 +16,7 @@ Define_Module(OriginatorQosAckPolicy); void OriginatorQosAckPolicy::initialize(int stage) { - ModeSetListener::initialize(stage); + ModeSetModuleBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { rateSelection = check_and_cast(getModuleByPath(par("rateSelectionModule"))); maxBlockAckPolicyFrameLength = par("maxBlockAckPolicyFrameLength"); diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.h b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.h index d25da018cdc..5e1b33914ab 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.h @@ -9,14 +9,14 @@ #define __INET_ORIGINATORQOSACKPOLICY_H #include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h" -#include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" #include "inet/linklayer/ieee80211/mac/contract/IOriginatorQoSAckPolicy.h" #include "inet/linklayer/ieee80211/mac/contract/IQosRateSelection.h" namespace inet { namespace ieee80211 { -class INET_API OriginatorQosAckPolicy : public ModeSetListener, public IOriginatorQoSAckPolicy +class INET_API OriginatorQosAckPolicy : public ModeSetModuleBase, public IOriginatorQoSAckPolicy { protected: IQosRateSelection *rateSelection = nullptr; diff --git a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.ned b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.ned index eb2f9236ab1..953c8901008 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.ned +++ b/src/inet/linklayer/ieee80211/mac/originator/OriginatorQosAckPolicy.ned @@ -16,6 +16,7 @@ import inet.linklayer.ieee80211.mac.contract.IOriginatorQosAckPolicy; simple OriginatorQosAckPolicy extends SimpleModule like IOriginatorQosAckPolicy { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider @class(OriginatorQosAckPolicy); string rateSelectionModule; diff --git a/src/inet/linklayer/ieee80211/mac/originator/QosRtsPolicy.cc b/src/inet/linklayer/ieee80211/mac/originator/QosRtsPolicy.cc index fa1e42f77c8..88591dc0be7 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/QosRtsPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/QosRtsPolicy.cc @@ -14,7 +14,7 @@ Define_Module(QosRtsPolicy); void QosRtsPolicy::initialize(int stage) { - ModeSetListener::initialize(stage); + ModeSetModuleBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { rtsThreshold = par("rtsThreshold"); ctsTimeout = par("ctsTimeout"); diff --git a/src/inet/linklayer/ieee80211/mac/originator/QosRtsPolicy.h b/src/inet/linklayer/ieee80211/mac/originator/QosRtsPolicy.h index ac5c59840bf..ae19897b45b 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/QosRtsPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/originator/QosRtsPolicy.h @@ -8,14 +8,14 @@ #ifndef __INET_QOSRTSPOLICY_H #define __INET_QOSRTSPOLICY_H -#include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" #include "inet/linklayer/ieee80211/mac/contract/IQosRateSelection.h" #include "inet/linklayer/ieee80211/mac/contract/IRtsPolicy.h" namespace inet { namespace ieee80211 { -class INET_API QosRtsPolicy : public ModeSetListener, public IRtsPolicy +class INET_API QosRtsPolicy : public ModeSetModuleBase, public IRtsPolicy { protected: IQosRateSelection *rateSelection = nullptr; diff --git a/src/inet/linklayer/ieee80211/mac/originator/QosRtsPolicy.ned b/src/inet/linklayer/ieee80211/mac/originator/QosRtsPolicy.ned index d19f5b55260..9d0625cc905 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/QosRtsPolicy.ned +++ b/src/inet/linklayer/ieee80211/mac/originator/QosRtsPolicy.ned @@ -16,6 +16,7 @@ import inet.linklayer.ieee80211.mac.contract.IRtsPolicy; simple QosRtsPolicy extends SimpleModule like IRtsPolicy { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider @class(QosRtsPolicy); string rateSelectionModule; double ctsTimeout @unit(s) = default(-1s); diff --git a/src/inet/linklayer/ieee80211/mac/originator/RtsPolicy.cc b/src/inet/linklayer/ieee80211/mac/originator/RtsPolicy.cc index fc75816f253..d2290f93dac 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/RtsPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/RtsPolicy.cc @@ -14,7 +14,7 @@ Define_Module(RtsPolicy); void RtsPolicy::initialize(int stage) { - ModeSetListener::initialize(stage); + ModeSetModuleBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { rateSelection = check_and_cast(getModuleByPath(par("rateSelectionModule"))); rtsThreshold = par("rtsThreshold"); diff --git a/src/inet/linklayer/ieee80211/mac/originator/RtsPolicy.h b/src/inet/linklayer/ieee80211/mac/originator/RtsPolicy.h index f8a8e042de5..af78eeca467 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/RtsPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/originator/RtsPolicy.h @@ -8,14 +8,14 @@ #ifndef __INET_RTSPOLICY_H #define __INET_RTSPOLICY_H -#include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" #include "inet/linklayer/ieee80211/mac/contract/IRateSelection.h" #include "inet/linklayer/ieee80211/mac/contract/IRtsPolicy.h" namespace inet { namespace ieee80211 { -class INET_API RtsPolicy : public ModeSetListener, public IRtsPolicy +class INET_API RtsPolicy : public ModeSetModuleBase, public IRtsPolicy { protected: int rtsThreshold = -1; diff --git a/src/inet/linklayer/ieee80211/mac/originator/RtsPolicy.ned b/src/inet/linklayer/ieee80211/mac/originator/RtsPolicy.ned index 331f0739d52..e963cca9d65 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/RtsPolicy.ned +++ b/src/inet/linklayer/ieee80211/mac/originator/RtsPolicy.ned @@ -16,6 +16,7 @@ import inet.linklayer.ieee80211.mac.contract.IRtsPolicy; simple RtsPolicy extends SimpleModule like IRtsPolicy { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider @class(RtsPolicy); string rateSelectionModule; double ctsTimeout @unit(s) = default(-1s); diff --git a/src/inet/linklayer/ieee80211/mac/originator/TxopProcedure.cc b/src/inet/linklayer/ieee80211/mac/originator/TxopProcedure.cc index 9b7e6184293..1b15a6ee6b2 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/TxopProcedure.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/TxopProcedure.cc @@ -20,7 +20,7 @@ Define_Module(TxopProcedure); void TxopProcedure::initialize(int stage) { - ModeSetListener::initialize(stage); + ModeSetModuleBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { limit = par("txopLimit"); WATCH(start); diff --git a/src/inet/linklayer/ieee80211/mac/originator/TxopProcedure.h b/src/inet/linklayer/ieee80211/mac/originator/TxopProcedure.h index 656b74e8055..820dd6dea8b 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/TxopProcedure.h +++ b/src/inet/linklayer/ieee80211/mac/originator/TxopProcedure.h @@ -9,14 +9,14 @@ #define __INET_TXOPPROCEDURE_H #include "inet/linklayer/ieee80211/mac/common/AccessCategory.h" -#include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" #include "inet/linklayer/ieee80211/mac/contract/IRateSelection.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" namespace inet { namespace ieee80211 { -class INET_API TxopProcedure : public ModeSetListener +class INET_API TxopProcedure : public ModeSetModuleBase { public: static simsignal_t txopStartedSignal; diff --git a/src/inet/linklayer/ieee80211/mac/originator/TxopProcedure.ned b/src/inet/linklayer/ieee80211/mac/originator/TxopProcedure.ned index dc0c3403564..7c4cf8fb987 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/TxopProcedure.ned +++ b/src/inet/linklayer/ieee80211/mac/originator/TxopProcedure.ned @@ -18,6 +18,7 @@ import inet.common.SimpleModule; simple TxopProcedure extends SimpleModule { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider @class(TxopProcedure); double txopLimit @unit(s) = default(-1s); @display("i=block/timer"); diff --git a/src/inet/linklayer/ieee80211/mac/protectionmechanism/OriginatorProtectionMechanism.cc b/src/inet/linklayer/ieee80211/mac/protectionmechanism/OriginatorProtectionMechanism.cc index a0ffcea12ed..866050cf176 100644 --- a/src/inet/linklayer/ieee80211/mac/protectionmechanism/OriginatorProtectionMechanism.cc +++ b/src/inet/linklayer/ieee80211/mac/protectionmechanism/OriginatorProtectionMechanism.cc @@ -17,7 +17,7 @@ Define_Module(OriginatorProtectionMechanism); void OriginatorProtectionMechanism::initialize(int stage) { - ModeSetListener::initialize(stage); + ModeSetModuleBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { rateSelection = check_and_cast(getModuleByPath(par("rateSelectionModule"))); } diff --git a/src/inet/linklayer/ieee80211/mac/protectionmechanism/OriginatorProtectionMechanism.h b/src/inet/linklayer/ieee80211/mac/protectionmechanism/OriginatorProtectionMechanism.h index 1366ad71399..7a455d44d00 100644 --- a/src/inet/linklayer/ieee80211/mac/protectionmechanism/OriginatorProtectionMechanism.h +++ b/src/inet/linklayer/ieee80211/mac/protectionmechanism/OriginatorProtectionMechanism.h @@ -9,13 +9,13 @@ #define __INET_ORIGINATORPROTECTIONMECHANISM_H #include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" -#include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" #include "inet/linklayer/ieee80211/mac/contract/IRateSelection.h" namespace inet { namespace ieee80211 { -class INET_API OriginatorProtectionMechanism : public ModeSetListener +class INET_API OriginatorProtectionMechanism : public ModeSetModuleBase { protected: IRateSelection *rateSelection = nullptr; diff --git a/src/inet/linklayer/ieee80211/mac/protectionmechanism/OriginatorProtectionMechanism.ned b/src/inet/linklayer/ieee80211/mac/protectionmechanism/OriginatorProtectionMechanism.ned index 6bd7cfc3340..b8311ff529b 100644 --- a/src/inet/linklayer/ieee80211/mac/protectionmechanism/OriginatorProtectionMechanism.ned +++ b/src/inet/linklayer/ieee80211/mac/protectionmechanism/OriginatorProtectionMechanism.ned @@ -19,6 +19,7 @@ import inet.common.SimpleModule; simple OriginatorProtectionMechanism extends SimpleModule { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider @class(OriginatorProtectionMechanism); string rateSelectionModule; @display("i=block/encrypt"); diff --git a/src/inet/linklayer/ieee80211/mac/protectionmechanism/SingleProtectionMechanism.cc b/src/inet/linklayer/ieee80211/mac/protectionmechanism/SingleProtectionMechanism.cc index 4b38b531005..47009317dcb 100644 --- a/src/inet/linklayer/ieee80211/mac/protectionmechanism/SingleProtectionMechanism.cc +++ b/src/inet/linklayer/ieee80211/mac/protectionmechanism/SingleProtectionMechanism.cc @@ -18,7 +18,7 @@ Define_Module(SingleProtectionMechanism); void SingleProtectionMechanism::initialize(int stage) { - ModeSetListener::initialize(stage); + ModeSetModuleBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { rateSelection = check_and_cast(getModuleByPath(par("rateSelectionModule"))); } diff --git a/src/inet/linklayer/ieee80211/mac/protectionmechanism/SingleProtectionMechanism.h b/src/inet/linklayer/ieee80211/mac/protectionmechanism/SingleProtectionMechanism.h index 65ee94ccf3f..8f2d19d577f 100644 --- a/src/inet/linklayer/ieee80211/mac/protectionmechanism/SingleProtectionMechanism.h +++ b/src/inet/linklayer/ieee80211/mac/protectionmechanism/SingleProtectionMechanism.h @@ -8,7 +8,7 @@ #ifndef __INET_SINGLEPROTECTIONMECHANISM_H #define __INET_SINGLEPROTECTIONMECHANISM_H -#include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" #include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" #include "inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h" @@ -24,7 +24,7 @@ namespace ieee80211 { // // 8.2.5.2 Setting for single and multiple protection under enhanced distributed channel access (EDCA) // -class INET_API SingleProtectionMechanism : public ModeSetListener +class INET_API SingleProtectionMechanism : public ModeSetModuleBase { protected: IQosRateSelection *rateSelection = nullptr; diff --git a/src/inet/linklayer/ieee80211/mac/protectionmechanism/SingleProtectionMechanism.ned b/src/inet/linklayer/ieee80211/mac/protectionmechanism/SingleProtectionMechanism.ned index c9ff5857cf9..22f0a555eb3 100644 --- a/src/inet/linklayer/ieee80211/mac/protectionmechanism/SingleProtectionMechanism.ned +++ b/src/inet/linklayer/ieee80211/mac/protectionmechanism/SingleProtectionMechanism.ned @@ -19,6 +19,7 @@ import inet.common.SimpleModule; simple SingleProtectionMechanism extends SimpleModule { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider @class(SingleProtectionMechanism); string rateSelectionModule; @display("i=block/encrypt"); diff --git a/src/inet/linklayer/ieee80211/mac/ratecontrol/AarfRateControl.ned b/src/inet/linklayer/ieee80211/mac/ratecontrol/AarfRateControl.ned index 9ec36a21ebc..c63d6453ce9 100644 --- a/src/inet/linklayer/ieee80211/mac/ratecontrol/AarfRateControl.ned +++ b/src/inet/linklayer/ieee80211/mac/ratecontrol/AarfRateControl.ned @@ -18,6 +18,7 @@ import inet.linklayer.ieee80211.mac.contract.IRateControl; simple AarfRateControl extends SimpleModule like IRateControl { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider @class(AarfRateControl); double initialRate @unit(bps) = default(-1bps); // -1 means the fastest mandatory rate double interval @unit(s) = default(50ms); // The rate (unconditionally) increases after each time interval. diff --git a/src/inet/linklayer/ieee80211/mac/ratecontrol/OnoeRateControl.ned b/src/inet/linklayer/ieee80211/mac/ratecontrol/OnoeRateControl.ned index 966753c790f..bb02df4ff13 100644 --- a/src/inet/linklayer/ieee80211/mac/ratecontrol/OnoeRateControl.ned +++ b/src/inet/linklayer/ieee80211/mac/ratecontrol/OnoeRateControl.ned @@ -17,6 +17,7 @@ import inet.linklayer.ieee80211.mac.contract.IRateControl; simple OnoeRateControl extends SimpleModule like IRateControl { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider @class(OnoeRateControl); double initialRate @unit(bps) = default(-1bps); // -1 means the fastest mandatory rate double interval @unit(s) = default(1s); diff --git a/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.cc b/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.cc index 551a31c4c9e..f0d161ab4c3 100644 --- a/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.cc +++ b/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.cc @@ -19,7 +19,9 @@ simsignal_t RateControlBase::datarateChangedSignal = cComponent::registerSignal( void RateControlBase::initialize(int stage) { - ModeSetListener::initialize(stage); + ModeSetModuleBase::initialize(stage); + if (stage == INITSTAGE_LINK_LAYER) + resetRateControl(); } const IIeee80211Mode *RateControlBase::increaseRateIfPossible(const IIeee80211Mode *currentMode) @@ -60,15 +62,6 @@ void RateControlBase::emitDatarateChangedSignal(const MacAddress& receiver, cons } } -void RateControlBase::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) -{ - Enter_Method("%s", cComponent::getSignalName(signalID)); - - if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - resetRateControl(); - } -} } /* namespace ieee80211 */ } /* namespace inet */ diff --git a/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.h b/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.h index 2a61b5028fa..5eb2fa2abf2 100644 --- a/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.h +++ b/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.h @@ -9,13 +9,13 @@ #define __INET_RATECONTROLBASE_H #include "inet/linklayer/common/MacAddress.h" -#include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" #include "inet/linklayer/ieee80211/mac/contract/IRateControl.h" namespace inet { namespace ieee80211 { -class INET_API RateControlBase : public ModeSetListener, public IRateControl +class INET_API RateControlBase : public ModeSetModuleBase, public IRateControl { public: static simsignal_t datarateChangedSignal; @@ -23,7 +23,6 @@ class INET_API RateControlBase : public ModeSetListener, public IRateControl protected: virtual int numInitStages() const override { return NUM_INIT_STAGES; } virtual void initialize(int stage) override; - virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) override; // The receiver MAC address of a transmitted (or received) frame, which keys the per-station state. virtual MacAddress getReceiverAddress(Packet *frame) const; diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.cc index 6408b443687..78eb9e886f2 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.cc @@ -49,12 +49,12 @@ static const IIeee80211Mode *getLegacyFallback(const Ieee80211ModeSet *modeSet, // a receiver-supported MCS/rate and CH_BANDWIDTH permitted by the BSS HT // Operation. The directional negotiated state is the model's source of the // receiver's capability advertisement. -static bool isCompatibleHtMode(const IIeee80211Mode *mode, const Ieee80211Mib::PeerHtState *peerHtState) +static bool isCompatibleHtMode(const IIeee80211Mode *mode, const Ieee80211Mib::PeerHtState *peerHtState, const Ieee80211HtOperation& operation) { if (mode == nullptr || peerHtState == nullptr || !peerHtState->valid) return false; - const auto& negotiated = peerHtState->negotiatedCapabilities; + const auto& negotiated = *peerHtState->negotiatedCapabilities; const auto& receiverCapabilities = negotiated.localTxPeerRx; if (!receiverCapabilities.valid) return false; @@ -65,7 +65,7 @@ static bool isCompatibleHtMode(const IIeee80211Mode *mode, const Ieee80211Mib::P auto bandwidth = mode->getDataMode()->getBandwidth(); if (receiverCapabilities.supportedChannelWidths.count(bandwidth) == 0 || - bandwidth > negotiated.operation.operatingChannelWidth) + bandwidth > operation.operatingChannelWidth) return false; // IEEE Std 802.11-2024, 10.17 and Table 9-224: a short guard interval @@ -105,7 +105,8 @@ static bool isBetterHtMode(const IIeee80211Mode *candidate, const IIeee80211Mode } // namespace const IIeee80211Mode *selectPeerCompatibleMode(const Ieee80211ModeSet *modeSet, - const Ieee80211Mib::PeerHtState *peerHtState, const IIeee80211Mode *mode, const MacAddress& peerAddress) + const Ieee80211Mib::PeerHtState *peerHtState, const IIeee80211Mode *mode, const MacAddress& peerAddress, + const Ieee80211HtOperation *operation, bool htEligible) { if (mode == nullptr || mode->getHtMcsIndex() < 0) return mode; @@ -115,9 +116,10 @@ const IIeee80211Mode *selectPeerCompatibleMode(const Ieee80211ModeSet *modeSet, throw cRuntimeError("HT mode '%s' is not contained in IEEE 802.11 mode set '%s'", mode->getName(), modeSet->getName()); - if (peerHtState == nullptr || !peerHtState->valid || !peerHtState->negotiatedCapabilities.localTxPeerRx.valid) + if (!htEligible || operation == nullptr || peerHtState == nullptr || !peerHtState->valid || + !peerHtState->negotiatedCapabilities || !peerHtState->negotiatedCapabilities->localTxPeerRx.valid) return getLegacyFallback(modeSet, mode, peerAddress); - if (isCompatibleHtMode(mode, peerHtState)) + if (isCompatibleHtMode(mode, peerHtState, *operation)) return mode; auto candidateBitrate = mode->getDataMode()->getNetBitrate(); @@ -126,7 +128,7 @@ const IIeee80211Mode *selectPeerCompatibleMode(const Ieee80211ModeSet *modeSet, const auto *candidate = modeSet->getMode(i); if (candidate->getHtMcsIndex() < 0 || candidate->getDataMode()->getNetBitrate() > candidateBitrate || - !isCompatibleHtMode(candidate, peerHtState)) + !isCompatibleHtMode(candidate, peerHtState, *operation)) continue; if (bestMode == nullptr || isBetterHtMode(candidate, bestMode)) bestMode = candidate; diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h b/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h index ba5eaebf764..cd9bebfaf8c 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h +++ b/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h @@ -24,7 +24,8 @@ INET_API const physicallayer::IIeee80211Mode *selectPeerCompatibleMode( const physicallayer::Ieee80211ModeSet *modeSet, const Ieee80211Mib::PeerHtState *peerHtState, const physicallayer::IIeee80211Mode *mode, - const MacAddress& peerAddress); + const MacAddress& peerAddress, + const Ieee80211HtOperation *operation, bool htEligible); } // namespace ieee80211 } // namespace inet diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc index add5ec700f1..953ceb5599c 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc @@ -22,10 +22,11 @@ Define_Module(QosRateSelection); void QosRateSelection::initialize(int stage) { - ModeSetListener::initialize(stage); + ModeSetModuleBase::initialize(stage); if (stage == INITSTAGE_LOCAL) mib.reference(this, "mibModule", true); if (stage == INITSTAGE_LINK_LAYER) { + fastestMandatoryMode = modeSet->getFastestMandatoryMode(); dataOrMgmtRateControl = dynamic_cast(findModuleByPath(par("rateControlModule"))); double multicastFrameBitrate = par("multicastFrameBitrate"); multicastFrameMode = (multicastFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(multicastFrameBitrate)); @@ -270,15 +271,6 @@ const IIeee80211Mode *QosRateSelection::computeMode(Packet *packet, const PtrgetReceiverAddress(), computeControlFrameMode(header, txopProcedure)); } -void QosRateSelection::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) -{ - Enter_Method("%s", cComponent::getSignalName(signalID)); - - if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - fastestMandatoryMode = modeSet->getFastestMandatoryMode(); - } -} void QosRateSelection::frameTransmitted(Packet *packet, const Ptr& header) { @@ -290,7 +282,8 @@ const IIeee80211Mode *QosRateSelection::getPeerCompatibleMode(const MacAddress& { if (mode == nullptr || peerAddress.isMulticast() || !mib || mode->getHtMcsIndex() < 0) return mode; - return selectPeerCompatibleMode(modeSet, mib->findPeerHtState(peerAddress), mode, peerAddress); + return selectPeerCompatibleMode(modeSet, mib->findPeerCapabilities(peerAddress), mode, peerAddress, + mib->hasHtOperation() ? &mib->getHtOperation() : nullptr, mib->relationshipAllowsHt(peerAddress)); } } /* namespace ieee80211 */ diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h index 569ef742d37..c84beacc05c 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h @@ -9,7 +9,7 @@ #define __INET_QOSRATESELECTION_H #include "inet/common/ModuleRefByPar.h" -#include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" #include "inet/linklayer/ieee80211/mac/contract/IQosRateSelection.h" #include "inet/linklayer/ieee80211/mac/contract/IRateControl.h" #include "inet/linklayer/ieee80211/mib/Ieee80211Mib.h" @@ -30,13 +30,12 @@ namespace ieee80211 { * 9.7.6.4 Rate selection for control frames that are not control response frames * 9.7.6.5 Rate selection for control response frames */ -class INET_API QosRateSelection : public IQosRateSelection, public ModeSetListener +class INET_API QosRateSelection : public IQosRateSelection, public ModeSetModuleBase { protected: IRateControl *dataOrMgmtRateControl = nullptr; ModuleRefByPar mib; - const physicallayer::Ieee80211ModeSet *modeSet = nullptr; std::map lastTransmittedFrameMode; // originator frame modes @@ -58,7 +57,6 @@ class INET_API QosRateSelection : public IQosRateSelection, public ModeSetListen protected: virtual int numInitStages() const override { return NUM_INIT_STAGES; } virtual void initialize(int stage) override; - virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) override; // Builds perReceiverDataFrameMode on first use. Deferred out of initialize() because peer // MAC addresses are assigned during INITSTAGE_LINK_LAYER with undefined intra-stage module diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned index 35bf0e1ace5..5cb9f9bff87 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned @@ -21,6 +21,7 @@ import inet.common.SimpleModule; simple QosRateSelection extends SimpleModule { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider @class(QosRateSelection); string rateControlModule; string mibModule = default("^.^.^.mib"); diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc index 9d28a5142cd..20b9522c46e 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc @@ -26,9 +26,9 @@ Define_Module(RateSelection); void RateSelection::initialize(int stage) { + ModeSetModuleBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { mib.reference(this, "mibModule", true); - getContainingNicModule(this)->subscribe(modesetChangedSignal, this); } else if (stage == INITSTAGE_LINK_LAYER) { dataOrMgmtRateControl = dynamic_cast(findModuleByPath(par("rateControlModule"))); @@ -184,15 +184,6 @@ const IIeee80211Mode *RateSelection::computeMode(Packet *packet, const Ptr(obj); - fastestMandatoryMode = modeSet->getFastestMandatoryMode(); - } -} void RateSelection::frameTransmitted(Packet *packet, const Ptr& header) { @@ -223,7 +214,8 @@ const IIeee80211Mode *RateSelection::getPeerCompatibleMode(const MacAddress& pee { if (mode == nullptr || peerAddress.isMulticast() || !mib || mode->getHtMcsIndex() < 0) return mode; - return selectPeerCompatibleMode(modeSet, mib->findPeerHtState(peerAddress), mode, peerAddress); + return selectPeerCompatibleMode(modeSet, mib->findPeerCapabilities(peerAddress), mode, peerAddress, + mib->hasHtOperation() ? &mib->getHtOperation() : nullptr, mib->relationshipAllowsHt(peerAddress)); } } // namespace ieee80211 diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h index 69441331453..3c407bcf694 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h @@ -9,7 +9,7 @@ #define __INET_RATESELECTION_H #include "inet/common/ModuleRefByPar.h" -#include "inet/common/SimpleModule.h" +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" #include "inet/linklayer/ieee80211/mac/contract/IRateControl.h" #include "inet/linklayer/ieee80211/mac/contract/IRateSelection.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" @@ -31,14 +31,13 @@ namespace ieee80211 { * 9.7.6.4 Rate selection for control frames that are not control response frames * 9.7.6.5 Rate selection for control response frames */ -class INET_API RateSelection : public IRateSelection, public SimpleModule, public cListener // FIXME +class INET_API RateSelection : public IRateSelection, public ModeSetModuleBase { protected: IRateControl *dataOrMgmtRateControl = nullptr; ModuleRefByPar mib; const physicallayer::IIeee80211Mode *fastestMandatoryMode = nullptr; - const physicallayer::Ieee80211ModeSet *modeSet = nullptr; std::map lastTransmittedFrameMode; // originator frame modes @@ -58,7 +57,6 @@ class INET_API RateSelection : public IRateSelection, public SimpleModule, publi protected: virtual int numInitStages() const override { return NUM_INIT_STAGES; } virtual void initialize(int stage) override; - virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) override; // Builds perReceiverDataFrameMode on first use. Deferred out of initialize() because peer // MAC addresses are assigned during INITSTAGE_LINK_LAYER with undefined intra-stage module diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned index 89f63916421..2015d0669f8 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned @@ -17,6 +17,7 @@ import inet.linklayer.ieee80211.mac.contract.IRateSelection; simple RateSelection extends SimpleModule like IRateSelection { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider @class(RateSelection); string rateControlModule; string mibModule = default("^.^.^.mib"); diff --git a/src/inet/linklayer/ieee80211/mac/recipient/CtsPolicy.cc b/src/inet/linklayer/ieee80211/mac/recipient/CtsPolicy.cc index 17edca5ffea..c8c1b5eb7e6 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/CtsPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/CtsPolicy.cc @@ -16,7 +16,7 @@ Define_Module(CtsPolicy); void CtsPolicy::initialize(int stage) { - ModeSetListener::initialize(stage); + ModeSetModuleBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { rx = check_and_cast(getModuleByPath(par("rxModule"))); rateSelection = check_and_cast(getModuleByPath(par("rateSelectionModule"))); diff --git a/src/inet/linklayer/ieee80211/mac/recipient/CtsPolicy.h b/src/inet/linklayer/ieee80211/mac/recipient/CtsPolicy.h index b86853715f2..3bd429aa4f5 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/CtsPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/recipient/CtsPolicy.h @@ -8,7 +8,7 @@ #ifndef __INET_CTSPOLICY_H #define __INET_CTSPOLICY_H -#include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" #include "inet/linklayer/ieee80211/mac/contract/ICtsPolicy.h" #include "inet/linklayer/ieee80211/mac/contract/IRateSelection.h" #include "inet/linklayer/ieee80211/mac/contract/IRx.h" @@ -16,7 +16,7 @@ namespace inet { namespace ieee80211 { -class INET_API CtsPolicy : public ModeSetListener, public ICtsPolicy +class INET_API CtsPolicy : public ModeSetModuleBase, public ICtsPolicy { protected: IRateSelection *rateSelection = nullptr; diff --git a/src/inet/linklayer/ieee80211/mac/recipient/CtsPolicy.ned b/src/inet/linklayer/ieee80211/mac/recipient/CtsPolicy.ned index d9d1f635d59..b7b04b55522 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/CtsPolicy.ned +++ b/src/inet/linklayer/ieee80211/mac/recipient/CtsPolicy.ned @@ -16,6 +16,7 @@ import inet.linklayer.ieee80211.mac.contract.ICtsPolicy; simple CtsPolicy extends SimpleModule like ICtsPolicy { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider @class(CtsPolicy); string rxModule; string rateSelectionModule; diff --git a/src/inet/linklayer/ieee80211/mac/recipient/QosCtsPolicy.cc b/src/inet/linklayer/ieee80211/mac/recipient/QosCtsPolicy.cc index afd8af57f9d..fd3ac6e86d6 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/QosCtsPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/QosCtsPolicy.cc @@ -16,7 +16,7 @@ Define_Module(QosCtsPolicy); void QosCtsPolicy::initialize(int stage) { - ModeSetListener::initialize(stage); + ModeSetModuleBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { rx = check_and_cast(getModuleByPath(par("rxModule"))); rateSelection = check_and_cast(getModuleByPath(par("rateSelectionModule"))); diff --git a/src/inet/linklayer/ieee80211/mac/recipient/QosCtsPolicy.h b/src/inet/linklayer/ieee80211/mac/recipient/QosCtsPolicy.h index b447b8ac786..bbf30022a2c 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/QosCtsPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/recipient/QosCtsPolicy.h @@ -15,7 +15,7 @@ namespace inet { namespace ieee80211 { -class INET_API QosCtsPolicy : public ModeSetListener, public ICtsPolicy +class INET_API QosCtsPolicy : public ModeSetModuleBase, public ICtsPolicy { protected: IRx *rx = nullptr; diff --git a/src/inet/linklayer/ieee80211/mac/recipient/QosCtsPolicy.ned b/src/inet/linklayer/ieee80211/mac/recipient/QosCtsPolicy.ned index a2d61af6111..ce7d9085eeb 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/QosCtsPolicy.ned +++ b/src/inet/linklayer/ieee80211/mac/recipient/QosCtsPolicy.ned @@ -16,6 +16,7 @@ import inet.linklayer.ieee80211.mac.contract.ICtsPolicy; simple QosCtsPolicy extends SimpleModule like ICtsPolicy { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider @class(QosCtsPolicy); string rxModule; string rateSelectionModule; diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckPolicy.cc b/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckPolicy.cc index af8077331bb..e1cd6e472da 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckPolicy.cc @@ -16,7 +16,7 @@ Define_Module(RecipientAckPolicy); void RecipientAckPolicy::initialize(int stage) { - ModeSetListener::initialize(stage); + ModeSetModuleBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { rateSelection = check_and_cast(getModuleByPath(par("rateSelectionModule"))); } diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckPolicy.h b/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckPolicy.h index d851cf03ba7..ddae91281be 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckPolicy.h @@ -8,14 +8,14 @@ #ifndef __INET_RECIPIENTACKPOLICY_H #define __INET_RECIPIENTACKPOLICY_H -#include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" #include "inet/linklayer/ieee80211/mac/contract/IRateSelection.h" #include "inet/linklayer/ieee80211/mac/contract/IRecipientAckPolicy.h" namespace inet { namespace ieee80211 { -class INET_API RecipientAckPolicy : public ModeSetListener, public IRecipientAckPolicy +class INET_API RecipientAckPolicy : public ModeSetModuleBase, public IRecipientAckPolicy { protected: IRateSelection *rateSelection = nullptr; diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckPolicy.ned b/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckPolicy.ned index 4eb039a564f..837ce77b858 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckPolicy.ned +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientAckPolicy.ned @@ -16,6 +16,7 @@ import inet.linklayer.ieee80211.mac.contract.IRecipientAckPolicy; simple RecipientAckPolicy extends SimpleModule like IRecipientAckPolicy { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider @class(RecipientAckPolicy); string rateSelectionModule; @display("i=block/control"); diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc index 29f793f0110..4a42f5f2836 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.cc @@ -16,7 +16,7 @@ Define_Module(RecipientQosAckPolicy); void RecipientQosAckPolicy::initialize(int stage) { - ModeSetListener::initialize(stage); + ModeSetModuleBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { rateSelection = check_and_cast(getModuleByPath(par("rateSelectionModule"))); } diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h index 9f4ce456784..931635fc406 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.h @@ -8,7 +8,7 @@ #ifndef __INET_RECIPIENTQOSACKPOLICY_H #define __INET_RECIPIENTQOSACKPOLICY_H -#include "inet/linklayer/ieee80211/mac/common/ModeSetListener.h" +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" #include "inet/linklayer/ieee80211/mac/contract/IQosRateSelection.h" #include "inet/linklayer/ieee80211/mac/contract/IRecipientAckPolicy.h" #include "inet/linklayer/ieee80211/mac/contract/IRecipientQosAckPolicy.h" @@ -16,7 +16,7 @@ namespace inet { namespace ieee80211 { -class INET_API RecipientQosAckPolicy : public ModeSetListener, public IRecipientAckPolicy, public IRecipientQosAckPolicy +class INET_API RecipientQosAckPolicy : public ModeSetModuleBase, public IRecipientAckPolicy, public IRecipientQosAckPolicy { protected: IQosRateSelection *rateSelection = nullptr; diff --git a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.ned b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.ned index 50971923a80..ec3ceb199de 100644 --- a/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.ned +++ b/src/inet/linklayer/ieee80211/mac/recipient/RecipientQosAckPolicy.ned @@ -16,6 +16,7 @@ import inet.linklayer.ieee80211.mac.contract.IRecipientQosAckPolicy; simple RecipientQosAckPolicy extends SimpleModule like IRecipientQosAckPolicy { parameters: + string modeSetModule = default(""); // Required catalog provider; supplied by the enclosing MAC // Configured MAC catalog provider @class(RecipientQosAckPolicy); string rateSelectionModule; @display("i=block/control"); diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211AgentSta.cc b/src/inet/linklayer/ieee80211/mgmt/Ieee80211AgentSta.cc index f98f3f8e699..7bb4edacdd0 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211AgentSta.cc +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211AgentSta.cc @@ -307,7 +307,7 @@ void Ieee80211AgentSta::processReassociateConfirm(Ieee80211Prim_ReassociateConfi { if (resp->getResultCode() != PRC_SUCCESS) { EV << "Reassociation error\n"; - bool isAssociated = mib->bssStationData.isAssociated; + bool isAssociated = mib->getBssStationData().isAssociated; emit(dropConfirmSignal, PR_REASSOCIATE_CONFIRM); if (!isAssociated) { EV << "Going back to scanning\n"; diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAdhoc.cc b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAdhoc.cc index 5b6b266b49e..f9671295849 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAdhoc.cc +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAdhoc.cc @@ -21,6 +21,13 @@ void Ieee80211MgmtAdhoc::initialize(int stage) } } +void Ieee80211MgmtAdhoc::prepareLocalOperation() +{ + // This no-beacon abstraction has no learned BSS channel/HT operation or + // accepted peer advertisements. Keep that absence distinct from local HT support. + mib->commitBss("", MacAddress::UNSPECIFIED_ADDRESS, nullptr, -1, nullptr); +} + void Ieee80211MgmtAdhoc::handleTimer(cMessage *msg) { ASSERT(false); diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAdhoc.h b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAdhoc.h index 45d285e9a85..45b1c899a77 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAdhoc.h +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAdhoc.h @@ -24,6 +24,7 @@ class INET_API Ieee80211MgmtAdhoc : public Ieee80211MgmtBase protected: virtual int numInitStages() const override { return NUM_INIT_STAGES; } virtual void initialize(int) override; + void prepareLocalOperation() override; /** Implements abstract Ieee80211MgmtBase method */ virtual void handleTimer(cMessage *msg) override; diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.cc b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.cc index 1b52306987e..c3e3106e75b 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.cc +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.cc @@ -90,8 +90,6 @@ void Ieee80211MgmtAp::receiveSignal(cComponent *source, simsignal_t signalID, cO auto transDetails = check_and_cast(details); frameTransmissionFinished(packet, transDetails->getStatus()); } - else - Ieee80211MgmtApBase::receiveSignal(source, signalID, obj, details); } Ieee80211MgmtAp::AssociationResponseDisposition Ieee80211MgmtAp::getAssociationResponseDisposition(const Packet *responseFrame, @@ -131,35 +129,34 @@ void Ieee80211MgmtAp::frameTransmissionFinished(const Packet *responseFrame, Fra if (status == FRAME_TRANSMISSION_STATUS_ACKNOWLEDGED) { if (sta->second.pendingAssociationSuccessful) { - bool wasAssociated = mib->bssAccessPointData.stations[address] == Ieee80211Mib::ASSOCIATED; + bool wasAssociated = mib->getPeerAssociationStatus(address) == Ieee80211Mib::ASSOCIATED; + // An acknowledged replacement starts a new relationship, even for equal capabilities. + mib->removePeerHtCapabilities(address); mib->commitAssociationId(address); - mib->bssAccessPointData.stations[address] = Ieee80211Mib::ASSOCIATED; + mib->setPeerAssociationStatus(address, Ieee80211Mib::ASSOCIATED); if (sta->second.pendingHtStateAvailable) { // IEEE Std 802.11-2024, 11.3.5.3: association state becomes effective only after the successful response exchange. - if (sta->second.pendingHtCapabilitiesValid && mib->isHtOperationSupported()) { - const auto& currentOperation = mib->getHtOperation(); - if (supportsBasicHtMcsSet(sta->second.pendingHtCapabilities, currentOperation)) - mib->setPeerHtCapabilities(address, sta->second.pendingHtCapabilities, currentOperation); - else - mib->removePeerHtCapabilities(address); - } + if (sta->second.pendingHtCapabilitiesValid && mib->isLocalHtCapable()) + mib->setPeerHtCapabilities(address, sta->second.pendingHtCapabilities); else mib->removePeerHtCapabilities(address); } clearPendingAssociation(&sta->second); + mib->publishStateChange(); // Signal delivery is synchronous; observers must see committed // station/peer state and no pending response transaction. if (!wasAssociated) sendAssocNotification(address); } - else if (mib->bssAccessPointData.stations[address] == Ieee80211Mib::ASSOCIATED) { + else if (mib->getPeerAssociationStatus(address) == Ieee80211Mib::ASSOCIATED) { // This model does not implement negotiated management-frame protection. // IEEE Std 802.11-2024, 11.3.5.3(p) for association and 11.3.5.5(n) // for same-AP reassociation therefore require the existing association // state to be cleared after this acknowledged refusal. mib->releaseAssociationId(address); - mib->bssAccessPointData.stations[address] = Ieee80211Mib::AUTHENTICATED; + mib->setPeerAssociationStatus(address, Ieee80211Mib::AUTHENTICATED); clearPendingAssociation(&sta->second); + mib->publishStateChange(); // Signal delivery is synchronous; observers must see the complete // downgraded state and no pending response transaction. sendDisAssocNotification(address); @@ -216,7 +213,7 @@ void Ieee80211MgmtAp::sendBeacon() body->setBeaconInterval(beaconInterval); body->setChannelNumber(getDsssParameterSetChannel()); addHtCapabilities(body); - if (mib->isHtOperationSupported()) + if (mib->isLocalHtCapable()) setHtOperation(body, getHtOperationBand(), mib->getHtOperation()); body->setChunkLength(B(8 + 2 + 2 + (2 + ssid.length()) + (body->getChannelNumber() != -1 ? 3 : 0)) + getSupportedRateElementsLength(body) + getHtMgmtElementsLength(body)); sendManagementFrame("Beacon", body, ST_BEACON, MacAddress::BROADCAST_ADDRESS); @@ -234,7 +231,7 @@ void Ieee80211MgmtAp::handleAuthenticationFrame(Packet *packet, const PtrgetTransmitterAddress(); sta = &staList[staAddress]; // this implicitly creates a new entry sta->address = staAddress; - mib->bssAccessPointData.stations[staAddress] = Ieee80211Mib::NOT_AUTHENTICATED; + mib->setPeerAssociationStatus(staAddress, Ieee80211Mib::NOT_AUTHENTICATED); sta->authSeqExpected = 1; } // reset authentication status, when starting a new auth sequence @@ -250,10 +247,10 @@ void Ieee80211MgmtAp::handleAuthenticationFrame(Packet *packet, const PtrbssAccessPointData.stations[sta->address] == Ieee80211Mib::ASSOCIATED; + bool wasAssociated = mib->getPeerAssociationStatus(sta->address) == Ieee80211Mib::ASSOCIATED; if (wasAssociated) mib->releaseAssociationId(sta->address); - mib->bssAccessPointData.stations[sta->address] = Ieee80211Mib::NOT_AUTHENTICATED; + mib->setPeerAssociationStatus(sta->address, Ieee80211Mib::NOT_AUTHENTICATED); mib->removePeerHtCapabilities(sta->address); sta->authSeqExpected = 1; if (wasAssociated) @@ -289,10 +286,10 @@ void Ieee80211MgmtAp::handleAuthenticationFrame(Packet *packet, const PtrbssAccessPointData.stations[sta->address] == Ieee80211Mib::ASSOCIATED; + bool wasAssociated = mib->getPeerAssociationStatus(sta->address) == Ieee80211Mib::ASSOCIATED; if (wasAssociated) mib->releaseAssociationId(sta->address); - mib->bssAccessPointData.stations[sta->address] = Ieee80211Mib::AUTHENTICATED; // TODO only when ACK of this frame arrives + mib->setPeerAssociationStatus(sta->address, Ieee80211Mib::AUTHENTICATED); // TODO only when ACK of this frame arrives mib->removePeerHtCapabilities(sta->address); if (wasAssociated) sendDisAssocNotification(sta->address); @@ -313,11 +310,11 @@ void Ieee80211MgmtAp::handleDeauthenticationFrame(Packet *packet, const PtrbssAccessPointData.stations[sta->address] == Ieee80211Mib::ASSOCIATED; + bool wasAssociated = mib->getPeerAssociationStatus(sta->address) == Ieee80211Mib::ASSOCIATED; // mark STA as not authenticated; alternatively, it could also be removed from staList if (wasAssociated) mib->releaseAssociationId(sta->address); - mib->bssAccessPointData.stations[sta->address] = Ieee80211Mib::NOT_AUTHENTICATED; + mib->setPeerAssociationStatus(sta->address, Ieee80211Mib::NOT_AUTHENTICATED); sta->authSeqExpected = 1; mib->removePeerHtCapabilities(sta->address); if (wasAssociated) @@ -340,7 +337,7 @@ void Ieee80211MgmtAp::handleAssociationRequestFrame(Packet *packet, const PtrbssAccessPointData.stations[sta->address] == Ieee80211Mib::NOT_AUTHENTICATED) { + if (!sta || mib->getPeerAssociationStatus(sta->address) == Ieee80211Mib::NOT_AUTHENTICATED) { // STA not authenticated: send error and return const auto& body = makeShared(); body->setReasonCode(RC_NONAUTH_ASS_REQUEST); @@ -350,7 +347,7 @@ void Ieee80211MgmtAp::handleAssociationRequestFrame(Packet *packet, const PtrpeekData(); - bool pendingHtOperationValid = mib->isHtOperationSupported(); + bool pendingHtOperationValid = mib->isLocalHtCapable(); Ieee80211HtOperation pendingHtOperation; if (pendingHtOperationValid) pendingHtOperation = mib->getHtOperation(); @@ -420,7 +417,7 @@ void Ieee80211MgmtAp::handleReassociationRequestFrame(Packet *packet, const Ptr< } if (sta != nullptr) clearPendingAssociation(sta); - if (!sta || mib->bssAccessPointData.stations[sta->address] == Ieee80211Mib::NOT_AUTHENTICATED) { + if (!sta || mib->getPeerAssociationStatus(sta->address) == Ieee80211Mib::NOT_AUTHENTICATED) { // STA not authenticated: send error and return const auto& body = makeShared(); body->setReasonCode(RC_NONAUTH_ASS_REQUEST); @@ -430,7 +427,7 @@ void Ieee80211MgmtAp::handleReassociationRequestFrame(Packet *packet, const Ptr< } const auto& requestBody = packet->peekData(); - bool pendingHtOperationValid = mib->isHtOperationSupported(); + bool pendingHtOperationValid = mib->isLocalHtCapable(); Ieee80211HtOperation pendingHtOperation; if (pendingHtOperationValid) pendingHtOperation = mib->getHtOperation(); @@ -494,10 +491,10 @@ void Ieee80211MgmtAp::handleDisassociationFrame(Packet *packet, const PtrbssAccessPointData.stations[sta->address] == Ieee80211Mib::ASSOCIATED; + bool wasAssociated = mib->getPeerAssociationStatus(sta->address) == Ieee80211Mib::ASSOCIATED; if (wasAssociated) mib->releaseAssociationId(sta->address); - mib->bssAccessPointData.stations[sta->address] = Ieee80211Mib::AUTHENTICATED; + mib->setPeerAssociationStatus(sta->address, Ieee80211Mib::AUTHENTICATED); mib->removePeerHtCapabilities(sta->address); if (wasAssociated) sendDisAssocNotification(sta->address); @@ -530,7 +527,7 @@ void Ieee80211MgmtAp::handleProbeRequestFrame(Packet *packet, const PtrsetBeaconInterval(beaconInterval); body->setChannelNumber(getDsssParameterSetChannel()); addHtCapabilities(body); - if (mib->isHtOperationSupported()) + if (mib->isLocalHtCapable()) setHtOperation(body, getHtOperationBand(), mib->getHtOperation()); body->setChunkLength(B(8 + 2 + 2 + (2 + ssid.length()) + (body->getChannelNumber() != -1 ? 3 : 0)) + getSupportedRateElementsLength(body) + getHtMgmtElementsLength(body)); sendManagementFrame("ProbeResp", body, ST_PROBERESPONSE, staAddress); diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.ned b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.ned index 95397135f3b..97553d8ae4a 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.ned +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.ned @@ -8,6 +8,7 @@ package inet.linklayer.ieee80211.mgmt; import inet.common.SimpleModule; +import inet.linklayer.ieee80211.mgmt.contract.IIeee80211BssProvider; // // Used in 802.11 infrastructure mode in an access point (AP). @@ -20,7 +21,7 @@ import inet.common.SimpleModule; // This module relies on a connected ~Ieee80211Mac for actual // reception and transmission of frames. // -simple Ieee80211MgmtAp extends SimpleModule like IIeee80211Mgmt +simple Ieee80211MgmtAp extends SimpleModule like IIeee80211Mgmt, IIeee80211BssProvider { parameters: @class(Ieee80211MgmtAp); diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtApBase.cc b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtApBase.cc index 8472d749736..8a1f7ef1f1a 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtApBase.cc +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtApBase.cc @@ -34,20 +34,56 @@ void Ieee80211MgmtApBase::initialize(int stage) Ieee80211MgmtBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { mib->mode = Ieee80211Mib::INFRASTRUCTURE; - mib->bssStationData.stationType = Ieee80211Mib::ACCESS_POINT; - mib->bssData.ssid = par("ssid").stdstringValue(); + mib->configureBssRole(Ieee80211Mib::ACCESS_POINT, par("ssid").stdstringValue()); radio = getModuleFromPar(par("radioModule"), this); radio->subscribe(ieee80211RadioChannelChangedSignal, this); } - else if (stage == INITSTAGE_LINK_LAYER) - mib->bssData.bssid = mib->address; - else if (stage == INITSTAGE_LAST && mib->isHtOperationSupported()) { - mib->setPrimaryChannel(mib->requirePrimaryChannel(), getHtOperationBand()); - const auto& operation = mib->getHtOperation(); - if (operation.operatingChannelWidth == MHz(40) && - !getHtOperationBand()->isHt40OperationSupported(operation.primaryChannel, operation.secondaryChannelOffset)) - throw cRuntimeError("Invalid 40 MHz HT operation for band '%s', primary channel index %d, secondary channel offset %d", - getHtOperationBand()->getName(), operation.primaryChannel, operation.secondaryChannelOffset); + +} + +void Ieee80211MgmtApBase::prepareBss() +{ + Enter_Method("prepareBss"); + if (!isUp()) + throw cRuntimeError("Cannot prepare a BSS while AP management is down"); + prepareConfiguration(); + prepareLocalOperation(); +} + +void Ieee80211MgmtApBase::installSimplifiedPeer(const MacAddress& address, const Ieee80211HtCapabilities *capabilities) +{ + Enter_Method("installSimplifiedPeer"); + prepareBss(); + // This method is the explicit no-air association completion boundary. + mib->setPeerAssociationStatus(address, Ieee80211Mib::ASSOCIATED); + if (capabilities != nullptr && mib->isLocalHtCapable()) + mib->setPeerHtCapabilities(address, *capabilities); + else + mib->removePeerHtCapabilities(address); + mib->publishStateChange(); +} + +void Ieee80211MgmtApBase::removeSimplifiedPeer(const MacAddress& address) +{ + Enter_Method("removeSimplifiedPeer"); + mib->removePeerAssociation(address); + mib->publishStateChange(); +} + +void Ieee80211MgmtApBase::prepareLocalOperation() +{ + if (mib->isLocalHtCapable()) { + int channel = radioChannel; + if (channel < 0) + throw cRuntimeError("IEEE 802.11 primary channel is unavailable"); + const auto *band = getHtOperationBand(); + band->getStandardChannelNumber(channel); + auto operation = computeLocalHtOperation(channel, band); + mib->commitBss(mib->getBssData().ssid, mib->address, band, channel, &operation); + } + else { + mib->commitBss(mib->getBssData().ssid, mib->address, radioBand, + radioChannel, nullptr); } } @@ -59,14 +95,20 @@ void Ieee80211MgmtApBase::receiveSignal(cComponent *source, simsignal_t signalID EV << "Updating AP primary channel to " << value << ".\n"; const auto *channelDetails = dynamic_cast(details); const auto *band = channelDetails == nullptr ? nullptr : channelDetails->getBand(); - if (mib->isHtOperationSupported()) { + if (value < 0 || value > 255) + throw cRuntimeError("IEEE 802.11 primary channel must be in the range 0..255"); + if (mib->isLocalHtCapable()) { if (band == nullptr) throw cRuntimeError("HT Operation channel conversion requires radioChannelChanged with IEEE 802.11 band details"); - mib->setPrimaryChannel(value, band); + band->getStandardChannelNumber(value); } - else - mib->setPrimaryChannel(value); + // Physical context is retained while down; it does not activate a BSS. radioBand = band; + radioChannel = value; + if (mib->hasPreparedLocalCapabilities() && isUp()) { + prepareLocalOperation(); + mib->publishStateChange(); + } } } @@ -92,7 +134,7 @@ int Ieee80211MgmtApBase::getDsssParameterSetChannel() const return radioBand->getStandardChannelNumber(channelIndex); } catch (const cRuntimeError&) { - if (mib->isHtOperationSupported()) + if (mib->isLocalHtCapable()) throw; // Modeling simplification: nonstandard legacy bands can operate without // a standards channel mapping. Omit DSSS rather than invent a wire value. diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtApBase.h b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtApBase.h index 6fb1cdfc3e8..0ab191b7e35 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtApBase.h +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtApBase.h @@ -10,6 +10,7 @@ #include "inet/common/packet/Packet.h" #include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.h" +#include "inet/linklayer/ieee80211/mgmt/contract/IIeee80211BssProvider.h" namespace inet { @@ -27,10 +28,16 @@ namespace ieee80211 { * with utility functions that are useful for implementing AP functionality. * */ -class INET_API Ieee80211MgmtApBase : public Ieee80211MgmtBase +class INET_API Ieee80211MgmtApBase : public Ieee80211MgmtBase, public IIeee80211BssProvider { + public: + void prepareBss() override; + void installSimplifiedPeer(const MacAddress& address, const Ieee80211HtCapabilities *capabilities) override; + void removeSimplifiedPeer(const MacAddress& address) override; + protected: cModule *radio = nullptr; + int radioChannel = -1; const physicallayer::IIeee80211Band *radioBand = nullptr; // Immutable band observed via radioChannelChanged const physicallayer::IIeee80211Band *getHtOperationBand() const; @@ -38,6 +45,7 @@ class INET_API Ieee80211MgmtApBase : public Ieee80211MgmtBase virtual int numInitStages() const override { return NUM_INIT_STAGES; } virtual void initialize(int) override; + void prepareLocalOperation() override; using Ieee80211MgmtBase::receiveSignal; virtual void receiveSignal(cComponent *source, simsignal_t signalID, intval_t value, cObject *details) override; }; diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtApSimplified.ned b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtApSimplified.ned index c1926380be1..cf8b1f96940 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtApSimplified.ned +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtApSimplified.ned @@ -8,6 +8,7 @@ package inet.linklayer.ieee80211.mgmt; import inet.common.SimpleModule; +import inet.linklayer.ieee80211.mgmt.contract.IIeee80211BssProvider; // // Used in 802.11 infrastructure mode in an access point (AP). @@ -15,7 +16,7 @@ import inet.common.SimpleModule; // This management module variant does not send or expect to receive any // management frames -- it simply treats all stations as associated all the time. // -simple Ieee80211MgmtApSimplified extends SimpleModule like IIeee80211Mgmt +simple Ieee80211MgmtApSimplified extends SimpleModule like IIeee80211Mgmt, IIeee80211BssProvider { parameters: @class(Ieee80211MgmtApSimplified); diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.cc b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.cc index 3c3bc33903a..4ff5f53563e 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.cc +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.cc @@ -34,55 +34,94 @@ void Ieee80211MgmtBase::initialize(int stage) myIface = getContainingNicModule(this); numMgmtFramesReceived = 0; numMgmtFramesDropped = 0; - getContainingNicModule(this)->subscribe(modesetChangedSignal, this); + configurationProvider.reference(this, "macModule", true); WATCH(numMgmtFramesReceived); WATCH(numMgmtFramesDropped); } + else if (stage == INITSTAGE_LINK_LAYER) { + prepareConfiguration(); + if (isUp()) + prepareLocalOperation(); + mib->publishStateChange(); + } } -void Ieee80211MgmtBase::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) +void Ieee80211MgmtBase::prepareConfiguration() { - Enter_Method("%s", cComponent::getSignalName(signalID)); - - if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - supportedRates = Ieee80211SupportedRatesElement(); - extendedSupportedRates = Ieee80211ExtendedSupportedRatesElement(); - int rateIndex = 0; - int extendedRateIndex = 0; - // Supported Rates carries the legacy OperationalRateSet only. HT/VHT - // MCS support is advertised through the corresponding capabilities - // elements (IEEE Std 802.11-2024, 9.4.2.3, 9.4.2.54.4, 11.1.4.6). - for (const auto *mode : modeSet->getLegacyOperationalModes()) { - bool isBasicRate = modeSet->getIsMandatory(mode); - double rate = mode->getDataMode()->getNetBitrate().get(); - if (rateIndex < 8) { - supportedRates.rate[rateIndex] = rate; - supportedRates.basicRate[rateIndex] = isBasicRate; - rateIndex++; - } - else if (extendedRateIndex < 255) { - extendedSupportedRates.rate[extendedRateIndex] = rate; - extendedSupportedRates.basicRate[extendedRateIndex] = isBasicRate; - extendedRateIndex++; - } - else - throw cRuntimeError("Mode set '%s' contains more than 263 legacy operational rates", modeSet->getName()); + if (configurationPrepared) + return; + configurationProvider->prepareLocalCapabilities(); + modeSet = configurationProvider->getConfiguredModeSet(); + if (modeSet == nullptr) + throw cRuntimeError("Configured IEEE 802.11 mode catalog is unavailable"); + configurationPrepared = true; + supportedRates = Ieee80211SupportedRatesElement(); + extendedSupportedRates = Ieee80211ExtendedSupportedRatesElement(); + int rateIndex = 0; + int extendedRateIndex = 0; + // Supported Rates carries the legacy OperationalRateSet only. HT/VHT + // MCS support is advertised through the corresponding capabilities + // elements (IEEE Std 802.11-2024, 9.4.2.3, 9.4.2.54.4, 11.1.4.6). + for (const auto *mode : modeSet->getLegacyOperationalModes()) { + bool isBasicRate = modeSet->getIsMandatory(mode); + double rate = mode->getDataMode()->getNetBitrate().get(); + if (rateIndex < 8) { + supportedRates.rate[rateIndex] = rate; + supportedRates.basicRate[rateIndex] = isBasicRate; + rateIndex++; + } + else if (extendedRateIndex < 255) { + extendedSupportedRates.rate[extendedRateIndex] = rate; + extendedSupportedRates.basicRate[extendedRateIndex] = isBasicRate; + extendedRateIndex++; } - supportedRates.numRates = rateIndex; - extendedSupportedRates.numRates = extendedRateIndex; + else + throw cRuntimeError("Mode set '%s' contains more than 263 legacy operational rates", modeSet->getName()); + } + supportedRates.numRates = rateIndex; + extendedSupportedRates.numRates = extendedRateIndex; +} + +Ieee80211HtOperation Ieee80211MgmtBase::computeLocalHtOperation(int primaryChannel, const IIeee80211Band *band) const +{ + Ieee80211HtOperation operation; + operation.primaryChannel = primaryChannel; + int offset = mib->par("htSecondaryChannelOffset"); + if (offset != 0 && offset != 1 && offset != 3) + throw cRuntimeError("htSecondaryChannelOffset must be 0, 1, or 3"); + if (offset != 0 && mib->getLocalHtCapabilities().supportedChannelWidths.count(MHz(40)) == 0) + throw cRuntimeError("40 MHz HT operation requires a configured PHY that can operate a 40 MHz channel width"); + if (offset != 0 && band != nullptr && !band->isHt40OperationSupported(primaryChannel, offset)) { + EV_WARN << "Configured 40 MHz HT operation is unsupported on primary channel " << primaryChannel + << " in band '" << band->getName() << "'; falling back to 20 MHz BSS operation.\n"; + offset = 0; } + operation.secondaryChannelOffset = offset; + operation.operatingChannelWidth = offset != 0 ? MHz(40) : MHz(20); + int protection = mib->par("htProtectionMode"); + if (protection < 0 || protection > 3) + throw cRuntimeError("htProtectionMode must be between 0 and 3"); + operation.protectionMode = static_cast(protection); + const auto& mandatory = modeSet->getHtMcsMandatory(); + for (int mcs = 0; mcs < 77; mcs++) + operation.basicMcsSupported[mcs] = mandatory[mcs] && mib->getLocalHtCapabilities().rxMcsSupported[mcs]; + return operation; +} + +void Ieee80211MgmtBase::prepareLocalOperation() +{ + } void Ieee80211MgmtBase::addHtCapabilities(const Ptr& frame) const { - if (mib->isHtOperationSupported()) - setHtCapabilities(frame, mib->localHtCapabilities); + if (mib->isLocalHtCapable()) + setHtCapabilities(frame, mib->getLocalHtCapabilities()); } void Ieee80211MgmtBase::addHtOperation(const Ptr& frame, const physicallayer::IIeee80211Band *band) const { - if (mib->isHtOperationSupported()) + if (mib->isLocalHtCapable()) setHtOperation(frame, band, mib->getHtOperation()); } @@ -111,6 +150,7 @@ void Ieee80211MgmtBase::handleMessageWhenUp(cMessage *msg) } else throw cRuntimeError("Unknown message"); + mib->publishStateChange(); } void Ieee80211MgmtBase::sendDown(Packet *frame) @@ -187,11 +227,14 @@ void Ieee80211MgmtBase::processFrame(Packet *packet, const PtrclearPeerHtCapabilities(); + mib->clearBss(); + mib->publishStateChange(); } } // namespace ieee80211 diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.h b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.h index 145da89e902..9527212cd75 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.h +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.h @@ -14,6 +14,7 @@ #include "inet/common/packet/Packet.h" #include "inet/linklayer/common/MacAddress.h" #include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" +#include "inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.h" #include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame_m.h" #include "inet/linklayer/ieee80211/mib/Ieee80211Mib.h" #include "inet/networklayer/contract/IInterfaceTable.h" @@ -35,7 +36,9 @@ class INET_API Ieee80211MgmtBase : public OperationalBase, public cListener ModuleRefByPar mib; ModuleRefByPar interfaceTable; NetworkInterface *myIface = nullptr; - physicallayer::Ieee80211ModeSet *modeSet = nullptr; + ModuleRefByPar configurationProvider; + const physicallayer::Ieee80211ModeSet *modeSet = nullptr; + bool configurationPrepared = false; Ieee80211SupportedRatesElement supportedRates; Ieee80211ExtendedSupportedRatesElement extendedSupportedRates; @@ -46,7 +49,7 @@ class INET_API Ieee80211MgmtBase : public OperationalBase, public cListener protected: virtual int numInitStages() const override { return NUM_INIT_STAGES; } virtual void initialize(int) override; - virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) override; + void prepareConfiguration(); /** Dispatches incoming messages to handleTimer(), handleUpperMessage() or processFrame(). */ virtual void handleMessageWhenUp(cMessage *msg) override; @@ -83,6 +86,9 @@ class INET_API Ieee80211MgmtBase : public OperationalBase, public cListener } /** Adds the local HT advertisement to a frame when the authoritative PHY profile supports HT operation. */ + Ieee80211HtOperation computeLocalHtOperation(int primaryChannel, const physicallayer::IIeee80211Band *band) const; + virtual void prepareLocalOperation(); + virtual void addHtCapabilities(const Ptr& frame) const; virtual void addHtOperation(const Ptr& frame, const physicallayer::IIeee80211Band *band) const; @@ -109,7 +115,7 @@ class INET_API Ieee80211MgmtBase : public OperationalBase, public cListener virtual bool isModuleStartStage(int stage) const override { return stage == ModuleStartOperation::STAGE_PHYSICAL_LAYER; } virtual bool isModuleStopStage(int stage) const override { return stage == ModuleStopOperation::STAGE_PHYSICAL_LAYER; } - virtual void handleStartOperation(LifecycleOperation *operation) override { start(); } + void handleStartOperation(LifecycleOperation *operation) override { start(); mib->publishStateChange(); } virtual void handleStopOperation(LifecycleOperation *operation) override { stop(); } virtual void handleCrashOperation(LifecycleOperation *operation) override { stop(); } diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.cc b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.cc index 859a5c108ea..afa0c3221f5 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.cc +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.cc @@ -152,8 +152,8 @@ void Ieee80211MgmtSta::initialize(int stage) if (stage == INITSTAGE_LOCAL) { mib->mode = Ieee80211Mib::INFRASTRUCTURE; - mib->bssStationData.stationType = Ieee80211Mib::STATION; - mib->bssStationData.isAssociated = false; + mib->configureBssRole(Ieee80211Mib::STATION); + mib->setAssociated(false); isScanning = false; assocTimeoutMsg = nullptr; @@ -352,7 +352,7 @@ void Ieee80211MgmtSta::startAuthentication(ApInfo *ap, simtime_t timeout) void Ieee80211MgmtSta::startAssociation(ApInfo *ap, simtime_t timeout) { - if (mib->bssStationData.isAssociated || assocTimeoutMsg) + if (mib->getBssStationData().isAssociated || assocTimeoutMsg) throw cRuntimeError("startAssociation: already associated or association currently in progress"); if (!ap->isAuthenticated) throw cRuntimeError("startAssociation: not yet authenticated with AP address='%s'", ap->address.str().c_str()); @@ -378,7 +378,7 @@ void Ieee80211MgmtSta::startAssociation(ApInfo *ap, simtime_t timeout) void Ieee80211MgmtSta::startReassociation(ApInfo *ap, simtime_t timeout) { - if (!mib->bssStationData.isAssociated || assocTimeoutMsg) + if (!mib->getBssStationData().isAssociated || assocTimeoutMsg) throw cRuntimeError("startReassociation: not associated or association currently in progress"); if (!ap->isAuthenticated) throw cRuntimeError("startReassociation: not authenticated with AP address='%s'", ap->address.str().c_str()); @@ -416,7 +416,7 @@ void Ieee80211MgmtSta::processScanCommand(Ieee80211Prim_ScanRequest *ctrl) if (isScanning) throw cRuntimeError("processScanCommand: scanning already in progress"); - if (mib->bssStationData.isAssociated) + if (mib->getBssStationData().isAssociated) disassociate(); // clear existing AP list (and cancel any pending authentications) -- we want to start with a clean page @@ -532,7 +532,7 @@ void Ieee80211MgmtSta::processDeauthenticateCommand(Ieee80211Prim_Deauthenticate if (!ap) throw cRuntimeError("processDeauthenticateCommand: AP not known: address = %s", address.str().c_str()); - if (mib->bssStationData.isAssociated && assocAP.address == address) + if (mib->getBssStationData().isAssociated && assocAP.address == address) disassociate(); else if (assocTimeoutMsg && assocTimeoutMsg->getContextPointer() == ap) cancelPendingAssociation(); @@ -570,7 +570,7 @@ void Ieee80211MgmtSta::processAssociateCommand(Ieee80211Prim_AssociateRequest *c void Ieee80211MgmtSta::processReassociateCommand(Ieee80211Prim_ReassociateRequest *ctrl) { const MacAddress& address = ctrl->getAddress(); - if (!mib->bssStationData.isAssociated) { + if (!mib->getBssStationData().isAssociated) { auto confirm = new Ieee80211Prim_ReassociateConfirm(); confirm->setAddress(address); sendConfirm(confirm, PRC_REFUSED); @@ -592,7 +592,7 @@ void Ieee80211MgmtSta::processDisassociateCommand(Ieee80211Prim_DisassociateRequ { const MacAddress& address = ctrl->getAddress(); - if (mib->bssStationData.isAssociated && address == assocAP.address) { + if (mib->getBssStationData().isAssociated && address == assocAP.address) { disassociate(); } else if (assocTimeoutMsg) { @@ -613,24 +613,26 @@ void Ieee80211MgmtSta::processDisassociateCommand(Ieee80211Prim_DisassociateRequ void Ieee80211MgmtSta::disassociate() { EV << "Disassociating from AP address=" << assocAP.address << "\n"; - ASSERT(mib->bssStationData.isAssociated); + ASSERT(mib->getBssStationData().isAssociated); cancelPendingAssociation(); clearCurrentAssociation(); } void Ieee80211MgmtSta::clearCurrentAssociation() { - ASSERT(mib->bssStationData.isAssociated); - mib->bssStationData.isAssociated = false; + ASSERT(mib->getBssStationData().isAssociated); + mib->setAssociated(false); mib->removePeerHtCapabilities(assocAP.address); cancelAndDelete(assocAP.beaconTimeoutMsg); assocAP.beaconTimeoutMsg = nullptr; assocAP = AssociatedApInfo(); // clear it + mib->clearBss(); + mib->publishStateChange(); } bool Ieee80211MgmtSta::terminateCurrentAssociationFromPeer(const MacAddress& address) { - if (!mib->bssStationData.isAssociated || address != assocAP.address) + if (!mib->getBssStationData().isAssociated || address != assocAP.address) return false; // Keep a stable AP-list object for the primitive confirmation while the @@ -665,7 +667,7 @@ void Ieee80211MgmtSta::stop() clearAPList(); - if (mib->bssStationData.isAssociated) + if (mib->getBssStationData().isAssociated) clearCurrentAssociation(); else { cancelAndDelete(assocAP.beaconTimeoutMsg); @@ -793,7 +795,7 @@ void Ieee80211MgmtSta::handleDeauthenticationFrame(Packet *packet, const Ptr(assocTimeoutMsg->getContextPointer()) : nullptr; bool isPendingAp = pendingAp != nullptr && pendingAp->address == address; - bool isCurrentAp = mib->bssStationData.isAssociated && address == assocAP.address; + bool isCurrentAp = mib->getBssStationData().isAssociated && address == assocAP.address; // IEEE Std 802.11-2024, 11.3.4.5: deauthentication from the current AP // returns the STA to State 1. Do this before consulting the discovery @@ -922,15 +924,15 @@ void Ieee80211MgmtSta::processAssociationResponse(Packet *packet, const PtrbssStationData.isAssociated || assocAP.address != ap->address) + else if (!mib->getBssStationData().isAssociated || assocAP.address != ap->address) mib->removePeerHtCapabilities(ap->address); } else { EV << "Association successful, AP address=" << ap->address << "\n"; - if (mib->bssStationData.isAssociated) { + if (mib->getBssStationData().isAssociated) { EV << "Breaking existing association with AP address=" << assocAP.address << "\n"; - mib->bssStationData.isAssociated = false; + mib->setAssociated(false); mib->removePeerHtCapabilities(assocAP.address); cancelAndDelete(assocAP.beaconTimeoutMsg); assocAP.beaconTimeoutMsg = nullptr; @@ -938,12 +940,15 @@ void Ieee80211MgmtSta::processAssociationResponse(Packet *packet, const PtrbssData.ssid = ap->ssid; - mib->bssData.bssid = ap->address; - mib->bssStationData.isAssociated = true; + mib->commitBss(ap->ssid, ap->address, band != nullptr ? band : ap->band, + responseHtStatus == HtAssociationResponseStatus::VALID_HT ? responseHtOperation.primaryChannel : ap->channel, + responseHtStatus == HtAssociationResponseStatus::VALID_HT ? &responseHtOperation : nullptr); + mib->setAssociated(true); (ApInfo&)assocAP = (*ap); + assocAP.beaconTimeoutMsg = new cMessage("beaconTimeout", MK_BEACON_TIMEOUT); + scheduleAfter(MAX_BEACONS_MISSED * assocAP.beaconInterval, assocAP.beaconTimeoutMsg); if (responseHtStatus == HtAssociationResponseStatus::VALID_HT) - mib->setPeerHtCapabilities(ap->address, responseHtCapabilities, responseHtOperation); + mib->setPeerHtCapabilities(ap->address, responseHtCapabilities); else { mib->removePeerHtCapabilities(ap->address); if (responseHtStatus == HtAssociationResponseStatus::INVALID_HT) { @@ -958,10 +963,8 @@ void Ieee80211MgmtSta::processAssociationResponse(Packet *packet, const PtrpublishStateChange(); emit(l2AssociatedSignal, myIface, ap); - - assocAP.beaconTimeoutMsg = new cMessage("beaconTimeout", MK_BEACON_TIMEOUT); - scheduleAfter(MAX_BEACONS_MISSED * assocAP.beaconInterval, assocAP.beaconTimeoutMsg); } // report back to agent @@ -983,7 +986,7 @@ Ieee80211MgmtSta::HtAssociationResponseStatus Ieee80211MgmtSta::classifyAssociat // A non-HT station deliberately ignores HT elements. This preserves the // genuine legacy association path even when an HT-capable AP includes its // normal response elements. - if (!mib->isHtOperationSupported()) + if (!mib->isLocalHtCapable()) return HtAssociationResponseStatus::LEGACY; if (!responseHtCapabilitiesPresent && !responseHtOperationPresent) return HtAssociationResponseStatus::LEGACY; @@ -1016,8 +1019,8 @@ Ieee80211MgmtSta::HtAssociationResponseStatus Ieee80211MgmtSta::classifyAssociat // Reassociation Responses. Use the selected BSS's Beacon/Probe Response // advertisement instead of interpreting those reserved response bits. responseHtOperation.basicMcsSupported = selectedBssHtOperation->basicMcsSupported; - auto negotiated = negotiateHtCapabilities(mib->localHtCapabilities, responseHtCapabilities, responseHtOperation); - if (!supportsBasicHtMcsSet(mib->localHtCapabilities, responseHtOperation) || + auto negotiated = negotiateHtCapabilities(mib->getLocalHtCapabilities(), responseHtCapabilities); + if (!supportsBasicHtMcsSet(mib->getLocalHtCapabilities(), responseHtOperation) || !negotiated.localTxPeerRx.valid || !negotiated.localRxPeerTx.valid) { reason = "HT capabilities and operation have no bidirectionally usable common mode"; return HtAssociationResponseStatus::INVALID_HT; @@ -1027,7 +1030,7 @@ Ieee80211MgmtSta::HtAssociationResponseStatus Ieee80211MgmtSta::classifyAssociat bool Ieee80211MgmtSta::isHtBssSupported(const ApInfo *ap, std::string& reason) const { - if (!mib->isHtOperationSupported()) + if (!mib->isLocalHtCapable()) return true; if (!ap->htCapabilitiesPresent && !ap->htOperationPresent) return true; @@ -1035,8 +1038,8 @@ bool Ieee80211MgmtSta::isHtBssSupported(const ApInfo *ap, std::string& reason) c reason = "selected BSS contains only one of the HT Capabilities and HT Operation elements"; return false; } - auto negotiated = negotiateHtCapabilities(mib->localHtCapabilities, ap->htCapabilities, ap->htOperation); - if (!supportsBasicHtMcsSet(mib->localHtCapabilities, ap->htOperation) || + auto negotiated = negotiateHtCapabilities(mib->getLocalHtCapabilities(), ap->htCapabilities); + if (!supportsBasicHtMcsSet(mib->getLocalHtCapabilities(), ap->htOperation) || !negotiated.localTxPeerRx.valid || !negotiated.localRxPeerTx.valid) { reason = "selected BSS HT advertisement has no bidirectionally usable common mode"; return false; @@ -1058,11 +1061,11 @@ void Ieee80211MgmtSta::handleReassociationFailure(ApInfo *ap) { // IEEE Std 802.11-2024, 11.3.5.4(f): failed or timed-out reassociation // disassociates the STA only when the target is its current AP. - if (shouldDisassociateOnReassociationFailure(mib->bssStationData.isAssociated, assocAP.address, ap->address)) + if (shouldDisassociateOnReassociationFailure(mib->getBssStationData().isAssociated, assocAP.address, ap->address)) disassociate(); else { mib->removePeerHtCapabilities(ap->address); - if (mib->bssStationData.isAssociated) + if (mib->getBssStationData().isAssociated) changeChannel(assocAP.channel); } } @@ -1107,7 +1110,7 @@ void Ieee80211MgmtSta::handleBeaconFrame(Packet *packet, const PtrbssStationData.isAssociated && header->getTransmitterAddress() == assocAP.address) { + if (accepted && mib->getBssStationData().isAssociated && header->getTransmitterAddress() == assocAP.address) { EV << "Beacon is from associated AP, restarting beacon timeout timer\n"; ASSERT(assocAP.beaconTimeoutMsg != nullptr); rescheduleAfter(MAX_BEACONS_MISSED * assocAP.beaconInterval, assocAP.beaconTimeoutMsg); @@ -1116,6 +1119,7 @@ void Ieee80211MgmtSta::handleBeaconFrame(Packet *packet, const PtrpublishStateChange(); delete packet; } @@ -1154,10 +1158,11 @@ bool Ieee80211MgmtSta::storeAPInfo(Packet *packet, const PtrgetExtendedSupportedRates(); bool htCapabilitiesPresent = body->getHtCapabilitiesPresent(); bool htOperationPresent = body->getHtOperationPresent(); - bool ignoreHt = mib != nullptr && !mib->isHtOperationSupported(); + bool ignoreHt = mib != nullptr && !mib->isLocalHtCapable(); std::string reason; const auto& channelInd = packet->findTag(); const auto *receivedChannel = channelInd != nullptr ? channelInd->getChannel() : nullptr; + candidate.band = receivedChannel != nullptr ? receivedChannel->getBand() : nullptr; // IEEE Std 802.11-2024, 9.4.2.4: Current Channel is a standard // channel number. Without this optional element, use the receive channel. int legacyChannel = receivedChannel != nullptr ? receivedChannel->getChannelNumber() : -1; @@ -1235,6 +1240,7 @@ bool Ieee80211MgmtSta::storeAPInfo(Packet *packet, const Ptrchannel = candidate.channel; + ap->band = candidate.band; ap->address = candidate.address; ap->ssid = candidate.ssid; ap->supportedRates = candidate.supportedRates; @@ -1249,23 +1255,17 @@ bool Ieee80211MgmtSta::storeAPInfo(Packet *packet, const PtrisAuthenticated = candidate.isAuthenticated; ap->authSeqExpected = candidate.authSeqExpected; ap->authTimeoutMsg = candidate.authTimeoutMsg; - bool currentAssociatedAp = mib != nullptr && mib->bssStationData.isAssociated && address == assocAP.address; + bool currentAssociatedAp = mib != nullptr && mib->getBssStationData().isAssociated && address == assocAP.address; bool isBeacon = header->getType() == ST_BEACON || (header->getType() != ST_PROBERESPONSE && dynamicPtrCast(body) == nullptr); if (currentAssociatedAp && isBeacon) { (ApInfo&)assocAP = *ap; - mib->bssData.ssid = ap->ssid; - if (mib->isHtOperationSupported() && candidate.htCapabilitiesPresent && candidate.htOperationPresent) { - auto negotiated = negotiateHtCapabilities(mib->localHtCapabilities, candidate.htCapabilities, candidate.htOperation); - if (supportsBasicHtMcsSet(mib->localHtCapabilities, candidate.htOperation) && - negotiated.localTxPeerRx.valid && negotiated.localRxPeerTx.valid) { - EV_INFO << "Refreshing authoritative HT state for associated AP address=" << address << "\n"; - mib->setPeerHtCapabilities(address, candidate.htCapabilities, candidate.htOperation); - } - else { - EV_WARN << "Beacon from associated AP has unusable HT advertisement: removing peer HT state for AP address=" << address << "\n"; - mib->removePeerHtCapabilities(address); - } + mib->commitBss(ap->ssid, ap->address, ap->band, ap->channel, + candidate.htOperationPresent ? &candidate.htOperation : nullptr); + if (mib->isLocalHtCapable() && candidate.htCapabilitiesPresent && candidate.htOperationPresent) { + mib->setPeerHtCapabilities(address, candidate.htCapabilities); + if (!mib->relationshipAllowsHt(address)) + EV_WARN << "Beacon from associated AP has unusable HT operation; retaining capability knowledge.\n"; } else { EV_INFO << "Beacon from associated AP has no usable HT advertisement or STA is legacy: removing peer HT state for AP address=" << address << "\n"; diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.h b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.h index 326c7638644..a2c4c6ce47a 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.h +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.h @@ -72,6 +72,7 @@ class INET_API Ieee80211MgmtSta : public Ieee80211MgmtBase // struct ApInfo : public cObject { int channel; // internal zero-based radio channel index + const physicallayer::IIeee80211Band *band = nullptr; MacAddress address; // alias bssid std::string ssid; Ieee80211SupportedRatesElement supportedRates; diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtStaSimplified.cc b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtStaSimplified.cc index 614ea26bb49..3a7b775e0f1 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtStaSimplified.cc +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtStaSimplified.cc @@ -8,6 +8,7 @@ #include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtStaSimplified.h" #include "inet/networklayer/common/L3AddressResolver.h" +#include "inet/linklayer/ieee80211/mgmt/contract/IIeee80211BssProvider.h" namespace inet { @@ -47,14 +48,12 @@ void Ieee80211MgmtStaSimplified::initialize(int stage) Ieee80211MgmtBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { mib->mode = Ieee80211Mib::INFRASTRUCTURE; - mib->bssStationData.stationType = Ieee80211Mib::STATION; - mib->bssStationData.isAssociated = true; + mib->configureBssRole(Ieee80211Mib::STATION); } else if (stage == INITSTAGE_LINK_LAYER) { - configureAssociation(); + if (isUp()) + configureAssociation(); } - else if (stage == INITSTAGE_LAST) - configureAssociation(); } void Ieee80211MgmtStaSimplified::handleStartOperation(LifecycleOperation *operation) @@ -68,26 +67,31 @@ void Ieee80211MgmtStaSimplified::configureAssociation() { L3AddressResolver addressResolver; auto accessPointAddress = addressResolver.resolve(par("accessPointAddress"), L3AddressResolver::ADDR_MAC).toMac(); - mib->bssData.bssid = accessPointAddress; auto apMib = findAccessPointMib(accessPointAddress); - apMib->bssAccessPointData.stations[mib->address] = Ieee80211Mib::ASSOCIATED; - mib->bssData.ssid = apMib->bssData.ssid; - mib->bssStationData.isAssociated = true; + auto *apManagement = check_and_cast(apMib->getParentModule()->getSubmodule("mgmt")); + apManagement->prepareBss(); + mib->commitBss(apMib->getBssData().ssid, accessPointAddress, apMib->getOperationBand(), + apMib->hasPrimaryChannel() ? apMib->requirePrimaryChannel() : -1, + mib->isLocalHtCapable() && apMib->hasHtOperation() ? &apMib->getHtOperation() : nullptr); + mib->setAssociated(true); // Simplified management is an explicit no-air abstraction: install the state that the // Association Request/Response exchange would have committed in detailed management. - if (mib->isHtOperationSupported() && apMib->isHtOperationSupported()) { - mib->setPeerHtCapabilities(apMib->address, apMib->localHtCapabilities, apMib->getHtOperation()); - apMib->setPeerHtCapabilities(mib->address, mib->localHtCapabilities, apMib->getHtOperation()); + if (mib->isLocalHtCapable() && apMib->isLocalHtCapable()) { + mib->setPeerHtCapabilities(apMib->address, apMib->getLocalHtCapabilities()); + } + apManagement->installSimplifiedPeer(mib->address, mib->isLocalHtCapable() ? &mib->getLocalHtCapabilities() : nullptr); + mib->publishStateChange(); } void Ieee80211MgmtStaSimplified::stop() { - mib->bssStationData.isAssociated = false; - auto apMib = findAccessPointMib(mib->bssData.bssid, false); + auto accessPointAddress = mib->getBssData().bssid; + mib->clearBss(); + auto apMib = findAccessPointMib(accessPointAddress, false); if (apMib != nullptr) { - apMib->bssAccessPointData.stations.erase(mib->address); - apMib->removePeerHtCapabilities(mib->address); + auto *apManagement = check_and_cast(apMib->getParentModule()->getSubmodule("mgmt")); + apManagement->removeSimplifiedPeer(mib->address); } Ieee80211MgmtBase::stop(); } diff --git a/src/inet/linklayer/ieee80211/mgmt/contract/IIeee80211BssProvider.h b/src/inet/linklayer/ieee80211/mgmt/contract/IIeee80211BssProvider.h new file mode 100644 index 00000000000..8ff9719e626 --- /dev/null +++ b/src/inet/linklayer/ieee80211/mgmt/contract/IIeee80211BssProvider.h @@ -0,0 +1,23 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + + +#ifndef INET_IIEEE80211BSSPROVIDER_H +#define INET_IIEEE80211BSSPROVIDER_H +#include "inet/linklayer/common/MacAddress.h" +#include "inet/linklayer/ieee80211/mib/Ieee80211HtCapabilities.h" +namespace inet::ieee80211 { +/** AP-owned preparation and relationship transitions for the simplified no-air model. */ +class INET_API IIeee80211BssProvider +{ + public: + virtual ~IIeee80211BssProvider() = default; + virtual void prepareBss() = 0; + virtual void installSimplifiedPeer(const MacAddress& address, const Ieee80211HtCapabilities *capabilities) = 0; + virtual void removeSimplifiedPeer(const MacAddress& address) = 0; +}; +} // namespace inet::ieee80211 +#endif diff --git a/src/inet/linklayer/ieee80211/mgmt/contract/IIeee80211BssProvider.ned b/src/inet/linklayer/ieee80211/mgmt/contract/IIeee80211BssProvider.ned new file mode 100644 index 00000000000..b35d80814d9 --- /dev/null +++ b/src/inet/linklayer/ieee80211/mgmt/contract/IIeee80211BssProvider.ned @@ -0,0 +1,12 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + + +package inet.linklayer.ieee80211.mgmt.contract; +// AP-owned preparation and relationship transitions for simplified management. +moduleinterface IIeee80211BssProvider +{ +} diff --git a/src/inet/linklayer/ieee80211/mib/Ieee80211HtCapabilities.h b/src/inet/linklayer/ieee80211/mib/Ieee80211HtCapabilities.h index 2174ce5c387..5d58918bc85 100644 --- a/src/inet/linklayer/ieee80211/mib/Ieee80211HtCapabilities.h +++ b/src/inet/linklayer/ieee80211/mib/Ieee80211HtCapabilities.h @@ -53,6 +53,15 @@ struct Ieee80211HtCapabilities bool shortGi20 = false; bool shortGi40 = false; int maxAmpduLengthExponent = 0; + + bool operator==(const Ieee80211HtCapabilities& other) const + { + return supportedChannelWidths == other.supportedChannelWidths && rxMcsSupported == other.rxMcsSupported && + txMcsSetDefined == other.txMcsSetDefined && txRxMcsSetNotEqual == other.txRxMcsSetNotEqual && + txMaxNss == other.txMaxNss && txUnequalModulation == other.txUnequalModulation && + txMcsNss.maxMcsPerNss == other.txMcsNss.maxMcsPerNss && ldpc == other.ldpc && greenfield == other.greenfield && + shortGi20 == other.shortGi20 && shortGi40 == other.shortGi40 && maxAmpduLengthExponent == other.maxAmpduLengthExponent; + } }; /** Model-backed subset of the HT Operation element (IEEE Std 802.11-2024, 9.4.2.55). */ @@ -63,6 +72,13 @@ struct Ieee80211HtOperation int secondaryChannelOffset = 0; Ieee80211HtProtectionMode protectionMode = Ieee80211HtProtectionMode::NO_PROTECTION; std::array basicMcsSupported = {}; + + bool operator==(const Ieee80211HtOperation& other) const + { + return operatingChannelWidth == other.operatingChannelWidth && primaryChannel == other.primaryChannel && + secondaryChannelOffset == other.secondaryChannelOffset && protectionMode == other.protectionMode && + basicMcsSupported == other.basicMcsSupported; + } }; struct Ieee80211HtDirectionalCapabilities @@ -83,16 +99,14 @@ struct Ieee80211NegotiatedHtCapabilities Ieee80211HtCapabilities peerAdvertisement; Ieee80211HtDirectionalCapabilities localTxPeerRx; Ieee80211HtDirectionalCapabilities localRxPeerTx; - Ieee80211HtOperation operation; }; inline Ieee80211NegotiatedHtCapabilities negotiateHtCapabilities(const Ieee80211HtCapabilities& local, - const Ieee80211HtCapabilities& peer, const Ieee80211HtOperation& operation) + const Ieee80211HtCapabilities& peer) { Ieee80211NegotiatedHtCapabilities negotiated; negotiated.localAdvertisement = local; negotiated.peerAdvertisement = peer; - negotiated.operation = operation; for (const auto& width : local.supportedChannelWidths) if (peer.supportedChannelWidths.count(width)) { negotiated.localTxPeerRx.supportedChannelWidths.insert(width); diff --git a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.cc b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.cc index e5c8fd008a3..bb147edcd19 100644 --- a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.cc +++ b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.cc @@ -18,16 +18,19 @@ namespace ieee80211 { Define_Module(Ieee80211Mib); +simsignal_t Ieee80211Mib::bssStateChangedSignal = cComponent::registerSignal("bssStateChanged"); + void Ieee80211Mib::initialize(int stage) { if (stage == INITSTAGE_LOCAL) { - configuredSecondaryChannelOffset = par("htSecondaryChannelOffset"); WATCH(address); WATCH(mode); WATCH(qos); WATCH(localHtCapabilitiesValid); - WATCH(configuredSecondaryChannelOffset); + WATCH(localCapabilitiesPrepared); WATCH(primaryChannelAvailable); + WATCH(bssActive); + WATCH(htOperationPresent); WATCH(bssData.bssid); WATCH(bssStationData.stationType); WATCH(bssStationData.isAssociated); @@ -44,183 +47,182 @@ void Ieee80211Mib::initialize(int stage) } } -int Ieee80211Mib::requirePrimaryChannel() const +void Ieee80211Mib::checkStateMutation() const { - if (!primaryChannelAvailable) - throw cRuntimeError("IEEE 802.11 primary channel is unavailable"); - return htOperation.primaryChannel; + if (publishingStateChange) + throw cRuntimeError("Cannot mutate IEEE 802.11 state during bssStateChanged notification"); } -void Ieee80211Mib::setPrimaryChannel(int primaryChannel) +void Ieee80211Mib::commitBss(const std::string& ssid, const MacAddress& bssid, const physicallayer::IIeee80211Band *band, + int channel, const Ieee80211HtOperation *operation) { - setPrimaryChannel(primaryChannel, nullptr); + checkStateMutation(); + if (channel < -1 || channel > 255 || (operation != nullptr && (channel < 0 || operation->primaryChannel != channel))) + throw cRuntimeError("Inconsistent IEEE 802.11 BSS channel snapshot"); + if (operation != nullptr && band != nullptr) + band->getStandardChannelNumber(channel); + bool changed = !bssActive || bssData.ssid != ssid || bssData.bssid != bssid || operationBand != band || + primaryChannelAvailable != (channel >= 0) || (channel >= 0 && htOperation.primaryChannel != channel) || + htOperationPresent != (operation != nullptr) || (operation != nullptr && !(htOperation == *operation)); + bssData.ssid = ssid; + bssData.bssid = bssid; + bssActive = true; + operationBand = band; + primaryChannelAvailable = channel >= 0; + htOperationPresent = operation != nullptr; + htOperation = operation != nullptr ? *operation : Ieee80211HtOperation(); + htOperation.primaryChannel = channel; + stateChangePending |= changed; } -void Ieee80211Mib::setPrimaryChannel(int primaryChannel, const physicallayer::IIeee80211Band *band) +void Ieee80211Mib::clearBss() { - if (primaryChannel < 0 || primaryChannel > 255) - throw cRuntimeError("IEEE 802.11 primary channel must be in the range 0..255, not %d", primaryChannel); - - if (band != nullptr) { - try { - band->getStandardChannelNumber(primaryChannel); - } - catch (const cRuntimeError&) { - throw cRuntimeError("Invalid primary channel %d for band '%s'", primaryChannel, band->getName()); - } + checkStateMutation(); + stateChangePending |= bssActive || !peerHtStates.empty() || !bssAccessPointData.stations.empty() || + !bssAccessPointData.associationIds.empty(); + bssActive = false; + htOperationPresent = false; + primaryChannelAvailable = false; + operationBand = nullptr; + bssStationData.isAssociated = false; + bssAccessPointData.stations.clear(); + bssAccessPointData.associationIds.clear(); + associationIdReservations.clear(); + peerHtStates.clear(); +} - if (localHtCapabilitiesValid) { - if (configuredSecondaryChannelOffset != 0) { - if (band->isHt40OperationSupported(primaryChannel, configuredSecondaryChannelOffset)) { - htOperation.secondaryChannelOffset = configuredSecondaryChannelOffset; - htOperation.operatingChannelWidth = MHz(40); - } - else { - // IEEE Std 802.11-2024, 11.15.2 and 11.15.3.1: fallback to 20 MHz BSS operation - EV_WARN << "Configured 40 MHz HT operation (offset " << configuredSecondaryChannelOffset - << ") is unsupported on primary channel " << primaryChannel - << " in band '" << band->getName() << "'; falling back to 20 MHz BSS operation.\n"; - htOperation.secondaryChannelOffset = 0; - htOperation.operatingChannelWidth = MHz(20); - } - } - else { - htOperation.secondaryChannelOffset = 0; - htOperation.operatingChannelWidth = MHz(20); - } - } +void Ieee80211Mib::publishStateChange() +{ + Enter_Method("publishStateChange"); + checkStateMutation(); + if (!stateChangePending) + return; + stateChangePending = false; + publishingStateChange = true; + try { + // No borrowed payload: observers query the committed MIB during this callback. + emit(bssStateChangedSignal, bssActive); } - - htOperation.primaryChannel = primaryChannel; - primaryChannelAvailable = true; - - if (localHtCapabilitiesValid) { - for (auto& entry : peerHtStates) { - if (entry.second.valid) { - entry.second.negotiatedCapabilities = negotiateHtCapabilities(localHtCapabilities, - entry.second.advertisedCapabilities, htOperation); - if (++entry.second.generation == 0) - entry.second.generation = 1; - } - } + catch (...) { + publishingStateChange = false; + throw; } + publishingStateChange = false; +} + +void Ieee80211Mib::configureBssRole(BssStationType stationType, const std::string& ssid) +{ + checkStateMutation(); + if (bssActive) + throw cRuntimeError("Cannot configure an active BSS role"); + bssStationData.stationType = stationType; + bssData.ssid = ssid; +} + +void Ieee80211Mib::setAssociated(bool associated) +{ + checkStateMutation(); + stateChangePending |= bssStationData.isAssociated != associated; + bssStationData.isAssociated = associated; +} + +Ieee80211Mib::BssMemberStatus Ieee80211Mib::getPeerAssociationStatus(const MacAddress& address) const +{ + auto it = bssAccessPointData.stations.find(address); + return it == bssAccessPointData.stations.end() ? NOT_AUTHENTICATED : it->second; +} + +void Ieee80211Mib::setPeerAssociationStatus(const MacAddress& address, BssMemberStatus status) +{ + checkStateMutation(); + auto it = bssAccessPointData.stations.find(address); + stateChangePending |= it == bssAccessPointData.stations.end() || it->second != status; + bssAccessPointData.stations[address] = status; +} + +void Ieee80211Mib::removePeerAssociation(const MacAddress& address) +{ + checkStateMutation(); + stateChangePending |= bssAccessPointData.stations.erase(address) != 0; + releaseAssociationId(address); +} + +int Ieee80211Mib::requirePrimaryChannel() const +{ + if (!primaryChannelAvailable) + throw cRuntimeError("IEEE 802.11 primary channel is unavailable"); + return htOperation.primaryChannel; } const Ieee80211HtOperation& Ieee80211Mib::getHtOperation() const { - requirePrimaryChannel(); + if (!hasHtOperation()) + throw cRuntimeError("No committed IEEE 802.11 HT operation is available"); return htOperation; } -void Ieee80211Mib::updateLocalHtCapabilities(const physicallayer::Ieee80211ModeSet *modeSet, - const std::set& operationalChannelWidths, int operationalHtSpatialStreamLimit) +void Ieee80211Mib::installLocalHtCapabilities(const Ieee80211HtCapabilities& capabilities, bool htSupported) { - // The radio publishes its initial channel at PHYSICAL_LAYER before the MAC - // publishes its mode set at LINK_LAYER. Preserve that independent BSS - // operation input when rebuilding the mode-derived capability subset. - bool wasPrimaryChannelAvailable = primaryChannelAvailable; - int primaryChannel = htOperation.primaryChannel; - localHtCapabilities = Ieee80211HtCapabilities(); - htOperation = Ieee80211HtOperation(); - htOperation.primaryChannel = primaryChannel; - primaryChannelAvailable = wasPrimaryChannelAvailable; - localHtCapabilitiesValid = modeSet != nullptr && modeSet->isHtOperationSupported(); - if (!localHtCapabilitiesValid) { - clearPeerHtCapabilities(); + checkStateMutation(); + if (localCapabilitiesPrepared && localHtCapabilities == capabilities && localHtCapabilitiesValid == htSupported) return; - } - if (operationalHtSpatialStreamLimit <= 0) - throw cRuntimeError("HT operation requires a positive operational spatial-stream limit"); - - // IEEE Std 802.11-2024, 9.4.2.54.4 and 9.4.2.55: advertise exactly the - // HT modes come from the authoritative mode set, while advertised channel - // widths are restricted to those the configured transmitter and receiver - // can actually operate. In particular, do not infer dense MCS blocks or HT - // widths from legacy/VHT modes that happen to share the set. - const auto& mandatoryMcs = modeSet->getHtMcsMandatory(); - for (auto channelWidth : modeSet->getHtSupportedChannelWidths()) - if (operationalChannelWidths.count(channelWidth) != 0) - localHtCapabilities.supportedChannelWidths.insert(channelWidth); - localHtCapabilities.shortGi20 = localHtCapabilities.supportedChannelWidths.count(MHz(20)) != 0 && - modeSet->isHtShortGuardIntervalSupported(MHz(20)); - localHtCapabilities.shortGi40 = localHtCapabilities.supportedChannelWidths.count(MHz(40)) != 0 && - modeSet->isHtShortGuardIntervalSupported(MHz(40)); - for (int index = 0; index < modeSet->getNumModes(); index++) { - const auto *mode = modeSet->getMode(index); - int mcs = mode->getHtMcsIndex(); - if (mcs >= 0 && mcs < 77 && operationalChannelWidths.count(mode->getDataMode()->getBandwidth()) != 0 && - mode->getDataMode()->getNumberOfSpatialStreams() <= operationalHtSpatialStreamLimit) - localHtCapabilities.rxMcsSupported[mcs] = true; - } - for (int mcs = 0; mcs < 77; mcs++) - htOperation.basicMcsSupported[mcs] = mandatoryMcs[mcs] && localHtCapabilities.rxMcsSupported[mcs]; - // The equal-case Tx MCS set is represented by the maximum MCS index per - // spatial-stream group. Rebuild it from the filtered Rx bitmap; MCS 32 is - // not part of this map's MCS 0..31 NSS encoding. - localHtCapabilities.txMcsNss = Ieee80211HtMcsNssMap(); - for (int mcs = 0; mcs < 32; mcs++) { - if (localHtCapabilities.rxMcsSupported[mcs]) { - int nss = mcs / 8; - localHtCapabilities.txMcsNss.maxMcsPerNss[nss] = std::max(localHtCapabilities.txMcsNss.maxMcsPerNss[nss], mcs % 8); - } - } - if (localHtCapabilities.supportedChannelWidths.empty()) - throw cRuntimeError("HT operation mode set '%s' does not provide an HT channel width", modeSet->getName()); - localHtCapabilities.maxAmpduLengthExponent = par("htMaxAmpduLengthExponent"); - if (localHtCapabilities.maxAmpduLengthExponent < 0 || localHtCapabilities.maxAmpduLengthExponent > 3) - throw cRuntimeError("htMaxAmpduLengthExponent must be between 0 and 3"); - - configuredSecondaryChannelOffset = par("htSecondaryChannelOffset"); - if (configuredSecondaryChannelOffset != 0 && configuredSecondaryChannelOffset != 1 && configuredSecondaryChannelOffset != 3) - throw cRuntimeError("htSecondaryChannelOffset must be 0, 1, or 3"); - htOperation.secondaryChannelOffset = configuredSecondaryChannelOffset; - bool use40Mhz = htOperation.secondaryChannelOffset != 0; - if (use40Mhz && localHtCapabilities.supportedChannelWidths.count(MHz(40)) == 0) - throw cRuntimeError("40 MHz HT operation requires a configured PHY that can operate a 40 MHz channel width"); - htOperation.operatingChannelWidth = use40Mhz ? MHz(40) : MHz(20); - int protectionMode = par("htProtectionMode"); - if (protectionMode < 0 || protectionMode > 3) - throw cRuntimeError("htProtectionMode must be between 0 and 3"); - htOperation.protectionMode = static_cast(protectionMode); - for (auto& entry : peerHtStates) { - if (entry.second.valid) { - entry.second.negotiatedCapabilities = negotiateHtCapabilities(localHtCapabilities, - entry.second.advertisedCapabilities, htOperation); - if (++entry.second.generation == 0) - entry.second.generation = 1; - } - } + if ((localCapabilitiesPrepared && bssActive) || !peerHtStates.empty()) + throw cRuntimeError("Cannot replace local HT capabilities with active BSS or peer relationships"); + localHtCapabilities = capabilities; + localHtCapabilitiesValid = htSupported; + localCapabilitiesPrepared = true; } -const Ieee80211Mib::PeerHtState *Ieee80211Mib::findPeerHtState(const MacAddress& address) const +const Ieee80211Mib::PeerHtState *Ieee80211Mib::findPeerCapabilities(const MacAddress& address) const { auto it = peerHtStates.find(address); return it == peerHtStates.end() || !it->second.valid ? nullptr : &it->second; } -void Ieee80211Mib::setPeerHtCapabilities(const MacAddress& address, const Ieee80211HtCapabilities& capabilities, - const Ieee80211HtOperation& operation) +bool Ieee80211Mib::relationshipAllowsHt(const MacAddress& address) const +{ + const auto *peer = findPeerCapabilities(address); + if (!isLocalHtCapable() || !hasHtOperation() || peer == nullptr || !peer->negotiatedCapabilities) + return false; + const auto& capabilities = *peer->negotiatedCapabilities; + return capabilities.localTxPeerRx.valid && capabilities.localRxPeerTx.valid && + supportsBasicHtMcsSet(bssStationData.stationType == ACCESS_POINT ? peer->advertisedCapabilities : localHtCapabilities, htOperation); +} + +const Ieee80211Mib::PeerHtState *Ieee80211Mib::findPeerHtState(const MacAddress& address) const +{ + return relationshipAllowsHt(address) ? findPeerCapabilities(address) : nullptr; +} + +void Ieee80211Mib::setPeerHtCapabilities(const MacAddress& address, const Ieee80211HtCapabilities& capabilities) { + checkStateMutation(); if (!localHtCapabilitiesValid) - throw cRuntimeError("Cannot install peer HT capabilities when local HT operation is disabled"); + throw cRuntimeError("Cannot install peer HT capabilities when local HT is disabled"); auto& state = peerHtStates[address]; - state.valid = true; + if (state.valid && state.advertisedCapabilities == capabilities && state.negotiatedCapabilities && + state.negotiatedCapabilities->localAdvertisement == localHtCapabilities) + return; + auto derived = std::make_shared(negotiateHtCapabilities(localHtCapabilities, capabilities)); state.advertisedCapabilities = capabilities; - state.negotiatedCapabilities = negotiateHtCapabilities(localHtCapabilities, capabilities, operation); - if (++state.generation == 0) - state.generation = 1; + state.negotiatedCapabilities = derived; + state.valid = true; + stateChangePending = true; EV_INFO << "Installed peer HT state, peer = " << address - << ", txValid = " << state.negotiatedCapabilities.localTxPeerRx.valid - << ", rxValid = " << state.negotiatedCapabilities.localRxPeerTx.valid << endl; + << ", txValid = " << derived->localTxPeerRx.valid + << ", rxValid = " << derived->localRxPeerTx.valid << endl; } void Ieee80211Mib::removePeerHtCapabilities(const MacAddress& address) { - peerHtStates.erase(address); + checkStateMutation(); + stateChangePending |= peerHtStates.erase(address) != 0; } void Ieee80211Mib::clearPeerHtCapabilities() { + checkStateMutation(); + stateChangePending |= !peerHtStates.empty(); peerHtStates.clear(); } @@ -252,6 +254,7 @@ const char *Ieee80211Mib::getStationTypeStr(Ieee80211Mib::BssStationType station short Ieee80211Mib::reserveAssociationId(const MacAddress& address) { + checkStateMutation(); // IEEE Std 802.11-2024, 9.4.1.8: an AP assigns AID values in the range 1 through 2007. auto committed = bssAccessPointData.associationIds.find(address); if (committed != bssAccessPointData.associationIds.end()) @@ -278,6 +281,7 @@ short Ieee80211Mib::reserveAssociationId(const MacAddress& address) short Ieee80211Mib::commitAssociationId(const MacAddress& address) { + checkStateMutation(); auto committed = bssAccessPointData.associationIds.find(address); if (committed != bssAccessPointData.associationIds.end()) { associationIdReservations.erase(address); @@ -291,12 +295,14 @@ short Ieee80211Mib::commitAssociationId(const MacAddress& address) if (entry.second == aid) throw cRuntimeError("Reserved IEEE 802.11 association ID %d is already committed", aid); bssAccessPointData.associationIds[address] = aid; + stateChangePending = true; associationIdReservations.erase(reserved); return aid; } void Ieee80211Mib::cancelAssociationIdReservation(const MacAddress& address) { + checkStateMutation(); associationIdReservations.erase(address); } @@ -308,13 +314,16 @@ short Ieee80211Mib::allocateAssociationId(const MacAddress& address) void Ieee80211Mib::releaseAssociationId(const MacAddress& address) { + checkStateMutation(); associationIdReservations.erase(address); - bssAccessPointData.associationIds.erase(address); + stateChangePending |= bssAccessPointData.associationIds.erase(address) != 0; removePeerHtCapabilities(address); } void Ieee80211Mib::clearAssociationIds() { + checkStateMutation(); + stateChangePending |= !bssAccessPointData.stations.empty() || !bssAccessPointData.associationIds.empty(); bssAccessPointData.stations.clear(); associationIdReservations.clear(); bssAccessPointData.associationIds.clear(); diff --git a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.h b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.h index 691257c2f07..c5622a85a02 100644 --- a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.h +++ b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.h @@ -8,6 +8,8 @@ #ifndef __INET_IEEE80211MIB_H #define __INET_IEEE80211MIB_H +#include + #include "inet/common/SimpleModule.h" #include "inet/linklayer/common/MacAddress.h" #include "inet/linklayer/ieee80211/mib/Ieee80211HtCapabilities.h" @@ -24,6 +26,8 @@ namespace ieee80211 { class INET_API Ieee80211Mib : public SimpleModule { public: + static simsignal_t bssStateChangedSignal; + enum Mode { INFRASTRUCTURE, INDEPENDENT, @@ -63,8 +67,7 @@ class INET_API Ieee80211Mib : public SimpleModule public: bool valid = false; Ieee80211HtCapabilities advertisedCapabilities; - Ieee80211NegotiatedHtCapabilities negotiatedCapabilities; - uint64_t generation = 0; + std::shared_ptr negotiatedCapabilities; }; public: @@ -72,6 +75,7 @@ class INET_API Ieee80211Mib : public SimpleModule Mode mode = static_cast(-1); bool qos = false; + private: BssData bssData; BssStationData bssStationData; BssAccessPointData bssAccessPointData; @@ -82,8 +86,15 @@ class INET_API Ieee80211Mib : public SimpleModule private: Ieee80211HtOperation htOperation; - int configuredSecondaryChannelOffset = 0; + bool localCapabilitiesPrepared = false; bool primaryChannelAvailable = false; + bool bssActive = false; + bool htOperationPresent = false; + const physicallayer::IIeee80211Band *operationBand = nullptr; + bool stateChangePending = false; + bool publishingStateChange = false; + + void checkStateMutation() const; std::map associationIdReservations; std::map peerHtStates; @@ -91,6 +102,15 @@ class INET_API Ieee80211Mib : public SimpleModule virtual void initialize(int stage) override; public: + const BssData& getBssData() const { return bssData; } + const BssStationData& getBssStationData() const { return bssStationData; } + const BssAccessPointData& getBssAccessPointData() const { return bssAccessPointData; } + const Ieee80211HtCapabilities& getLocalHtCapabilities() const { return localHtCapabilities; } + void configureBssRole(BssStationType stationType, const std::string& ssid = ""); + void setAssociated(bool associated); + BssMemberStatus getPeerAssociationStatus(const MacAddress& address) const; + void setPeerAssociationStatus(const MacAddress& address, BssMemberStatus status); + void removePeerAssociation(const MacAddress& address); static const char *getModeStr(Ieee80211Mib::Mode mode); static const char *getStationTypeStr(Ieee80211Mib::BssStationType stationType); std::string getSsidStr() const; @@ -100,16 +120,26 @@ class INET_API Ieee80211Mib : public SimpleModule short allocateAssociationId(const MacAddress& address); void releaseAssociationId(const MacAddress& address); void clearAssociationIds(); - void updateLocalHtCapabilities(const physicallayer::Ieee80211ModeSet *modeSet, - const std::set& operationalChannelWidths, int operationalHtSpatialStreamLimit); - bool isHtOperationSupported() const { return localHtCapabilitiesValid; } + // Initialization/preparation only. A changed profile requires inactive BSS and no peers. + void installLocalHtCapabilities(const Ieee80211HtCapabilities& capabilities, bool htSupported); + bool hasPreparedLocalCapabilities() const { return localCapabilitiesPrepared; } + bool isLocalHtCapable() const { return localHtCapabilitiesValid; } + bool hasActiveBss() const { return bssActive; } + bool hasHtOperation() const { return bssActive && htOperationPresent; } + const physicallayer::IIeee80211Band *getOperationBand() const { return operationBand; } + void commitBss(const std::string& ssid, const MacAddress& bssid, const physicallayer::IIeee80211Band *band, + int channel, const Ieee80211HtOperation *operation); + void clearBss(); + // Management publishes only after its required transaction/timer bookkeeping. + // Synchronous observers may query state; nested mutation is rejected. + void publishStateChange(); bool hasPrimaryChannel() const { return primaryChannelAvailable; } int requirePrimaryChannel() const; - void setPrimaryChannel(int primaryChannel); - void setPrimaryChannel(int primaryChannel, const physicallayer::IIeee80211Band *band); const Ieee80211HtOperation& getHtOperation() const; + bool relationshipAllowsHt(const MacAddress& address) const; + const PeerHtState *findPeerCapabilities(const MacAddress& address) const; const PeerHtState *findPeerHtState(const MacAddress& address) const; - void setPeerHtCapabilities(const MacAddress& address, const Ieee80211HtCapabilities& capabilities, const Ieee80211HtOperation& operation); + void setPeerHtCapabilities(const MacAddress& address, const Ieee80211HtCapabilities& capabilities); void removePeerHtCapabilities(const MacAddress& address); void clearPeerHtCapabilities(); }; diff --git a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned index 70474b3d95b..ac929589c40 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); + @signal[bssStateChanged](type=bool); // committed active state; synchronous read-only observation // Model-backed subset of IEEE Std 802.11-2024 HT capability/operation state; not a full Annex C MIB. int htMaxAmpduLengthExponent = default(0); // maximum received A-MPDU length exponent (0..3) int htSecondaryChannelOffset = default(0); // BSS operation policy: 0=20 MHz, 1=40 MHz above, 3=40 MHz below; bounded by the mode set diff --git a/src/inet/networklayer/configurator/base/L3NetworkConfiguratorBase.cc b/src/inet/networklayer/configurator/base/L3NetworkConfiguratorBase.cc index c8fbb5d406e..0b81bbedc0b 100644 --- a/src/inet/networklayer/configurator/base/L3NetworkConfiguratorBase.cc +++ b/src/inet/networklayer/configurator/base/L3NetworkConfiguratorBase.cc @@ -509,7 +509,7 @@ std::string L3NetworkConfiguratorBase::getWirelessId(NetworkInterface *networkIn #endif // INET_WITH_PHYSICALLAYERWIRELESSCOMMON #ifdef INET_WITH_IEEE80211 if (auto mibModule = dynamic_cast(interfaceModule->getSubmodule("mib"))) { - auto ssid = mibModule->bssData.ssid; + auto ssid = mibModule->getBssData().ssid; if (ssid.length() != 0) return mediumName + ":" + ssid; } diff --git a/src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211ReceiverCapabilities.h b/src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211ReceiverCapabilities.h new file mode 100644 index 00000000000..d3d0c1fcedc --- /dev/null +++ b/src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211ReceiverCapabilities.h @@ -0,0 +1,25 @@ +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +#ifndef INET_IIEEE80211RECEIVERCAPABILITIES_H +#define INET_IIEEE80211RECEIVERCAPABILITIES_H + +#include "inet/common/Units.h" + +namespace inet::physicallayer { + +/** Read-only implemented HT abilities, ready after physical-layer initialization. + * False means unsupported, independently of the selected BSS operation. + */ +class INET_API IIeee80211ReceiverCapabilities +{ + public: + virtual ~IIeee80211ReceiverCapabilities() = default; + [[nodiscard]] virtual bool isHtChannelWidthSupported(Hz channelWidth) const = 0; + [[nodiscard]] virtual bool isHtShortGuardIntervalSupported(Hz channelWidth) const = 0; +}; + +} // namespace inet::physicallayer + +#endif diff --git a/src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211ReceiverCapabilities.ned b/src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211ReceiverCapabilities.ned new file mode 100644 index 00000000000..684d0bae75f --- /dev/null +++ b/src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211ReceiverCapabilities.ned @@ -0,0 +1,10 @@ +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +package inet.physicallayer.wireless.ieee80211.contract; + +// Implemented HT abilities queried through the matching C++ contract. +moduleinterface IIeee80211ReceiverCapabilities +{ +} diff --git a/src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211TransmitterCapabilities.h b/src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211TransmitterCapabilities.h new file mode 100644 index 00000000000..7e1856a044e --- /dev/null +++ b/src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211TransmitterCapabilities.h @@ -0,0 +1,24 @@ +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +#ifndef INET_IIEEE80211TRANSMITTERCAPABILITIES_H +#define INET_IIEEE80211TRANSMITTERCAPABILITIES_H + +#include "inet/common/Units.h" + +namespace inet::physicallayer { + +/** Read-only implemented HT abilities, ready after physical-layer initialization. + * False means unsupported, independently of the selected BSS operation. + */ +class INET_API IIeee80211TransmitterCapabilities +{ + public: + virtual ~IIeee80211TransmitterCapabilities() = default; + [[nodiscard]] virtual bool isHtChannelWidthSupported(Hz channelWidth) const = 0; +}; + +} // namespace inet::physicallayer + +#endif diff --git a/src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211TransmitterCapabilities.ned b/src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211TransmitterCapabilities.ned new file mode 100644 index 00000000000..ffbf8febd2e --- /dev/null +++ b/src/inet/physicallayer/wireless/ieee80211/contract/IIeee80211TransmitterCapabilities.ned @@ -0,0 +1,10 @@ +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +package inet.physicallayer.wireless.ieee80211.contract; + +// Implemented HT abilities queried through the matching C++ contract. +moduleinterface IIeee80211TransmitterCapabilities +{ +} diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.cc b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.cc index 4c9d2ecd6d1..7609deade75 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.cc +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.cc @@ -99,6 +99,11 @@ void Ieee80211Receiver::setChannelNumber(int channelNumber) setChannel(new Ieee80211Channel(band, channelNumber)); } +bool Ieee80211Receiver::isHtShortGuardIntervalSupported(Hz channelWidth) const +{ + return isHtChannelWidthSupported(channelWidth) && modeSet->isHtShortGuardIntervalSupported(channelWidth); +} + bool Ieee80211Receiver::isHtChannelWidthSupported(Hz channelWidth) const { // The receiver listens around the primary-channel center and cannot yet diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h index 450e8b36c45..d71d16deee1 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h @@ -9,6 +9,7 @@ #define __INET_IEEE80211RECEIVER_H #include "inet/physicallayer/wireless/common/base/packetlevel/FlatReceiverBase.h" +#include "inet/physicallayer/wireless/ieee80211/contract/IIeee80211ReceiverCapabilities.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" @@ -17,7 +18,7 @@ namespace inet { namespace physicallayer { -class INET_API Ieee80211Receiver : public FlatReceiverBase +class INET_API Ieee80211Receiver : public FlatReceiverBase, public IIeee80211ReceiverCapabilities { protected: const Ieee80211ModeSet *modeSet = nullptr; @@ -41,7 +42,8 @@ class INET_API Ieee80211Receiver : public FlatReceiverBase virtual void setBand(const IIeee80211Band *band); virtual void setChannel(const Ieee80211Channel *channel); virtual void setChannelNumber(int channelNumber); - virtual bool isHtChannelWidthSupported(Hz channelWidth) const; + bool isHtChannelWidthSupported(Hz channelWidth) const override; + bool isHtShortGuardIntervalSupported(Hz channelWidth) const override; }; } // namespace physicallayer diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.ned b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.ned index c0c19e2afc2..3d1a0bc720d 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.ned +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.ned @@ -7,6 +7,8 @@ package inet.physicallayer.wireless.ieee80211.packetlevel; +import inet.physicallayer.wireless.ieee80211.contract.IIeee80211ReceiverCapabilities; + import inet.physicallayer.wireless.common.base.packetlevel.NarrowbandReceiverBase; @@ -20,7 +22,7 @@ import inet.physicallayer.wireless.common.base.packetlevel.NarrowbandReceiverBas // @see ~Ieee80211Transmitter, ~Ieee80211ScalarRadio, // ~Ieee80211ScalarRadioMedium. // -module Ieee80211Receiver extends NarrowbandReceiverBase +module Ieee80211Receiver extends NarrowbandReceiverBase like IIeee80211ReceiverCapabilities { parameters: string opMode @enum("a","b","g(erp)","g(mixed)","n(mixed-2.4Ghz)","p","ac"); diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h index 05b6f29194c..354a42153bd 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h @@ -9,6 +9,7 @@ #define __INET_IEEE80211TRANSMITTER_H #include "inet/physicallayer/wireless/common/base/packetlevel/FlatTransmitterBase.h" +#include "inet/physicallayer/wireless/ieee80211/contract/IIeee80211TransmitterCapabilities.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" @@ -19,7 +20,7 @@ namespace inet { namespace physicallayer { -class INET_API Ieee80211Transmitter : public FlatTransmitterBase +class INET_API Ieee80211Transmitter : public FlatTransmitterBase, public IIeee80211TransmitterCapabilities { protected: const Ieee80211ModeSet *modeSet = nullptr; @@ -45,7 +46,7 @@ class INET_API Ieee80211Transmitter : public FlatTransmitterBase virtual void setChannelNumber(int channelNumber); virtual const Ieee80211Channel *getChannel() const { return channel; } - virtual bool isHtChannelWidthSupported(Hz channelWidth) const; + virtual bool isHtChannelWidthSupported(Hz channelWidth) const override; virtual const ITransmission *createTransmission(const IRadio *radio, const Packet *packet, simtime_t startTime) const override; }; diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.ned b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.ned index 3d50ab670e5..7d5cedb7505 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.ned +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.ned @@ -7,6 +7,8 @@ package inet.physicallayer.wireless.ieee80211.packetlevel; +import inet.physicallayer.wireless.ieee80211.contract.IIeee80211TransmitterCapabilities; + import inet.physicallayer.wireless.common.base.packetlevel.NarrowbandTransmitterBase; // @@ -14,7 +16,7 @@ import inet.physicallayer.wireless.common.base.packetlevel.NarrowbandTransmitter // // @see ~Ieee80211Receiver, ~Ieee80211Radio, ~Ieee80211RadioMedium. // -module Ieee80211Transmitter extends NarrowbandTransmitterBase +module Ieee80211Transmitter extends NarrowbandTransmitterBase like IIeee80211TransmitterCapabilities { parameters: string opMode @enum("a","b","g(erp)","g(mixed)","n(mixed-2.4Ghz)","p","ac"); diff --git a/tests/module/Ieee80211AgentStaReassociation_1.test b/tests/module/Ieee80211AgentStaReassociation_1.test index 2462b058ff6..283ee059dde 100644 --- a/tests/module/Ieee80211AgentStaReassociation_1.test +++ b/tests/module/Ieee80211AgentStaReassociation_1.test @@ -73,11 +73,11 @@ class TestIeee80211MgmtSta : public Ieee80211MgmtSta void setAssociated(const MacAddress& address) { Enter_Method("setAssociated"); - if (mib->bssStationData.isAssociated) + if (mib->getBssStationData().isAssociated) clearCurrentAssociation(); ensureAccessPoint(address); - mib->bssData.bssid = address; - mib->bssStationData.isAssociated = true; + mib->commitBss("SSID", address, nullptr, -1, nullptr); + mib->setAssociated(true); assocAP = AssociatedApInfo(); assocAP.address = address; assocAP.channel = 1; @@ -99,7 +99,7 @@ class TestIeee80211MgmtSta : public Ieee80211MgmtSta void beginPendingReassociation(const MacAddress& targetAddress) { Enter_Method("beginPendingReassociation"); - ASSERT(mib->bssStationData.isAssociated); + ASSERT(mib->getBssStationData().isAssociated); ASSERT(assocTimeoutMsg == nullptr); auto target = ensureAccessPoint(targetAddress); assocTimeoutMsg = new cMessage("assocTimeout", 2); @@ -127,7 +127,7 @@ class TestIeee80211MgmtSta : public Ieee80211MgmtSta processAssociationResponse(packet, header, true); } - bool isAssociated() const { return mib->bssStationData.isAssociated; } + bool isAssociated() const { return mib->getBssStationData().isAssociated; } MacAddress getAssociatedAddress() const { return assocAP.address; } bool hasBeaconTimer() const { return assocAP.beaconTimeoutMsg != nullptr; } bool hasPendingReassociation() const { return assocTimeoutMsg != nullptr && reassociationInProgress; } @@ -182,7 +182,7 @@ class CompletionListener : public cListener acceptSeen = true; acceptCount++; events.push_back("accept"); - callbackStateValid = callbackStateValid && agent->getPreviousAp() == expectedAddress && mib->bssStationData.isAssociated; + callbackStateValid = callbackStateValid && agent->getPreviousAp() == expectedAddress && mib->getBssStationData().isAssociated; } virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *value, cObject *details) override @@ -197,7 +197,7 @@ class CompletionListener : public cListener } else return; - callbackStateValid = callbackStateValid && acceptSeen && agent->getPreviousAp() == expectedAddress && mib->bssStationData.isAssociated; + callbackStateValid = callbackStateValid && acceptSeen && agent->getPreviousAp() == expectedAddress && mib->getBssStationData().isAssociated; } }; diff --git a/tests/module/Ieee80211ConfigurationContracts_1.test b/tests/module/Ieee80211ConfigurationContracts_1.test new file mode 100644 index 00000000000..5b59c0fa6e7 --- /dev/null +++ b/tests/module/Ieee80211ConfigurationContracts_1.test @@ -0,0 +1,202 @@ +%description: +Explicit catalog dependency with a replacement MAC provider outside any interface, +missing-provider diagnostics, and prepared legacy/HT ad hoc lifecycle state. +The external-upper composition replaces only its TAP endpoint before initialization; +no external OS device is opened and no real-time packet I/O is claimed. + +%file: ConfigurationContracts.cc +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" +#include "inet/linklayer/ieee80211/mib/Ieee80211Mib.h" +#include "inet/emulation/common/ExtInterface.h" +#include "inet/linklayer/ieee80211/mac/contract/IRateControl.h" +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +class OfflineTap : public cSimpleModule +{ + protected: + virtual void handleMessage(cMessage *message) override { delete message; } +}; +Define_Module(OfflineTap); + +// Keep the production external-upper NED, interface class, MAC, management, +// PHY and parameter routing. Replace only the OS endpoint before child init. +class OfflineExtUpper : public ExtInterface +{ + protected: + virtual void initialize(int stage) override + { + if (stage == INITSTAGE_LOCAL) { + auto *tap = getSubmodule("tap"); + auto *fromLlc = tap->gate("lowerLayerIn")->getPreviousGate(); + auto *toClassifier = tap->gate("lowerLayerOut")->getNextGate(); + fromLlc->disconnect(); + tap->gate("lowerLayerOut")->disconnect(); + tap->deleteModule(); + auto *offline = cModuleType::get("OfflineTap")->create("tap", this); + offline->finalizeParameters(); + offline->buildInside(); + fromLlc->connectTo(offline->gate("lowerLayerIn")); + offline->gate("lowerLayerOut")->connectTo(toClassifier); + } + ExtInterface::initialize(stage); + } +}; +Define_Module(OfflineExtUpper); + +class TestCatalogProvider : public cSimpleModule, public IIeee80211MacConfiguration +{ + protected: + const Ieee80211ModeSet *catalog = nullptr; + virtual void initialize() override { catalog = Ieee80211ModeSet::getModeSet("g(mixed)"); } + public: + virtual const Ieee80211ModeSet *getConfiguredModeSet() const override { return catalog; } + virtual void prepareLocalCapabilities() override { throw cRuntimeError("Catalog-only consumer must not prepare HT"); } +}; +Define_Module(TestCatalogProvider); + +class TestCatalogConsumer : public ModeSetModuleBase +{ + protected: + virtual void initialize(int stage) override + { + if (par("missing")) { + if (stage == INITSTAGE_LOCAL) { + bool rejected = false; + try { ModeSetModuleBase::initialize(stage); } + catch (const cRuntimeError& e) { rejected = std::string(e.what()).find("absent") != std::string::npos; } + ASSERT(rejected); + std::cout << "Missing explicit catalog provider rejected.\n"; + } + return; + } + ModeSetModuleBase::initialize(stage); + if (stage == INITSTAGE_LINK_LAYER) { + ASSERT(modeSet == Ieee80211ModeSet::getModeSet("g(mixed)")); + std::cout << "Standalone consumer queried replacement provider after LOCAL.\n"; + } + } +}; +Define_Module(TestCatalogConsumer); + +class TestAdhocState : public cSimpleModule +{ + protected: + virtual int numInitStages() const override { return NUM_INIT_STAGES; } + virtual void initialize(int stage) override + { + if (stage == INITSTAGE_NETWORK_CONFIGURATION) { + check(true); + auto *rateControl = check_and_cast(getModuleByPath("^.ht.wlan[0].mac.dcf.rateControl")); + auto *rate = rateControl->getRate(MacAddress("02:00:00:00:00:09")); + ASSERT(rate->getDataMode()->getNetBitrate() == Mbps(65)); + std::cout << "Onoe initial rate is prepared before first network-configuration query.\n"; + auto *external = check_and_cast(getModuleByPath("^.external.wlan[0].mib")); + ASSERT(external->hasPreparedLocalCapabilities() && external->isLocalHtCapable()); + ASSERT(external->hasActiveBss() && !external->hasHtOperation()); + std::cout << "External-upper production dependency routing prepared with an offline TAP endpoint.\n"; + scheduleAt(SimTime(1500, SIMTIME_NS), new cMessage("down")); + scheduleAt(SimTime(2500, SIMTIME_NS), new cMessage("restarted")); + } + } + void check(bool active) + { + for (const char *name : {"^.legacy.wlan[0].mib", "^.ht.wlan[0].mib"}) { + auto *mib = check_and_cast(getModuleByPath(name)); + ASSERT(mib->hasPreparedLocalCapabilities()); + ASSERT(mib->isLocalHtCapable() == (std::string(name).find(".ht.") != std::string::npos)); + ASSERT(mib->hasActiveBss() == active); + ASSERT(!mib->hasHtOperation()); + ASSERT(!mib->hasPrimaryChannel()); + } + } + virtual void handleMessage(cMessage *message) override + { + bool restarted = std::string(message->getName()) == "restarted"; + check(restarted); + if (restarted) + std::cout << "Legacy and HT ad hoc state cleared and restored without fabricated HT operation.\n"; + delete message; + } +}; +Define_Module(TestAdhocState); + +%file: test.ned +import inet.common.SimpleModule; +import inet.emulation.linklayer.ieee80211.ExtUpperIeee80211Interface; +import inet.common.scenario.ScenarioManager; +import inet.node.inet.AdhocHost; +import inet.linklayer.ieee80211.mac.contract.IIeee80211MacConfiguration; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; +simple OfflineTap { parameters: @class(::OfflineTap); gates: input lowerLayerIn; output lowerLayerOut; } +module OfflineExtUpper extends ExtUpperIeee80211Interface { parameters: @class(::OfflineExtUpper); } +simple TestCatalogProvider like IIeee80211MacConfiguration { parameters: @class(::TestCatalogProvider); } +simple TestCatalogConsumer extends SimpleModule { + parameters: + @class(::TestCatalogConsumer); + string modeSetModule; + bool missing = default(false); +} +simple TestAdhocState extends SimpleModule { parameters: @class(::TestAdhocState); } +network ConfigurationContracts { + submodules: + consumer: TestCatalogConsumer { parameters: modeSetModule = "^.provider"; } + missing: TestCatalogConsumer { parameters: modeSetModule = "^.absent"; missing = true; } + provider: TestCatalogProvider; + radioMedium: Ieee80211ScalarRadioMedium; + scenarioManager: ScenarioManager; + legacy: AdhocHost; + ht: AdhocHost; + external: AdhocHost; + observer: TestAdhocState; +} + +%inifile: omnetpp.ini +[General] +network = ConfigurationContracts +ned-path = .;../../../../src;../../lib +sim-time-limit = 3us +seed-set = 0 +record-vector-results = false +record-scalar-results = false +**.hasStatus = true +**.ipv4.configurator.networkConfiguratorModule = "" +*.external.wlan[0].typename = "OfflineExtUpper" +*.external.wlan[0].device = "offline-test" +*.external.wlan[0].opMode = "n(mixed-2.4Ghz)" +*.external.wlan[0].mgmt.typename = "Ieee80211MgmtAdhoc" +*.external.wlan[0].agent.typename = "" +*.ht.wlan[0].mac.dcf.rateControl.typename = "OnoeRateControl" +*.ht.wlan[0].mac.dcf.rateControl.initialRate = 65Mbps +*.ht.wlan[0].opMode = "n(mixed-2.4Ghz)" +*.legacy.wlan[0].opMode = "g(mixed)" +**.radio.bandName = "2.4 GHz" +**.radio.channelNumber = 6 +**.mobility.initFromDisplayString = false +**.mobility.constraintAreaMinX = 0m +**.mobility.constraintAreaMinY = 0m +**.mobility.constraintAreaMinZ = 0m +**.mobility.constraintAreaMaxX = 100m +**.mobility.constraintAreaMaxY = 100m +**.mobility.constraintAreaMaxZ = 0m +*.scenarioManager.script = xmldoc("scenario.xml") + +%file: scenario.xml + + + + + +%contains: stdout +Missing explicit catalog provider rejected. +%contains: stdout +Standalone consumer queried replacement provider after LOCAL. +%contains: stdout +Legacy and HT ad hoc state cleared and restored without fabricated HT operation. + +%contains: stdout +External-upper production dependency routing prepared with an offline TAP endpoint. + +%contains: stdout +Onoe initial rate is prepared before first network-configuration query. diff --git a/tests/module/Ieee80211HtAntennaRateControl_1.test b/tests/module/Ieee80211HtAntennaRateControl_1.test index 0518558627c..d064c84efe2 100644 --- a/tests/module/Ieee80211HtAntennaRateControl_1.test +++ b/tests/module/Ieee80211HtAntennaRateControl_1.test @@ -101,12 +101,12 @@ class Ieee80211HtAntennaRateControlTest : public cSimpleModule auto staMib = check_and_cast(staInterface->getSubmodule("mib")); auto apMib = check_and_cast(apInterface->getSubmodule("mib")); - ASSERT(staMib->bssStationData.isAssociated); - ASSERT(apMib->bssAccessPointData.stations.at(staMib->address) == Ieee80211Mib::ASSOCIATED); - ASSERT(staMib->localHtCapabilitiesValid); - ASSERT(apMib->localHtCapabilitiesValid); - checkCapabilities(staMib->localHtCapabilities); - checkCapabilities(apMib->localHtCapabilities); + ASSERT(staMib->getBssStationData().isAssociated); + ASSERT(apMib->getBssAccessPointData().stations.at(staMib->address) == Ieee80211Mib::ASSOCIATED); + ASSERT(staMib->isLocalHtCapable()); + ASSERT(apMib->isLocalHtCapable()); + checkCapabilities(staMib->getLocalHtCapabilities()); + checkCapabilities(apMib->getLocalHtCapabilities()); checkBasicMcs(apMib->getHtOperation()); auto staPeer = staMib->findPeerHtState(apMib->address); @@ -114,14 +114,14 @@ class Ieee80211HtAntennaRateControlTest : public cSimpleModule ASSERT(staPeer != nullptr); ASSERT(apPeer != nullptr); checkCapabilities(staPeer->advertisedCapabilities); - checkBasicMcs(staPeer->negotiatedCapabilities.operation); + checkBasicMcs(staMib->getHtOperation()); checkCapabilities(apPeer->advertisedCapabilities); - checkBasicMcs(apPeer->negotiatedCapabilities.operation); + checkBasicMcs(apMib->getHtOperation()); for (int mcs = 8; mcs < 77; mcs++) { - ASSERT(!staPeer->negotiatedCapabilities.localTxPeerRx.supportedMcs[mcs]); - ASSERT(!staPeer->negotiatedCapabilities.localRxPeerTx.supportedMcs[mcs]); - ASSERT(!apPeer->negotiatedCapabilities.localTxPeerRx.supportedMcs[mcs]); - ASSERT(!apPeer->negotiatedCapabilities.localRxPeerTx.supportedMcs[mcs]); + ASSERT(!staPeer->negotiatedCapabilities->localTxPeerRx.supportedMcs[mcs]); + ASSERT(!staPeer->negotiatedCapabilities->localRxPeerTx.supportedMcs[mcs]); + ASSERT(!apPeer->negotiatedCapabilities->localTxPeerRx.supportedMcs[mcs]); + ASSERT(!apPeer->negotiatedCapabilities->localRxPeerTx.supportedMcs[mcs]); } auto rateControl = check_and_cast(getModuleByPath("^.sta.wlan[0].mac.dcf.rateControl")); diff --git a/tests/module/Ieee80211HtAssociation_1.test b/tests/module/Ieee80211HtAssociation_1.test index aeee34722e2..c7b9271445b 100644 --- a/tests/module/Ieee80211HtAssociation_1.test +++ b/tests/module/Ieee80211HtAssociation_1.test @@ -120,27 +120,27 @@ class Ieee80211HtAssociationChecker : public SimpleModule, public cListener const auto& staAddress = staMib->address; ASSERT(apMib->requirePrimaryChannel() == 11); ASSERT(channelChangeTimer == nullptr); - ASSERT(apMib->bssAccessPointData.stations.at(staAddress) == Ieee80211Mib::ASSOCIATED); - ASSERT(apMib->bssAccessPointData.associationIds.at(staAddress) != 0); + ASSERT(apMib->getBssAccessPointData().stations.at(staAddress) == Ieee80211Mib::ASSOCIATED); + ASSERT(apMib->getBssAccessPointData().associationIds.at(staAddress) != 0); ASSERT(apMib->findPeerHtState(staAddress) != nullptr); ASSERT(apMib->findPeerHtState(staAddress)->valid); ASSERT(associationResponseWireChannelChecked); ASSERT(staTunedToInternalChannelSix); ASSERT(apMib->hasPrimaryChannel()); ASSERT(apMib->requirePrimaryChannel() == 11); - ASSERT(apMib->findPeerHtState(staAddress)->negotiatedCapabilities.operation.primaryChannel == 11); - ASSERT(staMib->bssStationData.isAssociated); + ASSERT(apMib->getHtOperation().primaryChannel == 11); + ASSERT(staMib->getBssStationData().isAssociated); // IEEE Std 802.11-2024, 9.4.2.54.2/Figure 9-456 and Table 9-224: the n(mixed-2.4Ghz) // profile exposes short GI for the PHY-supported 20 MHz width. The // mode catalog also describes 40 MHz modes, but the current packet PHY // cannot operate a primary/secondary compound channel and must not // advertise that width. - ASSERT(staMib->localHtCapabilities.shortGi20); - ASSERT(!staMib->localHtCapabilities.shortGi40); - ASSERT(apMib->localHtCapabilities.shortGi20); - ASSERT(!apMib->localHtCapabilities.shortGi40); - ASSERT(staMib->localHtCapabilities.supportedChannelWidths.count(MHz(20)) == 1); - ASSERT(staMib->localHtCapabilities.supportedChannelWidths.count(MHz(40)) == 0); + ASSERT(staMib->getLocalHtCapabilities().shortGi20); + ASSERT(!staMib->getLocalHtCapabilities().shortGi40); + ASSERT(apMib->getLocalHtCapabilities().shortGi20); + ASSERT(!apMib->getLocalHtCapabilities().shortGi40); + ASSERT(staMib->getLocalHtCapabilities().supportedChannelWidths.count(MHz(20)) == 1); + ASSERT(staMib->getLocalHtCapabilities().supportedChannelWidths.count(MHz(40)) == 0); // Bridge the MIB-derived capability contract to the actual management // frame serializer: the n(mixed-2.4Ghz) profile must advertise only // PHY-supported short-GI bits in HT Capabilities (IEEE Std 802.11-2024, 9.4.2.54.2, @@ -152,7 +152,7 @@ class Ieee80211HtAssociationChecker : public SimpleModule, public cListener rates.numRates = 1; rates.rate[0] = 6; serializedResponse->setSupportedRates(rates); - setHtCapabilities(serializedResponse, staMib->localHtCapabilities); + setHtCapabilities(serializedResponse, staMib->getLocalHtCapabilities()); serializedResponse->setChunkLength(B(37)); // fixed fields/rates (9) + HT Capabilities IE (28) Packet serializedPacket("mib-ht-capabilities", serializedResponse); auto serializedBytes = serializedPacket.peekAllAsBytes()->getBytes(); @@ -162,7 +162,7 @@ class Ieee80211HtAssociationChecker : public SimpleModule, public cListener ASSERT((serializedBytes[11] & (1 << 6)) == 0); ASSERT(staMib->findPeerHtState(apMib->address) != nullptr); ASSERT(staMib->findPeerHtState(apMib->address)->valid); - ASSERT(staMib->findPeerHtState(apMib->address)->negotiatedCapabilities.operation.primaryChannel == 6); + ASSERT(staMib->getHtOperation().primaryChannel == 6); ASSERT(apAssociationNotifications == 1); EV << "RTS-protected association committed at AP and STA.\n"; } diff --git a/tests/module/Ieee80211HtCapabilityPreparation_1.test b/tests/module/Ieee80211HtCapabilityPreparation_1.test new file mode 100644 index 00000000000..8cb8c6327a7 --- /dev/null +++ b/tests/module/Ieee80211HtCapabilityPreparation_1.test @@ -0,0 +1,157 @@ +%description: +The production MAC assembles HT abilities through replaceable PHY query contracts. +A transmitter adapter unrelated to Ieee80211Transmitter contributes different widths; +receiver short GI remains an independent contribution. Capability installation and +repeated preparation preserve operation. This is synthetic initialization coverage, +not a 40 MHz PHY exchange. + +%file: TestHtPreparation.cc +#include "inet/common/SimpleModule.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Mac.h" +#include "inet/physicallayer/wireless/ieee80211/contract/IIeee80211TransmitterCapabilities.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h" + +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +class ContributionTransmitter : public ITransmitter, public IIeee80211TransmitterCapabilities +{ + public: + const ITransmitter *delegate = nullptr; + bool allow40 = false; + virtual W getMaxPower() const override { return delegate->getMaxPower(); } + virtual m getMaxCommunicationRange() const override { return delegate->getMaxCommunicationRange(); } + virtual m getMaxInterferenceRange() const override { return delegate->getMaxInterferenceRange(); } + virtual const ITransmission *createTransmission(const IRadio *radio, const Packet *packet, simtime_t time) const override + { return delegate->createTransmission(radio, packet, time); } + virtual bool isHtChannelWidthSupported(Hz width) const override + { return width == MHz(20) || (allow40 && width == MHz(40)); } +}; + +class ContributionRadio : public Ieee80211Radio +{ + ContributionTransmitter contribution; + protected: + virtual void initialize(int stage) override + { + Ieee80211Radio::initialize(stage); + if (stage == INITSTAGE_LOCAL) { + contribution.delegate = transmitter; + contribution.allow40 = par("allow40"); + } + } + public: + virtual const ITransmitter *getTransmitter() const override { return &contribution; } +}; +Define_Module(ContributionRadio); + +class ContributionReceiver : public Ieee80211Receiver +{ + public: + virtual bool isHtChannelWidthSupported(Hz width) const override + { return width == MHz(20) || width == MHz(40); } + virtual bool isHtShortGuardIntervalSupported(Hz width) const override + { return width == MHz(20); } +}; +Define_Module(ContributionReceiver); + +class HtPreparationChecker : public SimpleModule +{ + protected: + virtual int numInitStages() const override { return NUM_INIT_STAGES; } + virtual void initialize(int stage) override + { + if (stage != INITSTAGE_LAST) + return; + for (const char *name : {"^.narrow.wlan[0]", "^.wide.wlan[0]"}) { + auto *nic = getModuleByPath(name); + auto *mib = check_and_cast(nic->getSubmodule("mib")); + auto *mac = check_and_cast(nic->getSubmodule("mac")); + ASSERT(mib->hasPreparedLocalCapabilities()); + ASSERT(mib->getLocalHtCapabilities().supportedChannelWidths.count(MHz(20)) == 1); + ASSERT(mib->getLocalHtCapabilities().shortGi20); + ASSERT(!mib->getLocalHtCapabilities().shortGi40); + bool wide = std::string(name).find("wide") != std::string::npos; + ASSERT(mib->getLocalHtCapabilities().supportedChannelWidths.count(MHz(40)) == (wide ? 1 : 0)); + auto operation = mib->getHtOperation(); + mac->prepareLocalCapabilities(); + ASSERT(mib->getHtOperation().primaryChannel == operation.primaryChannel); + ASSERT(mib->getHtOperation().basicMcsSupported == operation.basicMcsSupported); + } + auto *storage = check_and_cast(getModuleByPath("^.storage")); + Ieee80211HtOperation operation; + operation.primaryChannel = 6; + operation.protectionMode = Ieee80211HtProtectionMode::NON_HT_MIXED; + operation.basicMcsSupported[2] = true; + storage->commitBss("storage", MacAddress::UNSPECIFIED_ADDRESS, nullptr, 6, &operation); + Ieee80211HtCapabilities capabilities; + capabilities.supportedChannelWidths.insert(MHz(20)); + capabilities.rxMcsSupported[0] = true; + storage->installLocalHtCapabilities(capabilities, true); + ASSERT(storage->getHtOperation().primaryChannel == 6); + ASSERT(storage->getHtOperation().protectionMode == operation.protectionMode); + ASSERT(storage->getHtOperation().basicMcsSupported == operation.basicMcsSupported); + std::cout << "Independent capability preparation and replacement contributions verified.\n"; + } +}; +Define_Module(HtPreparationChecker); + +%file: test.ned +import inet.common.SimpleModule; +import inet.linklayer.ieee80211.mib.Ieee80211Mib; +import inet.node.wireless.AccessPoint; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadio; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211Receiver; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; + +module ContributionRadio extends Ieee80211ScalarRadio { + parameters: + bool allow40 = default(false); + @class(::ContributionRadio); +} +module ContributionReceiver extends Ieee80211Receiver { + parameters: + @class(::ContributionReceiver); +} +simple HtPreparationChecker extends SimpleModule { + parameters: + @class(::HtPreparationChecker); +} +network HtPreparationNetwork { + submodules: + radioMedium: Ieee80211ScalarRadioMedium; + narrow: AccessPoint; + wide: AccessPoint; + storage: Ieee80211Mib; + checker: HtPreparationChecker; +} + +%inifile: omnetpp.ini +[General] +network = HtPreparationNetwork +ned-path = .;../../../../src;../../lib +sim-time-limit = 0s +seed-set = 0 +record-vector-results = false +record-scalar-results = false +**.wlan[*].mgmt.typename = "Ieee80211MgmtApSimplified" +**.wlan[*].opMode = "n(mixed-2.4Ghz)" +**.wlan[*].radio.typename = "ContributionRadio" +**.wlan[*].radio.receiver.typename = "ContributionReceiver" +*.wide.wlan[*].radio.allow40 = true +**.wlan[*].radio.bandName = "2.4 GHz" +**.wlan[*].radio.channelNumber = 6 +**.wlan[*].bitrate = 65Mbps +**.wlan[*].radio.antenna.numAntennas = 1 +**.wlan[*].radio.transmitter.power = 100mW +**.wlan[*].radio.receiver.sensitivity = -85dBm +**.wlan[*].radio.receiver.snirThreshold = 4dB +**.mobility.initFromDisplayString = false +**.mobility.initialX = 10m +**.mobility.initialY = 10m +**.mobility.initialZ = 0m + +%contains: stdout +Independent capability preparation and replacement contributions verified. diff --git a/tests/module/Ieee80211MgmtApChannelChange_1.test b/tests/module/Ieee80211MgmtApChannelChange_1.test index 324c0369224..3a0969059bf 100644 --- a/tests/module/Ieee80211MgmtApChannelChange_1.test +++ b/tests/module/Ieee80211MgmtApChannelChange_1.test @@ -109,9 +109,9 @@ class TestIeee80211MgmtAp : public Ieee80211MgmtAp rates.numRates = 1; rates.rate[0] = 6; body->setSupportedRates(rates); - if (mib->isHtOperationSupported()) { + if (mib->isLocalHtCapable()) { body->setHtCapabilitiesPresent(true); - auto staCaps = mib->localHtCapabilities; + auto staCaps = mib->getLocalHtCapabilities(); staCaps.supportedChannelWidths.insert(MHz(20)); staCaps.supportedChannelWidths.insert(MHz(40)); body->setHtCapabilities(makeHtCapabilitiesElement(staCaps)); @@ -188,9 +188,9 @@ class Ieee80211MgmtApChannelChangeTest : public cSimpleModule const auto *peerState = mib->findPeerHtState(staAddress); ASSERT(peerState != nullptr); ASSERT(peerState->valid); - ASSERT(peerState->negotiatedCapabilities.operation.primaryChannel == 0); - ASSERT(peerState->negotiatedCapabilities.operation.secondaryChannelOffset == 1); - ASSERT(peerState->negotiatedCapabilities.operation.operatingChannelWidth == MHz(40)); + ASSERT(mib->getHtOperation().primaryChannel == 0); + ASSERT(mib->getHtOperation().secondaryChannelOffset == 1); + ASSERT(mib->getHtOperation().operatingChannelWidth == MHz(40)); // Verify beacon advertised HT Operation matches 40 MHz SCA operation mgmt->emitBeaconNow(); @@ -204,7 +204,7 @@ class Ieee80211MgmtApChannelChangeTest : public cSimpleModule // Verify rate selection selects 40 MHz mode for peer const auto *mcs7_40 = findHtMode(modeSet, 7, MHz(40)); ASSERT(mcs7_40 != nullptr); - const auto *selectedMode = selectPeerCompatibleMode(modeSet, peerState, mcs7_40, staAddress); + const auto *selectedMode = selectPeerCompatibleMode(modeSet, peerState, mcs7_40, staAddress, &mib->getHtOperation(), mib->relationshipAllowsHt(staAddress)); ASSERT(selectedMode == mcs7_40); std::cout << "Station associated and negotiated 40 MHz HT operation.\n"; @@ -222,9 +222,9 @@ class Ieee80211MgmtApChannelChangeTest : public cSimpleModule peerState = mib->findPeerHtState(staAddress); ASSERT(peerState != nullptr); ASSERT(peerState->valid); - ASSERT(peerState->negotiatedCapabilities.operation.primaryChannel == 10); - ASSERT(peerState->negotiatedCapabilities.operation.secondaryChannelOffset == 0); - ASSERT(peerState->negotiatedCapabilities.operation.operatingChannelWidth == MHz(20)); + ASSERT(mib->getHtOperation().primaryChannel == 10); + ASSERT(mib->getHtOperation().secondaryChannelOffset == 0); + ASSERT(mib->getHtOperation().operatingChannelWidth == MHz(20)); // Verify beacon advertised HT Operation reflects 20 MHz BSS operation mgmt->emitBeaconNow(); @@ -234,7 +234,7 @@ class Ieee80211MgmtApChannelChangeTest : public cSimpleModule ASSERT(!beaconElem.staChannelWidth40Mhz); // Verify rate selection for existing peer restricts to 20 MHz - selectedMode = selectPeerCompatibleMode(modeSet, peerState, mcs7_40, staAddress); + selectedMode = selectPeerCompatibleMode(modeSet, peerState, mcs7_40, staAddress, &mib->getHtOperation(), mib->relationshipAllowsHt(staAddress)); ASSERT(selectedMode != nullptr); ASSERT(selectedMode != mcs7_40); ASSERT(selectedMode->getDataMode()->getBandwidth() == MHz(20)); @@ -253,9 +253,9 @@ class Ieee80211MgmtApChannelChangeTest : public cSimpleModule peerState = mib->findPeerHtState(staAddress); ASSERT(peerState != nullptr); ASSERT(peerState->valid); - ASSERT(peerState->negotiatedCapabilities.operation.primaryChannel == 0); - ASSERT(peerState->negotiatedCapabilities.operation.secondaryChannelOffset == 1); - ASSERT(peerState->negotiatedCapabilities.operation.operatingChannelWidth == MHz(40)); + ASSERT(mib->getHtOperation().primaryChannel == 0); + ASSERT(mib->getHtOperation().secondaryChannelOffset == 1); + ASSERT(mib->getHtOperation().operatingChannelWidth == MHz(40)); // Verify beacon advertised HT Operation restored to 40 MHz mgmt->emitBeaconNow(); @@ -265,7 +265,7 @@ class Ieee80211MgmtApChannelChangeTest : public cSimpleModule ASSERT(beaconElem.staChannelWidth40Mhz); // Verify rate selection permits 40 MHz again - selectedMode = selectPeerCompatibleMode(modeSet, peerState, mcs7_40, staAddress); + selectedMode = selectPeerCompatibleMode(modeSet, peerState, mcs7_40, staAddress, &mib->getHtOperation(), mib->relationshipAllowsHt(staAddress)); ASSERT(selectedMode == mcs7_40); std::cout << "Dynamic channel change back to 0 restored 40 MHz HT operation.\n"; @@ -283,7 +283,9 @@ class Ieee80211MgmtApChannelChangeTest : public cSimpleModule bool threwMibInvalidChannel = false; try { - mib->setPrimaryChannel(99, mgmt->getHtOperationBand()); + auto invalidOperation = mib->getHtOperation(); + invalidOperation.primaryChannel = 99; + mib->commitBss("test", mib->address, mgmt->getHtOperationBand(), 99, &invalidOperation); } catch (const cRuntimeError& e) { threwMibInvalidChannel = true; @@ -313,9 +315,9 @@ class Ieee80211MgmtApChannelChangeTest : public cSimpleModule peerState = mib->findPeerHtState(staAddress); ASSERT(peerState != nullptr); ASSERT(peerState->valid); - ASSERT(peerState->negotiatedCapabilities.operation.primaryChannel == 0); - ASSERT(peerState->negotiatedCapabilities.operation.secondaryChannelOffset == 1); - ASSERT(peerState->negotiatedCapabilities.operation.operatingChannelWidth == MHz(40)); + ASSERT(mib->getHtOperation().primaryChannel == 0); + ASSERT(mib->getHtOperation().secondaryChannelOffset == 1); + ASSERT(mib->getHtOperation().operatingChannelWidth == MHz(40)); // Advertised beacon reflects new band standard channel number (36) mgmt->emitBeaconNow(); @@ -327,7 +329,7 @@ class Ieee80211MgmtApChannelChangeTest : public cSimpleModule // Also verify that band change to a band without standard channel numbers throws bool threwInvalidBand = false; try { - mib->setPrimaryChannel(0, &physicallayer::Ieee80211CompliantBands::band5GHz); + mib->commitBss("test", mib->address, &physicallayer::Ieee80211CompliantBands::band5GHz, 0, &mib->getHtOperation()); } catch (const cRuntimeError& e) { threwInvalidBand = true; @@ -342,8 +344,9 @@ class Ieee80211MgmtApChannelChangeTest : public cSimpleModule // 7. Band notifications may arrive during initialization before the MAC // publishes whether HT operation is supported. A legacy/non-HT AP must // retain the radio's channel without applying HT-only band validation. - mib->updateLocalHtCapabilities(nullptr, {}, 0); - ASSERT(!mib->isHtOperationSupported()); + mib->clearBss(); + mib->installLocalHtCapabilities(Ieee80211HtCapabilities(), false); + ASSERT(!mib->isLocalHtCapable()); bool threwNonHtBandChange = false; try { radio->setBand(&physicallayer::Ieee80211CompliantBands::band5GHz); diff --git a/tests/module/Ieee80211MgmtApGenericRadio_1.test b/tests/module/Ieee80211MgmtApGenericRadio_1.test index 130617f9d90..0113dbbd6b9 100644 --- a/tests/module/Ieee80211MgmtApGenericRadio_1.test +++ b/tests/module/Ieee80211MgmtApGenericRadio_1.test @@ -24,7 +24,7 @@ class TestIeee80211MgmtApGenericRadio : public Ieee80211MgmtAp { Ieee80211MgmtAp::initialize(stage); if (stage == INITSTAGE_LAST) { - ASSERT(!mib->isHtOperationSupported()); + ASSERT(!mib->isLocalHtCapable()); ASSERT(!mib->hasPrimaryChannel()); probeTimer = new cMessage("injectProbe"); scheduleAt(SimTime(2, SIMTIME_MS), probeTimer); diff --git a/tests/module/Ieee80211MgmtApHcfQueueDrop_1.test b/tests/module/Ieee80211MgmtApHcfQueueDrop_1.test index 843fb1ade0b..3f301e472f6 100644 --- a/tests/module/Ieee80211MgmtApHcfQueueDrop_1.test +++ b/tests/module/Ieee80211MgmtApHcfQueueDrop_1.test @@ -46,7 +46,7 @@ class TestIeee80211MgmtApHcfQueueDrop : public Ieee80211MgmtAp Enter_Method("markAuthenticated"); auto& sta = staList[address]; sta.address = address; - mib->bssAccessPointData.stations[address] = Ieee80211Mib::AUTHENTICATED; + mib->setPeerAssociationStatus(address, Ieee80211Mib::AUTHENTICATED); } void submitAssociationRequest(const MacAddress& address) @@ -95,10 +95,10 @@ class TestIeee80211MgmtApHcfQueueDrop : public Ieee80211MgmtAp void cancelAssociationIdReservation(const MacAddress& address) { mib->cancelAssociationIdReservation(address); } short getCommittedAssociationId(const MacAddress& address) const { - auto it = mib->bssAccessPointData.associationIds.find(address); - return it == mib->bssAccessPointData.associationIds.end() ? 0 : it->second; + auto it = mib->getBssAccessPointData().associationIds.find(address); + return it == mib->getBssAccessPointData().associationIds.end() ? 0 : it->second; } - Ieee80211Mib::BssMemberStatus getStationStatus(const MacAddress& address) const { return mib->bssAccessPointData.stations.at(address); } + Ieee80211Mib::BssMemberStatus getStationStatus(const MacAddress& address) const { return mib->getBssAccessPointData().stations.at(address); } protected: virtual void frameTransmissionFinished(const Packet *responseFrame, FrameTransmissionStatus status) override diff --git a/tests/module/Ieee80211MgmtApHcfRtsTimeout_1.test b/tests/module/Ieee80211MgmtApHcfRtsTimeout_1.test index 81a602e75f5..2b7a597b3ab 100644 --- a/tests/module/Ieee80211MgmtApHcfRtsTimeout_1.test +++ b/tests/module/Ieee80211MgmtApHcfRtsTimeout_1.test @@ -33,7 +33,7 @@ class TestIeee80211MgmtApHcf : public Ieee80211MgmtAp { auto& sta = staList[address]; sta.address = address; - mib->bssAccessPointData.stations[address] = Ieee80211Mib::AUTHENTICATED; + mib->setPeerAssociationStatus(address, Ieee80211Mib::AUTHENTICATED); } short submitAssociationRequest(const MacAddress& address) @@ -70,12 +70,12 @@ class TestIeee80211MgmtApHcf : public Ieee80211MgmtAp bool hasCommittedAssociationId(const MacAddress& address) const { - return mib->bssAccessPointData.associationIds.find(address) != mib->bssAccessPointData.associationIds.end(); + return mib->getBssAccessPointData().associationIds.find(address) != mib->getBssAccessPointData().associationIds.end(); } Ieee80211Mib::BssMemberStatus getStationStatus(const MacAddress& address) const { - return mib->bssAccessPointData.stations.at(address); + return mib->getBssAccessPointData().stations.at(address); } protected: diff --git a/tests/module/Ieee80211MgmtApLifecycle_1.test b/tests/module/Ieee80211MgmtApLifecycle_1.test index f91973e1096..f2f726667a5 100644 --- a/tests/module/Ieee80211MgmtApLifecycle_1.test +++ b/tests/module/Ieee80211MgmtApLifecycle_1.test @@ -69,9 +69,9 @@ class TestIeee80211MgmtAp : public Ieee80211MgmtAp rates.numRates = 1; rates.rate[0] = 6; body->setSupportedRates(rates); - if (mib->isHtOperationSupported()) { + if (mib->isLocalHtCapable()) { body->setHtCapabilitiesPresent(true); - body->setHtCapabilities(makeHtCapabilitiesElement(mib->localHtCapabilities)); + body->setHtCapabilities(makeHtCapabilitiesElement(mib->getLocalHtCapabilities())); } body->setChunkLength(B(100)); packet->insertAtBack(body); @@ -103,18 +103,18 @@ class TestIeee80211MgmtAp : public Ieee80211MgmtAp bool hasStation(const MacAddress& address) const { - return mib->bssAccessPointData.stations.find(address) != mib->bssAccessPointData.stations.end(); + return mib->getBssAccessPointData().stations.find(address) != mib->getBssAccessPointData().stations.end(); } Ieee80211Mib::BssMemberStatus getStationStatus(const MacAddress& address) const { - auto it = mib->bssAccessPointData.stations.find(address); - return it == mib->bssAccessPointData.stations.end() ? Ieee80211Mib::NOT_AUTHENTICATED : it->second; + auto it = mib->getBssAccessPointData().stations.find(address); + return it == mib->getBssAccessPointData().stations.end() ? Ieee80211Mib::NOT_AUTHENTICATED : it->second; } bool hasCommittedAid(const MacAddress& address) const { - return mib->bssAccessPointData.associationIds.find(address) != mib->bssAccessPointData.associationIds.end(); + return mib->getBssAccessPointData().associationIds.find(address) != mib->getBssAccessPointData().associationIds.end(); } bool hasPeerHtState(const MacAddress& address) const @@ -159,12 +159,18 @@ class Ieee80211MgmtApLifecycleTest : public cSimpleModule wait(SimTime(15, SIMTIME_US)); // wait until t=15us (AP is down) // Verify AP teardown: stations, AIDs, and peer HT states are completely cleared - ASSERT(mib->bssAccessPointData.stations.empty()); - ASSERT(mib->bssAccessPointData.associationIds.empty()); + ASSERT(mib->getBssAccessPointData().stations.empty()); + ASSERT(mib->getBssAccessPointData().associationIds.empty()); ASSERT(mgmt->isStaListEmpty()); ASSERT(!mgmt->hasPeerHtState(staAddress)); std::cout << "AP lifecycle teardown cleared all station, AID, and peer HT states.\n"; + ASSERT(!mib->hasActiveBss() && !mib->hasHtOperation()); + bool rejectedPreparation = false; + try { mgmt->prepareBss(); } + catch (const cRuntimeError&) { rejectedPreparation = true; } + ASSERT(rejectedPreparation && !mib->hasActiveBss()); + // 3. AP is restarted at t=20us via ScenarioManager wait(SimTime(10, SIMTIME_US)); // wait until t=25us (AP is restarted and up) diff --git a/tests/module/Ieee80211MgmtApMalformedHtCap_1.test b/tests/module/Ieee80211MgmtApMalformedHtCap_1.test index 7520d813245..ce5af20ec4a 100644 --- a/tests/module/Ieee80211MgmtApMalformedHtCap_1.test +++ b/tests/module/Ieee80211MgmtApMalformedHtCap_1.test @@ -62,7 +62,7 @@ class Ieee80211MgmtApMalformedHtCapAp : public Ieee80211MgmtAp { auto& sta = staList[address]; sta.address = address; - mib->bssAccessPointData.stations[address] = Ieee80211Mib::AUTHENTICATED; + mib->setPeerAssociationStatus(address, Ieee80211Mib::AUTHENTICATED); } void submitAssociationRequest(const MacAddress& address, bool malformed, short exponent = 4) @@ -76,7 +76,7 @@ class Ieee80211MgmtApMalformedHtCapAp : public Ieee80211MgmtAp rates.rate[0] = 1; body->setSupportedRates(rates); body->setHtCapabilitiesPresent(true); - auto elem = makeHtCapabilitiesElement(mib->localHtCapabilities); + auto elem = makeHtCapabilitiesElement(mib->getLocalHtCapabilities()); if (malformed) elem.maxAmpduLengthExponent = exponent; body->setHtCapabilities(elem); @@ -118,7 +118,7 @@ class Ieee80211MgmtApMalformedHtCapAp : public Ieee80211MgmtAp rates.rate[0] = 1; body->setSupportedRates(rates); body->setHtCapabilitiesPresent(true); - auto elem = makeHtCapabilitiesElement(mib->localHtCapabilities); + auto elem = makeHtCapabilitiesElement(mib->getLocalHtCapabilities()); if (malformed) elem.maxAmpduLengthExponent = exponent; body->setHtCapabilities(elem); @@ -180,9 +180,9 @@ class Ieee80211MgmtApMalformedHtCapTest : public cSimpleModule ASSERT(mgmt->hasPendingAssociation(sta1)); mgmt->finishResponse(sta1, ST_ASSOCIATIONRESPONSE); ASSERT(!mgmt->hasPendingAssociation(sta1)); - ASSERT(mib->bssAccessPointData.stations.at(sta1) == Ieee80211Mib::AUTHENTICATED); + ASSERT(mib->getBssAccessPointData().stations.at(sta1) == Ieee80211Mib::AUTHENTICATED); ASSERT(mib->findPeerHtState(sta1) == nullptr); - ASSERT(mib->bssAccessPointData.associationIds.find(sta1) == mib->bssAccessPointData.associationIds.end()); + ASSERT(mib->getBssAccessPointData().associationIds.find(sta1) == mib->getBssAccessPointData().associationIds.end()); std::cout << "Malformed AssociationRequest (exponent 4) refused with SC_UNSUP_CAP.\n"; // 2. Malformed AssociationRequest (exponent = -1) refused with SC_UNSUP_CAP @@ -193,7 +193,7 @@ class Ieee80211MgmtApMalformedHtCapTest : public cSimpleModule ASSERT(mgmt->hasPendingAssociation(sta1)); mgmt->finishResponse(sta1, ST_ASSOCIATIONRESPONSE); ASSERT(!mgmt->hasPendingAssociation(sta1)); - ASSERT(mib->bssAccessPointData.stations.at(sta1) == Ieee80211Mib::AUTHENTICATED); + ASSERT(mib->getBssAccessPointData().stations.at(sta1) == Ieee80211Mib::AUTHENTICATED); ASSERT(mib->findPeerHtState(sta1) == nullptr); std::cout << "Malformed AssociationRequest (exponent -1) refused with SC_UNSUP_CAP.\n"; @@ -204,7 +204,7 @@ class Ieee80211MgmtApMalformedHtCapTest : public cSimpleModule ASSERT(mgmt->sentFrames.back().aid > 0); mgmt->finishResponse(sta1, ST_ASSOCIATIONRESPONSE); ASSERT(!mgmt->hasPendingAssociation(sta1)); - ASSERT(mib->bssAccessPointData.stations.at(sta1) == Ieee80211Mib::ASSOCIATED); + ASSERT(mib->getBssAccessPointData().stations.at(sta1) == Ieee80211Mib::ASSOCIATED); ASSERT(mib->findPeerHtState(sta1) != nullptr); std::cout << "Valid AssociationRequest succeeded with SC_SUCCESSFUL.\n"; @@ -217,9 +217,9 @@ class Ieee80211MgmtApMalformedHtCapTest : public cSimpleModule ASSERT(mgmt->hasPendingAssociation(sta1)); mgmt->finishResponse(sta1, ST_REASSOCIATIONRESPONSE); ASSERT(!mgmt->hasPendingAssociation(sta1)); - ASSERT(mib->bssAccessPointData.stations.at(sta1) == Ieee80211Mib::AUTHENTICATED); + ASSERT(mib->getBssAccessPointData().stations.at(sta1) == Ieee80211Mib::AUTHENTICATED); ASSERT(mib->findPeerHtState(sta1) == nullptr); - ASSERT(mib->bssAccessPointData.associationIds.find(sta1) == mib->bssAccessPointData.associationIds.end()); + ASSERT(mib->getBssAccessPointData().associationIds.find(sta1) == mib->getBssAccessPointData().associationIds.end()); std::cout << "Malformed ReassociationRequest (exponent 4) refused with SC_UNSUP_CAP and cleared association.\n"; // 5. Malformed ReassociationRequest (exponent = -1) from authenticated station @@ -231,12 +231,12 @@ class Ieee80211MgmtApMalformedHtCapTest : public cSimpleModule ASSERT(mgmt->hasPendingAssociation(sta2)); mgmt->finishResponse(sta2, ST_REASSOCIATIONRESPONSE); ASSERT(!mgmt->hasPendingAssociation(sta2)); - ASSERT(mib->bssAccessPointData.stations.at(sta2) == Ieee80211Mib::AUTHENTICATED); + ASSERT(mib->getBssAccessPointData().stations.at(sta2) == Ieee80211Mib::AUTHENTICATED); ASSERT(mib->findPeerHtState(sta2) == nullptr); std::cout << "Malformed ReassociationRequest (exponent -1) refused with SC_UNSUP_CAP.\n"; // 6. Malformed AssociationRequest with contradictory Tx MCS set fields refused with SC_UNSUP_CAP - auto badTxElem = makeHtCapabilitiesElement(mib->localHtCapabilities); + auto badTxElem = makeHtCapabilitiesElement(mib->getLocalHtCapabilities()); badTxElem.txMcsSetDefined = false; badTxElem.txRxMcsSetNotEqual = true; mgmt->submitAssociationRequestWithElement(sta1, badTxElem); @@ -246,7 +246,7 @@ class Ieee80211MgmtApMalformedHtCapTest : public cSimpleModule ASSERT(mgmt->hasPendingAssociation(sta1)); mgmt->finishResponse(sta1, ST_ASSOCIATIONRESPONSE); ASSERT(!mgmt->hasPendingAssociation(sta1)); - ASSERT(mib->bssAccessPointData.stations.at(sta1) == Ieee80211Mib::AUTHENTICATED); + ASSERT(mib->getBssAccessPointData().stations.at(sta1) == Ieee80211Mib::AUTHENTICATED); ASSERT(mib->findPeerHtState(sta1) == nullptr); std::cout << "Malformed AssociationRequest (contradictory Tx MCS) refused with SC_UNSUP_CAP.\n"; } diff --git a/tests/module/Ieee80211MgmtApQueueDrop_1.test b/tests/module/Ieee80211MgmtApQueueDrop_1.test index 44a499578c2..7a4444bd1ad 100644 --- a/tests/module/Ieee80211MgmtApQueueDrop_1.test +++ b/tests/module/Ieee80211MgmtApQueueDrop_1.test @@ -36,7 +36,7 @@ class TestIeee80211MgmtApQueueDrop : public Ieee80211MgmtAp Enter_Method("markAuthenticated"); auto& sta = staList[address]; sta.address = address; - mib->bssAccessPointData.stations[address] = Ieee80211Mib::AUTHENTICATED; + mib->setPeerAssociationStatus(address, Ieee80211Mib::AUTHENTICATED); } void submitAssociationRequest(const MacAddress& address) @@ -66,7 +66,7 @@ class TestIeee80211MgmtApQueueDrop : public Ieee80211MgmtAp short reserveAssociationId(const MacAddress& address) { return mib->reserveAssociationId(address); } void cancelAssociationIdReservation(const MacAddress& address) { mib->cancelAssociationIdReservation(address); } - Ieee80211Mib::BssMemberStatus getStationStatus(const MacAddress& address) const { return mib->bssAccessPointData.stations.at(address); } + Ieee80211Mib::BssMemberStatus getStationStatus(const MacAddress& address) const { return mib->getBssAccessPointData().stations.at(address); } protected: virtual void frameTransmissionFinished(const Packet *frame, FrameTransmissionStatus status) override diff --git a/tests/module/Ieee80211MgmtApReassociationSnapshot_1.test b/tests/module/Ieee80211MgmtApReassociationSnapshot_1.test index fa3e2efcbd0..6499d04c5d2 100644 --- a/tests/module/Ieee80211MgmtApReassociationSnapshot_1.test +++ b/tests/module/Ieee80211MgmtApReassociationSnapshot_1.test @@ -41,7 +41,7 @@ class Ieee80211MgmtApReassociationSnapshotAp : public Ieee80211MgmtAp { auto& sta = staList[address]; sta.address = address; - mib->bssAccessPointData.stations[address] = Ieee80211Mib::AUTHENTICATED; + mib->setPeerAssociationStatus(address, Ieee80211Mib::AUTHENTICATED); } void submitReassociationRequest(const MacAddress& address) @@ -56,7 +56,7 @@ class Ieee80211MgmtApReassociationSnapshotAp : public Ieee80211MgmtAp rates.rate[0] = 1; body->setSupportedRates(rates); body->setHtCapabilitiesPresent(true); - body->setHtCapabilities(makeHtCapabilitiesElement(mib->localHtCapabilities)); + body->setHtCapabilities(makeHtCapabilitiesElement(mib->getLocalHtCapabilities())); body->setChunkLength(B(1)); packet->insertAtBack(body); auto header = makeShared(); @@ -93,6 +93,30 @@ class Ieee80211MgmtApReassociationSnapshotAp : public Ieee80211MgmtAp Define_Module(Ieee80211MgmtApReassociationSnapshotAp); +class ApCommitObserver : public cListener +{ + public: + Ieee80211MgmtApReassociationSnapshotAp *management; + MacAddress peer; + int notifications = 0; + bool expectPending = true; + ApCommitObserver(Ieee80211MgmtApReassociationSnapshotAp *management, const MacAddress& peer) : management(management), peer(peer) {} + virtual void receiveSignal(cComponent *source, simsignal_t, bool active, cObject *) override + { + auto *mib = check_and_cast(source); + notifications++; + ASSERT(active && mib->hasHtOperation()); + ASSERT(mib->getOperationBand() != nullptr); + ASSERT(mib->requirePrimaryChannel() == mib->getHtOperation().primaryChannel); + ASSERT(management->hasPendingAssociation(peer) == expectPending); + if (!expectPending) { + ASSERT(mib->getPeerAssociationStatus(peer) == Ieee80211Mib::ASSOCIATED); + ASSERT(mib->getBssAccessPointData().associationIds.at(peer) > 0); + ASSERT(mib->findPeerHtState(peer) != nullptr); + } + } +}; + class Ieee80211MgmtApReassociationSnapshotTest : public cSimpleModule { public: @@ -111,15 +135,28 @@ class Ieee80211MgmtApReassociationSnapshotTest : public cSimpleModule ASSERT(mgmt->getAdvertisedPrimaryChannel() == 7); ASSERT(mib->requirePrimaryChannel() == 6); + ApCommitObserver observer(mgmt, staAddress); + mib->subscribe(Ieee80211Mib::bssStateChangedSignal, &observer); radio->setChannelNumber(11); ASSERT(mib->requirePrimaryChannel() == 11); + observer.expectPending = false; mgmt->finishReassociationResponse(staAddress); ASSERT(!mgmt->hasPendingAssociation(staAddress)); - ASSERT(mib->bssAccessPointData.stations.at(staAddress) == Ieee80211Mib::ASSOCIATED); + ASSERT(mib->getBssAccessPointData().stations.at(staAddress) == Ieee80211Mib::ASSOCIATED); const auto *peerState = mib->findPeerHtState(staAddress); ASSERT(peerState != nullptr); - ASSERT(peerState->negotiatedCapabilities.operation.primaryChannel == 11); + ASSERT(mib->getHtOperation().primaryChannel == 11); + auto previous = peerState->negotiatedCapabilities; + auto previousAid = mib->getBssAccessPointData().associationIds.at(staAddress); + mgmt->submitReassociationRequest(staAddress); + ASSERT(mib->findPeerHtState(staAddress)->negotiatedCapabilities == previous); + mgmt->finishReassociationResponse(staAddress); + ASSERT(mib->findPeerHtState(staAddress)->negotiatedCapabilities != previous); + ASSERT(mib->getBssAccessPointData().associationIds.at(staAddress) == previousAid); + ASSERT(observer.notifications == 3); + mib->unsubscribe(Ieee80211Mib::bssStateChangedSignal, &observer); + std::cout << "AP observers see completed transactions; equal same-address replacement creates a fresh capability cache.\n"; std::cout << "AP reassociation reconciles committed HT Operation with current channel after channel change.\n"; } }; @@ -188,3 +225,6 @@ record-scalar-results = false %contains: stdout AP reassociation reconciles committed HT Operation with current channel after channel change. + +%contains: stdout +AP observers see completed transactions; equal same-address replacement creates a fresh capability cache. diff --git a/tests/module/Ieee80211MgmtApTimeout_1.test b/tests/module/Ieee80211MgmtApTimeout_1.test index 54b9bfe4916..527fc8e7ce5 100644 --- a/tests/module/Ieee80211MgmtApTimeout_1.test +++ b/tests/module/Ieee80211MgmtApTimeout_1.test @@ -91,7 +91,7 @@ class TestIeee80211MgmtAp : public Ieee80211MgmtAp auto& sta = staList[address]; sta.address = address; clearPendingAssociation(&sta); - mib->bssAccessPointData.stations[address] = status; + mib->setPeerAssociationStatus(address, status); short aid = mib->reserveAssociationId(address); sta.pendingAssociationSuccessful = true; sta.pendingAssociationTransactionId = createAssociationTransactionId(); @@ -105,9 +105,9 @@ class TestIeee80211MgmtAp : public Ieee80211MgmtAp sta.address = address; clearPendingAssociation(&sta); short aid = mib->allocateAssociationId(address); - mib->bssAccessPointData.stations[address] = Ieee80211Mib::ASSOCIATED; - if (mib->isHtOperationSupported()) - mib->setPeerHtCapabilities(address, mib->localHtCapabilities, mib->getHtOperation()); + mib->setPeerAssociationStatus(address, Ieee80211Mib::ASSOCIATED); + if (mib->isLocalHtCapable()) + mib->setPeerHtCapabilities(address, mib->getLocalHtCapabilities()); ASSERT(mib->reserveAssociationId(address) == aid); sta.pendingAssociationSuccessful = true; sta.pendingAssociationTransactionId = createAssociationTransactionId(); @@ -158,15 +158,15 @@ class TestIeee80211MgmtAp : public Ieee80211MgmtAp short reserveAssociationId(const MacAddress& address) { return mib->reserveAssociationId(address); } void cancelAssociationIdReservation(const MacAddress& address) { mib->cancelAssociationIdReservation(address); } - bool hasCommittedAssociationId(const MacAddress& address) const { return mib->bssAccessPointData.associationIds.find(address) != mib->bssAccessPointData.associationIds.end(); } + bool hasCommittedAssociationId(const MacAddress& address) const { return mib->getBssAccessPointData().associationIds.find(address) != mib->getBssAccessPointData().associationIds.end(); } bool hasPeerHtState(const MacAddress& address) const { return mib->findPeerHtState(address) != nullptr; } - short getCommittedAssociationId(const MacAddress& address) const { return mib->bssAccessPointData.associationIds.at(address); } - Ieee80211Mib::BssMemberStatus getStationStatus(const MacAddress& address) const { return mib->bssAccessPointData.stations.at(address); } + short getCommittedAssociationId(const MacAddress& address) const { return mib->getBssAccessPointData().associationIds.at(address); } + Ieee80211Mib::BssMemberStatus getStationStatus(const MacAddress& address) const { return mib->getBssAccessPointData().stations.at(address); } void markAuthenticated(const MacAddress& address) { auto& sta = staList[address]; sta.address = address; - mib->bssAccessPointData.stations[address] = Ieee80211Mib::AUTHENTICATED; + mib->setPeerAssociationStatus(address, Ieee80211Mib::AUTHENTICATED); } void setAuthenticationSequenceExpected(const MacAddress& address, int sequenceNumber) diff --git a/tests/module/Ieee80211MgmtStaBeaconUpdate_1.test b/tests/module/Ieee80211MgmtStaBeaconUpdate_1.test index af38f0eb4a8..2c48332ac68 100644 --- a/tests/module/Ieee80211MgmtStaBeaconUpdate_1.test +++ b/tests/module/Ieee80211MgmtStaBeaconUpdate_1.test @@ -47,18 +47,22 @@ class TestIeee80211MgmtStaBeaconUpdate : public Ieee80211MgmtSta associationConfirms++; } + Ieee80211HtOperation testOperation; + void setPrimaryChannelForTest(int channel) { Enter_Method("setPrimaryChannelForTest"); - mib->setPrimaryChannel(channel); + testOperation.primaryChannel = channel; } void enable40MhzLocalCapabilities() { Enter_Method("enable40MhzLocalCapabilities"); - mib->localHtCapabilities.supportedChannelWidths.insert(MHz(40)); - mib->localHtCapabilities.shortGi20 = true; - mib->localHtCapabilities.shortGi40 = true; + auto capabilities = mib->getLocalHtCapabilities(); + capabilities.supportedChannelWidths.insert(MHz(40)); + capabilities.shortGi20 = true; + capabilities.shortGi40 = true; + mib->installLocalHtCapabilities(capabilities, true); } const ApInfo *getCachedAp(const MacAddress& address) const @@ -66,7 +70,7 @@ class TestIeee80211MgmtStaBeaconUpdate : public Ieee80211MgmtSta return const_cast(this)->lookupAP(address); } - bool isAssociatedForTest() const { return mib->bssStationData.isAssociated; } + bool isAssociatedForTest() const { return mib->getBssStationData().isAssociated; } const AssociatedApInfo& getAssocApForTest() const { return assocAP; } bool hasPeerHtState(const MacAddress& address) const { return mib->findPeerHtState(address) != nullptr; } const Ieee80211Mib::PeerHtState *getPeerHtState(const MacAddress& address) const { return mib->findPeerHtState(address); } @@ -82,7 +86,7 @@ class TestIeee80211MgmtStaBeaconUpdate : public Ieee80211MgmtSta apList.push_back(ApInfo()); ap = &apList.back(); ap->address = address; - ap->channel = mib->getHtOperation().primaryChannel; + ap->channel = testOperation.primaryChannel; ap->ssid = "test-bss"; ap->beaconInterval = SimTime(100, SIMTIME_MS); } @@ -109,8 +113,8 @@ class TestIeee80211MgmtStaBeaconUpdate : public Ieee80211MgmtSta rates.rate[0] = 6; body->setSupportedRates(rates); - auto capabilities = mib->localHtCapabilities; - auto operation = mib->getHtOperation(); + Ieee80211HtCapabilities capabilities; + auto operation = testOperation; operation.operatingChannelWidth = MHz(responseOperatingChannelWidth); operation.secondaryChannelOffset = responseSecondaryChannelOffset; capabilities.supportedChannelWidths.insert(MHz(20)); @@ -130,7 +134,7 @@ class TestIeee80211MgmtStaBeaconUpdate : public Ieee80211MgmtSta body->setChunkLength(B(9) + getHtMgmtElementsLength(body)); auto packet = new Packet("AssociationResponse"); packet->insertAtBack(body); - int channelNumber = mib->getHtOperation().primaryChannel; + int channelNumber = testOperation.primaryChannel; physicallayer::Ieee80211Channel channel(band, channelNumber); packet->addTag()->setChannel(&channel); auto header = makeShared(); @@ -152,7 +156,7 @@ class TestIeee80211MgmtStaBeaconUpdate : public Ieee80211MgmtSta Enter_Method("deliverBeaconFrame"); auto body = makeShared(); body->setSSID("test-bss"); - int primaryChannel = mib->getHtOperation().primaryChannel; + int primaryChannel = testOperation.primaryChannel; body->setChannelNumber(primaryChannel); body->setBeaconInterval(beaconInterval); Ieee80211SupportedRatesElement rates; @@ -163,8 +167,8 @@ class TestIeee80211MgmtStaBeaconUpdate : public Ieee80211MgmtSta if (htPresent) { Ieee80211HtCapabilities capabilities; capabilities.supportedChannelWidths.insert(MHz(20)); - if (operatingChannelWidth == 40) - capabilities.supportedChannelWidths.insert(MHz(40)); + // Capability stays 20/40 MHz when only BSS operation narrows. + capabilities.supportedChannelWidths.insert(MHz(40)); capabilities.shortGi20 = shortGi20; capabilities.shortGi40 = shortGi40; int maxMcs = -1; @@ -218,7 +222,7 @@ class TestIeee80211MgmtStaBeaconUpdate : public Ieee80211MgmtSta Enter_Method("deliverProbeResponseFrame"); auto body = makeShared(); body->setSSID("test-bss"); - int primaryChannel = mib->getHtOperation().primaryChannel; + int primaryChannel = testOperation.primaryChannel; body->setChannelNumber(primaryChannel); body->setBeaconInterval(SimTime(100, SIMTIME_MS)); Ieee80211SupportedRatesElement rates; @@ -263,6 +267,38 @@ class TestIeee80211MgmtStaBeaconUpdate : public Ieee80211MgmtSta Define_Module(TestIeee80211MgmtStaBeaconUpdate); +// Synchronous observer checks the production commit boundary, including the +// owning management module's timer and accepted snapshot, without retaining borrows. +class BssCommitObserver : public cListener +{ + public: + TestIeee80211MgmtStaBeaconUpdate *management; + int notifications = 0; + explicit BssCommitObserver(TestIeee80211MgmtStaBeaconUpdate *management) : management(management) {} + virtual void receiveSignal(cComponent *source, simsignal_t, bool active, cObject *) override + { + auto *mib = check_and_cast(source); + notifications++; + ASSERT(active && mib->hasActiveBss()); + ASSERT(mib->getBssStationData().isAssociated); + const auto& accepted = management->getAssocApForTest(); + ASSERT(mib->getBssData().bssid == accepted.address); + ASSERT(mib->getBssData().ssid == accepted.ssid); + ASSERT(mib->getOperationBand() != nullptr); + ASSERT(mib->requirePrimaryChannel() == accepted.channel); + ASSERT(mib->hasHtOperation() == accepted.htOperationPresent); + if (mib->hasHtOperation()) + ASSERT(mib->getHtOperation() == accepted.htOperation); + ASSERT(management->hasBeaconTimeoutForTest()); + ASSERT(management->getBeaconTimeoutArrivalForTest() == simTime() + 3.5 * accepted.beaconInterval); + // The documented notification contract rejects nested state mutations. + bool rejected = false; + try { mib->clearBss(); } + catch (const cRuntimeError&) { rejected = true; } + ASSERT(rejected && mib->hasActiveBss()); + } +}; + class Ieee80211MgmtStaBeaconUpdateTest : public cSimpleModule { public: @@ -272,6 +308,7 @@ class Ieee80211MgmtStaBeaconUpdateTest : public cSimpleModule virtual void activity() override { auto mgmt = check_and_cast(getModuleByPath("^.sta.wlan[0].mgmt")); + auto staMib = check_and_cast(getModuleByPath("^.sta.wlan[0].mib")); auto dcfRateSelection = check_and_cast(getModuleByPath("^.sta.wlan[0].mac.dcf.rateSelection")); auto qosRateSelection = check_and_cast(getModuleByPath("^.sta.wlan[0].mac.hcf.rateSelection")); const auto *modeSet = physicallayer::Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"); @@ -296,6 +333,9 @@ class Ieee80211MgmtStaBeaconUpdateTest : public cSimpleModule } }; + BssCommitObserver first(mgmt), second(mgmt); + staMib->subscribe(Ieee80211Mib::bssStateChangedSignal, &first); + staMib->subscribe(Ieee80211Mib::bssStateChangedSignal, &second); mgmt->setPrimaryChannelForTest(6); mgmt->enable40MhzLocalCapabilities(); @@ -318,6 +358,8 @@ class Ieee80211MgmtStaBeaconUpdateTest : public cSimpleModule // Verify rate selection selects 40 MHz Short GI MCS 7 (150 Mbps) checkUnicastMode(MHz(40), true, 7); + auto initialCache = mgmt->getPeerHtState(apAddress)->negotiatedCapabilities; + // 3. Subsequent Beacon from associated AP switches HT Operation to 20 MHz wait(SimTime(1, SIMTIME_US)); mgmt->deliverBeaconFrame(apAddress, true, 20, 0, true, true); @@ -325,17 +367,33 @@ class Ieee80211MgmtStaBeaconUpdateTest : public cSimpleModule ASSERT(mgmt->hasPeerHtState(apAddress)); ASSERT(mgmt->getAssocApForTest().htOperation.operatingChannelWidth == MHz(20)); ASSERT(mgmt->getAssocApForTest().htOperation.secondaryChannelOffset == 0); - ASSERT(mgmt->getPeerHtState(apAddress)->negotiatedCapabilities.operation.operatingChannelWidth == MHz(20)); + ASSERT(staMib->getHtOperation().operatingChannelWidth == MHz(20)); + ASSERT(mgmt->getPeerHtState(apAddress)->negotiatedCapabilities == initialCache); // Rate selection must demote to 20 MHz Short GI MCS 7; cannot retain obsolete 40 MHz constraint checkUnicastMode(MHz(20), true, 7); + ASSERT(first.notifications == 2 && second.notifications == 2); + // Equal accepted information refreshes liveness, without rederivation or publication. + auto deadlineBeforeEqual = mgmt->getBeaconTimeoutArrivalForTest(); + wait(SimTime(1, SIMTIME_US)); + mgmt->deliverBeaconFrame(apAddress, true, 20, 0, true, true); + ASSERT(mgmt->getBeaconTimeoutArrivalForTest() > deadlineBeforeEqual); + ASSERT(first.notifications == 2 && second.notifications == 2); + ASSERT(mgmt->getPeerHtState(apAddress)->negotiatedCapabilities == initialCache); + // Reverse registration order for the remaining changed and rejected inputs. + staMib->unsubscribe(Ieee80211Mib::bssStateChangedSignal, &first); + staMib->unsubscribe(Ieee80211Mib::bssStateChangedSignal, &second); + staMib->subscribe(Ieee80211Mib::bssStateChangedSignal, &second); + staMib->subscribe(Ieee80211Mib::bssStateChangedSignal, &first); + // 4. Subsequent Beacon disables Short Guard Interval (20 MHz, Long GI) wait(SimTime(1, SIMTIME_US)); mgmt->deliverBeaconFrame(apAddress, true, 20, 0, false, false); ASSERT(mgmt->isAssociatedForTest()); ASSERT(mgmt->hasPeerHtState(apAddress)); ASSERT(!mgmt->getAssocApForTest().htCapabilities.shortGi20); - ASSERT(!mgmt->getPeerHtState(apAddress)->negotiatedCapabilities.localTxPeerRx.receiverShortGi20); + ASSERT(!mgmt->getPeerHtState(apAddress)->negotiatedCapabilities->localTxPeerRx.receiverShortGi20); + ASSERT(mgmt->getPeerHtState(apAddress)->negotiatedCapabilities != initialCache); // Rate selection must demote to 20 MHz Long GI MCS 7; cannot retain obsolete Short GI constraint checkUnicastMode(MHz(20), false, 7); @@ -346,8 +404,9 @@ class Ieee80211MgmtStaBeaconUpdateTest : public cSimpleModule ASSERT(mgmt->hasPeerHtState(apAddress)); ASSERT(mgmt->getAssocApForTest().htCapabilities.rxMcsSupported[0]); ASSERT(!mgmt->getAssocApForTest().htCapabilities.rxMcsSupported[7]); - ASSERT(mgmt->getPeerHtState(apAddress)->negotiatedCapabilities.localTxPeerRx.supportedMcs[0]); - ASSERT(!mgmt->getPeerHtState(apAddress)->negotiatedCapabilities.localTxPeerRx.supportedMcs[7]); + ASSERT(mgmt->getPeerHtState(apAddress)->negotiatedCapabilities->localTxPeerRx.supportedMcs[0]); + ASSERT(!mgmt->getPeerHtState(apAddress)->negotiatedCapabilities->localTxPeerRx.supportedMcs[7]); + ASSERT(mgmt->getPeerHtState(apAddress)->negotiatedCapabilities != initialCache); // Rate selection must demote to MCS 0; cannot retain obsolete MCS 7 constraint checkUnicastMode(MHz(20), false, 0); @@ -372,11 +431,20 @@ class Ieee80211MgmtStaBeaconUpdateTest : public cSimpleModule ASSERT(mgmt->hasBeaconTimeoutForTest()); ASSERT(mgmt->getAssocApForTest().htOperationPresent); ASSERT(mgmt->getAssocApForTest().htOperation.basicMcsSupported[32]); - // MIB peer HT state remains absent because Basic MCS is unsupported + // Accepted capabilities remain cached, but HT is ineligible because Basic MCS is unsupported ASSERT(!mgmt->hasPeerHtState(apAddress)); // Rate selection remains on legacy mode checkUnicastMode(MHz(20), false, -1); + // A Basic-MCS-only recovery retains accepted capability knowledge. + auto temporarilyIneligibleCache = staMib->findPeerCapabilities(apAddress)->negotiatedCapabilities; + wait(SimTime(1, SIMTIME_US)); + mgmt->deliverBeaconFrame(apAddress, true, 20, 0, true, false, + {0, 1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7}); + ASSERT(mgmt->hasPeerHtState(apAddress)); + ASSERT(staMib->findPeerCapabilities(apAddress)->negotiatedCapabilities == temporarilyIneligibleCache); + checkUnicastMode(MHz(20), true, 7); + // 8. Subsequent Beacon restores full 40 MHz Short GI HT advertisement + updates beacon interval wait(SimTime(1, SIMTIME_US)); simtime_t newBeaconInterval = SimTime(200, SIMTIME_MS); @@ -393,6 +461,8 @@ class Ieee80211MgmtStaBeaconUpdateTest : public cSimpleModule // Rate selection restores 40 MHz Short GI MCS 7 (150 Mbps) checkUnicastMode(MHz(40), true, 7); + int notificationsBeforeRejected = first.notifications; + // 9. Subsequent malformed Beacon (invalid secondaryChannelOffset = 2) is rejected wait(SimTime(1, SIMTIME_US)); simtime_t deadlineBeforeMalformed = mgmt->getBeaconTimeoutArrivalForTest(); @@ -411,9 +481,14 @@ class Ieee80211MgmtStaBeaconUpdateTest : public cSimpleModule // Cached AP list has the probe response, but assocAP and MIB peer HT state retain 40 MHz SGI ASSERT(mgmt->getCachedAp(apAddress)->htOperation.operatingChannelWidth == MHz(20)); ASSERT(mgmt->getAssocApForTest().htOperation.operatingChannelWidth == MHz(40)); - ASSERT(mgmt->getPeerHtState(apAddress)->negotiatedCapabilities.operation.operatingChannelWidth == MHz(40)); + ASSERT(staMib->getHtOperation().operatingChannelWidth == MHz(40)); checkUnicastMode(MHz(40), true, 7); + ASSERT(first.notifications == notificationsBeforeRejected); + ASSERT(first.notifications == second.notifications); + staMib->unsubscribe(Ieee80211Mib::bssStateChangedSignal, &first); + staMib->unsubscribe(Ieee80211Mib::bssStateChangedSignal, &second); + std::cout << "Atomic BSS observers, equal-beacon liveness, and reentrancy contract verified.\n"; std::cout << "Subsequent Beacon updates authoritative associated-AP snapshot and MIB peer HT state.\n"; std::cout << "Unicast rate selection follows dynamic channel-width, guard-interval, and MCS updates.\n"; std::cout << "Legacy and unusable HT updates preserve association while falling back to legacy modes.\n"; @@ -502,3 +577,6 @@ Unicast rate selection follows dynamic channel-width, guard-interval, and MCS up %contains: stdout Legacy and unusable HT updates preserve association while falling back to legacy modes. + +%contains: stdout +Atomic BSS observers, equal-beacon liveness, and reentrancy contract verified. diff --git a/tests/module/Ieee80211MgmtStaDeauthentication_1.test b/tests/module/Ieee80211MgmtStaDeauthentication_1.test index 956c1db7728..84edea91127 100644 --- a/tests/module/Ieee80211MgmtStaDeauthentication_1.test +++ b/tests/module/Ieee80211MgmtStaDeauthentication_1.test @@ -42,11 +42,11 @@ class TestIeee80211MgmtSta : public Ieee80211MgmtSta void setAssociated(const MacAddress& address, bool cacheAp = true) { Enter_Method("setAssociated"); - ASSERT(!mib->bssStationData.isAssociated); + ASSERT(!mib->getBssStationData().isAssociated); if (cacheAp) ensureAccessPoint(address); - mib->bssData.bssid = address; - mib->bssStationData.isAssociated = true; + mib->commitBss("SSID", address, nullptr, -1, nullptr); + mib->setAssociated(true); assocAP = AssociatedApInfo(); assocAP.address = address; assocAP.channel = 1; @@ -60,8 +60,10 @@ class TestIeee80211MgmtSta : public Ieee80211MgmtSta void installPeerHtState(const MacAddress& address) { Enter_Method("installPeerHtState"); - mib->setPrimaryChannel(1); - mib->setPeerHtCapabilities(address, mib->localHtCapabilities, mib->getHtOperation()); + Ieee80211HtOperation operation; + operation.primaryChannel = 1; + mib->commitBss("SSID", address, nullptr, 1, &operation); + mib->setPeerHtCapabilities(address, mib->getLocalHtCapabilities()); } void authenticatePeer(const MacAddress& address) @@ -90,7 +92,7 @@ class TestIeee80211MgmtSta : public Ieee80211MgmtSta bool hasPendingAssociation() const { return assocTimeoutMsg != nullptr; } bool isPendingAssociationScheduled() const { return assocTimeoutMsg != nullptr && assocTimeoutMsg->isScheduled(); } bool isReassociationPending() const { return reassociationInProgress; } - bool isAssociated() const { return mib->bssStationData.isAssociated; } + bool isAssociated() const { return mib->getBssStationData().isAssociated; } bool hasBeaconTimer() const { return assocAP.beaconTimeoutMsg != nullptr; } MacAddress getAssociatedAddress() const { return assocAP.address; } bool isAuthenticated(const MacAddress& address) const @@ -131,9 +133,9 @@ class TestIeee80211MgmtSta : public Ieee80211MgmtSta lastAssociationResult = resultCode; lastAssociationConfirmationSawTerminalState = !ap->isAuthenticated && assocTimeoutMsg == nullptr; confirmationSawClearedAssociation = confirmationSawClearedAssociation || - (!mib->bssStationData.isAssociated && assocAP.address.isUnspecified()); + (!mib->getBssStationData().isAssociated && assocAP.address.isUnspecified()); confirmationSawDeauthenticatedAssociation = confirmationSawDeauthenticatedAssociation || - (!mib->bssStationData.isAssociated && assocAP.address.isUnspecified() && !ap->isAuthenticated); + (!mib->getBssStationData().isAssociated && assocAP.address.isUnspecified() && !ap->isAuthenticated); } virtual void sendReassociationConfirm(ApInfo *ap, Ieee80211PrimResultCode resultCode) override @@ -143,9 +145,9 @@ class TestIeee80211MgmtSta : public Ieee80211MgmtSta lastReassociationResult = resultCode; lastReassociationConfirmationSawTerminalState = !ap->isAuthenticated && assocTimeoutMsg == nullptr; confirmationSawClearedAssociation = confirmationSawClearedAssociation || - (!mib->bssStationData.isAssociated && assocAP.address.isUnspecified()); + (!mib->getBssStationData().isAssociated && assocAP.address.isUnspecified()); confirmationSawDeauthenticatedAssociation = confirmationSawDeauthenticatedAssociation || - (!mib->bssStationData.isAssociated && assocAP.address.isUnspecified() && !ap->isAuthenticated); + (!mib->getBssStationData().isAssociated && assocAP.address.isUnspecified() && !ap->isAuthenticated); } }; diff --git a/tests/module/Ieee80211MgmtStaDisassociation_1.test b/tests/module/Ieee80211MgmtStaDisassociation_1.test index 87b77c7265b..90b660c82ee 100644 --- a/tests/module/Ieee80211MgmtStaDisassociation_1.test +++ b/tests/module/Ieee80211MgmtStaDisassociation_1.test @@ -43,10 +43,10 @@ class TestIeee80211MgmtSta : public Ieee80211MgmtSta void setAssociated(const MacAddress& address) { Enter_Method("setAssociated"); - ASSERT(!mib->bssStationData.isAssociated); + ASSERT(!mib->getBssStationData().isAssociated); ensureAccessPoint(address); - mib->bssData.bssid = address; - mib->bssStationData.isAssociated = true; + mib->commitBss("SSID", address, nullptr, -1, nullptr); + mib->setAssociated(true); assocAP = AssociatedApInfo(); assocAP.address = address; assocAP.channel = 1; @@ -85,7 +85,7 @@ class TestIeee80211MgmtSta : public Ieee80211MgmtSta bool hasPendingAssociation() const { return assocTimeoutMsg != nullptr; } bool isPendingAssociationScheduled() const { return assocTimeoutMsg != nullptr && assocTimeoutMsg->isScheduled(); } bool isReassociationPending() const { return reassociationInProgress; } - bool isAssociated() const { return mib->bssStationData.isAssociated; } + bool isAssociated() const { return mib->getBssStationData().isAssociated; } MacAddress getAssociatedAddress() const { return assocAP.address; } void deliverDisassociation(const MacAddress& transmitter, const MacAddress& address3) @@ -127,7 +127,7 @@ class TestIeee80211MgmtSta : public Ieee80211MgmtSta lastAssociationAddress = ap->address; lastAssociationResult = resultCode; confirmationSawClearedAssociation = confirmationSawClearedAssociation || - (!mib->bssStationData.isAssociated && assocAP.address.isUnspecified()); + (!mib->getBssStationData().isAssociated && assocAP.address.isUnspecified()); } virtual void sendReassociationConfirm(ApInfo *ap, Ieee80211PrimResultCode resultCode) override @@ -136,7 +136,7 @@ class TestIeee80211MgmtSta : public Ieee80211MgmtSta lastReassociationAddress = ap->address; lastReassociationResult = resultCode; confirmationSawClearedAssociation = confirmationSawClearedAssociation || - (!mib->bssStationData.isAssociated && assocAP.address.isUnspecified()); + (!mib->getBssStationData().isAssociated && assocAP.address.isUnspecified()); } }; diff --git a/tests/module/Ieee80211MgmtStaDiscovery_1.test b/tests/module/Ieee80211MgmtStaDiscovery_1.test index 530a1d23dbf..fc4650abf71 100644 --- a/tests/module/Ieee80211MgmtStaDiscovery_1.test +++ b/tests/module/Ieee80211MgmtStaDiscovery_1.test @@ -105,8 +105,8 @@ class TestIeee80211MgmtStaDiscovery : public Ieee80211MgmtSta rates.numRates = 1; rates.rate[0] = 6; body->setSupportedRates(rates); - setHtCapabilities(body, mib->localHtCapabilities); - setHtOperation(body, &physicallayer::Ieee80211CompliantBands::band2_4GHz, mib->getHtOperation()); + setHtCapabilities(body, mib->getLocalHtCapabilities()); + setHtOperation(body, &physicallayer::Ieee80211CompliantBands::band2_4GHz, getTestOperation()); if (malformedCapabilities) { auto capabilities = body->getHtCapabilities(); capabilities.maxAmpduLengthExponent = 4; @@ -122,7 +122,7 @@ class TestIeee80211MgmtStaDiscovery : public Ieee80211MgmtSta packet->insertAtBack(body); packet->addTag()->setPower(mW(1)); const auto *band = &physicallayer::Ieee80211CompliantBands::band2_4GHz; - physicallayer::Ieee80211Channel channel(band, mib->getHtOperation().primaryChannel); + physicallayer::Ieee80211Channel channel(band, getTestOperation().primaryChannel); if (channelIndicationPresent) packet->addTag()->setChannel(&channel); auto header = makeShared(); @@ -242,8 +242,8 @@ class TestIeee80211MgmtStaDiscovery : public Ieee80211MgmtSta rates.rate[0] = 6; body->setSupportedRates(rates); if (htForm != ResponseHtForm::LEGACY) { - auto capabilities = mib->localHtCapabilities; - auto operation = mib->getHtOperation(); + auto capabilities = mib->getLocalHtCapabilities(); + auto operation = getTestOperation(); if (responsePrimaryChannel >= 0) operation.primaryChannel = responsePrimaryChannel; if (responseOperatingChannelWidth == 20) @@ -281,7 +281,7 @@ class TestIeee80211MgmtStaDiscovery : public Ieee80211MgmtSta auto packet = new Packet("AssociationResponse"); packet->insertAtBack(body); const auto *band = &physicallayer::Ieee80211CompliantBands::band2_4GHz; - int channelNumber = responsePrimaryChannel >= 0 ? responsePrimaryChannel : mib->getHtOperation().primaryChannel; + int channelNumber = responsePrimaryChannel >= 0 ? responsePrimaryChannel : getTestOperation().primaryChannel; physicallayer::Ieee80211Channel channel(band, channelNumber); if (channelIndicationPresent) packet->addTag()->setChannel(&channel); @@ -294,13 +294,13 @@ class TestIeee80211MgmtStaDiscovery : public Ieee80211MgmtSta { Enter_Method("resetAssociationState"); cancelPendingAssociation(); - if (mib->bssStationData.isAssociated) + if (mib->getBssStationData().isAssociated) clearCurrentAssociation(); } bool hasPeerHtState(const MacAddress& address) const { return mib->findPeerHtState(address) != nullptr; } - bool isAssociatedForTest() const { return mib->bssStationData.isAssociated; } + bool isAssociatedForTest() const { return mib->getBssStationData().isAssociated; } const MacAddress& getAssociatedAddressForTest() const { return assocAP.address; } bool hasBeaconTimeoutForTest() const { return assocAP.beaconTimeoutMsg != nullptr; } simtime_t getBeaconTimeoutArrivalForTest() const { return assocAP.beaconTimeoutMsg->getArrivalTime(); } @@ -312,13 +312,17 @@ class TestIeee80211MgmtStaDiscovery : public Ieee80211MgmtSta void setLegacyForTest() { Enter_Method("setLegacyForTest"); - mib->localHtCapabilitiesValid = false; + mib->clearBss(); + mib->installLocalHtCapabilities(Ieee80211HtCapabilities(), false); } + Ieee80211HtOperation testOperation; + const Ieee80211HtOperation& getTestOperation() const { return testOperation; } + void setPrimaryChannelForTest(int channel) { Enter_Method("setPrimaryChannelForTest"); - mib->setPrimaryChannel(channel); + testOperation.primaryChannel = channel; } protected: @@ -445,7 +449,7 @@ class Ieee80211MgmtStaDiscoveryTest : public cSimpleModule, public cListener // reassociation to another AP keeps the current association and // restores the current AP's channel before reporting failure. mgmt->finishAuthenticationForTest(probeAddress); - staMib->setPeerHtCapabilities(probeAddress, staMib->localHtCapabilities, staMib->getHtOperation()); + staMib->setPeerHtCapabilities(probeAddress, staMib->getLocalHtCapabilities()); mgmt->clearChangedChannelsForTest(); mgmt->startReassociationForTest(probeAddress); mgmt->deliverResponse(probeAddress, true, SC_DATARATE_UNSUP, TestIeee80211MgmtStaDiscovery::ResponseHtForm::LEGACY); @@ -462,7 +466,7 @@ class Ieee80211MgmtStaDiscoveryTest : public cSimpleModule, public cListener ASSERT(mgmt->reassociationConfirms == 1); ASSERT(mgmt->lastReassociationResult == PRC_REFUSED); - staMib->setPeerHtCapabilities(probeAddress, staMib->localHtCapabilities, staMib->getHtOperation()); + staMib->setPeerHtCapabilities(probeAddress, staMib->getLocalHtCapabilities()); mgmt->clearChangedChannelsForTest(); mgmt->startReassociationForTest(probeAddress); mgmt->deliverReassociationTimeoutForTest(); @@ -500,7 +504,7 @@ class Ieee80211MgmtStaDiscoveryTest : public cSimpleModule, public cListener Ieee80211HtOperation sparseOperation; sparseOperation.operatingChannelWidth = MHz(20); sparseOperation.basicMcsSupported[0] = true; - staMib->setPeerHtCapabilities(address, sparsePeer, sparseOperation); + staMib->setPeerHtCapabilities(address, sparsePeer); const auto *dcfSparseMode = dcfRateSelection->computeMode(&ratePacket, dataHeader); const auto *qosSparseMode = qosRateSelection->computeMode(&ratePacket, dataHeader, nullptr); ASSERT(dcfSparseMode == qosSparseMode); @@ -541,7 +545,7 @@ class Ieee80211MgmtStaDiscoveryTest : public cSimpleModule, public cListener const auto *qosMulticastMode = qosRateSelection->computeMode(&ratePacket, multicastHeader, nullptr); ASSERT(dcfMulticastMode->getHtMcsIndex() >= 0); ASSERT(qosMulticastMode->getHtMcsIndex() >= 0); - staMib->setPeerHtCapabilities(address, staMib->localHtCapabilities, staMib->getHtOperation()); + staMib->setPeerHtCapabilities(address, staMib->getLocalHtCapabilities()); ASSERT(dcfRateSelection->computeMode(&ratePacket, dataHeader)->getHtMcsIndex() >= 0); ASSERT(qosRateSelection->computeMode(&ratePacket, dataHeader, nullptr)->getHtMcsIndex() >= 0); diff --git a/tests/module/Ieee80211MgmtStaLifecycle_1.test b/tests/module/Ieee80211MgmtStaLifecycle_1.test index 8873f0901e5..8d74d4b9d7b 100644 --- a/tests/module/Ieee80211MgmtStaLifecycle_1.test +++ b/tests/module/Ieee80211MgmtStaLifecycle_1.test @@ -46,7 +46,7 @@ class TestIeee80211MgmtSta : public Ieee80211MgmtSta ASSERT(apList.empty()); ASSERT(assocTimeoutMsg == nullptr); ASSERT(!reassociationInProgress); - ASSERT(!mib->bssStationData.isAssociated); + ASSERT(!mib->getBssStationData().isAssociated); ASSERT(assocAP.address.isUnspecified()); ASSERT(assocAP.beaconTimeoutMsg == nullptr); } @@ -86,7 +86,7 @@ class TestIeee80211MgmtSta : public Ieee80211MgmtSta { Enter_Method("startReassociationPhase"); assertCleanEntryState(); - ASSERT(mib->isHtOperationSupported()); + ASSERT(mib->isLocalHtCapable()); const auto currentAddress = getCurrentAccessPointAddress(); auto targetAp = addAccessPoint(getTargetAccessPointAddress(), true); @@ -94,9 +94,7 @@ class TestIeee80211MgmtSta : public Ieee80211MgmtSta // This is the coherent state produced by a successful Association // Response: an associated AP, negotiated peer HT state, and beacon // timeout. startReassociation() then enters the real pending path. - mib->bssData.ssid = "lifecycle-ssid"; - mib->bssData.bssid = currentAddress; - mib->bssStationData.isAssociated = true; + mib->setAssociated(true); assocAP = AssociatedApInfo(); assocAP.channel = 6; assocAP.address = currentAddress; @@ -109,7 +107,8 @@ class TestIeee80211MgmtSta : public Ieee80211MgmtSta Ieee80211HtOperation operation; operation.primaryChannel = 6; operation.basicMcsSupported[0] = true; - mib->setPeerHtCapabilities(currentAddress, mib->localHtCapabilities, operation); + mib->commitBss("lifecycle-ssid", currentAddress, nullptr, 6, &operation); + mib->setPeerHtCapabilities(currentAddress, mib->getLocalHtCapabilities()); startReassociation(targetAp, timerDelay); } @@ -125,13 +124,13 @@ class TestIeee80211MgmtSta : public Ieee80211MgmtSta bool hasPendingAssociation() const { return assocTimeoutMsg != nullptr; } bool hasBeaconTimer() const { return assocAP.beaconTimeoutMsg != nullptr; } bool hasCurrentPeerHtState() const { return mib->findPeerHtState(getCurrentAccessPointAddress()) != nullptr; } - bool isAssociated() const { return mib->bssStationData.isAssociated; } + bool isAssociated() const { return mib->getBssStationData().isAssociated; } bool isReassociationPending() const { return reassociationInProgress; } bool isScanningNow() const { return isScanning; } bool isApListEmpty() const { return apList.empty(); } MacAddress getAssociatedAddress() const { return assocAP.address; } - const std::string& getCachedSsid() const { return mib->bssData.ssid; } - MacAddress getCachedBssid() const { return mib->bssData.bssid; } + const std::string& getCachedSsid() const { return mib->getBssData().ssid; } + MacAddress getCachedBssid() const { return mib->getBssData().bssid; } }; Define_Module(TestIeee80211MgmtSta); diff --git a/tests/module/Ieee80211MgmtStaSimplifiedInitialization_1.test b/tests/module/Ieee80211MgmtStaSimplifiedInitialization_1.test index acc48310936..695c1203b7c 100644 --- a/tests/module/Ieee80211MgmtStaSimplifiedInitialization_1.test +++ b/tests/module/Ieee80211MgmtStaSimplifiedInitialization_1.test @@ -1,13 +1,16 @@ %description: Verify simplified station BSS identity is visible to automatic network -configuration while negotiated HT peer state is installed only at the last +configuration together with negotiated HT peer state, before the last initialization stage. Verify shutdown removes the AP-side association and HT peer state and startup restores both. Verify crash performs the same cleanup, -and remains safe when the recorded AP can no longer be resolved. +and remains safe when the recorded AP can no longer be resolved. Legacy stations +associated with HT APs retain BSS identity/channel without accepting HT operation, +in both declaration orders and across shutdown/crash/restart. %file: TestInitializationObserver.cc #include "inet/common/InitStages.h" +#include "inet/linklayer/ieee80211/mgmt/contract/IIeee80211BssProvider.h" #include "inet/linklayer/ieee80211/mib/Ieee80211Mib.h" #include "inet/networklayer/common/L3AddressResolver.h" #include "inet/networklayer/common/NetworkInterface.h" @@ -28,9 +31,38 @@ class TestInitializationObserver : public cSimpleModule protected: virtual int numInitStages() const override { return NUM_INIT_STAGES; } + void checkLegacyStations(bool associated) + { + for (const char *name : {"^.legacyFirst.wlan[0].mib", "^.legacySecond.wlan[0].mib"}) { + auto *sta = check_and_cast(getModuleByPath(name)); + auto *ap = check_and_cast(getModuleByPath("^.ap.wlan[0].mib")); + ASSERT(sta->hasPreparedLocalCapabilities() && !sta->isLocalHtCapable()); + ASSERT(ap->isLocalHtCapable() && ap->hasHtOperation()); + ASSERT(sta->hasActiveBss() == associated); + ASSERT(sta->getBssStationData().isAssociated == associated); + ASSERT(!sta->hasHtOperation()); + ASSERT(!sta->relationshipAllowsHt(ap->address)); + ASSERT(!ap->relationshipAllowsHt(sta->address)); + ASSERT(sta->findPeerCapabilities(ap->address) == nullptr); + ASSERT(ap->findPeerCapabilities(sta->address) == nullptr); + if (associated) { + ASSERT(sta->getBssData().bssid == ap->address); + ASSERT(sta->getBssData().ssid == ap->getBssData().ssid); + ASSERT(sta->hasPrimaryChannel() && sta->requirePrimaryChannel() == 6); + ASSERT(sta->getOperationBand() == ap->getOperationBand()); + ASSERT(ap->getPeerAssociationStatus(sta->address) == ieee80211::Ieee80211Mib::ASSOCIATED); + } + else { + ASSERT(!sta->hasPrimaryChannel()); + ASSERT(ap->getBssAccessPointData().stations.count(sta->address) == 0); + } + } + } + virtual void initialize(int stage) override { if (stage == INITSTAGE_NETWORK_CONFIGURATION || stage == INITSTAGE_LAST) { + checkLegacyStations(true); auto configurator = check_and_cast(getModuleByPath("^.configurator")); auto apInterface = check_and_cast(getModuleByPath("^.ap.wlan[0]")); auto staInterface = check_and_cast(getModuleByPath("^.sta.wlan[0]")); @@ -41,22 +73,33 @@ class TestInitializationObserver : public cSimpleModule ASSERT(apMib->hasPrimaryChannel()); ASSERT(apMib->requirePrimaryChannel() == 6); if (stage == INITSTAGE_NETWORK_CONFIGURATION) { - ASSERT(apMib->bssData.ssid == "review-ssid"); - ASSERT(staMib->bssData.ssid == apMib->bssData.ssid); + ASSERT(apMib->getBssData().ssid == "review-ssid"); + ASSERT(staMib->getBssData().ssid == apMib->getBssData().ssid); ASSERT(configurator->getTestWirelessId(apInterface) == configurator->getTestWirelessId(staInterface)); ASSERT(!staMib->address.isUnspecified()); - ASSERT(apMib->bssAccessPointData.stations.find(MacAddress::UNSPECIFIED_ADDRESS) == apMib->bssAccessPointData.stations.end()); - ASSERT(apMib->bssAccessPointData.stations.at(staMib->address) == ieee80211::Ieee80211Mib::ASSOCIATED); - ASSERT(apMib->findPeerHtState(staMib->address) == nullptr); - ASSERT(staMib->findPeerHtState(apMib->address) == nullptr); + ASSERT(apMib->getBssAccessPointData().stations.find(MacAddress::UNSPECIFIED_ADDRESS) == apMib->getBssAccessPointData().stations.end()); + ASSERT(apMib->getBssAccessPointData().stations.at(staMib->address) == ieee80211::Ieee80211Mib::ASSOCIATED); + ASSERT(apMib->findPeerHtState(staMib->address) != nullptr); + ASSERT(staMib->findPeerHtState(apMib->address) != nullptr); + auto *apFirst = check_and_cast(getModuleByPath("^.apFirst.wlan[0].mib")); + auto *staSecond = check_and_cast(getModuleByPath("^.staSecond.wlan[0].mib")); + ASSERT(staSecond->getBssData().ssid == apFirst->getBssData().ssid); + ASSERT(staSecond->findPeerHtState(apFirst->address) != nullptr); + ASSERT(apFirst->findPeerHtState(staSecond->address) != nullptr); + auto cache = apMib->findPeerHtState(staMib->address)->negotiatedCapabilities; + auto *provider = check_and_cast(apInterface->getSubmodule("mgmt")); + provider->prepareBss(); + provider->prepareBss(); + ASSERT(apMib->findPeerHtState(staMib->address)->negotiatedCapabilities == cache); + std::cout << "Both declaration orders and repeated BSS preparation verified.\n"; std::cout << "Simplified STA wireless identity available during network configuration.\n"; } else { ASSERT(apMib->findPeerHtState(staMib->address) != nullptr); ASSERT(staMib->findPeerHtState(apMib->address) != nullptr); - ASSERT(apMib->findPeerHtState(staMib->address)->negotiatedCapabilities.operation.primaryChannel == 6); - ASSERT(staMib->findPeerHtState(apMib->address)->negotiatedCapabilities.operation.primaryChannel == 6); - std::cout << "Simplified STA HT peer state installed at last initialization stage.\n"; + ASSERT(apMib->getHtOperation().primaryChannel == 6); + ASSERT(staMib->getHtOperation().primaryChannel == 6); + std::cout << "Simplified STA HT peer state remains ready at last initialization stage.\n"; scheduleAt(SimTime(1500, SIMTIME_NS), new cMessage("checkShutdown")); scheduleAt(SimTime(2500, SIMTIME_NS), new cMessage("checkShutdownRestart")); scheduleAt(SimTime(3500, SIMTIME_NS), new cMessage("checkResolvableCrash")); @@ -73,53 +116,58 @@ class TestInitializationObserver : public cSimpleModule auto staInterface = check_and_cast(getModuleByPath("^.sta.wlan[0]")); auto apMib = check_and_cast(apInterface->getSubmodule("mib")); auto staMib = check_and_cast(staInterface->getSubmodule("mib")); + checkLegacyStations(strcmp(message->getName(), "checkShutdown") != 0 && + strcmp(message->getName(), "checkResolvableCrash") != 0); if (!strcmp(message->getName(), "checkShutdown")) { - ASSERT(!staMib->bssStationData.isAssociated); - ASSERT(apMib->bssAccessPointData.stations.find(staMib->address) == apMib->bssAccessPointData.stations.end()); + ASSERT(!staMib->getBssStationData().isAssociated); + ASSERT(apMib->getBssAccessPointData().stations.find(staMib->address) == apMib->getBssAccessPointData().stations.end()); ASSERT(apMib->findPeerHtState(staMib->address) == nullptr); ASSERT(staMib->findPeerHtState(apMib->address) == nullptr); std::cout << "Simplified STA AP-side association and HT peer state removed after shutdown.\n"; } else if (!strcmp(message->getName(), "checkShutdownRestart")) { - ASSERT(staMib->bssStationData.isAssociated); - ASSERT(staMib->bssData.ssid == apMib->bssData.ssid); - ASSERT(apMib->bssAccessPointData.stations.at(staMib->address) == ieee80211::Ieee80211Mib::ASSOCIATED); + ASSERT(staMib->getBssStationData().isAssociated); + ASSERT(staMib->getBssData().ssid == apMib->getBssData().ssid); + ASSERT(apMib->getBssAccessPointData().stations.at(staMib->address) == ieee80211::Ieee80211Mib::ASSOCIATED); ASSERT(apMib->findPeerHtState(staMib->address) != nullptr); ASSERT(staMib->findPeerHtState(apMib->address) != nullptr); std::cout << "Simplified STA association and HT peer state restored after shutdown restart.\n"; } else if (!strcmp(message->getName(), "checkResolvableCrash")) { - ASSERT(!staMib->bssStationData.isAssociated); - ASSERT(apMib->bssAccessPointData.stations.find(staMib->address) == apMib->bssAccessPointData.stations.end()); + ASSERT(!staMib->getBssStationData().isAssociated); + ASSERT(apMib->getBssAccessPointData().stations.find(staMib->address) == apMib->getBssAccessPointData().stations.end()); ASSERT(apMib->findPeerHtState(staMib->address) == nullptr); ASSERT(staMib->findPeerHtState(apMib->address) == nullptr); std::cout << "Simplified STA AP-side association and HT peer state removed after resolvable crash.\n"; } else if (!strcmp(message->getName(), "checkCrashRestart")) { - ASSERT(staMib->bssStationData.isAssociated); - ASSERT(staMib->bssData.ssid == apMib->bssData.ssid); - ASSERT(apMib->bssAccessPointData.stations.at(staMib->address) == ieee80211::Ieee80211Mib::ASSOCIATED); + ASSERT(staMib->getBssStationData().isAssociated); + ASSERT(staMib->getBssData().ssid == apMib->getBssData().ssid); + ASSERT(apMib->getBssAccessPointData().stations.at(staMib->address) == ieee80211::Ieee80211Mib::ASSOCIATED); ASSERT(apMib->findPeerHtState(staMib->address) != nullptr); ASSERT(staMib->findPeerHtState(apMib->address) != nullptr); std::cout << "Simplified STA association and HT peer state restored after crash restart.\n"; } else if (!strcmp(message->getName(), "prepareMissingApCrash")) { - ASSERT(apMib->bssAccessPointData.stations.at(staMib->address) == ieee80211::Ieee80211Mib::ASSOCIATED); + ASSERT(apMib->getBssAccessPointData().stations.at(staMib->address) == ieee80211::Ieee80211Mib::ASSOCIATED); ASSERT(apMib->findPeerHtState(staMib->address) != nullptr); ASSERT(staMib->findPeerHtState(apMib->address) != nullptr); - staMib->bssData.bssid = MacAddress("02:00:00:00:00:ff"); - ASSERT(staMib->bssData.bssid != apMib->address); - ASSERT(L3AddressResolver().findHostWithAddress(staMib->bssData.bssid) == nullptr); + auto operation = staMib->getHtOperation(); + staMib->commitBss(staMib->getBssData().ssid, MacAddress("02:00:00:00:00:ff"), + staMib->getOperationBand(), operation.primaryChannel, &operation); + ASSERT(staMib->getBssData().bssid != apMib->address); + ASSERT(L3AddressResolver().findHostWithAddress(staMib->getBssData().bssid) == nullptr); std::cout << "Simplified STA recorded BSSID made unresolvable before crash.\n"; } else if (!strcmp(message->getName(), "checkMissingApCrash")) { - ASSERT(!staMib->bssStationData.isAssociated); - ASSERT(staMib->bssData.bssid == MacAddress("02:00:00:00:00:ff")); - ASSERT(L3AddressResolver().findHostWithAddress(staMib->bssData.bssid) == nullptr); + ASSERT(!staMib->getBssStationData().isAssociated); + ASSERT(staMib->getBssData().bssid == MacAddress("02:00:00:00:00:ff")); + ASSERT(L3AddressResolver().findHostWithAddress(staMib->getBssData().bssid) == nullptr); ASSERT(staMib->findPeerHtState(apMib->address) == nullptr); - ASSERT(apMib->bssAccessPointData.stations.at(staMib->address) == ieee80211::Ieee80211Mib::ASSOCIATED); + ASSERT(apMib->getBssAccessPointData().stations.at(staMib->address) == ieee80211::Ieee80211Mib::ASSOCIATED); ASSERT(apMib->findPeerHtState(staMib->address) != nullptr); std::cout << "Simplified STA crash tolerated missing AP lookup and cleared local HT peer state.\n"; + std::cout << "Legacy STAs preserve non-HT operation across both declaration orders and lifecycle transitions.\n"; } else ASSERT(false); @@ -158,6 +206,21 @@ network Ieee80211MgmtStaSimplifiedInitializationTestNetwork radioMedium: Ieee80211ScalarRadioMedium; configurator: TestIpv4NetworkConfigurator; scenarioManager: ScenarioManager; + legacyFirst: WirelessHost { + parameters: + wlan[*].mgmt.typename = "Ieee80211MgmtStaSimplified"; + wlan[*].agent.typename = ""; + } + apFirst: AccessPoint { + parameters: + wlan[*].mgmt.typename = "Ieee80211MgmtApSimplified"; + wlan[*].agent.typename = ""; + } + staSecond: WirelessHost { + parameters: + wlan[*].mgmt.typename = "Ieee80211MgmtStaSimplified"; + wlan[*].agent.typename = ""; + } sta: WirelessHost { parameters: wlan[*].mgmt.typename = "Ieee80211MgmtStaSimplified"; @@ -168,6 +231,11 @@ network Ieee80211MgmtStaSimplifiedInitializationTestNetwork wlan[*].mgmt.typename = "Ieee80211MgmtApSimplified"; wlan[*].agent.typename = ""; } + legacySecond: WirelessHost { + parameters: + wlan[*].mgmt.typename = "Ieee80211MgmtStaSimplified"; + wlan[*].agent.typename = ""; + } observer: TestInitializationObserver; } @@ -181,11 +249,18 @@ cmdenv-express-mode = true record-vector-results = false record-scalar-results = false +*.apFirst.wlan[0].address = "02:00:00:00:00:02" +*.apFirst.wlan[0].mgmt.ssid = "ap-first" +*.apFirst.wlan[0].radio.channelNumber = 6 +*.staSecond.wlan[0].mgmt.accessPointAddress = "02:00:00:00:00:02" *.ap.wlan[0].address = "02:00:00:00:00:01" *.sta.wlan[0].address = "auto" *.ap.wlan[0].mgmt.ssid = "review-ssid" *.ap.wlan[0].radio.channelNumber = 6 *.sta.wlan[0].mgmt.accessPointAddress = "02:00:00:00:00:01" +*.legacy*.wlan[0].opMode = "g(mixed)" +*.legacy*.wlan[0].bitrate = 54Mbps +*.legacy*.wlan[0].mgmt.accessPointAddress = "02:00:00:00:00:01" **.wlan[0].opMode = "n(mixed-2.4Ghz)" **.hasStatus = true **.scenarioManager.script = xmldoc("scenario.xml") @@ -205,15 +280,23 @@ record-scalar-results = false + + + + + + + + @@ -224,7 +307,7 @@ record-scalar-results = false Simplified STA wireless identity available during network configuration. %contains: stdout -Simplified STA HT peer state installed at last initialization stage. +Simplified STA HT peer state remains ready at last initialization stage. %contains: stdout Simplified STA AP-side association and HT peer state removed after shutdown. @@ -243,3 +326,9 @@ Simplified STA recorded BSSID made unresolvable before crash. %contains: stdout Simplified STA crash tolerated missing AP lookup and cleared local HT peer state. + +%contains: stdout +Both declaration orders and repeated BSS preparation verified. + +%contains: stdout +Legacy STAs preserve non-HT operation across both declaration orders and lifecycle transitions. diff --git a/tests/protocol/wifi/11n/WifiHtAssociation.test b/tests/protocol/wifi/11n/WifiHtAssociation.test new file mode 100644 index 00000000000..959b54b69ac --- /dev/null +++ b/tests/protocol/wifi/11n/WifiHtAssociation.test @@ -0,0 +1,41 @@ +%description: +HT infrastructure authentication/association sequence through the real packet PHY. +Uses the existing 802.11n configuration with two antennas and QoS enabled. +Local/peer HT constraints are checked separately by module tests. + +%file: WifiHtAssociation.cc + +#include "ProtocolTest.h" + +namespace inet { +namespace protocoltest { + +// Frame type (combined type+subtype byte): 0=Assoc Request, 1=Assoc Response, +// 8=Beacon, 11=Authentication. +Define_ProtocolTest(wifi_ht_association) +{ + return ProtocolTest("wifi_ht_association") + .once(on("sta1.wlan[0].mac").signal("packetReceivedFromLower") + .filterPacket("ieee80211mac.type == 8").describe("a Beacon from the AP").within(2.0)) + .once(on("sta1.wlan[0].mac").signal("packetSentToLower") + .filterPacket("ieee80211mac.type == 11").describe("an Authentication request").within(2.0)) + .once(on("sta1.wlan[0].mac").signal("packetReceivedFromLower") + .filterPacket("ieee80211mac.type == 11").describe("the Authentication response").within(2.0)) + .once(on("sta1.wlan[0].mac").signal("packetSentToLower") + .filterPacket("ieee80211mac.type == 0").describe("an Association Request").within(2.0)) + .once(on("sta1.wlan[0].mac").signal("packetReceivedFromLower") + .filterPacket("ieee80211mac.type == 1").describe("the Association Response").within(2.0)); +} + +} // namespace protocoltest +} // namespace inet + +%inifile: test.ini + +[General] +*.tester.testName = "wifi_ht_association" +include ../../ini/_n.ini +include ../../ini/_base.ini + +%contains: stdout +PROTOCOLTEST wifi_ht_association: PASS diff --git a/tests/protocol/wifi/common/WifiDeauth.test b/tests/protocol/wifi/common/WifiDeauth.test index 4b569c7fa6e..80e188cf1c5 100644 --- a/tests/protocol/wifi/common/WifiDeauth.test +++ b/tests/protocol/wifi/common/WifiDeauth.test @@ -47,8 +47,8 @@ Define_ProtocolTest(wifi_deauth) .filterPacket("ieee80211mac.type == 8") .assertEvent([](const MatchContext& c) { auto mib = check_and_cast(c.event.node->getSubmodule("wlan", 0)->getSubmodule("mib")); - auto it = mib->bssAccessPointData.stations.find(MacAddress("02:00:00:00:00:03")); - return it != mib->bssAccessPointData.stations.end() && it->second == Ieee80211Mib::NOT_AUTHENTICATED; + auto it = mib->getBssAccessPointData().stations.find(MacAddress("02:00:00:00:00:03")); + return it != mib->getBssAccessPointData().stations.end() && it->second == Ieee80211Mib::NOT_AUTHENTICATED; }).describe("AP cleared the station authentication state").within(0.5)); } diff --git a/tests/unit/Ieee80211HtCapabilities_1.test b/tests/unit/Ieee80211HtCapabilities_1.test index dee6a669f79..2f479f16966 100644 --- a/tests/unit/Ieee80211HtCapabilities_1.test +++ b/tests/unit/Ieee80211HtCapabilities_1.test @@ -35,7 +35,7 @@ Ieee80211HtOperation operation; operation.operatingChannelWidth = MHz(20); operation.basicMcsSupported[0] = true; -auto negotiated = negotiateHtCapabilities(local, peer, operation); +auto negotiated = negotiateHtCapabilities(local, peer); ASSERT(negotiated.localTxPeerRx.valid); ASSERT(negotiated.localRxPeerTx.valid); ASSERT(negotiated.localTxPeerRx.supportedMcs[0]); @@ -53,14 +53,14 @@ ASSERT(negotiated.localRxPeerTx.receiverMaxAmpduLengthExponent == 1); // A 20 MHz-only peer may join a 20/40 MHz BSS. local.supportedChannelWidths.insert(MHz(40)); operation.operatingChannelWidth = MHz(40); -negotiated = negotiateHtCapabilities(local, peer, operation); +negotiated = negotiateHtCapabilities(local, peer); ASSERT(negotiated.localTxPeerRx.valid); ASSERT(negotiated.localTxPeerRx.supportedChannelWidths.count(MHz(20)) == 1); // Table 9-226 permits an undefined Tx MCS set; unknown is not "cannot transmit". peer.txMcsSetDefined = false; peer.txMcsNss = Ieee80211HtMcsNssMap(); -negotiated = negotiateHtCapabilities(local, peer, operation); +negotiated = negotiateHtCapabilities(local, peer); ASSERT(negotiated.localRxPeerTx.valid); operation.basicMcsSupported[1] = true; @@ -98,7 +98,7 @@ for (bool unequalModulation : {false, true}) { ASSERT(unequalRoundTrip.txRxMcsSetNotEqual); ASSERT(unequalRoundTrip.txMaxNss == 2); ASSERT(unequalRoundTrip.txUnequalModulation == unequalModulation); - auto unequalNegotiated = negotiateHtCapabilities(local, unequal, operation); + auto unequalNegotiated = negotiateHtCapabilities(local, unequal); ASSERT(unequalNegotiated.localRxPeerTx.valid); ASSERT(!unequalNegotiated.localRxPeerTx.supportedMcs[0]); } diff --git a/tests/unit/Ieee80211MibAssociationId_1.test b/tests/unit/Ieee80211MibAssociationId_1.test index aedebd81105..adb637a21c7 100644 --- a/tests/unit/Ieee80211MibAssociationId_1.test +++ b/tests/unit/Ieee80211MibAssociationId_1.test @@ -24,20 +24,20 @@ mib.cancelAssociationIdReservation(first); ASSERT(mib.reserveAssociationId(third) == 1); ASSERT(mib.commitAssociationId(second) == 2); -ASSERT(mib.bssAccessPointData.associationIds.at(second) == 2); +ASSERT(mib.getBssAccessPointData().associationIds.at(second) == 2); mib.cancelAssociationIdReservation(second); -ASSERT(mib.bssAccessPointData.associationIds.at(second) == 2); +ASSERT(mib.getBssAccessPointData().associationIds.at(second) == 2); ASSERT(mib.commitAssociationId(third) == 1); mib.releaseAssociationId(second); -ASSERT(mib.bssAccessPointData.associationIds.count(second) == 0); +ASSERT(mib.getBssAccessPointData().associationIds.count(second) == 0); ASSERT(mib.allocateAssociationId(fourth) == 2); mib.reserveAssociationId(first); -mib.bssAccessPointData.stations[first] = Ieee80211Mib::ASSOCIATED; +mib.setPeerAssociationStatus(first, Ieee80211Mib::ASSOCIATED); mib.clearAssociationIds(); -ASSERT(mib.bssAccessPointData.stations.empty()); -ASSERT(mib.bssAccessPointData.associationIds.empty()); +ASSERT(mib.getBssAccessPointData().stations.empty()); +ASSERT(mib.getBssAccessPointData().associationIds.empty()); ASSERT(mib.reserveAssociationId(second) == 1); EV << "Association ID ownership checks passed.\n"; diff --git a/tests/unit/Ieee80211PeerModeSelection_1.test b/tests/unit/Ieee80211PeerModeSelection_1.test index b472fd4e291..ab251b1f2e2 100644 --- a/tests/unit/Ieee80211PeerModeSelection_1.test +++ b/tests/unit/Ieee80211PeerModeSelection_1.test @@ -27,12 +27,18 @@ static const IIeee80211Mode *findHtMode(const Ieee80211ModeSet *modeSet, int mcs return nullptr; } -static Ieee80211Mib::PeerHtState makePeerState(std::initializer_list mcsIndexes, +struct TestPeer : Ieee80211Mib::PeerHtState { + Ieee80211HtOperation operation; +}; + +static TestPeer makePeerState(std::initializer_list mcsIndexes, std::initializer_list bandwidths, Hz operatingChannelWidth, bool shortGi20 = false, bool shortGi40 = false) { - Ieee80211Mib::PeerHtState state; + TestPeer state; + auto capabilities = std::make_shared(); + state.negotiatedCapabilities = capabilities; state.valid = true; - auto& receiverCapabilities = state.negotiatedCapabilities.localTxPeerRx; + auto& receiverCapabilities = capabilities->localTxPeerRx; receiverCapabilities.valid = true; for (int mcsIndex : mcsIndexes) receiverCapabilities.supportedMcs[mcsIndex] = true; @@ -40,10 +46,16 @@ static Ieee80211Mib::PeerHtState makePeerState(std::initializer_list mcsInd receiverCapabilities.supportedChannelWidths.insert(bandwidth); receiverCapabilities.receiverShortGi20 = shortGi20; receiverCapabilities.receiverShortGi40 = shortGi40; - state.negotiatedCapabilities.operation.operatingChannelWidth = operatingChannelWidth; + state.operation.operatingChannelWidth = operatingChannelWidth; return state; } +static const IIeee80211Mode *selectForTest(const Ieee80211ModeSet *modeSet, + const TestPeer *state, const IIeee80211Mode *mode, const MacAddress& peer, bool eligible = true) +{ + return selectPeerCompatibleMode(modeSet, state, mode, peer, state ? &state->operation : nullptr, eligible); +} + %activity: const auto *modeSet = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"); @@ -80,12 +92,12 @@ ASSERT(!modeSet->containsMode(modeOutsideSet)); const MacAddress peer("02:00:00:00:00:01"); auto shortGiPeer = makePeerState({8}, {MHz(20)}, MHz(20), true); -const auto *shortGiSelection = selectPeerCompatibleMode(modeSet, &shortGiPeer, mcs8Short, peer); +const auto *shortGiSelection = selectForTest(modeSet, &shortGiPeer, mcs8Short, peer); ASSERT(shortGiSelection == mcs8Short); ASSERT(shortGiSelection->isHtShortGuardInterval()); ASSERT(shortGiSelection->getDataMode()->getBandwidth() == MHz(20)); auto fortyMhzPeer = makePeerState({0}, {MHz(20), MHz(40)}, MHz(40), false, true); -const auto *fortyMhzSelection = selectPeerCompatibleMode(modeSet, &fortyMhzPeer, mcs0_40Short, peer); +const auto *fortyMhzSelection = selectForTest(modeSet, &fortyMhzPeer, mcs0_40Short, peer); ASSERT(fortyMhzSelection == mcs0_40Short); ASSERT(fortyMhzSelection->isHtShortGuardInterval()); ASSERT(fortyMhzSelection->getDataMode()->getBandwidth() == MHz(40)); @@ -93,16 +105,16 @@ ASSERT(fortyMhzSelection->getDataMode()->getBandwidth() == MHz(40)); // The complementary long-GI and short-GI variants are selectable when the // peer capabilities permit the corresponding exact mode. auto longGiMcs8Peer = makePeerState({8}, {MHz(20)}, MHz(20)); -ASSERT(selectPeerCompatibleMode(modeSet, &longGiMcs8Peer, mcs8Long, peer) == mcs8Long); +ASSERT(selectForTest(modeSet, &longGiMcs8Peer, mcs8Long, peer) == mcs8Long); auto shortGiMcs0Peer = makePeerState({0}, {MHz(20)}, MHz(20), true); -ASSERT(selectPeerCompatibleMode(modeSet, &shortGiMcs0Peer, mcs0Short, peer) == mcs0Short); +ASSERT(selectForTest(modeSet, &shortGiMcs0Peer, mcs0Short, peer) == mcs0Short); auto longGiFortyPeer = makePeerState({0}, {MHz(20), MHz(40)}, MHz(40)); -ASSERT(selectPeerCompatibleMode(modeSet, &longGiFortyPeer, mcs0_40Long, peer) == mcs0_40Long); +ASSERT(selectForTest(modeSet, &longGiFortyPeer, mcs0_40Long, peer) == mcs0_40Long); bool outsideModeRejected = false; try { auto compatiblePeer = makePeerState({0}, {MHz(20)}, MHz(20)); - selectPeerCompatibleMode(modeSet, &compatiblePeer, modeOutsideSet, peer); + selectForTest(modeSet, &compatiblePeer, modeOutsideSet, peer); } catch (const cRuntimeError&) { outsideModeRejected = true; @@ -112,58 +124,61 @@ ASSERT(outsideModeRejected); // The exact negotiated bitmap is not a contiguous maximum-MCS value: MCS 0 // and 2 are usable while MCS 1 is not (IEEE Std 802.11-2024, 10.6.5.8). auto sparsePeer = makePeerState({0, 2}, {MHz(20)}, MHz(20)); -ASSERT(selectPeerCompatibleMode(modeSet, &sparsePeer, mcs2Long, peer) == mcs2Long); -ASSERT(selectPeerCompatibleMode(modeSet, &sparsePeer, mcs1Long, peer) == mcs0Long); +ASSERT(selectForTest(modeSet, &sparsePeer, mcs2Long, peer) == mcs2Long); +ASSERT(selectForTest(modeSet, &sparsePeer, mcs1Long, peer) == mcs0Long); // A 20 MHz-only receiver cannot use a 40 MHz candidate, even when the BSS // itself permits 40 MHz operation. auto twentyOnlyPeer = makePeerState({0}, {MHz(20)}, MHz(40)); -const auto *twentyFallback = selectPeerCompatibleMode(modeSet, &twentyOnlyPeer, mcs0_40Short, peer); +const auto *twentyFallback = selectForTest(modeSet, &twentyOnlyPeer, mcs0_40Short, peer); ASSERT(twentyFallback == mcs0Long); ASSERT(twentyFallback->getDataMode()->getBandwidth() == MHz(20)); // The negotiated HT Operation width is an additional BSS-level limit over // the common capability widths. auto twentyOperationPeer = makePeerState({0}, {MHz(20), MHz(40)}, MHz(20)); -ASSERT(selectPeerCompatibleMode(modeSet, &twentyOperationPeer, mcs0_40Short, peer) == mcs0Long); +ASSERT(selectForTest(modeSet, &twentyOperationPeer, mcs0_40Short, peer) == mcs0Long); // A short-GI candidate falls back to the same-MCS long-GI variant when the // peer did not advertise the matching receiver capability bit. auto shortGiDisabledPeer = makePeerState({0, 8}, {MHz(20)}, MHz(20)); -const auto *shortGiFallback = selectPeerCompatibleMode(modeSet, &shortGiDisabledPeer, mcs8Short, peer); +const auto *shortGiFallback = selectForTest(modeSet, &shortGiDisabledPeer, mcs8Short, peer); ASSERT(shortGiFallback == mcs8Long); ASSERT(!shortGiFallback->isHtShortGuardInterval()); auto compatiblePeer = makePeerState({2}, {MHz(20)}, MHz(40)); -ASSERT(selectPeerCompatibleMode(modeSet, &compatiblePeer, mcs2Long, peer) == mcs2Long); +ASSERT(selectForTest(modeSet, &compatiblePeer, mcs2Long, peer) == mcs2Long); // Missing or invalid negotiated state keeps unicast HT selection on a legacy // operational fallback without increasing the caller's candidate bitrate. const auto *legacy12Mbps = modeSet->getMode(Mbps(12)); const auto *legacy6Mbps = modeSet->getMode(Mbps(6)); -ASSERT(selectPeerCompatibleMode(modeSet, nullptr, mcs2Long, peer) == legacy12Mbps); +ASSERT(selectForTest(modeSet, nullptr, mcs2Long, peer) == legacy12Mbps); +ASSERT(selectForTest(modeSet, &compatiblePeer, mcs2Long, peer, false) == legacy12Mbps); auto invalidPeer = makePeerState({2}, {MHz(20)}, MHz(20)); invalidPeer.valid = false; -ASSERT(selectPeerCompatibleMode(modeSet, &invalidPeer, mcs2Long, peer) == legacy12Mbps); +ASSERT(selectForTest(modeSet, &invalidPeer, mcs2Long, peer) == legacy12Mbps); auto invalidDirectionalPeer = makePeerState({2}, {MHz(20)}, MHz(20)); -invalidDirectionalPeer.negotiatedCapabilities.localTxPeerRx.valid = false; -ASSERT(selectPeerCompatibleMode(modeSet, &invalidDirectionalPeer, mcs2Long, peer) == legacy12Mbps); +auto invalidCapabilities = std::make_shared(*invalidDirectionalPeer.negotiatedCapabilities); +invalidCapabilities->localTxPeerRx.valid = false; +invalidDirectionalPeer.negotiatedCapabilities = invalidCapabilities; +ASSERT(selectForTest(modeSet, &invalidDirectionalPeer, mcs2Long, peer) == legacy12Mbps); -const auto *boundedMcs0Fallback = selectPeerCompatibleMode(modeSet, nullptr, mcs0Long, peer); +const auto *boundedMcs0Fallback = selectForTest(modeSet, nullptr, mcs0Long, peer); ASSERT(boundedMcs0Fallback == legacy6Mbps); ASSERT(boundedMcs0Fallback->getDataMode()->getNetBitrate() <= mcs0Long->getDataMode()->getNetBitrate()); // If no compatible HT mode is at or below the candidate bitrate, the result // is legacy rather than an HT mode that violates the negotiated bitmap. auto noHtFallbackPeer = makePeerState({76}, {MHz(20)}, MHz(20)); -const auto *noHtFallback = selectPeerCompatibleMode(modeSet, &noHtFallbackPeer, mcs0Long, peer); +const auto *noHtFallback = selectForTest(modeSet, &noHtFallbackPeer, mcs0Long, peer); ASSERT(noHtFallback == legacy6Mbps); ASSERT(noHtFallback->getHtMcsIndex() < 0); // Mode-set traversal and tie-breaks are stable and never depend on pointers. auto deterministicPeer = makePeerState({0, 2, 8}, {MHz(20)}, MHz(20)); -const auto *firstSelection = selectPeerCompatibleMode(modeSet, &deterministicPeer, mcs8Short, peer); -const auto *secondSelection = selectPeerCompatibleMode(modeSet, &deterministicPeer, mcs8Short, peer); +const auto *firstSelection = selectForTest(modeSet, &deterministicPeer, mcs8Short, peer); +const auto *secondSelection = selectForTest(modeSet, &deterministicPeer, mcs8Short, peer); ASSERT(firstSelection == secondSelection); ASSERT(firstSelection->getHtMcsIndex() == 8); ASSERT(!firstSelection->isHtShortGuardInterval()); From c913a63a8335ca39a319cb2089daf835ea5e95cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Fri, 18 Sep 2026 20:46:45 +0200 Subject: [PATCH 3/3] ieee80211: refactor: separate the read-only mode-set provider Catalog-only consumers should not require capability preparation. Introduce a paired C++/NED IIeee80211ModeSetProvider contract and make MAC configuration extend it with the preparation operation used by management. ModeSetModuleBase now depends only on the read-only provider. Migrate the replacement-provider fixture to the narrow contract, removing its throwing preparation stub. Update architecture, migration guidance and release notes to identify the contract custom catalog providers implement. Validation of the integrated changes: debug build, 5 focused module tests, 3 unit tests and 18 unchanged Wi-Fi/Ethernet/VLAN/configurator fingerprint cases pass. Scoped architecture checks pass; interface checking reports only the existing AV-CONTRACT-02 bodies. No recorded baseline changes. Plan: plan/done/80211htcapop-refactor-v3.md Change: src.ieee80211 | refactor | whatsnew migration | ieee80211-htcapop-v3 --- WHATSNEW | 7 ++--- .../design/ieee80211-model-architecture.md | 9 ++++--- doc/src/migration-guide/index.rst | 7 +++-- .../ieee80211/mac/common/ModeSetModuleBase.h | 4 +-- .../mac/contract/IIeee80211MacConfiguration.h | 9 +++---- .../contract/IIeee80211MacConfiguration.ned | 2 +- .../mac/contract/IIeee80211ModeSetProvider.h | 27 +++++++++++++++++++ .../contract/IIeee80211ModeSetProvider.ned | 13 +++++++++ .../Ieee80211ConfigurationContracts_1.test | 7 +++-- 9 files changed, 63 insertions(+), 22 deletions(-) create mode 100644 src/inet/linklayer/ieee80211/mac/contract/IIeee80211ModeSetProvider.h create mode 100644 src/inet/linklayer/ieee80211/mac/contract/IIeee80211ModeSetProvider.ned diff --git a/WHATSNEW b/WHATSNEW index 9e29f5c5c19..822692b152c 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -290,9 +290,10 @@ Notable backward incompatible changes are the following: without accepting the HT operation advertised by an HT access point. Custom WLAN modules must replace ModeSetListener and modesetChanged with - an explicit modeSetModule provider, and replace direct MIB BSS/profile - writes with the owner APIs. Custom HT PHY contributors use typed capability - interfaces. The migration guide describes these source and wiring changes. + an explicit modeSetModule using IIeee80211ModeSetProvider, and replace + direct MIB BSS/profile writes with the owner APIs. Custom HT PHY contributors + use typed capability interfaces. The migration guide describes these source + and wiring changes. The selected legacy QoS and non-QoS ad hoc fingerprints remain unchanged; no recorded fingerprint baseline was updated. diff --git a/doc/project/design/ieee80211-model-architecture.md b/doc/project/design/ieee80211-model-architecture.md index 7df73468377..a119c0e6d17 100644 --- a/doc/project/design/ieee80211-model-architecture.md +++ b/doc/project/design/ieee80211-model-architecture.md @@ -93,10 +93,11 @@ Concrete signals declare their source/scope, change condition, payload, and life it creates no second writable authority. Commands, queries, and required coordination use typed calls or protocol messages, rather than notifications. -**Implemented HT contracts.** `IIeee80211MacConfiguration` exposes the configured catalog after -`LOCAL` and an idempotent `prepareLocalCapabilities()` operation after PHY readiness. MAC consumers -resolve their `modeSetModule` dependency through `ModeSetModuleBase` at `LINK_LAYER`; the MAC NED -provides the default descendant path. Management uses `macModule`. `LINK_LAYER` depends explicitly +**Implemented HT contracts.** `IIeee80211ModeSetProvider` exposes the configured catalog after +`LOCAL`. `IIeee80211MacConfiguration` extends it with an idempotent `prepareLocalCapabilities()` +operation after PHY readiness. MAC consumers resolve their `modeSetModule` dependency through the +narrow provider in `ModeSetModuleBase` at `LINK_LAYER`; the MAC NED provides the default descendant +path. Management uses `macModule`. `LINK_LAYER` depends explicitly on both `PHYSICAL_LAYER` and `NETWORK_INTERFACE_CONFIGURATION`, so addresses and contribution inputs are available before simplified association. Its AP preparation/install/removal calls use `IIeee80211BssProvider`; no remote `LAST` callback is required. diff --git a/doc/src/migration-guide/index.rst b/doc/src/migration-guide/index.rst index a8f16c4fee1..5ef0061b526 100644 --- a/doc/src/migration-guide/index.rst +++ b/doc/src/migration-guide/index.rst @@ -8,14 +8,17 @@ IEEE 802.11 Capability and BSS State Ownership -------------------------------------------- Custom modules that used ``ModeSetListener`` or subscribed to ``modesetChanged`` -must obtain the configured catalog through ``IIeee80211MacConfiguration``. +must obtain the configured catalog through ``IIeee80211ModeSetProvider``. For catalog consumers, derive from ``ModeSetModuleBase``, declare a ``modeSetModule`` NED parameter, and call the base initialization before using ``modeSet`` at ``INITSTAGE_LINK_LAYER``. Keep ``NUM_INIT_STAGES``. The built-in MAC supplies the descendant parameter default; standalone consumers must point -it at a module implementing the C++ and NED configuration contracts. Move +it at a module implementing the C++ and NED mode-set provider contracts. Move algorithm initialization formerly performed by the signal callback to that initialization stage. Ordinary catalog queries must not reset algorithm state. +Management uses ``IIeee80211MacConfiguration``, which extends the mode-set +provider with capability preparation; catalog-only providers need not implement +that operation. Custom transmitters and receivers that contribute HT capabilities implement ``IIeee80211TransmitterCapabilities`` and ``IIeee80211ReceiverCapabilities``, diff --git a/src/inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h b/src/inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h index 2092283634e..47aca8ad69b 100644 --- a/src/inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h +++ b/src/inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h @@ -10,14 +10,14 @@ #include "inet/common/SimpleModule.h" #include "inet/common/ModuleRefByPar.h" -#include "inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.h" +#include "inet/linklayer/ieee80211/mac/contract/IIeee80211ModeSetProvider.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" namespace inet::ieee80211 { /** Base for modules with a declared, configuration-lifetime catalog dependency. */ class INET_API ModeSetModuleBase : public SimpleModule { protected: - ModuleRefByPar modeSetProvider; + ModuleRefByPar modeSetProvider; const physicallayer::Ieee80211ModeSet *modeSet = nullptr; int numInitStages() const override { return NUM_INIT_STAGES; } void initialize(int stage) override; diff --git a/src/inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.h b/src/inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.h index 27b67801365..e7dcc142d6b 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.h @@ -8,19 +8,16 @@ #ifndef INET_IIEEE80211MACCONFIGURATION_H #define INET_IIEEE80211MACCONFIGURATION_H -#include "inet/common/INETDefs.h" - -namespace inet::physicallayer { class Ieee80211ModeSet; } +#include "inet/linklayer/ieee80211/mac/contract/IIeee80211ModeSetProvider.h" namespace inet::ieee80211 { -/** Configured catalog is ready after LOCAL; explicit preparation requires PHY readiness. +/** MAC configuration extends catalog access with preparation after PHY readiness. * Repeated preparation preserves protocol and algorithm state. */ -class INET_API IIeee80211MacConfiguration +class INET_API IIeee80211MacConfiguration : public IIeee80211ModeSetProvider { public: virtual ~IIeee80211MacConfiguration() = default; - [[nodiscard]] virtual const physicallayer::Ieee80211ModeSet *getConfiguredModeSet() const = 0; virtual void prepareLocalCapabilities() = 0; }; } // namespace inet::ieee80211 diff --git a/src/inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.ned b/src/inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.ned index ed517dddcee..4bdadf22c67 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.ned +++ b/src/inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.ned @@ -7,6 +7,6 @@ package inet.linklayer.ieee80211.mac.contract; // Configured mode catalog and explicit capability preparation; see the C++ contract. -moduleinterface IIeee80211MacConfiguration +moduleinterface IIeee80211MacConfiguration extends IIeee80211ModeSetProvider { } diff --git a/src/inet/linklayer/ieee80211/mac/contract/IIeee80211ModeSetProvider.h b/src/inet/linklayer/ieee80211/mac/contract/IIeee80211ModeSetProvider.h new file mode 100644 index 00000000000..5646b2f9c16 --- /dev/null +++ b/src/inet/linklayer/ieee80211/mac/contract/IIeee80211ModeSetProvider.h @@ -0,0 +1,27 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + + +#ifndef INET_IIEEE80211MODESETPROVIDER_H +#define INET_IIEEE80211MODESETPROVIDER_H + +#include "inet/common/INETDefs.h" + +namespace inet::physicallayer { class Ieee80211ModeSet; } + +namespace inet::ieee80211 { + +/** Read-only configured catalog, available after LOCAL and stable across stop/restart. */ +class INET_API IIeee80211ModeSetProvider +{ + public: + virtual ~IIeee80211ModeSetProvider() = default; + [[nodiscard]] virtual const physicallayer::Ieee80211ModeSet *getConfiguredModeSet() const = 0; +}; + +} // namespace inet::ieee80211 + +#endif diff --git a/src/inet/linklayer/ieee80211/mac/contract/IIeee80211ModeSetProvider.ned b/src/inet/linklayer/ieee80211/mac/contract/IIeee80211ModeSetProvider.ned new file mode 100644 index 00000000000..5c008909b55 --- /dev/null +++ b/src/inet/linklayer/ieee80211/mac/contract/IIeee80211ModeSetProvider.ned @@ -0,0 +1,13 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + + +package inet.linklayer.ieee80211.mac.contract; + +// Read-only configured mode catalog; see the C++ contract. +moduleinterface IIeee80211ModeSetProvider +{ +} diff --git a/tests/module/Ieee80211ConfigurationContracts_1.test b/tests/module/Ieee80211ConfigurationContracts_1.test index 5b59c0fa6e7..ae36857923f 100644 --- a/tests/module/Ieee80211ConfigurationContracts_1.test +++ b/tests/module/Ieee80211ConfigurationContracts_1.test @@ -45,14 +45,13 @@ class OfflineExtUpper : public ExtInterface }; Define_Module(OfflineExtUpper); -class TestCatalogProvider : public cSimpleModule, public IIeee80211MacConfiguration +class TestCatalogProvider : public cSimpleModule, public IIeee80211ModeSetProvider { protected: const Ieee80211ModeSet *catalog = nullptr; virtual void initialize() override { catalog = Ieee80211ModeSet::getModeSet("g(mixed)"); } public: virtual const Ieee80211ModeSet *getConfiguredModeSet() const override { return catalog; } - virtual void prepareLocalCapabilities() override { throw cRuntimeError("Catalog-only consumer must not prepare HT"); } }; Define_Module(TestCatalogProvider); @@ -127,11 +126,11 @@ import inet.common.SimpleModule; import inet.emulation.linklayer.ieee80211.ExtUpperIeee80211Interface; import inet.common.scenario.ScenarioManager; import inet.node.inet.AdhocHost; -import inet.linklayer.ieee80211.mac.contract.IIeee80211MacConfiguration; +import inet.linklayer.ieee80211.mac.contract.IIeee80211ModeSetProvider; import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; simple OfflineTap { parameters: @class(::OfflineTap); gates: input lowerLayerIn; output lowerLayerOut; } module OfflineExtUpper extends ExtUpperIeee80211Interface { parameters: @class(::OfflineExtUpper); } -simple TestCatalogProvider like IIeee80211MacConfiguration { parameters: @class(::TestCatalogProvider); } +simple TestCatalogProvider like IIeee80211ModeSetProvider { parameters: @class(::TestCatalogProvider); } simple TestCatalogConsumer extends SimpleModule { parameters: @class(::TestCatalogConsumer);