diff --git a/WHATSNEW b/WHATSNEW index 1a61a1b6935..11a6544efaa 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -280,6 +280,32 @@ 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 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. + +17. IEEE 802.11 PHY mode API + + External implementations of IIeee80211DataMode must implement the new pure + virtual getGuardInterval() query. Return the modeled guard interval as a + simtime_t, or -1 when the PHY has no guard interval. The bitrate-based + Ieee80211ModeSet getMode() and findMode() overloads also take a trailing, + defaulted guardInterval argument. Ordinary existing calls still compile; + member-function pointers must use the new signature. See the migration guide. + Notable backward compatible changes are the following: 1. IEEE 802.11 per-station rate statistics @@ -334,6 +360,58 @@ Notable backward compatible changes are the following: ResidenceTimeTag and, with it, the FlowTag and the PacketEventTag of the packet. Those two tags now survive the measurement. +6. IEEE 802.11 HT/VHT guard intervals and timing + + HT MCS 32, 73, and 76 definitions are corrected. HT/VHT signal fields use + long-GI symbol timing independently of the data GI. Mixed-format HT and VHT + short-GI data airtimes are rounded to 4 us boundaries; HT greenfield retains + its unrounded short-GI airtime. Results and fingerprints may change for + simulations using the affected modes and frame formats. + + IIeee80211Mode duration-component queries are now pure virtual. Their + previous defaults are supplied by Ieee80211ModeBase; direct external + implementations must provide these queries or inherit the base defaults. + + RateSelection and QosRateSelection now accept dataFrameGuardInterval to + qualify a configured dataFrameBitrate. The default, -1s, leaves the GI + unspecified; an explicit value must match the selected mode's modeled GI. + The MAC now coordinates explicitly registered mode-set consumers, + updating MAC, management, rate selection, and active contention before + publishing modesetChanged from the MAC. Initialization queries the catalog; + unchanged catalogs retain algorithm state. Incompatible catalog-only requests + are rejected before opening a transition, allowing an explicit-mode retry. + Failures after PHY mutation remain fatal. External PHY + and contention implementations have new contracts described in the + migration guide. Default TXOP limits are resolved for each new TXOP after + a mode-set change; active TXOPs and configured overrides retain their limits. + + Radio reconfiguration preserves ongoing receptions when the incoming mode + and receiver listening configuration remain compatible. Transmit-only mode + changes and reapplying unchanged settings no longer interrupt reception. + Incompatible changes abort through the normal reception cleanup path. + + Management subclasses can override addVhtCapabilities and addVhtOperation + to customize advertisements produced by inherited AP/STA frame builders. + + Group-addressed frames use a legacy basic rate when that set is nonempty. + Eligible configured basic rates are preserved; other configured rates fall + back to the fastest legacy basic rate. For example, a configured 54 Mbps + group rate becomes 24 Mbps in the default mixed legacy catalog. This changes + airtime and fingerprints in affected existing scenarios. + + The ac catalog now contains both 800 ns and 400 ns GI for all 310 supported + VHT width/NSS/MCS tuples. Existing unqualified lookups, mandatory/basic flags, + and reference modes retain their previous choices. + + Detailed AP/STA management now exchanges VHT Capabilities and VHT Operation + elements. Both rate selectors constrain VHT unicast by local Tx and peer Rx + MCS/NSS maps, bandwidth, GI, and advertised long-GI rate limits. Association + loss and mode-set application invalidate peer state. Missing VHT negotiation + falls back to legacy rates, including simplified-management and ad-hoc setups; + this changes their airtime and fingerprints. Current detailed VHT operation + is limited to 20 MHz and long GI. See the migration guide for configuration + and the retained VHT-only profile limitations. + INET-4.7 (July 2026) — feature release -------------------------------------- diff --git a/doc/project/design/ieee80211-model-architecture.md b/doc/project/design/ieee80211-model-architecture.md index 1a8f75f9199..28ce6c8deeb 100644 --- a/doc/project/design/ieee80211-model-architecture.md +++ b/doc/project/design/ieee80211-model-architecture.md @@ -92,3 +92,44 @@ 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.** `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. + +`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. + +**Runtime catalog reconfiguration.** The MAC simple module implements the typed +`IIeee80211ModeSetCoordinator` contract. The radio resolves its coordinator through +`modeSetCoordinatorModule`; the containing interface only supplies default wiring. +Consumers register through their declared catalog/configuration provider when it supports +coordination. A read-only replacement provider need not implement that optional runtime role. + +An explicit changed-catalog transaction refreshes the MAC-assembled capability profile, +then management's local operation and dependent algorithms. HT compatibility results are +replaced only when capability inputs change; accepted peer knowledge remains relationship-scoped +and selection still checks eligibility and current operation. Reapplying the same catalog +does not reset algorithms. Ordinary preparation and stop/restart retain their existing contracts. +The MAC publishes `modesetChanged` only after a changed runtime catalog is applied; initialization +uses typed queries without a catalog notification. Membership changes and reentrant transitions +are rejected during application/publication. Failures after PHY mutation are fatal, without rollback. diff --git a/doc/src/migration-guide/index.rst b/doc/src/migration-guide/index.rst index 668ccf48149..f358c9ca9b3 100644 --- a/doc/src/migration-guide/index.rst +++ b/doc/src/migration-guide/index.rst @@ -4,6 +4,51 @@ 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 ``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 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``, +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 ------------------------------------------ @@ -60,6 +105,140 @@ answers; :cpp:`Arp` shows how. An implementation that resolves addresses without packets has nobody to ask. It overrides the method with an empty body, as :cpp:`GlobalArp` does, and the client then takes the address. +IEEE 802.11 Radio Reconfiguration and Management Hooks +---------------------------------------------------- + +Radio setters no longer implicitly interrupt compatible ongoing receptions. +Changing the transmit mode alone, reapplying unchanged receiver settings, or +changing the mode set while retaining the incoming mode preserves reception. +An incompatible receiver configuration still aborts reception and retains +arrival timers for normal cleanup. Custom callers should not rely on a no-op +setter or a transmit-mode change to cancel reception. + +``Ieee80211MgmtBase::addVhtCapabilities()`` and ``addVhtOperation()`` are now +virtual, like the HT advertisement helpers. Subclasses may override them to +customize advertisements in inherited frame builders; use ``override`` on +these declarations. Rebuild external management subclasses against the new +header and library. + +Migrating IEEE 802.11 PHY Modes +------------------------------ + +``IIeee80211Mode::getPreambleDuration()``, ``getHeaderDuration()`` and +``getDataDuration(b)`` are now pure virtual. Direct interface implementations +must implement them, or inherit ``Ieee80211ModeBase`` for the previous defaults. +Existing HT/VHT overrides retain their format-specific timing behavior. + +External implementations of ``IIeee80211DataMode`` must now implement the pure +virtual guard-interval query: + +.. code-block:: c++ + + const simtime_t getGuardInterval() const override; + +Return the modeled guard interval in simulation time units. For a PHY without a +guard interval, use an explicit override returning ``-1``. FHSS, DSSS, HR-DSSS, +and IR use this value; OFDM, HT, and VHT return their modeled interval. + +The bitrate-based ``Ieee80211ModeSet::getMode()`` and ``findMode()`` overloads +now take a trailing ``simtime_t guardInterval = -1`` argument. Existing ordinary +calls can omit it. Update member-function pointer declarations to include this +argument and supply it when invoking through a pointer. Rebuild external code +against the changed interface. + +``Ieee80211Mac`` implements ``IIeee80211ModeSetCoordinator`` for explicit runtime +catalog reconfiguration. The radio's ``modeSetCoordinatorModule`` points to the +MAC by default; a custom composition can provide another typed coordinator. +``ModeSetModuleBase`` registers derived-state consumers through the configured +catalog provider when that provider also supports coordination. Read-only +replacement providers remain usable without a coordinator. Management registers +through ``macModule`` in ``MANAGEMENT_STATE``. Preserve base initialization and +put required updates in ``applyModeSet()``, not notification callbacks. + +The MAC updates local capabilities, management updates its operation, and derived +consumers update their state before completion is published. Ordinary preparation +remains idempotent. Explicit profile replacement refreshes directional capability +caches only when their inputs change; operation changes do not rebuild those +caches. Peer information remains scoped to its relationship, with selection gated +by current eligibility and operation. Stop/restart retains prepared configuration. + +Observe ``modesetChanged`` from the MAC after a changed runtime catalog has been +applied. Initialization queries the configured catalog without a notification; +reapplying the same catalog does not reset algorithms or publish a catalog change. +The borrowed mode-set payload is immutable. Observers never finish the transition. +Standalone radios without a coordinator publish their own notification. Duplicate +registration is idempotent; late registration and membership changes during a +transition are rejected. Detach a consumer before deleting it. Participant or +observer exceptions remain fatal; partially applied changes cannot be resumed. + +Catalog-only changes now check transmitter compatibility before opening the +coordinated transition. After catching an incompatible-mode error, callers can +retry with ``setModeSetAndMode()`` and a valid explicit mode. Custom transmitter +implementations can override the non-mutating ``computeModeForModeSet()`` query +to match their catalog-only setter's resolution policy; it must reject an +unresolvable request without changing state. + +HT capability assembly queries the typed transmitter and receiver contributions +described above. Management obtains channel/band context from the PHY. Generic +legacy radios do not require HT contribution contracts. + +External ``IContention`` implementations must implement the new pure virtual +``updateTimingParameters(ifs, eifs, slotTime)`` method. On a runtime timing change, +retain completed whole backoff slots and the remaining random draw, restart the +applicable IFS and any unfinished slot, and update the expected grant time. +Unchanged timing preserves the existing schedule. This application must not emit +an intermediate mode-set notification or generate a new random backoff. + +Migrating VHT Catalogs and Peer Rate Selection +---------------------------------------------- + +The ``ac`` catalog provides both 800 ns and 400 ns GI for 310 legal VHT tuples +at 20/40/80/160 MHz and one through eight spatial streams. IEEE 802.11-2024, +21.5, Tables 21-29 through 21-60 exclude: 20 MHz MCS 9 except NSS 3 and 6; +80 MHz MCS 6 at NSS 3 and 7; 80 MHz MCS 9 at NSS 6; and 160 MHz MCS 9 at NSS 3. +The band/preamble envelope remains 5 GHz, mixed format. New variants are optional +catalog entries; historical mandatory/basic flags, reference/default modes, and +previously accepted unspecified-GI lookups are preserved. Explicit GI queries +can select either variant. This catalog does not establish operational support +for bonded primary/secondary channels. + +External ``IIeee80211Mode`` implementations must implement ``getVhtMcsIndex()``: +return the VHT MCS index (0 through 9), or -1 for other PHY families. +``Ieee80211ModeBase`` supplies the non-VHT default. VHT selection is independent +of the HT MCS bitmap. + +``Ieee80211MgmtAp`` and ``Ieee80211MgmtSta`` now exchange and interpret VHT +Capabilities and VHT Operation elements (IEEE 802.11-2024, 9.4.2.156 and +9.4.2.157). The MIB owns committed per-peer state. The AP commits after the +successful association/reassociation response is acknowledged; the STA commits +after receiving a successful response with usable capability and operation +information. Pending VHT snapshots cannot survive a local mode-set application. +Disassociation, deauthentication, teardown, and mode-set application remove +committed state. Authoritative beacons can refresh the associated AP's state. + +To restrict VHT reception or transmission, configure the corresponding map in +:ned:`Ieee80211Mib`; that module documents the parameters and their constraints. +For example, ``wlan[*].mib.vhtRxMcsMap = [7,-1,-1,-1,-1,-1,-1,-1]`` restricts +reception to one stream with MCS 0 through 7 while leaving transmission +configuration independent. + +Both DCF and HCF choose VHT unicast modes within local Tx and peer Rx maps, +local/BSS operation width, GI eligibility, and the optional advertised highest +long-GI rate limits. Selection never exceeds the requested rate. Missing or +incompatible VHT negotiation uses a legacy operational mode. Consequently, +``Ieee80211MgmtApSimplified``, ``Ieee80211MgmtStaSimplified``, and ad-hoc +compositions use legacy unicast until a management implementation supplies +valid VHT peer state. Existing VHT results, including ``lan80211ac/Ping1``, can +change even though unspecified-GI catalog lookup is preserved. + +The current packet-level detailed-management support envelope is 20 MHz with +long GI. The existing ``ac`` profile remains VHT-only and does not supply the +HT modes required for full standards-conforming VHT operation. In particular, +it does not negotiate HT-carried short-GI bits for 20/40 MHz. Wider catalog and +selector tests do not claim bonded-channel operation. MU, beamforming, 80+80, +extended NSS bandwidth signaling, and operating-mode notifications are not +implemented by this change. + Migrating ``FieldsChunkSerializer`` Subclasses --------------------------------------------- diff --git a/plan/done/80211htcapop-refactor-v3-evidence.md b/plan/done/80211htcapop-refactor-v3-evidence.md new file mode 100644 index 00000000000..d6d8b083689 --- /dev/null +++ b/plan/done/80211htcapop-refactor-v3-evidence.md @@ -0,0 +1,271 @@ +# HT capability/BSS refactor V3: implementation evidence + +This is historical evidence for the tree identified below. Fresh verification of the current +branch plus the Devin-comment fixes is recorded in +[HT/GI Devin comment closure](ht-gi-devin-comment-closure.md), including exact tree identity, +focused reruns, and the corrected discovery-test assertion. + +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/plan/done/ht-gi-devin-comment-closure.md b/plan/done/ht-gi-devin-comment-closure.md new file mode 100644 index 00000000000..c4a8ff531a0 --- /dev/null +++ b/plan/done/ht-gi-devin-comment-closure.md @@ -0,0 +1,136 @@ +# HT/GI Devin comment closure + +Status: completed, with the pre-existing full-interface gate findings recorded below. + +Base: `dffd66845707303f661ef148e9bca2c3cca94262`. Scope: the five comments assessed in this session. + +## Implementation contract + +- Invariant and owner: transmitter compatibility rejection must precede the radio/MAC transaction. + The transmitter owns exact-tuple resolution; radio coordinates updates. Failures after PHY mutation + remain fatal. A rejected preflight changes no catalog, current mode, peer cache or notification count. +- Entry/control path: both radio setters call `changeModeSet`; catalog-only changes preflight a const + transmitter query before `beginModeSetChange`. The transmitter setter reuses the same query. + Explicit changes keep their existing membership check and virtual setter dispatch. +- Affected artifacts: radio/transmitter C++ and transmitter declaration; management serializer masks; + peer-mode selector citations; `IIeee80211Mode` and existing `Ieee80211ModeBase`; transition module + and VHT management-element unit fixtures. HT/VHT duration overrides remain authoritative. +- Siblings/terminal paths: same/null catalogs retain existing transmitter semantics; explicit mode + changes and post-mutation participant failures retain their contract. Serializer read/write masks + stay symmetric. No lifecycle or packet ownership change. +- Boundaries: exact bitrate/bandwidth/NSS/GI tuple; null mode/catalog behavior unchanged. Duration + units and arithmetic unchanged. VHT subtype placement remains identical to HT, with distinct bits; + this does not add band/capability presence validation. +- Verification: debug and release library builds; filtered transition/failure/registration and VHT + association modules; HT/GI/VHT mode, management-element and peer-selection units; the earlier + capability/BSS evidence's focused units/modules/protocols and two legacy ad hoc fingerprints. + Extend the transition fixture with rejection followed by successful explicit retry, and VHT codec + coverage for request-operation rejection and independent HT/VHT presence. + +Self-validation: owners, callers, base-class implementations, fixture names and runner interfaces +checked before editing. No affected source path is sealed. One deterministic run/seed is sufficient +for these API and codec invariants. No baseline update, commit, or push is part of this task. + +## Standards + +IEEE Std 802.11-2024: 10.6.13.1/.2 (directional VHT MCS/NSS and long-GI rate limits), +10.17 (short GI), 9.4.2.156.2 and Table 9-313 (width capability), 11.38.1 (BSS width), +Table 9-315 (rate field encoding). Subtype permissions: 9.3.3.2 and 9.3.3.5–10, +Tables 9-62 and 9-64–69. Retrieved from the local standards corpus; no normative behavior changes +are intended by adding citations. The selector's rate ceiling and fallback are model policy. + +## Evidence + +Date: 2026-09-19. Tested tree: base commit above plus the source/test patch with SHA-256 +`cd580f78b349b24d84077b135a674591f2d3f01d6a6c518116c0206a277753b3`. +The patch and per-file hashes are retained in `report/ht-gi-devin-closure/source.patch` and +`source-manifest.json`. These identify an uncommitted tree, not an unchanged HEAD. +The historical V3 evidence remains historical. + +All commands ran at the checkout root except the fingerprint wrapper, run in `tests/fingerprint`. +OMNeT++ 6.4.0aipre2 / clang; debug libraries and runtime assertions for behavioral tests. +Module/protocol fixtures use their checked-in deterministic configuration, run 0 and seed 0/default; +the failure fixture deliberately exercises its 14 configuration combinations and asserts fatal outcomes. +Fingerprints use run 0, seed 0/default, and 10 seconds simulated time. + +| Gate | Outcome | Log under `/tmp/` (also archived in `report/ht-gi-devin-closure/logs/`) | +|---|---|---| +| `make -j12 MODE=debug` | PASS, exit 0; final incremental build also exit 0 | `ht-gi-closure-debug.log`, `ht-gi-closure-debug-final.log` | +| `make -j12 MODE=release` | PASS, exit 0 | `ht-gi-closure-release.log` | +| Focused units | Initial 8 PASS / 1 FAIL, exit 1; corrected VHT fixture rerun PASS, exit 0; all 9 selected cases now pass | `ht-gi-closure-unit.log`, `ht-gi-closure-vht-codec-retest.log` | +| Focused modules | Initial 27 PASS / 1 FAIL, exit 1; corrected stale discovery assertion rerun PASS, exit 0; all 28 selected cases now pass | `ht-gi-closure-module.log`, `ht-gi-closure-discovery-retest.log` | +| Focused protocols | 4 PASS, exit 0; no declared expected failures | `ht-gi-closure-protocol.log` | +| Legacy fingerprints | 2 PASS, all three ingredients match, exit 0 | `ht-gi-closure-fingerprint.log` | +| Scoped MAC architecture | PASS, exit 0 | `ht-gi-closure-architecture-mac.log` | +| Scoped PHY architecture | PASS, exit 0 | `ht-gi-closure-architecture-phy.log` | +| Mode interfaces | 5 PASS, exit 0 | `ht-gi-closure-mode-interfaces.log` | +| Full interfaces | FAIL, exit 1: 15 violations in unchanged files; not waived | `ht-gi-closure-interfaces.log` | +| `git diff --check` | PASS, exit 0 | No output | + +The first VHT fixture failed with `Invalid VHT MCS map entry 0`: generated wire-element defaults +are not legal VHT MCS map entries. The fixture now copies the valid reference elements already +used by the byte-encoding checks. The production codec required no further change. +The first discovery fixture failed at `dcfMulticastMode->getHtMcsIndex() >= 0`; see the scoped +correction below. Only those two failed cases were rerun after test-only corrections; the INET +library and other cases remained unchanged. No recorded fingerprint, statistical or stdout +expectation was rewritten. + +Exact selectors and commands: + +```bash +UNIT='Ieee80211(HtCapabilities_1|HtMgmtElements_1|HtModeSet_1|PeerModeSelection_1|MibAssociationId_1|HtGuardInterval_1|VhtModeSet_1|VhtMgmtElements_1|VhtPeerModeSelection_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|ModeSet(Transition|Failure|Registration|Retry)_1|ContentionModeSet_1|TxopModeSet_1|VhtAssociation_1)\.test$' +inet_run_unit_tests -m debug -f "$UNIT" +inet_run_module_tests -m debug -f "$MODULE" +inet_run_unit_tests -m debug -f 'Ieee80211VhtMgmtElements_1\.test$' +inet_run_module_tests -m debug -f 'Ieee80211MgmtStaDiscovery_1\.test$' +inet_run_protocol_tests -p inet -m debug -w '^tests/protocol/wifi$' -f 'Wifi(HtAssociation|Association|Reassociation|Deauth)\.test$' +# cwd: tests/fingerprint +./fingerprinttest -d -m '/examples/adhoc/qos/ .* -c Mac(NonQos|Qos) -r 0 ' -f tplx -f '~tNl' -f '~tND' examples.csv +# cwd: repository root +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-interfaces.sh src/inet/physicallayer/wireless/ieee80211/mode +doc/project/enforcement/check-interfaces.sh +``` + +Python runners used `MPLCONFIGDIR=/tmp/ht-gi-closure-matplotlib` after initial setup to avoid an +unwritable user cache directory. The optional missing `py4j` IDE integration did not prevent tests. + +## Self-audit and limits + +- Radio preflight is read-only and occurs before either guard. The transmitter setter uses the same + resolution logic, retaining same/null-catalog behavior and virtual setter dispatch. Post-mutation + failures remain fatal; the existing failure fixture passes all its expected-error checks. +- The transition fixture checks both PHY catalogs, registered consumers, the selected transmit mode, + peer-cache identity and notification counts after repeated rejection, then checks a successful + explicit retry. It uses a 40 MHz catalog mode without claiming real 40 MHz packet-PHY support. +- VHT serializer read/write permissions are distinct and symmetric. All seven subtype cases cover + independent HT/VHT capabilities presence; requests reject VHT Operation on encoding and decoding. + Band/capability/role advertisement validation is outside this subtype-mask change. +- The interface declares pure duration operations; the existing base provides identical defaults. + HT/VHT overrides retain their timing authority. Existing legacy `_get*` wrappers retain AS-03. +- Reviewed C++ dispatch/query purity, OMNeT++ synchronous notification and reentrancy, INET ownership + and codec paths, and WLAN mode identity and standards traceability. No new exception or sealed-path + edit. No packet-core change, baseline update, commit, or push. +- This is focused verification and author self-audit, not an independent review or a clean + repository-wide compliance verdict. The full interface gate still has 15 pre-existing findings in 14 headers, all byte-identical to + HEAD (`ht-gi-closure-interface-provenance.log`). + + +### Required scope adjustment found by verification + +The 28-module run found one stale assertion in `Ieee80211MgmtStaDiscovery_1.test`: it still +requires HT group transmission, predating `ea818d0631` (legacy basic group rates). The source path +selects the fastest mandatory legacy mode for this fixture, 24 Mbps. IEEE 802.11-2024 10.6.5.4 +requires non-HT transmission from the nonempty basic legacy set. Update those two assertions and +their explanatory comment, retaining unicast checks and recorded stdout. This is a test-code +correction, not regeneration of fingerprint/statistical/output baselines. No source seal applies. +Revalidate with the same discovery module only; production code is unchanged by this adjustment. + +## Commit preparation + +On the user's subsequent commit request, the verified changes were divided by decision into +new commits. Release and migration notes were added for preflight retry behavior and the pure +duration queries; these documentation-only additions do not change the tested source/test patch. +The source/test patch identity above remains relative to the pinned base, across the entire series. 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); diff --git a/src/inet/common/Simsignals.cc b/src/inet/common/Simsignals.cc index 126e6b6ea7e..c588aa820f5 100644 --- a/src/inet/common/Simsignals.cc +++ b/src/inet/common/Simsignals.cc @@ -23,6 +23,7 @@ simsignal_t linkBrokenSignal = cComponent::registerSignal("linkBroken"); simsignal_t modesetChangedSignal = cComponent::registerSignal("modesetChanged"); + simsignal_t interpacketGapStartedSignal = cComponent::registerSignal("interpacketGapStarted"); simsignal_t interpacketGapEndedSignal = cComponent::registerSignal("interpacketGapEnded"); @@ -153,4 +154,3 @@ void printSignalBanner(simsignal_t signalID, intval_t value, const cObject *deta } } // namespace inet - diff --git a/src/inet/common/Simsignals.h b/src/inet/common/Simsignals.h index 69a1363fe52..b6b20c0f461 100644 --- a/src/inet/common/Simsignals.h +++ b/src/inet/common/Simsignals.h @@ -32,6 +32,7 @@ extern INET_API simsignal_t // admin modesetChangedSignal, + interpacketGapStartedSignal, interpacketGapEndedSignal, @@ -152,4 +153,3 @@ void printSignalBanner(simsignal_t signalID, intval_t value, const cObject *deta } // namespace inet #endif - diff --git a/src/inet/linklayer/ieee80211/Ieee80211Interface.ned b/src/inet/linklayer/ieee80211/Ieee80211Interface.ned index e32eaba59fc..97b87db8034 100644 --- a/src/inet/linklayer/ieee80211/Ieee80211Interface.ned +++ b/src/inet/linklayer/ieee80211/Ieee80211Interface.ned @@ -73,6 +73,7 @@ module Ieee80211Interface extends NetworkInterface like IWirelessInterface **.bitrate = this.bitrate; mac.modeSet = default(this.opMode); mac.*.rateSelection.dataFrameBitrate = default(this.bitrate); + radio.modeSetCoordinatorModule = default(absPath(".mac")); *.macModule = default(absPath(".mac")); *.mibModule = default(absPath(".mib")); *.interfaceTableModule = default(absPath(this.interfaceTableModule)); @@ -129,4 +130,3 @@ module Ieee80211Interface extends NetworkInterface like IWirelessInterface classifier.in <-- { @display("m=n"); } <-- upperLayerIn; } - 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..a9454bcd533 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc @@ -11,6 +11,7 @@ #include "inet/common/INETUtils.h" #include "inet/common/ModuleAccess.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.h" #include "inet/common/ProtocolTag_m.h" #include "inet/common/packet/Message.h" #include "inet/common/packet/Packet.h" @@ -26,10 +27,11 @@ #include "inet/linklayer/ieee80211/mac/contract/IRx.h" #include "inet/linklayer/ieee80211/mac/contract/ITx.h" #include "inet/networklayer/contract/IInterfaceTable.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211ControlInfo_m.h" -#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h" +#include "inet/physicallayer/wireless/ieee80211/contract/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 { @@ -58,6 +60,7 @@ void Ieee80211Mac::initialize(int stage) fcsMode = parseFcsMode(par("fcsMode")); mib.reference(this, "mibModule", true); mib->qos = par("qosStation"); + radio = check_and_cast(gate("lowerLayerOut")->getNextGate()->getOwnerModule()); } else if (stage == INITSTAGE_LINK_LAYER) { cModule *llcModule = gate("upperLayerOut")->getNextGate()->getOwnerModule(); @@ -71,21 +74,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")); @@ -95,6 +84,129 @@ void Ieee80211Mac::initialize(int stage) if (mib->qos && !hcf) throw cRuntimeError("Missing hcf module, required for QoS"); } + else if (stage == INITSTAGE_NETWORK_CONFIGURATION) + modeSetInitialized = true; +} + +void Ieee80211Mac::prepareLocalCapabilities() +{ + Enter_Method("prepareLocalCapabilities"); + if (mib->hasPreparedLocalCapabilities()) + return; + updateLocalHtCapabilities(); +} + +void Ieee80211Mac::updateLocalHtCapabilities(bool reconfiguration) +{ + updateLocalVhtCapabilities(); + if (!modeSet->isHtOperationSupported()) { + if (reconfiguration) + mib->reconfigureLocalHtCapabilities(Ieee80211HtCapabilities(), false); + else + 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"); + + if (reconfiguration) + mib->reconfigureLocalHtCapabilities(localHtCapabilities, true); + else + mib->installLocalHtCapabilities(localHtCapabilities, true); +} + +void Ieee80211Mac::updateLocalVhtCapabilities() +{ + Ieee80211VhtCapabilities localVhtCapabilities; + bool supported = modeSet != nullptr && modeSet->getPhyType() == Ieee80211ModeSet::PhyType::VHT && mib->par("vhtSupported"); + if (!supported) { + mib->installLocalVhtCapabilities(localVhtCapabilities, false); + return; + } + int spatialStreamLimit = std::min(radio->getAntenna()->getNumAntennas(), modeSet->getMaximumNumberOfSpatialStreams()); + // Advertised maps are bounded by the actual long-GI primary-20 catalog, + // as well as the configured directional and antenna limits. + std::array, 8> catalogMcs = {}; + for (int i = 0; i < modeSet->getNumModes(); i++) { + auto mode = modeSet->getMode(i); + auto data = mode->getDataMode(); + int mcs = mode->getVhtMcsIndex(); + int nss = data->getNumberOfSpatialStreams(); + if (mcs >= 0 && mcs <= 9 && nss >= 1 && nss <= 8 && data->getBandwidth() == MHz(20) && + data->getGuardInterval() == SimTime(800, SIMTIME_NS)) + catalogMcs[nss - 1][mcs] = true; + } + auto readMap = [&](const char *parameter, std::array& map) { + auto values = check_and_cast(mib->par(parameter).objectValue()); + if (values->size() != 8) + throw cRuntimeError("%s requires eight per-NSS MCS maxima", parameter); + for (int i = 0; i < 8; i++) { + int value = values->get(i).intValue(); + if (value != -1 && value != 7 && value != 8 && value != 9) + throw cRuntimeError("%s entries must be -1, 7, 8 or 9", parameter); + int maximum = -1; + for (int mcs = 0; mcs <= value && catalogMcs[i][mcs]; mcs++) + if (mcs >= 7) + maximum = mcs; + map[i] = i < spatialStreamLimit ? maximum : -1; + } + if (!isValidVhtMcsMap(map)) + throw cRuntimeError("%s and the VHT catalog must support MCS 0 through 7 at one spatial stream", parameter); + }; + readMap("vhtRxMcsMap", localVhtCapabilities.rxMaxMcs); + readMap("vhtTxMcsMap", localVhtCapabilities.txMaxMcs); + // Intentional limitation of the current packet-level primary-channel PHY: + // management operates the existing VHT-only profile at 20 MHz. Its catalog + // is broader, but does not establish primary/secondary channel support. + // No HT SGI negotiation is claimed by that profile, so 20 MHz uses long GI. + mib->installLocalVhtCapabilities(localVhtCapabilities, true); } void Ieee80211Mac::initializeRadioMode() @@ -164,8 +276,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 +285,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 +295,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 +371,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 +412,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()); } @@ -346,6 +458,83 @@ void Ieee80211Mac::receiveSignal(cComponent *source, simsignal_t signalID, intva } } +void Ieee80211Mac::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) +{ + Enter_Method("%s", cComponent::getSignalName(signalID)); + // Mode-set application uses the coordinator contract, not notifications. +} + +void Ieee80211Mac::applyModeSet(const physicallayer::Ieee80211ModeSet *newModeSet) +{ + Enter_Method_Silent(); + modeSet = const_cast(newModeSet); + updateLocalHtCapabilities(true); +} + +void Ieee80211Mac::registerModeSetConsumer(cModule *consumer, Phase phase) +{ + Enter_Method_Silent(); + if (changingModeSet || modeSetInitialized) + throw cRuntimeError("Mode-set consumers must register before initialization completes"); + if (consumer == nullptr || consumer == this || getContainingNicModule(consumer) != getContainingNicModule(this) || + dynamic_cast(consumer) == nullptr) + throw cRuntimeError("Mode-set consumer must implement the transition contract"); + if (phase != MANAGEMENT_STATE && phase != DERIVED_STATE) + throw cRuntimeError("Invalid mode-set consumer phase"); + auto result = modeSetConsumers.emplace(consumer->getId(), phase); + if (!result.second && result.first->second != phase) + throw cRuntimeError("Mode-set consumer registered in two phases"); +} + +void Ieee80211Mac::unregisterModeSetConsumer(cModule *consumer) +{ + Enter_Method_Silent(); + if (changingModeSet) + throw cRuntimeError("Cannot detach a mode-set consumer during a transition"); + if (consumer == nullptr) + throw cRuntimeError("Cannot detach a null mode-set consumer"); + if (getContainingNicModule(consumer) != getContainingNicModule(this)) + throw cRuntimeError("Cannot detach a mode-set consumer from another interface"); + modeSetConsumers.erase(consumer->getId()); +} + +void Ieee80211Mac::beginModeSetChange(const Ieee80211ModeSet *newModeSet) +{ + Enter_Method_Silent(); + if (changingModeSet) + throw cRuntimeError("Reentrant interface mode-set change"); + if (!modeSetInitialized || !mib->hasPreparedLocalCapabilities()) + throw cRuntimeError("Cannot reconfigure before mode-set initialization completes"); + if (newModeSet == nullptr) + throw cRuntimeError("Cannot clear the mode set of an IEEE 802.11 interface"); + if (std::none_of(modeSetConsumers.begin(), modeSetConsumers.end(), [](const auto& entry) { return entry.second == MANAGEMENT_STATE; })) + throw cRuntimeError("Required management mode-set consumer is not registered"); + for (const auto& entry : modeSetConsumers) + if (getSimulation()->getModule(entry.first) == nullptr) + throw cRuntimeError("Mode-set consumer was deleted without unregistering"); + pendingModeSet = newModeSet; + changingModeSet = true; +} + +void Ieee80211Mac::completeModeSetChange(const Ieee80211ModeSet *newModeSet) +{ + Enter_Method_Silent(); + if (!changingModeSet || pendingModeSet != newModeSet) + throw cRuntimeError("Mode-set completion does not match the pending transition"); + if (modeSet != newModeSet) { + applyModeSet(newModeSet); + for (auto phase : {MANAGEMENT_STATE, DERIVED_STATE}) + for (const auto& entry : modeSetConsumers) + if (entry.second == phase) + check_and_cast( + getSimulation()->getModule(entry.first))->applyModeSet(newModeSet); + mib->publishStateChange(); + emit(modesetChangedSignal, const_cast(modeSet)); + } + pendingModeSet = nullptr; + changingModeSet = false; +} + void Ieee80211Mac::configureRadioMode(IRadio::RadioMode radioMode) { if (radio->getRadioMode() != radioMode) { diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h index 1114deaed00..387eee5546a 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" @@ -21,6 +22,8 @@ #include "inet/linklayer/ieee80211/mac/coordinationfunction/Pcf.h" #include "inet/linklayer/ieee80211/mib/Ieee80211Mib.h" #include "inet/physicallayer/wireless/common/contract/packetlevel/IRadio.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.h" namespace inet { namespace ieee80211 { @@ -35,13 +38,24 @@ 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 physicallayer::IIeee80211ModeSetListener, public physicallayer::IIeee80211ModeSetCoordinator { public: static simsignal_t frameTransmissionOutcomeSignal; + virtual const physicallayer::Ieee80211ModeSet *getModeSet() const override { return modeSet; } + virtual void applyModeSet(const physicallayer::Ieee80211ModeSet *modeSet) override; + void registerModeSetConsumer(cModule *consumer, Phase phase) override; + void unregisterModeSetConsumer(cModule *consumer) override; + void beginModeSetChange(const physicallayer::Ieee80211ModeSet *modeSet) override; + void completeModeSetChange(const physicallayer::Ieee80211ModeSet *modeSet) override; protected: FcsMode fcsMode; + std::map modeSetConsumers; + const physicallayer::Ieee80211ModeSet *pendingModeSet = nullptr; + bool changingModeSet = false; + bool modeSetInitialized = false; ModuleRefByPar mib; opp_component_ptr llc; @@ -65,8 +79,11 @@ class INET_API Ieee80211Mac : public MacProtocolBase virtual int numInitStages() const override { return NUM_INIT_STAGES; } virtual void initialize(int) override; virtual void initializeRadioMode(); + void updateLocalHtCapabilities(bool reconfiguration = false); + void updateLocalVhtCapabilities(); virtual void receiveSignal(cComponent *source, simsignal_t signalID, intval_t value, cObject *details) override; + virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) override; using MacProtocolBase::receiveSignal; virtual void configureRadioMode(physicallayer::IRadio::RadioMode radioMode); virtual void configureNetworkInterface() override; @@ -100,6 +117,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; @@ -115,4 +135,3 @@ class INET_API Ieee80211Mac : public MacProtocolBase } // namespace inet #endif - diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.ned b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.ned index 51f4198660a..09c308a0695 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,14 +87,15 @@ 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"; @display("i=block/layer"); @class(Ieee80211Mac); + @signal[modesetChanged](type=inet::physicallayer::Ieee80211ModeSet); // Completed runtime transition; observational only @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); @@ -135,4 +138,3 @@ module Ieee80211Mac extends MacProtocolBase like IIeee80211Mac @display("p=250,200"); } } - 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..5b3c93abf82 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")); @@ -65,7 +62,8 @@ void Dcaf::calculateTimingParameters() cwMin = modeSet->getCwMin(); if (cwMax == -1) cwMax = modeSet->getCwMax(); - cw = cwMin; + // Model reconfiguration preserves retry backoff within the new bounds. + cw = std::min(cwMax, std::max(cwMin, cw)); EV_DEBUG << "Contention window parameters are initialized: cw = " << cw << ", cwMin = " << cwMin << ", cwMax = " << cwMax << std::endl; } @@ -122,16 +120,14 @@ void Dcaf::expectedChannelAccess(simtime_t time) // don't care } -void Dcaf::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) +void Dcaf::applyModeSet(const physicallayer::Ieee80211ModeSet *newModeSet) { - Enter_Method("%s", cComponent::getSignalName(signalID)); - - if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - calculateTimingParameters(); - } + Enter_Method_Silent(); + modeSet = const_cast(newModeSet); + calculateTimingParameters(); + if (contention != nullptr) + contention->updateTimingParameters(ifs, eifs, slotTime); } } /* 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..2ff03b4eb25 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,12 @@ 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 { + public: + virtual void applyModeSet(const physicallayer::Ieee80211ModeSet *modeSet) override; + protected: - physicallayer::Ieee80211ModeSet *modeSet = nullptr; IContention *contention = nullptr; IChannelAccess::ICallback *callback = nullptr; @@ -43,7 +45,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..fad741bbc8d 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"))); @@ -74,7 +74,8 @@ void Edcaf::calculateTimingParameters() cwMin = getCwMin(ac, modeSet->getCwMin()); if (cwMax == -1) cwMax = getCwMax(ac, modeSet->getCwMax(), modeSet->getCwMin()); - cw = cwMin; + // Model reconfiguration preserves retry backoff within the new bounds. + cw = std::min(cwMax, std::max(cwMin, cw)); EV_DEBUG << "Contention window parameters are initialized: cw = " << cw << ", cwMin = " << cwMin << ", cwMax = " << cwMax << std::endl; } @@ -186,16 +187,14 @@ int Edcaf::getCwMin(AccessCategory ac, int aCwMin) } } -void Edcaf::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) +void Edcaf::applyModeSet(const physicallayer::Ieee80211ModeSet *newModeSet) { - Enter_Method("%s", cComponent::getSignalName(signalID)); - - if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - calculateTimingParameters(); - } + Enter_Method_Silent(); + modeSet = const_cast(newModeSet); + calculateTimingParameters(); + if (contention != nullptr) + contention->updateTimingParameters(ifs, eifs, slotTime); } } // 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..2a5f6262398 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,8 +28,11 @@ 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 { + public: + virtual void applyModeSet(const physicallayer::Ieee80211ModeSet *modeSet) override; + protected: IContention *contention = nullptr; IChannelAccess::ICallback *callback = nullptr; @@ -61,7 +64,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..462874a9611 --- /dev/null +++ b/src/inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.cc @@ -0,0 +1,29 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + + +#include "inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.h" +namespace inet::ieee80211 { +void ModeSetModuleBase::initialize(int stage) +{ + if (stage == INITSTAGE_LOCAL) { + modeSetProvider.reference(this, "modeSetModule", true); + if (auto coordinator = dynamic_cast(modeSetProvider.get())) + coordinator->registerModeSetConsumer(this, physicallayer::IIeee80211ModeSetCoordinator::DERIVED_STATE); + } + else if (stage == INITSTAGE_LINK_LAYER) { + modeSet = modeSetProvider->getConfiguredModeSet(); + if (modeSet == nullptr) + throw cRuntimeError("Configured IEEE 802.11 mode catalog is unavailable"); + } +} +void ModeSetModuleBase::applyModeSet(const physicallayer::Ieee80211ModeSet *newModeSet) +{ + Enter_Method_Silent(); + modeSet = newModeSet; +} +} // 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..c0850f34b94 --- /dev/null +++ b/src/inet/linklayer/ieee80211/mac/common/ModeSetModuleBase.h @@ -0,0 +1,31 @@ +// +// 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/IIeee80211ModeSetProvider.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.h" +namespace inet::ieee80211 { +/** Base for modules with a declared, configuration-lifetime catalog dependency. */ +class INET_API ModeSetModuleBase : public SimpleModule, public physicallayer::IIeee80211ModeSetListener +{ + public: + const physicallayer::Ieee80211ModeSet *getModeSet() const override { return modeSet; } + void applyModeSet(const physicallayer::Ieee80211ModeSet *newModeSet) override; + + 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/contention/Contention.cc b/src/inet/linklayer/ieee80211/mac/contention/Contention.cc index 4a1471e28f1..e4339774eca 100644 --- a/src/inet/linklayer/ieee80211/mac/contention/Contention.cc +++ b/src/inet/linklayer/ieee80211/mac/contention/Contention.cc @@ -85,6 +85,36 @@ void Contention::startContention(int cw, simtime_t ifs, simtime_t eifs, simtime_ handleWithFSM(START); } +void Contention::updateTimingParameters(simtime_t ifs, simtime_t eifs, simtime_t slotTime) +{ + Enter_Method_Silent(); + ASSERT(ifs >= 0 && eifs >= 0 && slotTime >= 0); + if (this->ifs == ifs && this->eifs == eifs && this->slotTime == slotTime) + return; + bool activeBackoff = fsm.getState() == IFS_AND_BACKOFF; + bool pendingEifs = endEifsTime > simTime(); + if (activeBackoff && this->slotTime > SIMTIME_ZERO) + computeRemainingBackoffSlots(); + this->ifs = ifs; + this->eifs = eifs; + this->slotTime = slotTime; + // Modeling policy for runtime reconfiguration: restart the applicable IFS + // and any unfinished slot, retaining the remaining integer backoff count. + if (pendingEifs) + endEifsTime = simTime() + eifs; + if (activeBackoff) { + backoffOptimizationDelta = SIMTIME_ZERO; + scheduledTransmissionTime = simTime() + (pendingEifs ? std::max(ifs, eifs) : ifs) + backoffSlots * slotTime; + cancelEvent(startTxEvent); + scheduleAt(scheduledTransmissionTime, startTxEvent); + // Keep EDCA's collision arbitration aligned without publishing an + // intermediate mode-set state or announcing a new random backoff. + callback->expectedChannelAccess(scheduledTransmissionTime); + if (hasGUI()) + updateDisplayString(scheduledTransmissionTime); + } +} + void Contention::handleWithFSM(EventType event) { emit(stateChangedSignal, fsm.getState()); diff --git a/src/inet/linklayer/ieee80211/mac/contention/Contention.h b/src/inet/linklayer/ieee80211/mac/contention/Contention.h index 3042528c88d..33528b753ac 100644 --- a/src/inet/linklayer/ieee80211/mac/contention/Contention.h +++ b/src/inet/linklayer/ieee80211/mac/contention/Contention.h @@ -71,6 +71,8 @@ class INET_API Contention : public SimpleModule, public IContention // TODO also add a switchToReception() method? because switching takes time, so we dont automatically switch to tx after completing a transmission! (as we may want to transmit immediate frames afterwards) virtual void startContention(int cw, simtime_t ifs, simtime_t eifs, simtime_t slotTime, ICallback *callback) override; + virtual void updateTimingParameters(simtime_t ifs, simtime_t eifs, simtime_t slotTime) override; + virtual void mediumStateChanged(bool mediumFree) override; virtual void corruptedFrameReceived() override; virtual bool isContentionInProgress() override { return fsm.getState() != IDLE; } diff --git a/src/inet/linklayer/ieee80211/mac/contract/IContention.h b/src/inet/linklayer/ieee80211/mac/contract/IContention.h index c29703bf6ab..63c2285960a 100644 --- a/src/inet/linklayer/ieee80211/mac/contract/IContention.h +++ b/src/inet/linklayer/ieee80211/mac/contract/IContention.h @@ -46,6 +46,10 @@ class INET_API IContention virtual ~IContention() {} virtual void startContention(int cw, simtime_t ifs, simtime_t eifs, simtime_t slotTime, ICallback *callback) = 0; + // Runtime timing change: retain completed whole backoff slots and restart the + // applicable IFS with the new timing. An unchanged tuple preserves the schedule. + // Does not draw a new backoff or emit notifications while a mode set is applied. + virtual void updateTimingParameters(simtime_t ifs, simtime_t eifs, simtime_t slotTime) = 0; virtual bool isContentionInProgress() = 0; // notifications diff --git a/src/inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.h b/src/inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.h new file mode 100644 index 00000000000..e7dcc142d6b --- /dev/null +++ b/src/inet/linklayer/ieee80211/mac/contract/IIeee80211MacConfiguration.h @@ -0,0 +1,24 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + + +#ifndef INET_IIEEE80211MACCONFIGURATION_H +#define INET_IIEEE80211MACCONFIGURATION_H + +#include "inet/linklayer/ieee80211/mac/contract/IIeee80211ModeSetProvider.h" + +namespace inet::ieee80211 { +/** MAC configuration extends catalog access with preparation after PHY readiness. + * Repeated preparation preserves protocol and algorithm state. + */ +class INET_API IIeee80211MacConfiguration : public IIeee80211ModeSetProvider +{ + public: + virtual ~IIeee80211MacConfiguration() = default; + 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..4bdadf22c67 --- /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 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/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..a85e98c3680 100644 --- a/src/inet/linklayer/ieee80211/mac/originator/TxopProcedure.cc +++ b/src/inet/linklayer/ieee80211/mac/originator/TxopProcedure.cc @@ -20,9 +20,10 @@ Define_Module(TxopProcedure); void TxopProcedure::initialize(int stage) { - ModeSetListener::initialize(stage); + ModeSetModuleBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { - limit = par("txopLimit"); + configuredLimit = par("txopLimit"); + limit = configuredLimit; WATCH(start); WATCH(protectionMechanism); } @@ -78,9 +79,12 @@ void TxopProcedure::startTxop(AccessCategory ac) Enter_Method("startTxop"); if (start != -1) throw cRuntimeError("Txop is already running"); - if (limit == -1) { + // Resolve the default for each new TXOP; a mode change must not alter + // the limit of a TXOP already in progress or a configured override. + if (configuredLimit == -1) limit = getTxopLimit(modeSet->getPhyType(), ac).get(); - } + else + limit = configuredLimit; // The STA selects between single and multiple protection when it transmits the first frame of a TXOP. // All subsequent frames transmitted by the STA in the same TXOP use the same class of duration settings. protectionMechanism = selectProtectionMechanism(ac); diff --git a/src/inet/linklayer/ieee80211/mac/originator/TxopProcedure.h b/src/inet/linklayer/ieee80211/mac/originator/TxopProcedure.h index 656b74e8055..c37a58a6e83 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; @@ -33,6 +33,7 @@ class INET_API TxopProcedure : public ModeSetListener protected: simtime_t start = -1; + simtime_t configuredLimit = -1; simtime_t limit = -1; ProtectionMechanism protectionMechanism = ProtectionMechanism::UNDEFINED_PROTECTION; 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..2ae0237638a 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,16 +62,13 @@ void RateControlBase::emitDatarateChangedSignal(const MacAddress& receiver, cons } } -void RateControlBase::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) +void RateControlBase::applyModeSet(const Ieee80211ModeSet *newModeSet) { - Enter_Method("%s", cComponent::getSignalName(signalID)); - - if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - resetRateControl(); - } + Enter_Method_Silent(); + modeSet = const_cast(newModeSet); + getInitialMode(); // Validate fixed initial rates even before the first peer is used. + 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..d4072b5fe81 100644 --- a/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.h +++ b/src/inet/linklayer/ieee80211/mac/ratecontrol/RateControlBase.h @@ -9,21 +9,21 @@ #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: + virtual void applyModeSet(const physicallayer::Ieee80211ModeSet *modeSet) override; static simsignal_t datarateChangedSignal; 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..7d9751369ae 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.cc @@ -5,6 +5,7 @@ // +#include #include #include "inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h" @@ -14,6 +15,23 @@ namespace ieee80211 { using namespace inet::physicallayer; +const IIeee80211Mode *selectGroupAddressedMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *requestedMode) +{ + // IEEE Std 802.11-2024, 10.6.5.1 and 10.6.5.4. The model advertises + // mandatory legacy operational modes as its BSS basic legacy rate set. + const auto *resolvedMode = modeSet->containsMode(requestedMode) ? requestedMode : modeSet->findCompatibleMode(requestedMode); + const IIeee80211Mode *legacyMode = nullptr; + for (const auto *candidate : modeSet->getLegacyOperationalModes()) { + if (!modeSet->getIsMandatory(candidate)) + continue; + if (candidate == resolvedMode) + return candidate; + if (legacyMode == nullptr || candidate->getDataMode()->getNetBitrate() > legacyMode->getDataMode()->getNetBitrate()) + legacyMode = candidate; + } + return legacyMode != nullptr ? legacyMode : requestedMode; +} + namespace { static const IIeee80211Mode *getLegacyFallback(const Ieee80211ModeSet *modeSet, @@ -49,12 +67,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 +83,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 +123,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 +134,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 +146,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; @@ -134,5 +154,85 @@ const IIeee80211Mode *selectPeerCompatibleMode(const Ieee80211ModeSet *modeSet, return bestMode != nullptr ? bestMode : getLegacyFallback(modeSet, mode, peerAddress); } +const IIeee80211Mode *selectPeerCompatibleVhtMode(const Ieee80211ModeSet *modeSet, + const Ieee80211VhtCapabilities& local, const Ieee80211VhtOperation& localOperation, + const Ieee80211Mib::PeerVhtState *peer, const IIeee80211Mode *requested) +{ + // IEEE Std 802.11-2024, 10.6.13.1 and 10.6.13.2 define directional + // VHT MCS/NSS and long-GI rate support. Width capability and BSS width + // are described in 9.4.2.156.2 (Table 9-313) and 11.38.1. + // The requested-rate ceiling, highest-rate choice and legacy fallback + // are model policy, not a prescribed IEEE rate-control algorithm. + if (requested == nullptr || requested->getVhtMcsIndex() < 0) + return requested; + if (modeSet == nullptr) + throw cRuntimeError("Cannot select a VHT mode without a mode set"); + const IIeee80211Mode *best = nullptr; + auto ceiling = requested->getDataMode()->getNetBitrate(); + if (peer != nullptr) { + const auto& remote = peer->advertisedCapabilities; + auto compatible = [&](const IIeee80211Mode *mode) { + int mcs = mode->getVhtMcsIndex(); + auto data = mode->getDataMode(); + int nss = data->getNumberOfSpatialStreams(); + auto width = data->getBandwidth(); + if (mcs < 0 || nss < 1 || nss > 8 || local.txMaxMcs[nss - 1] < mcs || remote.rxMaxMcs[nss - 1] < mcs || + width > localOperation.channelWidth || width > peer->operation.channelWidth || + (width > MHz(80) && (!local.supported160Mhz || !remote.supported160Mhz))) + return false; + // IEEE Std 802.11-2024, 10.17: receiver-advertised short GI for + // the selected width and local activation are both required. + // This model uses local capability flags as its activation limits. + bool shortGi = data->getGuardInterval() == SimTime(400, SIMTIME_NS); + bool giSupported = width == MHz(20) ? local.shortGi20 && remote.shortGi20 : + width == MHz(40) ? local.shortGi40 && remote.shortGi40 : + width == MHz(80) ? local.shortGi80 && remote.shortGi80 : local.shortGi160 && remote.shortGi160; + if (shortGi && !giSupported) + return false; + // Highest Supported Long GI Data Rate limits refer to long-GI rate, + // including when selecting a corresponding short-GI transmission. + auto longGiRate = data->getNetBitrate(); + if (shortGi) { + bool found = false; + for (int i = 0; i < modeSet->getNumModes(); i++) { + auto counterpart = modeSet->getMode(i); + auto counterpartData = counterpart->getDataMode(); + if (counterpart->getVhtMcsIndex() == mcs && counterpartData->getBandwidth() == width && + counterpartData->getNumberOfSpatialStreams() == nss && + counterpartData->getGuardInterval() == SimTime(800, SIMTIME_NS)) { + longGiRate = counterpartData->getNetBitrate(); + found = true; + break; + } + } + if (!found) + return false; + } + // IEEE Std 802.11-2024, 10.6.13.1/.2 and Table 9-315: + // compare floor(long-GI rate in Mb/s), also for short-GI modes. + int encodedRate = std::floor(longGiRate.get()); + return (local.txHighestLongGiRateMbps == 0 || encodedRate <= local.txHighestLongGiRateMbps) && + (remote.rxHighestLongGiRateMbps == 0 || encodedRate <= remote.rxHighestLongGiRateMbps); + }; + if (modeSet->containsMode(requested) && compatible(requested)) + return requested; + for (int i = 0; i < modeSet->getNumModes(); i++) { + auto mode = modeSet->getMode(i); + if (mode->getDataMode()->getNetBitrate() <= ceiling && compatible(mode) && + (best == nullptr || mode->getDataMode()->getNetBitrate() > best->getDataMode()->getNetBitrate())) + best = mode; + } + } + if (best != nullptr) + return best; + for (auto mode : modeSet->getLegacyOperationalModes()) + if (mode->getDataMode()->getNetBitrate() <= ceiling && + (best == nullptr || mode->getDataMode()->getNetBitrate() > best->getDataMode()->getNetBitrate())) + best = mode; + if (best == nullptr) + throw cRuntimeError("No legacy fallback for unnegotiated VHT mode"); + return best; +} + } // namespace ieee80211 } // namespace inet diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h b/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h index ba5eaebf764..ee2547fb299 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h +++ b/src/inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h @@ -14,6 +14,9 @@ namespace inet { namespace ieee80211 { +INET_API const physicallayer::IIeee80211Mode *selectGroupAddressedMode( + const physicallayer::Ieee80211ModeSet *modeSet, const physicallayer::IIeee80211Mode *requestedMode); + /** * Selects a mode that is compatible with the negotiated receive capabilities * of a peer. Non-HT modes are returned unchanged. A null peer state denotes @@ -24,7 +27,15 @@ 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); + +// VHT selection intersects local Tx and peer Rx; absent negotiation uses legacy. +INET_API const physicallayer::IIeee80211Mode *selectPeerCompatibleVhtMode( + const physicallayer::Ieee80211ModeSet *modeSet, + const Ieee80211VhtCapabilities& local, const Ieee80211VhtOperation& localOperation, + const Ieee80211Mib::PeerVhtState *peer, + const physicallayer::IIeee80211Mode *requested); } // 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..ab2226e6838 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.cc @@ -22,25 +22,13 @@ 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)); - double dataFrameBitrate = par("dataFrameBitrate"); - dataFrameMode = (dataFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(dataFrameBitrate), Hz(par("dataFrameBandwidth")), par("dataFrameNumSpatialStreams")); - double mgmtFrameBitrate = par("mgmtFrameBitrate"); - mgmtFrameMode = (mgmtFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(mgmtFrameBitrate)); - double controlFrameBitrate = par("controlFrameBitrate"); - controlFrameMode = (controlFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(controlFrameBitrate)); - double responseAckFrameBitrate = par("responseAckFrameBitrate"); - responseAckFrameMode = (responseAckFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseAckFrameBitrate)); - double responseBlockAckFrameBitrate = par("responseBlockAckFrameBitrate"); - responseBlockAckFrameMode = (responseBlockAckFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseBlockAckFrameBitrate)); - double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); - responseCtsFrameMode = (responseCtsFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseCtsFrameBitrate)); + updateModes(); } } @@ -56,7 +44,7 @@ void QosRateSelection::ensurePerReceiverModesResolved() throw cRuntimeError("dataFrameBitratePerReceiver: cannot resolve receiver interface module path '%s'", path.c_str()); auto networkInterface = check_and_cast(module); try { - auto mode = modeSet->getMode(bps(value.doubleValueInUnit("bps")), Hz(par("dataFrameBandwidth")), par("dataFrameNumSpatialStreams")); + auto mode = modeSet->getMode(bps(value.doubleValueInUnit("bps")), Hz(par("dataFrameBandwidth")), par("dataFrameNumSpatialStreams"), par("dataFrameGuardInterval")); perReceiverDataFrameMode[networkInterface->getMacAddress()] = mode; } catch (const cRuntimeError& e) { @@ -65,6 +53,31 @@ void QosRateSelection::ensurePerReceiverModesResolved() } } +void QosRateSelection::updateModes() +{ + if (modeSet == nullptr) + return; + fastestMandatoryMode = modeSet->getFastestMandatoryMode(); + double multicastFrameBitrate = par("multicastFrameBitrate"); + multicastFrameMode = (multicastFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(multicastFrameBitrate)); + double dataFrameBitrate = par("dataFrameBitrate"); + dataFrameMode = (dataFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(dataFrameBitrate), Hz(par("dataFrameBandwidth")), par("dataFrameNumSpatialStreams"), par("dataFrameGuardInterval")); + double mgmtFrameBitrate = par("mgmtFrameBitrate"); + mgmtFrameMode = (mgmtFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(mgmtFrameBitrate)); + double controlFrameBitrate = par("controlFrameBitrate"); + controlFrameMode = (controlFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(controlFrameBitrate)); + double responseAckFrameBitrate = par("responseAckFrameBitrate"); + responseAckFrameMode = (responseAckFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseAckFrameBitrate)); + double responseBlockAckFrameBitrate = par("responseBlockAckFrameBitrate"); + responseBlockAckFrameMode = (responseBlockAckFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseBlockAckFrameBitrate)); + double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); + responseCtsFrameMode = (responseCtsFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseCtsFrameBitrate)); + fastestMandatoryMode = modeSet->getFastestMandatoryMode(); + lastTransmittedFrameMode.clear(); + perReceiverDataFrameMode.clear(); + perReceiverResolved = false; +} + const IIeee80211Mode *QosRateSelection::getMode(Packet *packet, const Ptr& header) { const auto& modeReqTag = packet->findTag(); @@ -101,10 +114,8 @@ const IIeee80211Mode *QosRateSelection::computeResponseAckFrameMode(Packet *pack ASSERT(modeSet->containsMode(mode)); const IIeee80211Mode *responseMode; if (!responseAckFrameMode) { - if (modeSet->getIsMandatory(mode)) - responseMode = mode; - else if (auto slowerMode = modeSet->getSlowerMandatoryMode(mode)) - responseMode = slowerMode; + if (auto mandatoryMode = modeSet->getMandatoryModeAtOrBelow(mode)) + responseMode = mandatoryMode; else throw cRuntimeError("Mandatory mode not found"); } @@ -120,10 +131,8 @@ const IIeee80211Mode *QosRateSelection::computeResponseCtsFrameMode(Packet *pack ASSERT(modeSet->containsMode(mode)); const IIeee80211Mode *responseMode; if (!responseCtsFrameMode) { - if (modeSet->getIsMandatory(mode)) - responseMode = mode; - else if (auto slowerMode = modeSet->getSlowerMandatoryMode(mode)) - responseMode = slowerMode; + if (auto mandatoryMode = modeSet->getMandatoryModeAtOrBelow(mode)) + responseMode = mandatoryMode; else throw cRuntimeError("Mandatory mode not found"); } @@ -150,9 +159,15 @@ const IIeee80211Mode *QosRateSelection::computeResponseBlockAckFrameMode(Packet const IIeee80211Mode *QosRateSelection::computeDataOrMgmtFrameMode(const Ptr& dataOrMgmtHeader) { + if (dataOrMgmtHeader->getReceiverAddress().isMulticast()) { + const auto *requestedMode = multicastFrameMode; + if (requestedMode == nullptr) + requestedMode = dynamicPtrCast(dataOrMgmtHeader) ? dataFrameMode : mgmtFrameMode; + return selectGroupAddressedMode(modeSet, requestedMode != nullptr ? requestedMode : fastestMandatoryMode); + } // Per-receiver override for originated unicast data frames (see dataFrameBitratePerReceiver). // Wins over the interface-wide dataFrameMode / rate control; group-addressed and management - // frames are left to the existing rules below. + // frames were handled above. if (dynamicPtrCast(dataOrMgmtHeader) && !dataOrMgmtHeader->getReceiverAddress().isMulticast()) { ensurePerReceiverModesResolved(); auto it = perReceiverDataFrameMode.find(dataOrMgmtHeader->getReceiverAddress()); @@ -163,46 +178,9 @@ const IIeee80211Mode *QosRateSelection::computeDataOrMgmtFrameMode(const PtrgetReceiverAddress(), dataFrameMode); if (dynamicPtrCast(dataOrMgmtHeader) && mgmtFrameMode) return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), mgmtFrameMode); - // This subclause describes the rate selection rules for group addressed data and management frames, excluding - // the following: - // — Non-STBC Beacon and non-STBC PSMP frames - // — STBC group addressed data and management frames - // — Data frames located in an FMS stream (see 10.23.7) - if (dataOrMgmtHeader->getReceiverAddress().isMulticast()) { - // If the BSSBasicRateSet parameter is not empty, a data or management frame (excluding the frames listed - // above) with a group address in the Address 1 field shall be transmitted in a non-HT PPDU using one of the - // rates included in the BSSBasicRateSet parameter or the rate chosen by the AP, described in 10.23.7, if the data - // frames are part of an FMS stream. - // TODO BSSBasicRateSet - // If the BSSBasicRateSet parameter is empty and the BSSBasicMCSSet parameter is not empty, the frame shall - // be transmitted in an HT PPDU using one of the MCSs included in the BSSBasicMCSSet parameter. - - // If both the BSSBasicRateSet parameter and the BSSBasicMCSSet parameter are empty (e.g., a scanning STA - // that is not yet associated with a BSS), the frame shall be transmitted in a non-HT PPDU using one of the - // mandatory PHY rates. - // The rate control is not consulted for these frames. It adapts to the feedback of one - // peer, and a group-addressed frame has no peer: it is never acknowledged, so nothing - // would ever correct a rate chosen for it. - return fastestMandatoryMode; - } - // A data or management frame not identified in 9.7.5.1 through 9.7.5.5 shall be sent using any data rate or MCS - // subject to the following constraints: - // — A STA shall not transmit a frame using a rate or MCS that is not supported by the receiver STA or - // STAs, as reported in any Supported Rates element, Extended Supported Rates element, or - // Supported MCS field in management frames transmitted by the receiver STA. - // — A STA shall not transmit a frame using a value for the CH_BANDWIDTH parameter of the - // TXVECTOR that is not supported by the receiver STA. - // — A STA shall not initiate transmission of a frame at a data rate higher than the greatest rate in the - // OperationalRateSet or the HTOperationalMCSset, which are parameters of the MLME- - // JOIN.request primitive. - else { - // TODO Supported Rates element, Extended Supported Rates element - // TODO OperationalRateSet or the HTOperationalMCSset - if (dataOrMgmtRateControl) - return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), dataOrMgmtRateControl->getRate(dataOrMgmtHeader->getReceiverAddress())); - else - return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), fastestMandatoryMode); - } + if (dataOrMgmtRateControl) + return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), dataOrMgmtRateControl->getRate(dataOrMgmtHeader->getReceiverAddress())); + return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), fastestMandatoryMode); } const IIeee80211Mode *QosRateSelection::computeControlFrameMode(const Ptr& header, TxopProcedure *txopProcedure) @@ -270,14 +248,13 @@ const IIeee80211Mode *QosRateSelection::computeMode(Packet *packet, const PtrgetReceiverAddress(), computeControlFrameMode(header, txopProcedure)); } -void QosRateSelection::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) +void QosRateSelection::applyModeSet(const physicallayer::Ieee80211ModeSet *newModeSet) { - Enter_Method("%s", cComponent::getSignalName(signalID)); - - if (signalID == modesetChangedSignal) { - modeSet = check_and_cast(obj); - fastestMandatoryMode = modeSet->getFastestMandatoryMode(); - } + Enter_Method_Silent(); + modeSet = const_cast(newModeSet); + updateModes(); + if (getSimulation()->getContextType() != CTX_INITIALIZE) + ensurePerReceiverModesResolved(); } void QosRateSelection::frameTransmitted(Packet *packet, const Ptr& header) @@ -288,11 +265,16 @@ void QosRateSelection::frameTransmitted(Packet *packet, const PtrgetHtMcsIndex() < 0) + if (mode == nullptr || peerAddress.isMulticast() || !mib) return mode; - return selectPeerCompatibleMode(modeSet, mib->findPeerHtState(peerAddress), mode, peerAddress); + if (mode->getVhtMcsIndex() >= 0) + return selectPeerCompatibleVhtMode(modeSet, mib->getLocalVhtCapabilities(), mib->getLocalVhtOperation(), + mib->findPeerVhtState(peerAddress), mode); + if (mode->getHtMcsIndex() < 0) + return mode; + return selectPeerCompatibleMode(modeSet, mib->findPeerCapabilities(peerAddress), mode, peerAddress, + mib->hasHtOperation() ? &mib->getHtOperation() : nullptr, mib->relationshipAllowsHt(peerAddress)); } } /* namespace ieee80211 */ } /* namespace inet */ - diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h index 569ef742d37..60b353138f0 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,15 @@ 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 { + public: + virtual void applyModeSet(const physicallayer::Ieee80211ModeSet *modeSet) override; + protected: IRateControl *dataOrMgmtRateControl = nullptr; ModuleRefByPar mib; - const physicallayer::Ieee80211ModeSet *modeSet = nullptr; std::map lastTransmittedFrameMode; // originator frame modes @@ -58,7 +60,7 @@ 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; + virtual void updateModes(); // Builds perReceiverDataFrameMode on first use. Deferred out of initialize() because peer // MAC addresses are assigned during INITSTAGE_LINK_LAYER with undefined intra-stage module @@ -92,4 +94,3 @@ class INET_API QosRateSelection : public IQosRateSelection, public ModeSetListen } /* namespace inet */ #endif - diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned b/src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned index 35bf0e1ace5..35f9aa93181 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"); @@ -32,8 +33,9 @@ simple QosRateSelection extends SimpleModule double responseCtsFrameBitrate @unit(bps) = default(-1bps); double dataFrameBitrate @unit(bps) = default(-1bps); // Fastest - double dataFrameBandwidth @unit(Hz) = default(nan Hz); // Unspecified by default - int dataFrameNumSpatialStreams = default(-1); // Unspecified by default + double dataFrameBandwidth @unit(Hz) = default(nan Hz); // Explicit mode qualifier when dataFrameBitrate is specified + int dataFrameNumSpatialStreams = default(-1); // Explicit mode qualifier when dataFrameBitrate is specified + double dataFrameGuardInterval @unit(s) = default(-1s); // Explicit mode qualifier when dataFrameBitrate is specified; negative means unspecified, otherwise it must equal the selected PHY mode's modeled GI // Per-receiver unicast data-frame rate. Keys are peer interface module paths (e.g. // "host1.wlan[0]"), resolved to MAC addresses at run time; values are bitrates (bps). diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc index 9d28a5142cd..65c70a74557 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.cc @@ -8,6 +8,7 @@ #include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h" #include "inet/common/ModuleAccess.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.h" #include "inet/common/Simsignals.h" #include "inet/linklayer/ieee80211/mac/contract/IRateControl.h" #include "inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h" @@ -26,25 +27,13 @@ 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"))); - double multicastFrameBitrate = par("multicastFrameBitrate"); - multicastFrameMode = (multicastFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(multicastFrameBitrate)); - double dataFrameBitrate = par("dataFrameBitrate"); - dataFrameMode = (dataFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(dataFrameBitrate), Hz(par("dataFrameBandwidth")), par("dataFrameNumSpatialStreams")); - double mgmtFrameBitrate = par("mgmtFrameBitrate"); - mgmtFrameMode = (mgmtFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(mgmtFrameBitrate)); - double controlFrameBitrate = par("controlFrameBitrate"); - controlFrameMode = (controlFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(controlFrameBitrate)); - double responseAckFrameBitrate = par("responseAckFrameBitrate"); - responseAckFrameMode = (responseAckFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseAckFrameBitrate)); - double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); - responseCtsFrameMode = (responseCtsFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseCtsFrameBitrate)); - fastestMandatoryMode = modeSet->getFastestMandatoryMode(); + updateModes(); // WATCH(dataOrMgmtRateControl); // WATCH(*((cObject**)&fastestMandatoryMode)); @@ -75,7 +64,7 @@ void RateSelection::ensurePerReceiverModesResolved() throw cRuntimeError("dataFrameBitratePerReceiver: cannot resolve receiver interface module path '%s'", path.c_str()); auto networkInterface = check_and_cast(module); try { - auto mode = modeSet->getMode(bps(value.doubleValueInUnit("bps")), Hz(par("dataFrameBandwidth")), par("dataFrameNumSpatialStreams")); + auto mode = modeSet->getMode(bps(value.doubleValueInUnit("bps")), Hz(par("dataFrameBandwidth")), par("dataFrameNumSpatialStreams"), par("dataFrameGuardInterval")); perReceiverDataFrameMode[networkInterface->getMacAddress()] = mode; } catch (const cRuntimeError& e) { @@ -84,6 +73,28 @@ void RateSelection::ensurePerReceiverModesResolved() } } +void RateSelection::updateModes() +{ + if (modeSet == nullptr) + return; + double multicastFrameBitrate = par("multicastFrameBitrate"); + multicastFrameMode = (multicastFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(multicastFrameBitrate)); + double dataFrameBitrate = par("dataFrameBitrate"); + dataFrameMode = (dataFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(dataFrameBitrate), Hz(par("dataFrameBandwidth")), par("dataFrameNumSpatialStreams"), par("dataFrameGuardInterval")); + double mgmtFrameBitrate = par("mgmtFrameBitrate"); + mgmtFrameMode = (mgmtFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(mgmtFrameBitrate)); + double controlFrameBitrate = par("controlFrameBitrate"); + controlFrameMode = (controlFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(controlFrameBitrate)); + double responseAckFrameBitrate = par("responseAckFrameBitrate"); + responseAckFrameMode = (responseAckFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseAckFrameBitrate)); + double responseCtsFrameBitrate = par("responseCtsFrameBitrate"); + responseCtsFrameMode = (responseCtsFrameBitrate == -1) ? nullptr : modeSet->getMode(bps(responseCtsFrameBitrate)); + fastestMandatoryMode = modeSet->getFastestMandatoryMode(); + lastTransmittedFrameMode.clear(); + perReceiverDataFrameMode.clear(); + perReceiverResolved = false; +} + const IIeee80211Mode *RateSelection::getMode(Packet *packet, const Ptr& header) { const auto& modeReqTag = packet->findTag(); @@ -108,7 +119,7 @@ const IIeee80211Mode *RateSelection::computeResponseAckFrameMode(Packet *packet, else { auto mode = getMode(packet, dataOrMgmtHeader); ASSERT(modeSet->containsMode(mode)); - auto responseMode = modeSet->getIsMandatory(mode) ? mode : modeSet->getSlowerMandatoryMode(mode); // TODO BSSBasicRateSet + auto responseMode = modeSet->getMandatoryModeAtOrBelow(mode); // TODO BSSBasicRateSet return getPeerCompatibleMode(dataOrMgmtHeader->getTransmitterAddress(), responseMode); } } @@ -120,7 +131,7 @@ const IIeee80211Mode *RateSelection::computeResponseCtsFrameMode(Packet *packet, else { auto mode = getMode(packet, rtsFrame); ASSERT(modeSet->containsMode(mode)); - auto responseMode = modeSet->getIsMandatory(mode) ? mode : modeSet->getSlowerMandatoryMode(mode); // TODO BSSBasicRateSet + auto responseMode = modeSet->getMandatoryModeAtOrBelow(mode); // TODO BSSBasicRateSet return getPeerCompatibleMode(rtsFrame->getTransmitterAddress(), responseMode); } } @@ -140,28 +151,28 @@ const IIeee80211Mode *RateSelection::computeResponseCtsFrameMode(Packet *packet, // const IIeee80211Mode *RateSelection::computeDataOrMgmtFrameMode(const Ptr& dataOrMgmtHeader) { + if (dataOrMgmtHeader->getReceiverAddress().isMulticast()) { + const auto *requestedMode = multicastFrameMode; + if (requestedMode == nullptr) + requestedMode = dynamicPtrCast(dataOrMgmtHeader) ? dataFrameMode : mgmtFrameMode; + return selectGroupAddressedMode(modeSet, requestedMode != nullptr ? requestedMode : fastestMandatoryMode); + } // Per-receiver override for originated unicast data frames (see dataFrameBitratePerReceiver). // Wins over the interface-wide dataFrameMode / rate control; group-addressed and management - // frames are left to the existing rules below. + // frames were handled above. if (dynamicPtrCast(dataOrMgmtHeader) && !dataOrMgmtHeader->getReceiverAddress().isMulticast()) { ensurePerReceiverModesResolved(); auto it = perReceiverDataFrameMode.find(dataOrMgmtHeader->getReceiverAddress()); if (it != perReceiverDataFrameMode.end()) return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), it->second); } - if (dataOrMgmtHeader->getReceiverAddress().isMulticast() && multicastFrameMode) - return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), multicastFrameMode); if (dynamicPtrCast(dataOrMgmtHeader) && dataFrameMode) return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), dataFrameMode); if (dynamicPtrCast(dataOrMgmtHeader) && mgmtFrameMode) return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), mgmtFrameMode); - // Rate control adapts to the feedback of one peer, and a group-addressed frame has no peer: - // it is never acknowledged, so nothing would ever correct a rate chosen for it. Group-addressed - // frames therefore take a mandatory rate, as the clause above requires. - if (dataOrMgmtRateControl && !dataOrMgmtHeader->getReceiverAddress().isMulticast()) + if (dataOrMgmtRateControl) return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), dataOrMgmtRateControl->getRate(dataOrMgmtHeader->getReceiverAddress())); - else - return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), fastestMandatoryMode); + return getPeerCompatibleMode(dataOrMgmtHeader->getReceiverAddress(), fastestMandatoryMode); } // 802.11-1999 Std. @@ -184,14 +195,13 @@ const IIeee80211Mode *RateSelection::computeMode(Packet *packet, const Ptr(obj); - fastestMandatoryMode = modeSet->getFastestMandatoryMode(); - } + Enter_Method_Silent(); + modeSet = const_cast(newModeSet); + updateModes(); + if (getSimulation()->getContextType() != CTX_INITIALIZE) + ensurePerReceiverModesResolved(); } void RateSelection::frameTransmitted(Packet *packet, const Ptr& header) @@ -221,11 +231,16 @@ void RateSelection::emitDatarateSelected(cComponent *emitter, const PtrgetHtMcsIndex() < 0) + if (mode == nullptr || peerAddress.isMulticast() || !mib) return mode; - return selectPeerCompatibleMode(modeSet, mib->findPeerHtState(peerAddress), mode, peerAddress); + if (mode->getVhtMcsIndex() >= 0) + return selectPeerCompatibleVhtMode(modeSet, mib->getLocalVhtCapabilities(), mib->getLocalVhtOperation(), + mib->findPeerVhtState(peerAddress), mode); + if (mode->getHtMcsIndex() < 0) + return mode; + return selectPeerCompatibleMode(modeSet, mib->findPeerCapabilities(peerAddress), mode, peerAddress, + mib->hasHtOperation() ? &mib->getHtOperation() : nullptr, mib->relationshipAllowsHt(peerAddress)); } } // namespace ieee80211 } // namespace inet - diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h index 69441331453..a1ea7a92cf6 100644 --- a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h +++ b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.h @@ -9,11 +9,12 @@ #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" #include "inet/linklayer/ieee80211/mib/Ieee80211Mib.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.h" namespace inet { namespace ieee80211 { @@ -31,14 +32,17 @@ 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 { + public: + virtual const physicallayer::Ieee80211ModeSet *getModeSet() const override { return modeSet; } + virtual void applyModeSet(const physicallayer::Ieee80211ModeSet *modeSet) override; + protected: IRateControl *dataOrMgmtRateControl = nullptr; ModuleRefByPar mib; const physicallayer::IIeee80211Mode *fastestMandatoryMode = nullptr; - const physicallayer::Ieee80211ModeSet *modeSet = nullptr; std::map lastTransmittedFrameMode; // originator frame modes @@ -58,7 +62,7 @@ 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; + virtual void updateModes(); // Builds perReceiverDataFrameMode on first use. Deferred out of initialize() because peer // MAC addresses are assigned during INITSTAGE_LINK_LAYER with undefined intra-stage module @@ -100,4 +104,3 @@ class INET_API RateSelection : public IRateSelection, public SimpleModule, publi } // namespace inet #endif - diff --git a/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned b/src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned index 89f63916421..31f8effbe5e 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"); @@ -27,8 +28,9 @@ simple RateSelection extends SimpleModule like IRateSelection double responseCtsFrameBitrate @unit(bps) = default(-1bps); double dataFrameBitrate @unit(bps) = default(-1bps); // Fastest - double dataFrameBandwidth @unit(Hz) = default(nan Hz); // Unspecified by default - int dataFrameNumSpatialStreams = default(-1); // Unspecified by default + double dataFrameBandwidth @unit(Hz) = default(nan Hz); // Explicit mode qualifier when dataFrameBitrate is specified + int dataFrameNumSpatialStreams = default(-1); // Explicit mode qualifier when dataFrameBitrate is specified + double dataFrameGuardInterval @unit(s) = default(-1s); // Explicit mode qualifier when dataFrameBitrate is specified; negative means unspecified, otherwise it must equal the selected PHY mode's modeled GI // Per-receiver unicast data-frame rate. Keys are peer interface module paths (e.g. // "host1.wlan[0]"), resolved to MAC addresses at run time; values are bitrates (bps). diff --git a/src/inet/linklayer/ieee80211/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..9d9bb33399f 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.cc +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.cc @@ -18,6 +18,7 @@ #include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.h" #include "inet/linklayer/ieee80211/mgmt/Ieee80211BeaconInterval.h" #include "inet/linklayer/ieee80211/mgmt/Ieee80211HtMgmtElements.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211VhtMgmtElements.h" #include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtTransactionTag_m.h" #include "inet/networklayer/common/NetworkInterface.h" @@ -90,8 +91,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 +130,39 @@ 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->removePeerCapabilities(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); + mib->removePeerCapabilities(address); } + if (sta->second.pendingVhtCapabilitiesValid && mib->isVhtOperationSupported() && + sta->second.pendingVhtGeneration == mib->getVhtCapabilityGeneration()) + mib->setPeerVhtCapabilities(address, sta->second.pendingVhtCapabilities, mib->getLocalVhtOperation()); + else + mib->removePeerVhtCapabilities(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); @@ -200,6 +203,9 @@ void Ieee80211MgmtAp::clearPendingAssociation(StaInfo *sta) mib->cancelAssociationIdReservation(sta->address); sta->pendingAssociationSuccessful = false; sta->pendingAssociationTransactionId = 0; + sta->pendingVhtCapabilitiesValid = false; + sta->pendingVhtCapabilities = Ieee80211VhtCapabilities(); + sta->pendingVhtGeneration = 0; sta->pendingHtStateAvailable = false; sta->pendingHtCapabilitiesValid = false; sta->pendingHtCapabilities = Ieee80211HtCapabilities(); @@ -216,9 +222,11 @@ void Ieee80211MgmtAp::sendBeacon() body->setBeaconInterval(beaconInterval); body->setChannelNumber(getDsssParameterSetChannel()); addHtCapabilities(body); - if (mib->isHtOperationSupported()) + addVhtCapabilities(body); + addVhtOperation(body); + 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)); + body->setChunkLength(B(8 + 2 + 2 + (2 + ssid.length()) + (body->getChannelNumber() != -1 ? 3 : 0)) + getSupportedRateElementsLength(body) + getHtMgmtElementsLength(body) + getVhtMgmtElementsLength(body)); sendManagementFrame("Beacon", body, ST_BEACON, MacAddress::BROADCAST_ADDRESS); } @@ -234,7 +242,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,11 +258,11 @@ 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->removePeerHtCapabilities(sta->address); + mib->setPeerAssociationStatus(sta->address, Ieee80211Mib::NOT_AUTHENTICATED); + mib->removePeerCapabilities(sta->address); sta->authSeqExpected = 1; if (wasAssociated) sendDisAssocNotification(sta->address); @@ -289,11 +297,11 @@ 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->removePeerHtCapabilities(sta->address); + mib->setPeerAssociationStatus(sta->address, Ieee80211Mib::AUTHENTICATED); // TODO only when ACK of this frame arrives + mib->removePeerCapabilities(sta->address); if (wasAssociated) sendDisAssocNotification(sta->address); EV << "STA authenticated\n"; @@ -313,13 +321,13 @@ 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); + mib->removePeerCapabilities(sta->address); if (wasAssociated) sendDisAssocNotification(sta->address); } @@ -340,7 +348,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 +358,7 @@ void Ieee80211MgmtAp::handleAssociationRequestFrame(Packet *packet, const PtrpeekData(); - bool pendingHtOperationValid = mib->isHtOperationSupported(); + bool pendingHtOperationValid = mib->isLocalHtCapable(); Ieee80211HtOperation pendingHtOperation; if (pendingHtOperationValid) pendingHtOperation = mib->getHtOperation(); @@ -368,6 +376,13 @@ void Ieee80211MgmtAp::handleAssociationRequestFrame(Packet *packet, const PtrisVhtOperationSupported() && + decodeVhtCapabilities(requestBody, pendingVhtCapabilities); + bool vhtCapabilitiesMalformed = mib->isVhtOperationSupported() && + requestBody->getVhtCapabilitiesPresent() && !pendingVhtCapabilitiesValid; + bool basicVhtMcsSupported = !pendingVhtCapabilitiesValid || + supportsBasicVhtMcsSet(pendingVhtCapabilities, mib->getLocalVhtOperation()); bool basicHtMcsSupported = !htCapabilitiesMalformed && (!pendingHtCapabilitiesValid || supportsBasicHtMcsSet(pendingHtCapabilities, pendingHtOperation)); delete packet; @@ -382,13 +397,16 @@ void Ieee80211MgmtAp::handleAssociationRequestFrame(Packet *packet, const PtrsetStatusCode(statusCode); bool associationSuccessful = statusCode == SC_SUCCESSFUL; short associationId = associationSuccessful ? mib->reserveAssociationId(sta->address) : 0; body->setAid(associationId); sta->pendingAssociationSuccessful = associationSuccessful; + sta->pendingVhtCapabilitiesValid = pendingVhtCapabilitiesValid; + sta->pendingVhtCapabilities = pendingVhtCapabilities; + sta->pendingVhtGeneration = mib->getVhtCapabilityGeneration(); sta->pendingHtStateAvailable = true; sta->pendingHtCapabilitiesValid = pendingHtCapabilitiesValid; sta->pendingHtCapabilities = pendingHtCapabilities; @@ -397,7 +415,9 @@ void Ieee80211MgmtAp::handleAssociationRequestFrame(Packet *packet, const PtrpendingAssociationTransactionId = createAssociationTransactionId(); setSupportedRateElements(body); addHtCapabilities(body); - body->setChunkLength(B(2 + 2 + 2) + getSupportedRateElementsLength(body) + getHtMgmtElementsLength(body)); + addVhtCapabilities(body); + addVhtOperation(body); + body->setChunkLength(B(2 + 2 + 2) + getSupportedRateElementsLength(body) + getHtMgmtElementsLength(body) + getVhtMgmtElementsLength(body)); const char *frameName = associationSuccessful ? "AssocResp-OK" : (htCapabilitiesMalformed ? "AssocResp-UnsupportedHtCap" : "AssocResp-UnsupportedHtMcs"); sendManagementFrame(frameName, body, ST_ASSOCIATIONRESPONSE, sta->address, sta->pendingAssociationTransactionId); @@ -420,7 +440,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 +450,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(); @@ -448,6 +468,13 @@ void Ieee80211MgmtAp::handleReassociationRequestFrame(Packet *packet, const Ptr< htCapabilitiesMalformed = true; } } + Ieee80211VhtCapabilities pendingVhtCapabilities; + bool pendingVhtCapabilitiesValid = mib->isVhtOperationSupported() && + decodeVhtCapabilities(requestBody, pendingVhtCapabilities); + bool vhtCapabilitiesMalformed = mib->isVhtOperationSupported() && + requestBody->getVhtCapabilitiesPresent() && !pendingVhtCapabilitiesValid; + bool basicVhtMcsSupported = !pendingVhtCapabilitiesValid || + supportsBasicVhtMcsSet(pendingVhtCapabilities, mib->getLocalVhtOperation()); bool basicHtMcsSupported = !htCapabilitiesMalformed && (!pendingHtCapabilitiesValid || supportsBasicHtMcsSet(pendingHtCapabilities, pendingHtOperation)); delete packet; @@ -461,13 +488,16 @@ void Ieee80211MgmtAp::handleReassociationRequestFrame(Packet *packet, const Ptr< responseHtOperation.basicMcsSupported.fill(false); setHtOperation(body, getHtOperationBand(), responseHtOperation); } - Ieee80211StatusCode statusCode = htCapabilitiesMalformed ? SC_UNSUP_CAP : - (basicHtMcsSupported ? SC_SUCCESSFUL : SC_DATARATE_UNSUP); + Ieee80211StatusCode statusCode = htCapabilitiesMalformed || vhtCapabilitiesMalformed ? SC_UNSUP_CAP : + (basicHtMcsSupported && basicVhtMcsSupported ? SC_SUCCESSFUL : SC_DATARATE_UNSUP); body->setStatusCode(statusCode); bool associationSuccessful = statusCode == SC_SUCCESSFUL; short associationId = associationSuccessful ? mib->reserveAssociationId(sta->address) : 0; body->setAid(associationId); sta->pendingAssociationSuccessful = associationSuccessful; + sta->pendingVhtCapabilitiesValid = pendingVhtCapabilitiesValid; + sta->pendingVhtCapabilities = pendingVhtCapabilities; + sta->pendingVhtGeneration = mib->getVhtCapabilityGeneration(); sta->pendingHtStateAvailable = true; sta->pendingHtCapabilitiesValid = pendingHtCapabilitiesValid; sta->pendingHtCapabilities = pendingHtCapabilities; @@ -476,7 +506,9 @@ void Ieee80211MgmtAp::handleReassociationRequestFrame(Packet *packet, const Ptr< sta->pendingAssociationTransactionId = createAssociationTransactionId(); setSupportedRateElements(body); addHtCapabilities(body); - body->setChunkLength(B(2 + 2 + 2) + getSupportedRateElementsLength(body) + getHtMgmtElementsLength(body)); + addVhtCapabilities(body); + addVhtOperation(body); + body->setChunkLength(B(2 + 2 + 2) + getSupportedRateElementsLength(body) + getHtMgmtElementsLength(body) + getVhtMgmtElementsLength(body)); const char *frameName = associationSuccessful ? "ReassocResp-OK" : (htCapabilitiesMalformed ? "ReassocResp-UnsupportedHtCap" : "ReassocResp-UnsupportedHtMcs"); sendManagementFrame(frameName, body, ST_REASSOCIATIONRESPONSE, sta->address, sta->pendingAssociationTransactionId); @@ -494,11 +526,11 @@ 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->removePeerHtCapabilities(sta->address); + mib->setPeerAssociationStatus(sta->address, Ieee80211Mib::AUTHENTICATED); + mib->removePeerCapabilities(sta->address); if (wasAssociated) sendDisAssocNotification(sta->address); } @@ -530,9 +562,11 @@ void Ieee80211MgmtAp::handleProbeRequestFrame(Packet *packet, const PtrsetBeaconInterval(beaconInterval); body->setChannelNumber(getDsssParameterSetChannel()); addHtCapabilities(body); - if (mib->isHtOperationSupported()) + addVhtCapabilities(body); + addVhtOperation(body); + 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)); + body->setChunkLength(B(8 + 2 + 2 + (2 + ssid.length()) + (body->getChannelNumber() != -1 ? 3 : 0)) + getSupportedRateElementsLength(body) + getHtMgmtElementsLength(body) + getVhtMgmtElementsLength(body)); sendManagementFrame("ProbeResp", body, ST_PROBERESPONSE, staAddress); } diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.h b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.h index fe8c0c03192..9d65a8eb8d6 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.h +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.h @@ -37,6 +37,9 @@ class INET_API Ieee80211MgmtAp : public Ieee80211MgmtApBase int authSeqExpected; // when NOT_AUTHENTICATED: transaction sequence number of next expected auth frame bool pendingAssociationSuccessful = false; uint64_t pendingAssociationTransactionId = 0; + bool pendingVhtCapabilitiesValid = false; + Ieee80211VhtCapabilities pendingVhtCapabilities; + uint64_t pendingVhtGeneration = 0; bool pendingHtStateAvailable = false; bool pendingHtCapabilitiesValid = false; Ieee80211HtCapabilities pendingHtCapabilities; 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..3c5a90ba0ce 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtApBase.cc +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtApBase.cc @@ -18,6 +18,9 @@ #include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtApBase.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Band.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211RadioChannelChangedDetails.h" +#include "inet/physicallayer/wireless/common/contract/packetlevel/IRadio.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211Radio.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.h" namespace inet { @@ -34,20 +37,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 +98,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 +137,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..8023005a4c0 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.cc +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.cc @@ -9,12 +9,14 @@ #include "inet/common/INETUtils.h" #include "inet/common/ModuleAccess.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.h" #include "inet/common/ProtocolTag_m.h" #include "inet/common/lifecycle/LifecycleOperation.h" #include "inet/common/lifecycle/ModuleOperations.h" #include "inet/common/lifecycle/NodeStatus.h" #include "inet/linklayer/common/InterfaceTag_m.h" #include "inet/linklayer/ieee80211/mgmt/Ieee80211HtMgmtElements.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211VhtMgmtElements.h" #include "inet/networklayer/common/NetworkInterface.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" @@ -34,55 +36,122 @@ void Ieee80211MgmtBase::initialize(int stage) myIface = getContainingNicModule(this); numMgmtFramesReceived = 0; numMgmtFramesDropped = 0; - getContainingNicModule(this)->subscribe(modesetChangedSignal, this); + configurationProvider.reference(this, "macModule", true); + if (auto coordinator = dynamic_cast(configurationProvider.get())) + coordinator->registerModeSetConsumer(this, IIeee80211ModeSetCoordinator::MANAGEMENT_STATE); WATCH(numMgmtFramesReceived); WATCH(numMgmtFramesDropped); } + else if (stage == INITSTAGE_LINK_LAYER) { + prepareConfiguration(); + if (isUp()) + prepareLocalOperation(); + mib->publishStateChange(); + } +} + +void Ieee80211MgmtBase::prepareConfiguration() +{ + if (configurationPrepared) + return; + configurationProvider->prepareLocalCapabilities(); + modeSet = configurationProvider->getConfiguredModeSet(); + if (modeSet == nullptr) + throw cRuntimeError("Configured IEEE 802.11 mode catalog is unavailable"); + configurationPrepared = true; + updateSupportedRates(); +} + +void Ieee80211MgmtBase::applyModeSet(const Ieee80211ModeSet *newModeSet) +{ + Enter_Method_Silent(); + modeSet = newModeSet; + updateSupportedRates(); + if (isUp() && mib->hasActiveBss()) + prepareLocalOperation(); } -void Ieee80211MgmtBase::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) -{ - 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()); +void Ieee80211MgmtBase::updateSupportedRates() +{ + 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::addVhtCapabilities(const Ptr& frame) const +{ + if (mib->isVhtOperationSupported()) + setVhtCapabilities(frame, mib->getLocalVhtCapabilities()); +} + +void Ieee80211MgmtBase::addVhtOperation(const Ptr& frame) const +{ + if (mib->isVhtOperationSupported()) + setVhtOperation(frame, mib->getLocalVhtOperation()); } 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 +180,7 @@ void Ieee80211MgmtBase::handleMessageWhenUp(cMessage *msg) } else throw cRuntimeError("Unknown message"); + mib->publishStateChange(); } void Ieee80211MgmtBase::sendDown(Packet *frame) @@ -187,14 +257,16 @@ void Ieee80211MgmtBase::processFrame(Packet *packet, const PtrclearPeerHtCapabilities(); + mib->clearBss(); + mib->publishStateChange(); } } // namespace ieee80211 } // namespace inet - diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.h b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.h index 145da89e902..73d9b5f5486 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.h +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.h @@ -14,11 +14,13 @@ #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" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Band.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.h" namespace inet { @@ -28,14 +30,20 @@ namespace ieee80211 { * Abstract base class for 802.11 infrastructure mode management components. * */ -class INET_API Ieee80211MgmtBase : public OperationalBase, public cListener +class INET_API Ieee80211MgmtBase : public OperationalBase, public cListener, public physicallayer::IIeee80211ModeSetListener { + public: + virtual const physicallayer::Ieee80211ModeSet *getModeSet() const override { return modeSet; } + virtual void applyModeSet(const physicallayer::Ieee80211ModeSet *modeSet) override; + protected: // configuration ModuleRefByPar mib; 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 +54,8 @@ 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(); + void updateSupportedRates(); /** Dispatches incoming messages to handleTimer(), handleUpperMessage() or processFrame(). */ virtual void handleMessageWhenUp(cMessage *msg) override; @@ -82,7 +91,14 @@ class INET_API Ieee80211MgmtBase : public OperationalBase, public cListener return length; } + /** Adds local VHT capabilities; subclasses may customize advertisements in inherited frame builders. */ + virtual void addVhtCapabilities(const Ptr& frame) const; + /** Adds local VHT operation; subclasses may customize advertisements in inherited frame builders. */ + virtual void addVhtOperation(const Ptr& frame) const; /** 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 +125,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(); } @@ -124,4 +140,3 @@ class INET_API Ieee80211MgmtBase : public OperationalBase, public cListener } // namespace inet #endif - diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame.msg b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame.msg index 4725c5e1110..2e4f4375342 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame.msg +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame.msg @@ -180,6 +180,27 @@ struct Ieee80211HtOperationElement // // Frame body base class used to hide various frame body types // +// IEEE Std 802.11-2024, 9.4.2.156: modeled VHT Capabilities fields. +struct Ieee80211VhtCapabilitiesElement +{ + short supportedChannelWidthSet; + bool shortGi80; + bool shortGi160; + short rxMaxMcs[8]; + short txMaxMcs[8]; + short rxHighestLongGiRateMbps; + short txHighestLongGiRateMbps; +} + +// IEEE Std 802.11-2024, 9.4.2.157: fixed five-octet VHT Operation body. +struct Ieee80211VhtOperationElement +{ + short channelWidth; + short centerFrequencySegment0; + short centerFrequencySegment1; + short basicMaxMcs[8]; +} + class Ieee80211MgmtFrame extends FieldsChunk { bool extendedSupportedRatesPresent; @@ -188,13 +209,17 @@ class Ieee80211MgmtFrame extends FieldsChunk Ieee80211HtCapabilitiesElement htCapabilities; bool htOperationPresent; Ieee80211HtOperationElement htOperation; + bool vhtCapabilitiesPresent; + Ieee80211VhtCapabilitiesElement vhtCapabilities; + bool vhtOperationPresent; + Ieee80211VhtOperationElement vhtOperation; // Information elements this model does not represent (TIM, Country, ERP, RSN, // vendor-specific, ...), each kept verbatim as Element ID, Length and body, in wire // order. Empty for the frames the simulation builds itself. uint8_t unmodelledElements[]; // One entry per element in unmodelledElements: how many of the modelled elements // (SSID, Supported Rates, DSSS Parameter Set, Extended Supported Rates, HT - // Capabilities, HT Operation) preceded it on the wire, so that it is written back + // Capabilities, HT Operation, VHT Capabilities, VHT Operation) preceded it on the wire, so that it is written back // in its place among them. uint8_t unmodelledElementPositions[]; } diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrameSerializer.cc b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrameSerializer.cc index 5fb46ae01ad..ff3d63145d9 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrameSerializer.cc +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrameSerializer.cc @@ -35,8 +35,10 @@ Register_Serializer(Ieee80211ReassociationResponseFrame, Ieee80211MgmtFrameSeria static constexpr uint8_t HT_CAPABILITIES_ELEMENT_ID = 45; static constexpr uint8_t DSSS_PARAMETER_SET_ELEMENT_ID = 3; static constexpr uint8_t HT_OPERATION_ELEMENT_ID = 61; +static constexpr uint8_t SSID_ELEMENT_ID = 0; static constexpr uint8_t SUPPORTED_RATES_ELEMENT_ID = 1; static constexpr uint8_t EXTENDED_SUPPORTED_RATES_ELEMENT_ID = 50; +static constexpr uint8_t MAX_SSID_LENGTH = 32; static constexpr uint8_t MAX_SUPPORTED_RATES = 8; static constexpr uint16_t MAX_EXTENDED_SUPPORTED_RATES = 255; static constexpr double SUPPORTED_RATE_UNIT = 0.5; @@ -104,6 +106,23 @@ static void readUnmodelledElement(MemoryInputStream& stream, const PtrsetUnmodelledElementPositions(index, precedingModelledElementCount); } +static void validateSsidLength(size_t length) +{ + // IEEE Std 802.11-2024, 9.4.2.2: the SSID field contains zero to + // 32 octets. Zero octets indicates the wildcard SSID. + if (length > MAX_SSID_LENGTH) + throw cRuntimeError("Malformed SSID element length: %zu exceeds maximum %d", length, MAX_SSID_LENGTH); +} + +static void writeSsidElement(MemoryOutputStream& stream, const char *SSID) +{ + size_t length = strlen(SSID); + validateSsidLength(length); + stream.writeByte(SSID_ELEMENT_ID); + stream.writeByte(static_cast(length)); + stream.writeBytes(reinterpret_cast(SSID), B(length)); +} + static void validateSupportedRatesCount(int numRates) { // IEEE Std 802.11-2024, 9.4.2.3: the Supported Rates field contains @@ -312,15 +331,114 @@ static void writeHtOperationElement(MemoryOutputStream& stream, const Ieee80211H stream.writeByte(byte); } -enum HtElementPresence : unsigned int { +// IEEE Std 802.11-2024, 9.4.2.156 and 9.4.2.157. +static constexpr uint8_t VHT_CAPABILITIES_ELEMENT_ID = 191; +static constexpr uint8_t VHT_OPERATION_ELEMENT_ID = 192; + +static uint16_t encodeVhtMcsMap(const short *map) +{ + uint16_t result = 0; + for (int i = 0; i < 8; i++) { + int value = map[i]; + if (value != -1 && value != 7 && value != 8 && value != 9) + throw cRuntimeError("Invalid VHT MCS map entry %d", value); + result |= (value == -1 ? 3 : value - 7) << (2 * i); + } + return result; +} + +static void decodeVhtMcsMap(uint16_t encoded, short *map) +{ + for (int i = 0; i < 8; i++) { + int value = (encoded >> (2 * i)) & 3; + map[i] = value == 3 ? -1 : value + 7; + } +} + +static void writeVhtCapabilitiesElement(MemoryOutputStream& stream, const Ieee80211VhtCapabilitiesElement& capabilities) +{ + if (capabilities.supportedChannelWidthSet < 0 || capabilities.supportedChannelWidthSet > 2 || + capabilities.rxHighestLongGiRateMbps < 0 || capabilities.rxHighestLongGiRateMbps > 8191 || + capabilities.txHighestLongGiRateMbps < 0 || capabilities.txHighestLongGiRateMbps > 8191) + throw cRuntimeError("Invalid VHT Capabilities fields"); + stream.writeByte(VHT_CAPABILITIES_ELEMENT_ID); + stream.writeByte(12); + stream.writeUint32Le((capabilities.supportedChannelWidthSet << 2) | + (capabilities.shortGi80 << 5) | (capabilities.shortGi160 << 6)); + stream.writeUint16Le(encodeVhtMcsMap(capabilities.rxMaxMcs)); + stream.writeUint16Le(capabilities.rxHighestLongGiRateMbps); + stream.writeUint16Le(encodeVhtMcsMap(capabilities.txMaxMcs)); + stream.writeUint16Le(capabilities.txHighestLongGiRateMbps); +} + +static void writeVhtOperationElement(MemoryOutputStream& stream, const Ieee80211VhtOperationElement& operation) +{ + if (operation.channelWidth < 0 || operation.channelWidth > 3 || + operation.centerFrequencySegment0 < 0 || operation.centerFrequencySegment0 > 255 || + operation.centerFrequencySegment1 < 0 || operation.centerFrequencySegment1 > 255) + throw cRuntimeError("Invalid VHT Operation fields"); + stream.writeByte(VHT_OPERATION_ELEMENT_ID); + stream.writeByte(5); + stream.writeByte(operation.channelWidth); + stream.writeByte(operation.centerFrequencySegment0); + stream.writeByte(operation.centerFrequencySegment1); + stream.writeUint16Le(encodeVhtMcsMap(operation.basicMaxMcs)); +} + +static void readVhtCapabilitiesElement(MemoryInputStream& stream, int length, const Ptr& frame) +{ + if (length != 12 || frame->getVhtCapabilitiesPresent()) { + frame->markIncorrect(); + stream.seek(stream.getPosition() + B(length)); + return; + } + Ieee80211VhtCapabilitiesElement capabilities; + auto information = stream.readUint32Le(); + capabilities.supportedChannelWidthSet = (information >> 2) & 3; + capabilities.shortGi80 = information & (1 << 5); + capabilities.shortGi160 = information & (1 << 6); + decodeVhtMcsMap(stream.readUint16Le(), capabilities.rxMaxMcs); + capabilities.rxHighestLongGiRateMbps = stream.readUint16Le() & 8191; + decodeVhtMcsMap(stream.readUint16Le(), capabilities.txMaxMcs); + capabilities.txHighestLongGiRateMbps = stream.readUint16Le() & 8191; + if (capabilities.supportedChannelWidthSet == 3) + frame->markIncorrect(); + frame->setVhtCapabilities(capabilities); + frame->setVhtCapabilitiesPresent(true); +} + +static void readVhtOperationElement(MemoryInputStream& stream, int length, const Ptr& frame) +{ + if (length != 5 || frame->getVhtOperationPresent()) { + frame->markIncorrect(); + stream.seek(stream.getPosition() + B(length)); + return; + } + Ieee80211VhtOperationElement operation; + operation.channelWidth = stream.readByte(); + operation.centerFrequencySegment0 = stream.readByte(); + operation.centerFrequencySegment1 = stream.readByte(); + decodeVhtMcsMap(stream.readUint16Le(), operation.basicMaxMcs); + if (operation.channelWidth > 3) + frame->markIncorrect(); + frame->setVhtOperation(operation); + frame->setVhtOperationPresent(true); +} + +// IEEE Std 802.11-2024, 9.3.3.2 and 9.3.3.5-9.3.3.10, Tables 9-62 and +// 9-64 through 9-69: HT/VHT have the same subtype placement, but independent +// presence conditions. These masks express subtype permission only. +enum ManagementElementPresence : unsigned int { HT_ELEMENT_NONE = 0, HT_CAPABILITIES_ALLOWED = 1, HT_OPERATION_ALLOWED = 2, EXTENDED_SUPPORTED_RATES_ALLOWED = 4, BASIC_HT_MCS_SET_PRESENT = 8, + VHT_CAPABILITIES_ALLOWED = 16, + VHT_OPERATION_ALLOWED = 32, }; -static void writeHtElements(MemoryOutputStream& stream, ElementWriter& elements, const Ptr& frame, unsigned int allowedElements) +static void writeManagementElements(MemoryOutputStream& stream, ElementWriter& elements, const Ptr& frame, unsigned int allowedElements) { if (!(allowedElements & EXTENDED_SUPPORTED_RATES_ALLOWED) && frame->getExtendedSupportedRatesPresent()) throw cRuntimeError("Extended Supported Rates element is not allowed in this management frame subtype"); @@ -328,6 +446,10 @@ static void writeHtElements(MemoryOutputStream& stream, ElementWriter& elements, throw cRuntimeError("HT Capabilities element is not allowed in this management frame subtype"); if (!(allowedElements & HT_OPERATION_ALLOWED) && frame->getHtOperationPresent()) throw cRuntimeError("HT Operation element is not allowed in this management frame subtype"); + if (!(allowedElements & VHT_CAPABILITIES_ALLOWED) && frame->getVhtCapabilitiesPresent()) + throw cRuntimeError("VHT Capabilities element is not allowed in this management frame subtype"); + if (!(allowedElements & VHT_OPERATION_ALLOWED) && frame->getVhtOperationPresent()) + throw cRuntimeError("VHT Operation element is not allowed in this management frame subtype"); if (frame->getHtCapabilitiesPresent()) { elements.beginModelledElement(); writeHtCapabilitiesElement(stream, frame->getHtCapabilities()); @@ -336,6 +458,14 @@ static void writeHtElements(MemoryOutputStream& stream, ElementWriter& elements, elements.beginModelledElement(); writeHtOperationElement(stream, frame->getHtOperation(), allowedElements & BASIC_HT_MCS_SET_PRESENT); } + if (frame->getVhtCapabilitiesPresent()) { + elements.beginModelledElement(); + writeVhtCapabilitiesElement(stream, frame->getVhtCapabilities()); + } + if (frame->getVhtOperationPresent()) { + elements.beginModelledElement(); + writeVhtOperationElement(stream, frame->getVhtOperation()); + } } static void readHtCapabilitiesElement(MemoryInputStream& stream, int length, const Ptr& frame) @@ -425,7 +555,7 @@ static void readHtOperationElement(MemoryInputStream& stream, int length, const frame->setHtOperation(operation); } -static void readHtElements(MemoryInputStream& stream, const Ptr& frame, unsigned int allowedElements, int modelledElementCount) +static void readManagementElements(MemoryInputStream& stream, const Ptr& frame, unsigned int allowedElements, int modelledElementCount) { while (stream.getRemainingLength() != b(0)) { if (stream.getRemainingLength() < B(2)) { @@ -501,6 +631,30 @@ static void readHtElements(MemoryInputStream& stream, const PtrmarkIncorrect(); + stream.seek(stream.getPosition() + declaredBodyLength); + } + else { + bool wasPresent = frame->getVhtCapabilitiesPresent(); + readVhtCapabilitiesElement(stream, length, frame); + if (!wasPresent && frame->getVhtCapabilitiesPresent()) + modelledElementCount++; + } + } + else if (elementId == VHT_OPERATION_ELEMENT_ID) { + if (!(allowedElements & VHT_OPERATION_ALLOWED)) { + frame->markIncorrect(); + stream.seek(stream.getPosition() + declaredBodyLength); + } + else { + bool wasPresent = frame->getVhtOperationPresent(); + readVhtOperationElement(stream, length, frame); + if (!wasPresent && frame->getVhtOperationPresent()) + modelledElementCount++; + } + } else readUnmodelledElement(stream, frame, elementId, length, modelledElementCount); } @@ -611,18 +765,16 @@ static uint16_t encodeAssociationId(Ieee80211StatusCode statusCode, int aid) return 0; } -static int decodeAssociationId(Ieee80211StatusCode statusCode, uint16_t wireAid) +static int decodeAssociationId(Ieee80211AssociationResponseFrame& frame, uint16_t wireAid) { - if (statusCode == SC_SUCCESSFUL) { - if ((wireAid & ASSOCIATION_ID_MARKER) != ASSOCIATION_ID_MARKER) - throw cRuntimeError("Malformed successful Association Response AID: missing marker 0xC000"); + if (frame.getStatusCode() == SC_SUCCESSFUL) { const int aid = wireAid & ASSOCIATION_ID_MASK; - if (aid < 1 || aid > MAX_LOGICAL_ASSOCIATION_ID) - throw cRuntimeError("Malformed successful Association Response AID: %d", aid); - return aid; + if ((wireAid & ASSOCIATION_ID_MARKER) == ASSOCIATION_ID_MARKER && aid >= 1 && aid <= MAX_LOGICAL_ASSOCIATION_ID) + return aid; + frame.markIncorrect(); } - if (wireAid != 0) - throw cRuntimeError("Malformed unsuccessful Association Response AID: expected zero, got 0x%04x", wireAid); + else if (wireAid != 0) + frame.markIncorrect(); return 0; } @@ -643,30 +795,26 @@ void Ieee80211MgmtFrameSerializer::serializeFields(MemoryOutputStream& stream, c stream.writeUint16Le(authenticationFrame->getStatusCode()); // 4 Challenge text The challenge text information is present only in certain Authentication frames as defined in Table 7-17. // Last Vendor Specific One or more vendor-specific information elements may appear in this frame. This information element follows all other information elements. - writeHtElements(stream, elements, authenticationFrame, HT_ELEMENT_NONE); + writeManagementElements(stream, elements, authenticationFrame, HT_ELEMENT_NONE); } else if (auto deauthenticationFrame = dynamicPtrCast(chunk)) { // type = ST_DEAUTHENTICATION; stream.writeUint16Le(deauthenticationFrame->getReasonCode()); - writeHtElements(stream, elements, deauthenticationFrame, HT_ELEMENT_NONE); + writeManagementElements(stream, elements, deauthenticationFrame, HT_ELEMENT_NONE); } else if (auto disassociationFrame = dynamicPtrCast(chunk)) { // type = ST_DISASSOCIATION; stream.writeUint16Le(disassociationFrame->getReasonCode()); - writeHtElements(stream, elements, disassociationFrame, HT_ELEMENT_NONE); + writeManagementElements(stream, elements, disassociationFrame, HT_ELEMENT_NONE); } else if (auto probeRequestFrame = dynamicPtrCast(chunk)) { // type = ST_PROBEREQUEST; // 1 SSID - const char *SSID = probeRequestFrame->getSSID(); - unsigned int length = strlen(SSID); elements.beginModelledElement(); - stream.writeByte(0); // FIXME dummy, what is it? - stream.writeByte(length); - stream.writeBytes((uint8_t *)SSID, B(length)); + writeSsidElement(stream, probeRequestFrame->getSSID()); // 2 Supported rates writeSupportedRateElements(stream, elements, probeRequestFrame); - writeHtElements(stream, elements, probeRequestFrame, HT_CAPABILITIES_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED); + writeManagementElements(stream, elements, probeRequestFrame, HT_CAPABILITIES_ALLOWED | VHT_CAPABILITIES_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED); // 3 Request information May be included if dot11MultiDomainCapabilityEnabled is true. // 4 Extended Supported Rates The Extended Supported Rates element is present whenever there are more than eight supported rates, and it is optional otherwise. // Last Vendor Specific One or more vendor-specific information elements may appear in this frame. This information element follows all other information elements. @@ -680,16 +828,11 @@ void Ieee80211MgmtFrameSerializer::serializeFields(MemoryOutputStream& stream, c // 3 Current AP address stream.writeMacAddress(reassociationRequestFrame->getCurrentAP()); // 4 SSID - const char *SSID = reassociationRequestFrame->getSSID(); - unsigned int length = strlen(SSID); - // FIXME buffer.writeByte(buf + packetLength, ???); elements.beginModelledElement(); - stream.writeByte(0); // FIXME - stream.writeByte(length); - stream.writeBytes((uint8_t *)SSID, B(length)); + writeSsidElement(stream, reassociationRequestFrame->getSSID()); // 5 Supported rates writeSupportedRateElements(stream, elements, reassociationRequestFrame); - writeHtElements(stream, elements, reassociationRequestFrame, HT_CAPABILITIES_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED); + writeManagementElements(stream, elements, reassociationRequestFrame, HT_CAPABILITIES_ALLOWED | VHT_CAPABILITIES_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED); // 6 Extended Supported Rates The Extended Supported Rates element is present whenever there are more than eight supported rates, and it is optional otherwise. // 7 Power Capability The Power Capability element shall be present if dot11SpectrumManagementRequired is true. // 8 Supported Channels The Supported Channels element shall be present if dot11SpectrumManagementRequired is true. @@ -704,15 +847,11 @@ void Ieee80211MgmtFrameSerializer::serializeFields(MemoryOutputStream& stream, c // 2 Listen interval stream.writeUint16Le(associationRequestFrame->getListenInterval()); // 3 SSID - const char *SSID = associationRequestFrame->getSSID(); - unsigned int length = strlen(SSID); elements.beginModelledElement(); - stream.writeByte(0); // FIXME dummy, what is it? - stream.writeByte(length); - stream.writeBytes((uint8_t *)SSID, B(length)); + writeSsidElement(stream, associationRequestFrame->getSSID()); // 4 Supported rates writeSupportedRateElements(stream, elements, associationRequestFrame); - writeHtElements(stream, elements, associationRequestFrame, HT_CAPABILITIES_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED); + writeManagementElements(stream, elements, associationRequestFrame, HT_CAPABILITIES_ALLOWED | VHT_CAPABILITIES_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED); // 5 Extended Supported Rates The Extended Supported Rates element is present whenever there are more than eight supported rates, and it is optional otherwise. // 6 Power Capability The Power Capability element shall be present if dot11SpectrumManagementRequired is true. // 7 Supported Channel The Supported Channels element shall be present if dot11SpectrumManagementRequired is true. @@ -730,7 +869,7 @@ void Ieee80211MgmtFrameSerializer::serializeFields(MemoryOutputStream& stream, c stream.writeUint16Le(encodeAssociationId(associationResponseFrame->getStatusCode(), associationResponseFrame->getAid())); // 4 Supported rates writeSupportedRateElements(stream, elements, associationResponseFrame); - writeHtElements(stream, elements, associationResponseFrame, HT_CAPABILITIES_ALLOWED | HT_OPERATION_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED); + writeManagementElements(stream, elements, associationResponseFrame, HT_CAPABILITIES_ALLOWED | VHT_CAPABILITIES_ALLOWED | HT_OPERATION_ALLOWED | VHT_OPERATION_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED); // 5 Extended Supported Rates The Extended Supported Rates element is present whenever there are more than eight supported rates, and it is optional otherwise. // 6 EDCA Parameter Set // Last Vendor Specific One or more vendor-specific information elements may appear in this frame. This information element follows all other information elements. @@ -745,7 +884,7 @@ void Ieee80211MgmtFrameSerializer::serializeFields(MemoryOutputStream& stream, c stream.writeUint16Le(encodeAssociationId(reassociationResponseFrame->getStatusCode(), reassociationResponseFrame->getAid())); // 4 Supported rates writeSupportedRateElements(stream, elements, reassociationResponseFrame); - writeHtElements(stream, elements, reassociationResponseFrame, HT_CAPABILITIES_ALLOWED | HT_OPERATION_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED); + writeManagementElements(stream, elements, reassociationResponseFrame, HT_CAPABILITIES_ALLOWED | VHT_CAPABILITIES_ALLOWED | HT_OPERATION_ALLOWED | VHT_OPERATION_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED); // 5 Extended Supported Rates The Extended Supported Rates element is present whenever there are more than eight supported rates, and it is optional otherwise. // 6 EDCA Parameter Set // Last Vendor Specific One or more vendor-specific information elements may appear in this frame. This information element follows all other information elements. @@ -760,15 +899,11 @@ void Ieee80211MgmtFrameSerializer::serializeFields(MemoryOutputStream& stream, c // 3 Capability stream.writeUint16Le(beaconFrame->getCapabilityInformation()); // 4 Service Set Identifier (SSID) - const char *SSID = beaconFrame->getSSID(); - unsigned int length = strlen(SSID); elements.beginModelledElement(); - stream.writeByte(0); // FIXME - stream.writeByte(length); - stream.writeBytes((uint8_t *)SSID, B(length)); + writeSsidElement(stream, beaconFrame->getSSID()); // 5 Supported rates writeSupportedRateElements(stream, elements, beaconFrame); - writeHtElements(stream, elements, beaconFrame, HT_CAPABILITIES_ALLOWED | HT_OPERATION_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED | BASIC_HT_MCS_SET_PRESENT); + writeManagementElements(stream, elements, beaconFrame, HT_CAPABILITIES_ALLOWED | VHT_CAPABILITIES_ALLOWED | HT_OPERATION_ALLOWED | VHT_OPERATION_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED | BASIC_HT_MCS_SET_PRESENT); // 6 Frequency-Hopping (FH) Parameter Set The FH Parameter Set information element is present within Beacon frames generated by STAs using FH PHYs. // 8 CF Parameter Set The CF Parameter Set information element is present only within Beacon frames generated by APs supporting a PCF. // 9 IBSS Parameter Set The IBSS Parameter Set information element is present only within Beacon frames generated by STAs in an IBSS. @@ -799,15 +934,11 @@ void Ieee80211MgmtFrameSerializer::serializeFields(MemoryOutputStream& stream, c // 3 Capability stream.writeUint16Le(probeResponseFrame->getCapabilityInformation()); // 4 SSID - const char *SSID = probeResponseFrame->getSSID(); - unsigned int length = strlen(SSID); elements.beginModelledElement(); - stream.writeByte(0); // FIXME - stream.writeByte(length); - stream.writeBytes((uint8_t *)SSID, B(length)); + writeSsidElement(stream, probeResponseFrame->getSSID()); // 5 Supported rates writeSupportedRateElements(stream, elements, probeResponseFrame); - writeHtElements(stream, elements, probeResponseFrame, HT_CAPABILITIES_ALLOWED | HT_OPERATION_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED | BASIC_HT_MCS_SET_PRESENT); + writeManagementElements(stream, elements, probeResponseFrame, HT_CAPABILITIES_ALLOWED | VHT_CAPABILITIES_ALLOWED | HT_OPERATION_ALLOWED | VHT_OPERATION_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED | BASIC_HT_MCS_SET_PRESENT); // 6 FH Parameter Set The FH Parameter Set information element is present within Probe Response frames generated by STAs using FH PHYs. // 8 CF Parameter Set The CF Parameter Set information element is present only within Probe Response frames generated by APs supporting a PCF. // 9 IBSS Parameter Set The IBSS Parameter Set information element is present only within Probe Response frames generated by STAs in an IBSS. @@ -841,19 +972,19 @@ const Ptr Ieee80211MgmtFrameSerializer::deserializeFields(MemoryInputStre stream.readUint16Le(); frame->setSequenceNumber(stream.readUint16Le()); frame->setStatusCode((Ieee80211StatusCode)stream.readUint16Le()); - readHtElements(stream, frame, HT_ELEMENT_NONE, 0); + readManagementElements(stream, frame, HT_ELEMENT_NONE, 0); return frame; } else if (typeInfo == typeid(Ieee80211DeauthenticationFrame)) { auto frame = makeShared(); frame->setReasonCode((Ieee80211ReasonCode)stream.readUint16Le()); - readHtElements(stream, frame, HT_ELEMENT_NONE, 0); + readManagementElements(stream, frame, HT_ELEMENT_NONE, 0); return frame; } else if (typeInfo == typeid(Ieee80211DisassociationFrame)) { auto frame = makeShared(); frame->setReasonCode((Ieee80211ReasonCode)stream.readUint16Le()); - readHtElements(stream, frame, HT_ELEMENT_NONE, 0); + readManagementElements(stream, frame, HT_ELEMENT_NONE, 0); return frame; } else if (typeInfo == typeid(Ieee80211ProbeRequestFrame)) { @@ -864,7 +995,7 @@ const Ptr Ieee80211MgmtFrameSerializer::deserializeFields(MemoryInputStre Ieee80211SupportedRatesElement supRat; deserializeSupportedRates(stream, *frame, supRat); frame->setSupportedRates(supRat); - readHtElements(stream, frame, HT_CAPABILITIES_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED, 2); + readManagementElements(stream, frame, HT_CAPABILITIES_ALLOWED | VHT_CAPABILITIES_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED, 2); return frame; } else if (typeInfo == typeid(Ieee80211AssociationRequestFrame)) { @@ -877,7 +1008,7 @@ const Ptr Ieee80211MgmtFrameSerializer::deserializeFields(MemoryInputStre Ieee80211SupportedRatesElement supRat; deserializeSupportedRates(stream, *frame, supRat); frame->setSupportedRates(supRat); - readHtElements(stream, frame, HT_CAPABILITIES_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED, 2); + readManagementElements(stream, frame, HT_CAPABILITIES_ALLOWED | VHT_CAPABILITIES_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED, 2); return frame; } else if (typeInfo == typeid(Ieee80211ReassociationRequestFrame)) { @@ -892,31 +1023,31 @@ const Ptr Ieee80211MgmtFrameSerializer::deserializeFields(MemoryInputStre Ieee80211SupportedRatesElement supRat; deserializeSupportedRates(stream, *frame, supRat); frame->setSupportedRates(supRat); - readHtElements(stream, frame, HT_CAPABILITIES_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED, 2); + readManagementElements(stream, frame, HT_CAPABILITIES_ALLOWED | VHT_CAPABILITIES_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED, 2); return frame; } else if (typeInfo == typeid(Ieee80211AssociationResponseFrame)) { auto frame = makeShared(); frame->setCapabilityInformation(stream.readUint16Le()); frame->setStatusCode((Ieee80211StatusCode)stream.readUint16Le()); - frame->setAid(decodeAssociationId(frame->getStatusCode(), stream.readUint16Le())); + frame->setAid(decodeAssociationId(*frame, stream.readUint16Le())); Ieee80211SupportedRatesElement supRat; deserializeSupportedRates(stream, *frame, supRat); frame->setSupportedRates(supRat); - readHtElements(stream, frame, HT_CAPABILITIES_ALLOWED | HT_OPERATION_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED, 1); + readManagementElements(stream, frame, HT_CAPABILITIES_ALLOWED | VHT_CAPABILITIES_ALLOWED | HT_OPERATION_ALLOWED | VHT_OPERATION_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED, 1); return frame; } else if (typeInfo == typeid(Ieee80211ReassociationResponseFrame)) { auto frame = makeShared(); frame->setCapabilityInformation(stream.readUint16Le()); frame->setStatusCode((Ieee80211StatusCode)stream.readUint16Le()); - frame->setAid(decodeAssociationId(frame->getStatusCode(), stream.readUint16Le())); + frame->setAid(decodeAssociationId(*frame, stream.readUint16Le())); Ieee80211SupportedRatesElement supRat; deserializeSupportedRates(stream, *frame, supRat); frame->setSupportedRates(supRat); - readHtElements(stream, frame, HT_CAPABILITIES_ALLOWED | HT_OPERATION_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED, 1); + readManagementElements(stream, frame, HT_CAPABILITIES_ALLOWED | VHT_CAPABILITIES_ALLOWED | HT_OPERATION_ALLOWED | VHT_OPERATION_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED, 1); return frame; } else if (typeInfo == typeid(Ieee80211BeaconFrame)) { @@ -935,7 +1066,7 @@ const Ptr Ieee80211MgmtFrameSerializer::deserializeFields(MemoryInputStre Ieee80211SupportedRatesElement supRat; deserializeSupportedRates(stream, *frame, supRat); frame->setSupportedRates(supRat); - readHtElements(stream, frame, HT_CAPABILITIES_ALLOWED | HT_OPERATION_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED | BASIC_HT_MCS_SET_PRESENT, 2); + readManagementElements(stream, frame, HT_CAPABILITIES_ALLOWED | VHT_CAPABILITIES_ALLOWED | HT_OPERATION_ALLOWED | VHT_OPERATION_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED | BASIC_HT_MCS_SET_PRESENT, 2); return frame; } else if (typeInfo == typeid(Ieee80211ProbeResponseFrame)) { @@ -954,7 +1085,7 @@ const Ptr Ieee80211MgmtFrameSerializer::deserializeFields(MemoryInputStre Ieee80211SupportedRatesElement supRat; deserializeSupportedRates(stream, *frame, supRat); frame->setSupportedRates(supRat); - readHtElements(stream, frame, HT_CAPABILITIES_ALLOWED | HT_OPERATION_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED | BASIC_HT_MCS_SET_PRESENT, 2); + readManagementElements(stream, frame, HT_CAPABILITIES_ALLOWED | VHT_CAPABILITIES_ALLOWED | HT_OPERATION_ALLOWED | VHT_OPERATION_ALLOWED | EXTENDED_SUPPORTED_RATES_ALLOWED | BASIC_HT_MCS_SET_PRESENT, 2); return frame; } else diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.cc b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.cc index 859a5c108ea..7f9d4dc1704 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.cc +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.cc @@ -7,6 +7,7 @@ #include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.h" #include "inet/linklayer/ieee80211/mgmt/Ieee80211HtMgmtElements.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211VhtMgmtElements.h" #include "inet/common/INETUtils.h" #include "inet/common/ModuleAccess.h" @@ -152,8 +153,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 +353,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()); @@ -365,7 +366,9 @@ void Ieee80211MgmtSta::startAssociation(ApInfo *ap, simtime_t timeout) body->setSSID(ap->ssid.c_str()); setSupportedRateElements(body); addHtCapabilities(body); - body->setChunkLength(B(2 + 2 + (2 + strlen(body->getSSID()))) + getSupportedRateElementsLength(body) + getHtMgmtElementsLength(body)); + addVhtCapabilities(body); + body->setChunkLength(B(2 + 2 + (2 + strlen(body->getSSID()))) + getSupportedRateElementsLength(body) + getHtMgmtElementsLength(body) + getVhtMgmtElementsLength(body)); + pendingVhtGeneration = mib->getVhtCapabilityGeneration(); sendManagementFrame("Assoc", body, ST_ASSOCIATIONREQUEST, ap->address); reassociationInProgress = false; @@ -378,7 +381,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()); @@ -388,7 +391,9 @@ void Ieee80211MgmtSta::startReassociation(ApInfo *ap, simtime_t timeout) body->setSSID(ap->ssid.c_str()); setSupportedRateElements(body); addHtCapabilities(body); - body->setChunkLength(B(2 + 2 + 6 + (2 + strlen(body->getSSID()))) + getSupportedRateElementsLength(body) + getHtMgmtElementsLength(body)); + addVhtCapabilities(body); + body->setChunkLength(B(2 + 2 + 6 + (2 + strlen(body->getSSID()))) + getSupportedRateElementsLength(body) + getHtMgmtElementsLength(body) + getVhtMgmtElementsLength(body)); + pendingVhtGeneration = mib->getVhtCapabilityGeneration(); sendManagementFrame("Reassoc", body, ST_REASSOCIATIONREQUEST, ap->address); reassociationInProgress = true; assocTimeoutMsg = new cMessage("assocTimeout", MK_ASSOC_TIMEOUT); @@ -416,7 +421,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 @@ -487,7 +492,8 @@ void Ieee80211MgmtSta::sendProbeRequest() body->setSSID(scanning.ssid.c_str()); setSupportedRateElements(body); addHtCapabilities(body); - body->setChunkLength(B(2 + scanning.ssid.length()) + getSupportedRateElementsLength(body) + getHtMgmtElementsLength(body)); + addVhtCapabilities(body); + body->setChunkLength(B(2 + scanning.ssid.length()) + getSupportedRateElementsLength(body) + getHtMgmtElementsLength(body) + getVhtMgmtElementsLength(body)); sendManagementFrame("ProbeReq", body, ST_PROBEREQUEST, scanning.bssid); } @@ -532,7 +538,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 +576,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 +598,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 +619,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; - mib->removePeerHtCapabilities(assocAP.address); + ASSERT(mib->getBssStationData().isAssociated); + mib->setAssociated(false); + mib->removePeerCapabilities(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 +673,7 @@ void Ieee80211MgmtSta::stop() clearAPList(); - if (mib->bssStationData.isAssociated) + if (mib->getBssStationData().isAssociated) clearCurrentAssociation(); else { cancelAndDelete(assocAP.beaconTimeoutMsg); @@ -793,7 +801,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 @@ -825,7 +833,7 @@ void Ieee80211MgmtSta::handleDeauthenticationFrame(Packet *packet, const PtrauthTimeoutMsg); pendingAp->authTimeoutMsg = nullptr; } - mib->removePeerHtCapabilities(address); + mib->removePeerCapabilities(address); cancelPendingAssociation(); if (pendingReassociation) sendReassociationConfirm(pendingAp, PRC_REFUSED); @@ -850,7 +858,7 @@ void Ieee80211MgmtSta::handleDeauthenticationFrame(Packet *packet, const PtrisAuthenticated = false; - mib->removePeerHtCapabilities(address); + mib->removePeerCapabilities(address); delete packet; } @@ -896,6 +904,15 @@ void Ieee80211MgmtSta::processAssociationResponse(Packet *packet, const PtrpeekData(); int statusCode = responseBody->getStatusCode(); + Ieee80211VhtCapabilities responseVhtCapabilities; + Ieee80211VhtOperation responseVhtOperation; + bool responseVhtValid = mib->isVhtOperationSupported() && pendingVhtGeneration == mib->getVhtCapabilityGeneration() && + ap->vhtAdvertisementValid && + decodeVhtCapabilities(responseBody, responseVhtCapabilities) && + decodeVhtOperation(responseBody, responseVhtOperation) && + supportsBasicVhtMcsSet(mib->getLocalVhtCapabilities(), responseVhtOperation) && + supportsBasicVhtMcsSet(responseVhtCapabilities, responseVhtOperation); + HtAssociationResponseStatus responseHtStatus = HtAssociationResponseStatus::LEGACY; Ieee80211HtCapabilities responseHtCapabilities; Ieee80211HtOperation responseHtOperation; @@ -922,30 +939,33 @@ void Ieee80211MgmtSta::processAssociationResponse(Packet *packet, const PtrbssStationData.isAssociated || assocAP.address != ap->address) - mib->removePeerHtCapabilities(ap->address); + else if (!mib->getBssStationData().isAssociated || assocAP.address != ap->address) + mib->removePeerCapabilities(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->removePeerHtCapabilities(assocAP.address); + mib->setAssociated(false); + mib->removePeerCapabilities(assocAP.address); cancelAndDelete(assocAP.beaconTimeoutMsg); assocAP.beaconTimeoutMsg = nullptr; assocAP = AssociatedApInfo(); } // change our state to "associated" - mib->bssData.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); + mib->removePeerCapabilities(ap->address); if (responseHtStatus == HtAssociationResponseStatus::INVALID_HT) { EV_WARN << "Association succeeded without usable HT negotiation with AP address=" << ap->address << ": " << responseHtReason << "\n"; @@ -958,10 +978,13 @@ void Ieee80211MgmtSta::processAssociationResponse(Packet *packet, const PtrsetPeerVhtCapabilities(ap->address, responseVhtCapabilities, responseVhtOperation); + else + mib->removePeerVhtCapabilities(ap->address); - assocAP.beaconTimeoutMsg = new cMessage("beaconTimeout", MK_BEACON_TIMEOUT); - scheduleAfter(MAX_BEACONS_MISSED * assocAP.beaconInterval, assocAP.beaconTimeoutMsg); + mib->publishStateChange(); + emit(l2AssociatedSignal, myIface, ap); } // report back to agent @@ -983,7 +1006,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 +1039,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 +1050,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 +1058,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 +1081,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) + mib->removePeerCapabilities(ap->address); + if (mib->getBssStationData().isAssociated) changeChannel(assocAP.channel); } } @@ -1107,7 +1130,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 +1139,7 @@ void Ieee80211MgmtSta::handleBeaconFrame(Packet *packet, const PtrpublishStateChange(); delete packet; } @@ -1154,10 +1178,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; @@ -1220,6 +1245,11 @@ bool Ieee80211MgmtSta::storeAPInfo(Packet *packet, const PtrisVhtOperationSupported() && + decodeVhtCapabilities(body, candidate.vhtCapabilities) && + decodeVhtOperation(body, candidate.vhtOperation) && + supportsBasicVhtMcsSet(mib->getLocalVhtCapabilities(), candidate.vhtOperation) && + supportsBasicVhtMcsSet(candidate.vhtCapabilities, candidate.vhtOperation); candidate.beaconInterval = body->getBeaconInterval(); auto signalPowerInd = packet->getTag(); bool currentAp = address == assocAP.address; @@ -1235,6 +1265,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; @@ -1242,6 +1273,9 @@ bool Ieee80211MgmtSta::storeAPInfo(Packet *packet, const PtrextendedSupportedRates = candidate.extendedSupportedRates; ap->htCapabilitiesPresent = candidate.htCapabilitiesPresent; ap->htCapabilities = candidate.htCapabilities; + ap->vhtAdvertisementValid = candidate.vhtAdvertisementValid; + ap->vhtCapabilities = candidate.vhtCapabilities; + ap->vhtOperation = candidate.vhtOperation; ap->htOperationPresent = candidate.htOperationPresent; ap->htOperation = candidate.htOperation; ap->beaconInterval = candidate.beaconInterval; @@ -1249,28 +1283,26 @@ 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"; - mib->removePeerHtCapabilities(address); + mib->removePeerCapabilities(address); } + if (candidate.vhtAdvertisementValid) + mib->setPeerVhtCapabilities(address, candidate.vhtCapabilities, candidate.vhtOperation); + else + mib->removePeerVhtCapabilities(address); } else if (signalPowerInd != nullptr && currentAp) assocAP.rxPower = candidate.rxPower; diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.h b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.h index 326c7638644..39090accd92 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; @@ -79,6 +80,9 @@ class INET_API Ieee80211MgmtSta : public Ieee80211MgmtBase Ieee80211ExtendedSupportedRatesElement extendedSupportedRates; bool htCapabilitiesPresent = false; Ieee80211HtCapabilities htCapabilities; + bool vhtAdvertisementValid = false; + Ieee80211VhtCapabilities vhtCapabilities; + Ieee80211VhtOperation vhtOperation; bool htOperationPresent = false; Ieee80211HtOperation htOperation; simtime_t beaconInterval; @@ -125,6 +129,7 @@ class INET_API Ieee80211MgmtSta : public Ieee80211MgmtBase AccessPointList apList; // associated Access Point + uint64_t pendingVhtGeneration = 0; cMessage *assocTimeoutMsg; // if non-nullptr: association is in progress bool reassociationInProgress = false; AssociatedApInfo assocAP; 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/Ieee80211VhtMgmtElements.h b/src/inet/linklayer/ieee80211/mgmt/Ieee80211VhtMgmtElements.h new file mode 100644 index 00000000000..8b6d1a95fe7 --- /dev/null +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211VhtMgmtElements.h @@ -0,0 +1,106 @@ +// Copyright (C) 2026 INET Framework contributors +// SPDX-License-Identifier: LGPL-3.0-or-later +#ifndef __INET_IEEE80211VHTMGMTELEMENTS_H +#define __INET_IEEE80211VHTMGMTELEMENTS_H +#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame_m.h" +#include "inet/linklayer/ieee80211/mib/Ieee80211VhtCapabilities.h" + +namespace inet { +namespace ieee80211 { + +inline bool decodeVhtCapabilities(const Ptr& frame, Ieee80211VhtCapabilities& capabilities) +{ + capabilities = Ieee80211VhtCapabilities(); + if (!frame->getVhtCapabilitiesPresent() || frame->isIncorrect() || frame->isIncomplete()) + return false; + const auto& element = frame->getVhtCapabilities(); + // 80+80 support is not modeled. Value 2 also supports contiguous 160 MHz. + if (element.supportedChannelWidthSet < 0 || element.supportedChannelWidthSet > 2) + return false; + capabilities.supported160Mhz = element.supportedChannelWidthSet != 0; + capabilities.shortGi80 = element.shortGi80; + capabilities.shortGi160 = element.shortGi160; + capabilities.rxHighestLongGiRateMbps = element.rxHighestLongGiRateMbps; + capabilities.txHighestLongGiRateMbps = element.txHighestLongGiRateMbps; + if (capabilities.rxHighestLongGiRateMbps < 0 || capabilities.rxHighestLongGiRateMbps > 8191 || + capabilities.txHighestLongGiRateMbps < 0 || capabilities.txHighestLongGiRateMbps > 8191) + return false; + for (int i = 0; i < 8; i++) { + capabilities.rxMaxMcs[i] = element.rxMaxMcs[i]; + capabilities.txMaxMcs[i] = element.txMaxMcs[i]; + } + // VHT SGI at 20/40 MHz is conveyed by HT Capabilities, not the VHT IE. + if (frame->getHtCapabilitiesPresent()) { + capabilities.shortGi20 = frame->getHtCapabilities().shortGi20; + capabilities.shortGi40 = frame->getHtCapabilities().shortGi40; + } + return isValidVhtMcsMap(capabilities.rxMaxMcs) && isValidVhtMcsMap(capabilities.txMaxMcs); +} + +inline bool decodeVhtOperation(const Ptr& frame, Ieee80211VhtOperation& operation) +{ + operation = Ieee80211VhtOperation(); + if (!frame->getVhtOperationPresent() || frame->isIncorrect() || frame->isIncomplete()) + return false; + const auto& element = frame->getVhtOperation(); + if (element.centerFrequencySegment0 < 0 || element.centerFrequencySegment0 > 255 || + element.centerFrequencySegment1 < 0 || element.centerFrequencySegment1 > 255) + return false; + operation.centerFrequencySegment0 = element.centerFrequencySegment0; + operation.centerFrequencySegment1 = element.centerFrequencySegment1; + // IEEE Std 802.11-2024, 9.4.2.157: revised signaling uses width 1 for + // contiguous 160 MHz with segment centers separated by eight channels. + if (element.channelWidth == 0) + operation.channelWidth = frame->getHtOperationPresent() && frame->getHtOperation().staChannelWidth40Mhz ? MHz(40) : MHz(20); + else if (element.channelWidth == 1 && element.centerFrequencySegment0 != 0 && element.centerFrequencySegment1 == 0) + operation.channelWidth = MHz(80); + else if ((element.channelWidth == 2 && element.centerFrequencySegment0 != 0) || + (element.channelWidth == 1 && element.centerFrequencySegment0 != 0 && element.centerFrequencySegment1 != 0 && std::abs(element.centerFrequencySegment0 - element.centerFrequencySegment1) == 8)) + operation.channelWidth = MHz(160); + else + return false; // Unsupported 80+80 or invalid width/center encoding. + for (int i = 0; i < 8; i++) { + int value = element.basicMaxMcs[i]; + if (value != -1 && value != 7 && value != 8 && value != 9) + return false; + operation.basicMaxMcs[i] = value; + } + return true; +} + +inline void setVhtCapabilities(const Ptr& frame, const Ieee80211VhtCapabilities& capabilities) +{ + Ieee80211VhtCapabilitiesElement element; + element.supportedChannelWidthSet = capabilities.supported160Mhz ? 1 : 0; + element.shortGi80 = capabilities.shortGi80; + element.shortGi160 = capabilities.shortGi160; + element.rxHighestLongGiRateMbps = capabilities.rxHighestLongGiRateMbps; + element.txHighestLongGiRateMbps = capabilities.txHighestLongGiRateMbps; + for (int i = 0; i < 8; i++) { + element.rxMaxMcs[i] = capabilities.rxMaxMcs[i]; + element.txMaxMcs[i] = capabilities.txMaxMcs[i]; + } + frame->setVhtCapabilities(element); + frame->setVhtCapabilitiesPresent(true); +} + +inline void setVhtOperation(const Ptr& frame, const Ieee80211VhtOperation& operation) +{ + Ieee80211VhtOperationElement element; + element.channelWidth = operation.channelWidth <= MHz(40) ? 0 : 1; + element.centerFrequencySegment0 = operation.centerFrequencySegment0; + element.centerFrequencySegment1 = operation.centerFrequencySegment1; + for (int i = 0; i < 8; i++) + element.basicMaxMcs[i] = operation.basicMaxMcs[i]; + frame->setVhtOperation(element); + frame->setVhtOperationPresent(true); +} + +inline B getVhtMgmtElementsLength(const Ptr& frame) +{ + return B((frame->getVhtCapabilitiesPresent() ? 14 : 0) + (frame->getVhtOperationPresent() ? 7 : 0)); +} + +} // namespace ieee80211 +} // namespace inet +#endif 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..43accb52a97 100644 --- a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.cc +++ b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.cc @@ -8,7 +8,6 @@ #include "inet/linklayer/ieee80211/mib/Ieee80211Mib.h" #include - #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Band.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" @@ -18,16 +17,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,186 +46,258 @@ 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 |= !peerVhtStates.empty(); + peerVhtStates.clear(); + 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) -{ - // 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(); +void Ieee80211Mib::installLocalHtCapabilities(const Ieee80211HtCapabilities& capabilities, bool htSupported) +{ + 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); + 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::findPeerCapabilities(const MacAddress& address) const +{ + auto it = peerHtStates.find(address); + return it == peerHtStates.end() || !it->second.valid ? nullptr : &it->second; +} + +void Ieee80211Mib::reconfigureLocalHtCapabilities(const Ieee80211HtCapabilities& capabilities, bool htSupported) +{ + checkStateMutation(); + if (!localCapabilitiesPrepared) + throw cRuntimeError("Cannot reconfigure unprepared local HT capabilities"); + if (localHtCapabilities == capabilities && localHtCapabilitiesValid == htSupported) + return; + localHtCapabilities = capabilities; + localHtCapabilitiesValid = htSupported; 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 (entry.second.valid) + entry.second.negotiatedCapabilities = std::make_shared( + negotiateHtCapabilities(localHtCapabilities, entry.second.advertisedCapabilities)); } + stateChangePending = true; +} + +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 { - auto it = peerHtStates.find(address); - return it == peerHtStates.end() || !it->second.valid ? nullptr : &it->second; + return relationshipAllowsHt(address) ? findPeerCapabilities(address) : nullptr; } -void Ieee80211Mib::setPeerHtCapabilities(const MacAddress& address, const Ieee80211HtCapabilities& capabilities, - const Ieee80211HtOperation& operation) +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(); } +void Ieee80211Mib::installLocalVhtCapabilities(const Ieee80211VhtCapabilities& capabilities, bool supported) +{ + checkStateMutation(); + if (localVhtCapabilitiesValid == supported && localVhtCapabilities == capabilities) + return; + localVhtCapabilities = capabilities; + localVhtCapabilitiesValid = supported; + ++vhtCapabilityGeneration; + // Pending response snapshots are invalidated by the generation change. + // A newly configured local VHT profile requires fresh peer negotiation. + peerVhtStates.clear(); + stateChangePending = true; +} + +const Ieee80211Mib::PeerVhtState *Ieee80211Mib::findPeerVhtState(const MacAddress& address) const +{ + auto it = peerVhtStates.find(address); + return it == peerVhtStates.end() ? nullptr : &it->second; +} + +void Ieee80211Mib::setPeerVhtCapabilities(const MacAddress& address, const Ieee80211VhtCapabilities& capabilities, const Ieee80211VhtOperation& operation) +{ + checkStateMutation(); + if (!localVhtCapabilitiesValid || !isValidVhtMcsMap(capabilities.rxMaxMcs) || !isValidVhtMcsMap(capabilities.txMaxMcs) || + !supportsBasicVhtMcsSet(localVhtCapabilities, operation) || !supportsBasicVhtMcsSet(capabilities, operation)) { + removePeerVhtCapabilities(address); + return; + } + auto it = peerVhtStates.find(address); + if (it != peerVhtStates.end() && it->second.advertisedCapabilities == capabilities && it->second.operation == operation) + return; + peerVhtStates[address] = {capabilities, operation}; + stateChangePending = true; +} + +void Ieee80211Mib::removePeerVhtCapabilities(const MacAddress& address) +{ + checkStateMutation(); + stateChangePending |= peerVhtStates.erase(address) != 0; +} + +void Ieee80211Mib::removePeerCapabilities(const MacAddress& address) +{ + removePeerHtCapabilities(address); + removePeerVhtCapabilities(address); +} + +void Ieee80211Mib::clearPeerCapabilities() +{ + clearPeerHtCapabilities(); + stateChangePending |= !peerVhtStates.empty(); + peerVhtStates.clear(); +} + std::string Ieee80211Mib::getSsidStr() const { if (mode == INFRASTRUCTURE) @@ -252,6 +326,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 +353,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 +367,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,17 +386,20 @@ short Ieee80211Mib::allocateAssociationId(const MacAddress& address) void Ieee80211Mib::releaseAssociationId(const MacAddress& address) { + checkStateMutation(); associationIdReservations.erase(address); - bssAccessPointData.associationIds.erase(address); - removePeerHtCapabilities(address); + stateChangePending |= bssAccessPointData.associationIds.erase(address) != 0; + removePeerCapabilities(address); } void Ieee80211Mib::clearAssociationIds() { + checkStateMutation(); + stateChangePending |= !bssAccessPointData.stations.empty() || !bssAccessPointData.associationIds.empty(); bssAccessPointData.stations.clear(); associationIdReservations.clear(); bssAccessPointData.associationIds.clear(); - clearPeerHtCapabilities(); + clearPeerCapabilities(); } } // namespace ieee80211 diff --git a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.h b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.h index 691257c2f07..549e807e97b 100644 --- a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.h +++ b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.h @@ -8,9 +8,12 @@ #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" +#include "inet/linklayer/ieee80211/mib/Ieee80211VhtCapabilities.h" namespace inet { @@ -24,6 +27,8 @@ namespace ieee80211 { class INET_API Ieee80211Mib : public SimpleModule { public: + static simsignal_t bssStateChangedSignal; + enum Mode { INFRASTRUCTURE, INDEPENDENT, @@ -63,8 +68,12 @@ class INET_API Ieee80211Mib : public SimpleModule public: bool valid = false; Ieee80211HtCapabilities advertisedCapabilities; - Ieee80211NegotiatedHtCapabilities negotiatedCapabilities; - uint64_t generation = 0; + std::shared_ptr negotiatedCapabilities; + }; + + struct PeerVhtState { + Ieee80211VhtCapabilities advertisedCapabilities; + Ieee80211VhtOperation operation; }; public: @@ -72,6 +81,7 @@ class INET_API Ieee80211Mib : public SimpleModule Mode mode = static_cast(-1); bool qos = false; + private: BssData bssData; BssStationData bssStationData; BssAccessPointData bssAccessPointData; @@ -79,18 +89,39 @@ class INET_API Ieee80211Mib : public SimpleModule // This is a deliberately model-backed subset, not a full Annex C HT MIB implementation. bool localHtCapabilitiesValid = false; Ieee80211HtCapabilities localHtCapabilities; + uint64_t vhtCapabilityGeneration = 0; + bool localVhtCapabilitiesValid = false; + Ieee80211VhtCapabilities localVhtCapabilities; + Ieee80211VhtOperation localVhtOperation; 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; + std::map peerVhtStates; protected: 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,18 +131,40 @@ 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); + // Explicit coordinated reconfiguration; ordinary preparation remains guarded. + void reconfigureLocalHtCapabilities(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(); + void installLocalVhtCapabilities(const Ieee80211VhtCapabilities& capabilities, bool supported); + const Ieee80211VhtCapabilities& getLocalVhtCapabilities() const { return localVhtCapabilities; } + const Ieee80211VhtOperation& getLocalVhtOperation() const { return localVhtOperation; } + uint64_t getVhtCapabilityGeneration() const { return vhtCapabilityGeneration; } + bool isVhtOperationSupported() const { return localVhtCapabilitiesValid; } + const PeerVhtState *findPeerVhtState(const MacAddress& address) const; + void setPeerVhtCapabilities(const MacAddress& address, const Ieee80211VhtCapabilities& capabilities, const Ieee80211VhtOperation& operation); + void removePeerVhtCapabilities(const MacAddress& address); + void removePeerCapabilities(const MacAddress& address); + void clearPeerCapabilities(); }; } // namespace ieee80211 diff --git a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned index 70474b3d95b..35b54143ac6 100644 --- a/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned +++ b/src/inet/linklayer/ieee80211/mib/Ieee80211Mib.ned @@ -19,7 +19,12 @@ import inet.common.SimpleModule; simple Ieee80211Mib extends SimpleModule { parameters: + bool vhtSupported = default(true); // Effective only for a VHT mode set + object vhtRxMcsMap = default([9, 9, 9, 9, 9, 9, 9, 9]); // Per-NSS maximum MCS: -1, 7, 8 or 9; limited by PHY antennas and the primary-20 catalog + object vhtTxMcsMap = default([9, 9, 9, 9, 9, 9, 9, 9]); // Per-NSS maximum MCS: -1, 7, 8 or 9; limited by PHY antennas and the primary-20 catalog + @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/linklayer/ieee80211/mib/Ieee80211VhtCapabilities.h b/src/inet/linklayer/ieee80211/mib/Ieee80211VhtCapabilities.h new file mode 100644 index 00000000000..00da36591db --- /dev/null +++ b/src/inet/linklayer/ieee80211/mib/Ieee80211VhtCapabilities.h @@ -0,0 +1,65 @@ +// Copyright (C) 2026 INET Framework contributors +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef __INET_IEEE80211VHTCAPABILITIES_H +#define __INET_IEEE80211VHTCAPABILITIES_H + +#include +#include "inet/common/Units.h" + +namespace inet { +namespace ieee80211 { + +// IEEE Std 802.11-2024, 9.4.2.156.3: each map entry describes an inclusive +// MCS maximum (7, 8, 9), or -1 for an unsupported spatial-stream count. +struct INET_API Ieee80211VhtCapabilities +{ + std::array rxMaxMcs = {{-1, -1, -1, -1, -1, -1, -1, -1}}; + std::array txMaxMcs = {{-1, -1, -1, -1, -1, -1, -1, -1}}; + bool supported160Mhz = false; + bool shortGi20 = false; + bool shortGi40 = false; + bool shortGi80 = false; + bool shortGi160 = false; + int rxHighestLongGiRateMbps = 0; + int txHighestLongGiRateMbps = 0; + bool operator==(const Ieee80211VhtCapabilities& other) const { + return rxMaxMcs == other.rxMaxMcs && txMaxMcs == other.txMaxMcs && supported160Mhz == other.supported160Mhz && + shortGi20 == other.shortGi20 && shortGi40 == other.shortGi40 && shortGi80 == other.shortGi80 && + shortGi160 == other.shortGi160 && rxHighestLongGiRateMbps == other.rxHighestLongGiRateMbps && + txHighestLongGiRateMbps == other.txHighestLongGiRateMbps; + } +}; + +struct INET_API Ieee80211VhtOperation +{ + Hz channelWidth = MHz(20); + int centerFrequencySegment0 = 0; + int centerFrequencySegment1 = 0; + std::array basicMaxMcs = {{7, -1, -1, -1, -1, -1, -1, -1}}; + bool operator==(const Ieee80211VhtOperation& other) const { + return channelWidth == other.channelWidth && centerFrequencySegment0 == other.centerFrequencySegment0 && + centerFrequencySegment1 == other.centerFrequencySegment1 && basicMaxMcs == other.basicMaxMcs; + } +}; + +inline bool isValidVhtMcsMap(const std::array& map) +{ + for (int maximum : map) + if (maximum != -1 && maximum != 7 && maximum != 8 && maximum != 9) + return false; + return map[0] >= 7; +} + +inline bool supportsBasicVhtMcsSet(const Ieee80211VhtCapabilities& capabilities, const Ieee80211VhtOperation& operation) +{ + for (size_t nss = 0; nss < operation.basicMaxMcs.size(); nss++) + if (operation.basicMaxMcs[nss] >= 0 && + (capabilities.rxMaxMcs[nss] < operation.basicMaxMcs[nss] || capabilities.txMaxMcs[nss] < operation.basicMaxMcs[nss])) + return false; + return true; +} + +} // namespace ieee80211 +} // namespace inet +#endif 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/bitlevel/Ieee80211LayeredOfdmTransmitter.cc b/src/inet/physicallayer/wireless/ieee80211/bitlevel/Ieee80211LayeredOfdmTransmitter.cc index 30eaa47132d..31d98a3cab4 100644 --- a/src/inet/physicallayer/wireless/ieee80211/bitlevel/Ieee80211LayeredOfdmTransmitter.cc +++ b/src/inet/physicallayer/wireless/ieee80211/bitlevel/Ieee80211LayeredOfdmTransmitter.cc @@ -312,7 +312,7 @@ const ITransmission *Ieee80211LayeredOfdmTransmitter::createTransmission(const I // TODO: compute channel const simtime_t preambleDuration = mode->getPreambleLength(); const simtime_t headerDuration = mode->getHeaderMode()->getDuration(); - const simtime_t dataDuration = mode->getDataMode()->getDuration(packet->getDataLength()); + const simtime_t dataDuration = mode->getDataDuration(packet->getDataLength()); return new Ieee80211Transmission(transmitter, packet, startTime, endTime, preambleDuration, headerDuration, dataDuration, startPosition, endPosition, startOrientation, endOrientation, packetModel, bitModel, symbolModel, sampleModel, analogModel, mode, nullptr); } @@ -331,4 +331,3 @@ Ieee80211LayeredOfdmTransmitter::~Ieee80211LayeredOfdmTransmitter() } // namespace physicallayer } // namespace inet - diff --git a/src/inet/physicallayer/wireless/ieee80211/contract/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/contract/packetlevel/IIeee80211ModeSetCoordinator.h b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.h new file mode 100644 index 00000000000..90b15b79cf5 --- /dev/null +++ b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.h @@ -0,0 +1,41 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +#ifndef __INET_IIEEE80211MODESETCOORDINATOR_H +#define __INET_IIEEE80211MODESETCOORDINATOR_H + +#include "inet/common/INETDefs.h" + +namespace inet { +namespace physicallayer { + +class Ieee80211ModeSet; + +/** + * Same-interface mode-set transaction owner. Registration is explicit and unrelated + * to signal subscriptions. Consumers are modules implementing IIeee80211ModeSetListener. + * The coordinator updates MAC capabilities before MANAGEMENT_STATE and DERIVED_STATE; + * consumers within DERIVED_STATE are independent. MAC_STATE is reserved for the owner. + * Begin validates membership before PHY mutation; complete applies consumers and + * publishes the completed fact. Any failure after begin is fatal, without rollback. + */ +class INET_API IIeee80211ModeSetCoordinator +{ + public: + enum Phase { MAC_STATE, MANAGEMENT_STATE, DERIVED_STATE }; + virtual ~IIeee80211ModeSetCoordinator() = default; + // Register during initialization; repeated registration in the same phase is idempotent. + virtual void registerModeSetConsumer(cModule *consumer, Phase phase) = 0; + // Explicitly detach before deleting a consumer. Membership cannot change during a transaction. + virtual void unregisterModeSetConsumer(cModule *consumer) = 0; + virtual void beginModeSetChange(const Ieee80211ModeSet *modeSet) = 0; + virtual void completeModeSetChange(const Ieee80211ModeSet *modeSet) = 0; +}; + +} // namespace physicallayer +} // namespace inet + +#endif diff --git a/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.ned b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.ned new file mode 100644 index 00000000000..2b0a6e2e26b --- /dev/null +++ b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.ned @@ -0,0 +1,14 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +package inet.physicallayer.wireless.ieee80211.contract.packetlevel; + +// Coordinates registered MAC/PHY state consumers before publishing a mode-set fact. +moduleinterface IIeee80211ModeSetCoordinator +{ + parameters: + @signal[modesetChanged](type=inet::physicallayer::Ieee80211ModeSet); +} diff --git a/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.h b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.h new file mode 100644 index 00000000000..420b58cea0c --- /dev/null +++ b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.h @@ -0,0 +1,35 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +#ifndef __INET_IIEEE80211MODESETLISTENER_H +#define __INET_IIEEE80211MODESETLISTENER_H + +#include "inet/common/INETDefs.h" + +namespace inet { +namespace physicallayer { + +class Ieee80211ModeSet; + +/** + * Explicitly registered consumer of an interface's mode-set changes. The + * coordinator applies consumers before publishing modesetChanged. Implementing + * this contract or subscribing to that signal does not register a consumer. + * Applying a change must not notify observers. Failures are fatal simulation + * errors; partially applied changes are not rolled back. + */ +class INET_API IIeee80211ModeSetListener +{ + public: + virtual ~IIeee80211ModeSetListener() = default; + virtual const Ieee80211ModeSet *getModeSet() const = 0; + virtual void applyModeSet(const Ieee80211ModeSet *modeSet) = 0; +}; + +} // namespace physicallayer +} // namespace inet + +#endif diff --git a/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211Radio.h b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211Radio.h new file mode 100644 index 00000000000..f938d841ce3 --- /dev/null +++ b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211Radio.h @@ -0,0 +1,32 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +#ifndef __INET_IIEEE80211RADIO_H +#define __INET_IIEEE80211RADIO_H + +#include "inet/common/INETDefs.h" +#include "inet/common/Units.h" + +namespace inet { +namespace physicallayer { + +class Ieee80211Channel; + +/** Read-only IEEE 802.11 PHY capabilities, in addition to the IRadio role. */ +class INET_API IIeee80211Radio +{ + public: + virtual ~IIeee80211Radio() = default; + // True only when both the operational transmitter and receiver support this width. + virtual bool isHtChannelWidthSupported(units::values::Hz channelWidth) const = 0; + // Borrowed channel, or nullptr when no channel is configured. + virtual const Ieee80211Channel *getChannel() const = 0; +}; + +} // namespace physicallayer +} // namespace inet + +#endif diff --git a/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211Radio.ned b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211Radio.ned new file mode 100644 index 00000000000..42fe7ef481e --- /dev/null +++ b/src/inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211Radio.ned @@ -0,0 +1,14 @@ +// +// Copyright (C) 2026 INET Framework contributors +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +package inet.physicallayer.wireless.ieee80211.contract.packetlevel; + +import inet.physicallayer.wireless.common.contract.packetlevel.IRadio; + +// IEEE 802.11 radio with operational HT width and channel queries. +moduleinterface IIeee80211Radio extends IRadio +{ +} diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/IIeee80211Mode.h b/src/inet/physicallayer/wireless/ieee80211/mode/IIeee80211Mode.h index aa615b7d19e..9af016b4032 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/IIeee80211Mode.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/IIeee80211Mode.h @@ -42,8 +42,13 @@ class INET_API IIeee80211DataMode : public cObject, public IPrintableObject virtual bps getGrossBitrate() const = 0; virtual b getPaddingLength(b dataLength) const = 0; virtual b getCompleteLength(b dataLength) const = 0; + // Returns the raw duration of the encoded data symbol train. PPDU-format + // rules may round this duration at the enclosing mode level. virtual const simtime_t getDuration(b dataLength) const = 0; virtual const simtime_t getSymbolInterval() const = 0; + // Returns the guard interval used by the data symbols, or -1 when the PHY + // has no meaningful guard interval (for example, non-OFDM modes). + virtual const simtime_t getGuardInterval() const = 0; virtual const IModulation *getModulation() const = 0; virtual int getNumberOfSpatialStreams() const = 0; }; @@ -55,6 +60,8 @@ class INET_API IIeee80211Mode : public cObject, public IPrintableObject // other PHY generations. HT capability derivation must use this typed // mode contract rather than concrete-type or name-based inference. virtual int getHtMcsIndex() const = 0; + // VHT MCS index (0..9), or -1 for other PHY generations. + virtual int getVhtMcsIndex() const = 0; // Returns whether this mode uses the optional 400 ns HT guard interval. // Non-HT modes deliberately report false. virtual bool isHtShortGuardInterval() const = 0; @@ -68,6 +75,9 @@ class INET_API IIeee80211Mode : public cObject, public IPrintableObject IIeee80211HeaderMode *_getHeaderMode() const { return const_cast(getHeaderMode()); } IIeee80211DataMode *_getDataMode() const { return const_cast(getDataMode()); } virtual const simtime_t getDuration(b dataLength) const = 0; + virtual const simtime_t getPreambleDuration() const = 0; + virtual const simtime_t getHeaderDuration() const = 0; + virtual const simtime_t getDataDuration(b dataLength) const = 0; virtual const simtime_t getSlotTime() const = 0; virtual const simtime_t getSifsTime() const = 0; virtual const simtime_t getRifsTime() const = 0; diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssMode.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssMode.h index 7b81268c9b7..af511f9a772 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssMode.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssMode.h @@ -64,6 +64,7 @@ class INET_API Ieee80211DsssDataMode : public Ieee80211DsssChunkMode, public IIe public: Ieee80211DsssDataMode(const DpskModulationBase *modulation); + virtual const simtime_t getGuardInterval() const override { return -1; } virtual Hz getBandwidth() const override { return MHz(22); } virtual bps getNetBitrate() const override { return Mbps(1) * modulation->getConstellationSize() / 2; } virtual bps getGrossBitrate() const override { return getNetBitrate(); } diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211FhssMode.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211FhssMode.h index 39295cf2bd5..2b29fe15539 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211FhssMode.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211FhssMode.h @@ -58,6 +58,7 @@ class INET_API Ieee80211FhssDataMode : public IIeee80211DataMode public: Ieee80211FhssDataMode(const GfskModulationBase *modulation); + virtual const simtime_t getGuardInterval() const override { return -1; } virtual Hz getBandwidth() const override { return Hz(NaN); } virtual bps getNetBitrate() const override { return Mbps(1) * modulation->getConstellationSize() / 2; } virtual bps getGrossBitrate() const override { return getNetBitrate(); } diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HrDsssMode.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HrDsssMode.h index 82324fe4a02..724daa40875 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HrDsssMode.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HrDsssMode.h @@ -74,6 +74,7 @@ class INET_API Ieee80211HrDsssDataMode : public IIeee80211DataMode public: Ieee80211HrDsssDataMode(bps bitrate); + virtual const simtime_t getGuardInterval() const override { return -1; } virtual Hz getBandwidth() const override { return MHz(22); } virtual bps getNetBitrate() const override { return bitrate; } virtual bps getGrossBitrate() const override { return bitrate; } diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.cc b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.cc index 319128cce20..f74a0a7433b 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.cc +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.cc @@ -161,6 +161,16 @@ unsigned int Ieee80211HtPreambleMode::computeNumberOfHTLongTrainings(unsigned in return numberOfSpaceTimeStreams == 3 ? 4 : numberOfSpaceTimeStreams; } +const simtime_t Ieee80211HtPreambleMode::getDurationBeforeHeader() const +{ + if (preambleFormat == HT_PREAMBLE_MIXED) + return getNonHTShortTrainingSequenceDuration() + getNonHTLongTrainingFieldDuration() + legacySignalMode->getDuration(); + else if (preambleFormat == HT_PREAMBLE_GREENFIELD) + return getHTGreenfieldShortTrainingFieldDuration() + getFirstHTLongTrainingFieldDuration(); + else + throw cRuntimeError("Unknown preamble format"); +} + const simtime_t Ieee80211HtPreambleMode::getDuration() const { // 20.3.7 Mathematical description of signals @@ -178,12 +188,8 @@ const simtime_t Ieee80211HtPreambleMode::getDuration() const bps Ieee80211HtSignalMode::computeGrossBitrate() const { unsigned int numberOfCodedBitsPerSymbol = modulation->getSubcarrierModulation()->getCodeWordSize() * getNumberOfDataSubcarriers(); - if (guardIntervalType == HT_GUARD_INTERVAL_LONG) - return bps(numberOfCodedBitsPerSymbol / getSymbolInterval()); - else if (guardIntervalType == HT_GUARD_INTERVAL_SHORT) - return bps(numberOfCodedBitsPerSymbol / getShortGISymbolInterval()); - else - throw cRuntimeError("Unknown guard interval type"); + // IEEE Std 802.11-2024, 19.3.11.11.6: the short GI applies only to the Data field. + return bps(numberOfCodedBitsPerSymbol / getSymbolInterval()); } bps Ieee80211HtSignalMode::computeNetBitrate() const @@ -239,6 +245,16 @@ bps Ieee80211HtModeBase::getGrossBitrate() const return grossBitrate; } +const simtime_t Ieee80211HtDataMode::getGuardInterval() const +{ + if (guardIntervalType == HT_GUARD_INTERVAL_LONG) + return getGIDuration(); + else if (guardIntervalType == HT_GUARD_INTERVAL_SHORT) + return getShortGIDuration(); + else + throw cRuntimeError("Unknown guard interval type"); +} + int Ieee80211HtModeBase::getNumberOfDataSubcarriers() const { return Ieee80211Htmcs::getNumberOfDataSubcarriers(bandwidth, mcsIndex); @@ -285,6 +301,22 @@ const simtime_t Ieee80211HtDataMode::getDuration(b dataLength) const return numberOfSymbols * getSymbolInterval(); } +const simtime_t Ieee80211HtMode::getDuration(b dataLength) const +{ + auto dataDuration = dataMode->getDuration(dataLength); + if (preambleMode->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED && + dataMode->getGuardIntervalType() == Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT) + { + // IEEE Std 802.11-2024, 19.4.3, Eq. (19-90): mixed-format short-GI + // Data airtime is rounded up to a 4 us boundary. Eq. (19-92) leaves + // greenfield short-GI Data airtime at its raw 3.6 us symbol duration. + auto longGiSymbolInterval = dataMode->getDFTPeriod() + dataMode->getGIDuration(); + auto numberOfLongGiSymbols = (dataDuration.raw() + longGiSymbolInterval.raw() - 1) / longGiSymbolInterval.raw(); + dataDuration = SimTime::fromRaw(numberOfLongGiSymbols * longGiSymbolInterval.raw()); + } + return preambleMode->getDuration() + dataDuration; +} + const simtime_t Ieee80211HtMode::getSlotTime() const { if (centerFrequencyMode == BAND_2_4GHZ) @@ -326,7 +358,7 @@ Ieee80211HtCompliantModes::~Ieee80211HtCompliantModes() const Ieee80211HtMode *Ieee80211HtCompliantModes::getCompliantMode(const Ieee80211Htmcs *mcsMode, Ieee80211HtMode::BandMode centerFrequencyMode, Ieee80211HtPreambleMode::HighTroughputPreambleFormat preambleFormat, Ieee80211HtModeBase::GuardIntervalType guardIntervalType) { const char *name = ""; // TODO - auto htModeId = std::make_tuple(mcsMode->getBandwidth(), mcsMode->getMcsIndex(), guardIntervalType); + auto htModeId = std::make_tuple(mcsMode->getBandwidth(), mcsMode->getMcsIndex(), centerFrequencyMode, preambleFormat, guardIntervalType); auto mode = singleton.modeCache.find(htModeId); if (mode == singleton.modeCache.end()) { const Ieee80211OfdmModulation *modulation = nullptr; @@ -348,7 +380,7 @@ const Ieee80211HtMode *Ieee80211HtCompliantModes::getCompliantMode(const Ieee802 const Ieee80211HtDataMode *dataMode = new Ieee80211HtDataMode(mcsMode, mcsMode->getBandwidth(), guardIntervalType); const Ieee80211HtPreambleMode *preambleMode = new Ieee80211HtPreambleMode(htSignal, legacySignal, preambleFormat, dataMode->getNumberOfSpatialStreams()); const Ieee80211HtMode *htMode = new Ieee80211HtMode(name, preambleMode, dataMode, centerFrequencyMode); - singleton.modeCache.insert(std::pair, const Ieee80211HtMode *>(htModeId, htMode)); + singleton.modeCache.insert(std::pair, const Ieee80211HtMode *>(htModeId, htMode)); return htMode; } return mode->second; @@ -442,7 +474,9 @@ const DI Ieee80211HtmcsTable::htMcs29BW40MHz([](){ return new Ie const DI Ieee80211HtmcsTable::htMcs30BW40MHz([](){ return new Ieee80211Htmcs(30, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(40));}); const DI Ieee80211HtmcsTable::htMcs31BW40MHz([](){ return new Ieee80211Htmcs(31, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Ieee80211HtCompliantCodes::htConvolutionalCode5_6, MHz(40));}); -const DI Ieee80211HtmcsTable::htMcs32BW40MHz([](){ return new Ieee80211Htmcs(32, &BpskModulation::singleton, &BpskModulation::singleton, &BpskModulation::singleton, &BpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode1_2, MHz(40));}); +// IEEE Std 802.11-2024, Table 19-35: optional MCS 32 is one BPSK stream. +// This corrects the previous incorrect 4-stream all-BPSK constructor. +const DI Ieee80211HtmcsTable::htMcs32BW40MHz([](){ return new Ieee80211Htmcs(32, &BpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode1_2, MHz(40));}); const DI Ieee80211HtmcsTable::htMcs33BW20MHz([](){ return new Ieee80211Htmcs(33, &Qam16Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode1_2, MHz(20));}); const DI Ieee80211HtmcsTable::htMcs34BW20MHz([](){ return new Ieee80211Htmcs(34, &Qam64Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode1_2, MHz(20));}); @@ -490,7 +524,9 @@ const DI Ieee80211HtmcsTable::htMcs72BW20MHz([](){ return new Ie const DI Ieee80211HtmcsTable::htMcs73BW20MHz([](){ return new Ieee80211Htmcs(73, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam16Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(20));}); const DI Ieee80211HtmcsTable::htMcs74BW20MHz([](){ return new Ieee80211Htmcs(74, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam16Modulation::singleton, &Qam16Modulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(20));}); const DI Ieee80211HtmcsTable::htMcs75BW20MHz([](){ return new Ieee80211Htmcs(75, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(20));}); -const DI Ieee80211HtmcsTable::htMcs76BW20MHz([](){ return new Ieee80211Htmcs(76, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(20));}); +// IEEE Std 802.11-2024, Table 19-38 (continued): MCS 76 uses 64-QAM for streams 1-3 and 16-QAM for stream 4. +// This corrects the previous incorrect QPSK modulation for the 4th stream. +const DI Ieee80211HtmcsTable::htMcs76BW20MHz([](){ return new Ieee80211Htmcs(76, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam16Modulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(20));}); const DI Ieee80211HtmcsTable::htMcs33BW40MHz([](){ return new Ieee80211Htmcs(33, &Qam16Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode1_2, MHz(40));}); const DI Ieee80211HtmcsTable::htMcs34BW40MHz([](){ return new Ieee80211Htmcs(34, &Qam64Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode1_2, MHz(40));}); @@ -536,7 +572,9 @@ const DI Ieee80211HtmcsTable::htMcs69BW40MHz([](){ return new Ie const DI Ieee80211HtmcsTable::htMcs70BW40MHz([](){ return new Ieee80211Htmcs(70, &Qam64Modulation::singleton, &Qam16Modulation::singleton, &Qam16Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(40));}); const DI Ieee80211HtmcsTable::htMcs71BW40MHz([](){ return new Ieee80211Htmcs(71, &Qam64Modulation::singleton, &Qam16Modulation::singleton, &Qam16Modulation::singleton, &Qam16Modulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(40));}); const DI Ieee80211HtmcsTable::htMcs72BW40MHz([](){ return new Ieee80211Htmcs(72, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &QpskModulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(40));}); -const DI Ieee80211HtmcsTable::htMcs73BW40MHz([](){ return new Ieee80211Htmcs(73, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(40));}); +// IEEE Std 802.11-2024, Table 19-41: MCS 73 uses 64-QAM for streams 1-2 and 16-QAM for stream 3. +// This corrects the previous incorrect 64-QAM modulation for the 3rd stream. +const DI Ieee80211HtmcsTable::htMcs73BW40MHz([](){ return new Ieee80211Htmcs(73, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam16Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(40));}); const DI Ieee80211HtmcsTable::htMcs74BW40MHz([](){ return new Ieee80211Htmcs(74, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam16Modulation::singleton, &Qam16Modulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(40));}); const DI Ieee80211HtmcsTable::htMcs75BW40MHz([](){ return new Ieee80211Htmcs(75, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &Qam64Modulation::singleton, &QpskModulation::singleton, &Ieee80211OfdmCompliantCodes::ofdmConvolutionalCode3_4, MHz(40));}); @@ -544,4 +582,3 @@ const DI Ieee80211HtmcsTable::htMcs76BW40MHz([](){ return new Ie } /* namespace physicallayer */ } /* namespace inet */ - diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h index 9184765f083..5e5d43b6fa3 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h @@ -153,6 +153,7 @@ class INET_API Ieee80211HtPreambleMode : public IIeee80211PreambleMode, public I virtual const simtime_t getSecondAndSubsequentHTLongTrainingFielDuration() const { return 4E-6; } // HT-LTFs, s = 2,3,..,n virtual unsigned int getNumberOfHtLongTrainings() const { return numberOfHTLongTrainings; } + virtual const simtime_t getDurationBeforeHeader() const; virtual const simtime_t getDuration() const override; virtual Ptr createPreamble() const override { return makeShared(); } @@ -218,7 +219,8 @@ class INET_API Ieee80211HtDataMode : public IIeee80211DataMode, public Ieee80211 virtual bps getGrossBitrate() const override { return Ieee80211HtModeBase::getGrossBitrate(); } virtual const Ieee80211Htmcs *getModulationAndCodingScheme() const { return modulationAndCodingScheme; } virtual const Ieee80211HtCode *getCode() const { return modulationAndCodingScheme->getCode(); } - virtual const simtime_t getSymbolInterval() const override { return Ieee80211HtTimingRelatedParametersBase::getSymbolInterval(); } + virtual const simtime_t getGuardInterval() const override; + virtual const simtime_t getSymbolInterval() const override { return getDFTPeriod() + getGuardInterval(); } virtual const Ieee80211OfdmModulation *getModulation() const override { return modulationAndCodingScheme->getModulation(); } }; @@ -263,7 +265,10 @@ class INET_API Ieee80211HtMode : public Ieee80211ModeBase virtual int getMpduMaxLength() const override { return 65535; } // in octets virtual BandMode getCenterFrequencyMode() const { return centerFrequencyMode; } - virtual const simtime_t getDuration(b dataLength) const override { return preambleMode->getDuration() + dataMode->getDuration(dataLength); } + virtual const simtime_t getDuration(b dataLength) const override; + virtual const simtime_t getPreambleDuration() const override { return preambleMode->getDurationBeforeHeader(); } + virtual const simtime_t getHeaderDuration() const override { return preambleMode->getDuration() - getPreambleDuration(); } + virtual const simtime_t getDataDuration(b dataLength) const override { return getDuration(dataLength) - preambleMode->getDuration(); } }; // A specification of the high-throughput (HT) physical layer (PHY) @@ -462,7 +467,7 @@ class INET_API Ieee80211HtCompliantModes protected: static OPP_THREAD_LOCAL const Ieee80211HtCompliantModes singleton; - mutable std::map, const Ieee80211HtMode *> modeCache; + mutable std::map, const Ieee80211HtMode *> modeCache; public: Ieee80211HtCompliantModes(); diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211IrMode.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211IrMode.h index 86170cfc24b..4479d8e2365 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211IrMode.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211IrMode.h @@ -66,6 +66,7 @@ class INET_API Ieee80211IrDataMode : public IIeee80211DataMode public: Ieee80211IrDataMode(const PpmModulationBase *modulation); + virtual const simtime_t getGuardInterval() const override { return -1; } virtual Hz getBandwidth() const override { return Hz(NaN); } virtual bps getNetBitrate() const override { return Mbps(1) * modulation->getConstellationSize() / 2; } virtual bps getGrossBitrate() const override { return getNetBitrate(); } diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeBase.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeBase.h index dd5d6fdd0ca..cb0fa7f3306 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeBase.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeBase.h @@ -21,8 +21,12 @@ class INET_API Ieee80211ModeBase : public IIeee80211Mode public: Ieee80211ModeBase(const char *name) : name(name) {} virtual int getHtMcsIndex() const override { return -1; } + virtual int getVhtMcsIndex() const override { return -1; } virtual bool isHtShortGuardInterval() const override { return false; } virtual const char *getName() const override { return name.c_str(); } + virtual const simtime_t getPreambleDuration() const override { return getPreambleMode()->getDuration(); } + virtual const simtime_t getHeaderDuration() const override { return getHeaderMode()->getDuration(); } + virtual const simtime_t getDataDuration(b dataLength) const override { return getDuration(dataLength) - getPreambleDuration() - getHeaderDuration(); } }; } /* namespace physicallayer */ diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc index 5a53166e2cc..e27e289f704 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc @@ -221,7 +221,8 @@ const DelayedInitializer> Ieee80211ModeSet::modeSe { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode12Mbps, true }, { true, &Ieee80211ErpOfdmCompliantModes::erpOfdmMode24Mbps, true } }, Ieee80211HtCompliantModes::getCompliantMode(&Ieee80211HtmcsTable::htMcs0BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, Ieee80211HtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG), PhyType::HT, true), - Ieee80211ModeSet("ac", { + Ieee80211ModeSet("ac", []() { + std::vector entries { { true, Ieee80211VhtCompliantModes::getCompliantMode(&Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, Ieee80211VhtMode::BAND_5GHZ, Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG) }, { true, Ieee80211VhtCompliantModes::getCompliantMode(&Ieee80211VhtmcsTable::vhtMcs1BW20MHzNss1, Ieee80211VhtMode::BAND_5GHZ, Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG) }, { true, Ieee80211VhtCompliantModes::getCompliantMode(&Ieee80211VhtmcsTable::vhtMcs2BW20MHzNss1, Ieee80211VhtMode::BAND_5GHZ, Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG) }, @@ -537,9 +538,27 @@ const DelayedInitializer> Ieee80211ModeSet::modeSe { true, &Ieee80211OfdmCompliantModes::ofdmMode6MbpsCS20MHz, true }, { true, &Ieee80211OfdmCompliantModes::ofdmMode12MbpsCS20MHz, true }, { true, &Ieee80211OfdmCompliantModes::ofdmMode24MbpsCS20MHz, true }, + }; + // IEEE Std 802.11-2024, 21.5 and Tables 21-29 through 21-60: + // each valid VHT tuple supports long GI, with short GI optional. + // New variants have lower unqualified-lookup priority than historical + // entries, preserving choices even across floating-point rate ties. + // Existing mandatory/basic flags and reference choices remain. + const size_t originalSize = entries.size(); + for (size_t i = 0; i < originalSize; i++) { + const auto *data = dynamic_cast(entries[i].mode->getDataMode()); + if (data != nullptr) { + auto gi = data->getGuardIntervalType() == Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG ? + Ieee80211VhtModeBase::HT_GUARD_INTERVAL_SHORT : Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG; + entries.push_back({false, Ieee80211VhtCompliantModes::getCompliantMode(data->getModulationAndCodingScheme(), + Ieee80211VhtMode::BAND_5GHZ, Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, gi), false, false}); + } + } + return entries; + }(), // Intentional model limitation: unlike IEEE Std 802.11-2024, 11.38.1, // this VHT-only profile has no selectable HT modes. - }, Ieee80211VhtCompliantModes::getCompliantMode(&Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, Ieee80211VhtMode::BAND_5GHZ, Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG), PhyType::VHT),}; }); + Ieee80211VhtCompliantModes::getCompliantMode(&Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, Ieee80211VhtMode::BAND_5GHZ, Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG), PhyType::VHT),}; }); Ieee80211ModeSet::Ieee80211ModeSet(const char *name, const std::vector entries, const IIeee80211Mode *referenceMode, PhyType phyType, bool htOperationSupported) : @@ -578,6 +597,8 @@ Ieee80211ModeSet::Ieee80211ModeSet(const char *name, const std::vector en if (this->referenceMode->getLegacyCwMin() < 0 || this->referenceMode->getLegacyCwMax() < this->referenceMode->getLegacyCwMin()) throw cRuntimeError("Reference mode '%s' in IEEE 802.11 mode set '%s' has invalid contention window bounds", this->referenceMode->getName(), this->name.c_str()); std::vector *nonConstEntries = const_cast *>(&this->entries); + // Keep equal-bitrate modes in declaration order because unqualified lookups + // intentionally preserve the historically preferred mode. std::stable_sort(nonConstEntries->begin(), nonConstEntries->end(), EntryNetBitrateComparator()); // Explicit Supported-Rates eligibility on the authoritative Entry keeps // HT/VHT MCSs out without concrete-type or name-based inference. The @@ -663,41 +684,80 @@ bool Ieee80211ModeSet::getIsMandatory(const IIeee80211Mode *mode) const return entries[getModeIndex(mode)].isMandatory; } -const IIeee80211Mode *Ieee80211ModeSet::findMode(bps bitrate, Hz bandwidth, int numSpatialStreams) const +const IIeee80211Mode *Ieee80211ModeSet::findCompatibleMode(const IIeee80211Mode *mode) const +{ + if (mode == nullptr) + return nullptr; + + const auto sourceDataMode = mode->getDataMode(); + const auto sourceBitrate = sourceDataMode->getNetBitrate(); + const auto sourceBandwidth = sourceDataMode->getBandwidth(); + const auto sourceGuardInterval = sourceDataMode->getGuardInterval(); + for (const auto& entry : entries) { + const auto candidateDataMode = entry.mode->getDataMode(); + const auto candidateBandwidth = candidateDataMode->getBandwidth(); + const auto candidateGuardInterval = candidateDataMode->getGuardInterval(); + const bool bandwidthMatches = (std::isnan(sourceBandwidth.get()) && std::isnan(candidateBandwidth.get())) || + (!std::isnan(sourceBandwidth.get()) && !std::isnan(candidateBandwidth.get()) && sourceBandwidth == candidateBandwidth); + // Absence is part of the source tuple, not findMode()'s wildcard. + const bool guardIntervalMatches = (sourceGuardInterval < SIMTIME_ZERO && candidateGuardInterval < SIMTIME_ZERO) || + (sourceGuardInterval >= SIMTIME_ZERO && candidateGuardInterval == sourceGuardInterval); + if (candidateDataMode->getNetBitrate() == sourceBitrate && + bandwidthMatches && candidateDataMode->getNumberOfSpatialStreams() == sourceDataMode->getNumberOfSpatialStreams() && + guardIntervalMatches) + return entry.mode; + } + return nullptr; +} + +const IIeee80211Mode *Ieee80211ModeSet::findMode(bps bitrate, Hz bandwidth, int numSpatialStreams, simtime_t guardInterval) const { - return findMode(bitrate - Mbps(0.05), bitrate + Mbps(0.05), bandwidth, numSpatialStreams); + return findMode(bitrate - Mbps(0.05), bitrate + Mbps(0.05), bandwidth, numSpatialStreams, guardInterval); } -const IIeee80211Mode *Ieee80211ModeSet::findMode(bps minBitrate, bps maxBitrate, Hz bandwidth, int numSpatialStreams) const +const IIeee80211Mode *Ieee80211ModeSet::findMode(bps minBitrate, bps maxBitrate, Hz bandwidth, int numSpatialStreams, simtime_t guardInterval) const { - for (size_t index = 0; index < entries.size(); index++) { - auto mode = entries[index].mode; - auto dataMode = mode->getDataMode(); - auto bitrate = dataMode->getNetBitrate(); - if (minBitrate <= bitrate && bitrate <= maxBitrate && - (std::isnan(bandwidth.get()) || dataMode->getBandwidth() == bandwidth) && - (numSpatialStreams == -1 || dataMode->getNumberOfSpatialStreams() == numSpatialStreams)) - { - return entries[index].mode; + // Preserve prior unqualified queries even when a newly added GI variant + // sorts earlier because its mathematically equal bitrate rounds differently. + for (bool preferred : {true, false}) { + for (size_t index = 0; index < entries.size(); index++) { + if (guardInterval < SIMTIME_ZERO && entries[index].isPreferredForUnqualifiedLookup != preferred) + continue; + auto mode = entries[index].mode; + auto dataMode = mode->getDataMode(); + auto bitrate = dataMode->getNetBitrate(); + bool guardIntervalMatches = guardInterval < SIMTIME_ZERO || + dataMode->getGuardInterval() == guardInterval; + if (minBitrate <= bitrate && bitrate <= maxBitrate && + (std::isnan(bandwidth.get()) || dataMode->getBandwidth() == bandwidth) && + (numSpatialStreams == -1 || dataMode->getNumberOfSpatialStreams() == numSpatialStreams) && + guardIntervalMatches) + { + return entries[index].mode; + } } + if (guardInterval >= SIMTIME_ZERO) + break; } return nullptr; } -const IIeee80211Mode *Ieee80211ModeSet::getMode(bps bitrate, Hz bandwidth, int numSpatialStreams) const +const IIeee80211Mode *Ieee80211ModeSet::getMode(bps bitrate, Hz bandwidth, int numSpatialStreams, simtime_t guardInterval) const { - const IIeee80211Mode *mode = getMode(bitrate - Mbps(0.05), bitrate + Mbps(0.05), bandwidth, numSpatialStreams); + const IIeee80211Mode *mode = getMode(bitrate - Mbps(0.05), bitrate + Mbps(0.05), bandwidth, numSpatialStreams, guardInterval); if (mode == nullptr) - throw cRuntimeError("Unknown bitrate: %g in operation mode: '%s'", bitrate.get(), getName()); + throw cRuntimeError("Unknown mode for bitrate %g bps, bandwidth %g Hz, %d spatial streams, and %s guard interval in operation mode '%s'", + bitrate.get(), bandwidth.get(), numSpatialStreams, guardInterval.str().c_str(), getName()); else return mode; } -const IIeee80211Mode *Ieee80211ModeSet::getMode(bps minBitrate, bps maxBitrate, Hz bandwidth, int numSpatialStreams) const +const IIeee80211Mode *Ieee80211ModeSet::getMode(bps minBitrate, bps maxBitrate, Hz bandwidth, int numSpatialStreams, simtime_t guardInterval) const { - const IIeee80211Mode *mode = findMode(minBitrate, maxBitrate, bandwidth, numSpatialStreams); + const IIeee80211Mode *mode = findMode(minBitrate, maxBitrate, bandwidth, numSpatialStreams, guardInterval); if (mode == nullptr) - throw cRuntimeError("Unknown bitrate: (%g - %g) in operation mode: '%s'", minBitrate.get(), maxBitrate.get(), getName()); + throw cRuntimeError("Unknown mode for bitrate range (%g - %g) bps, bandwidth %g Hz, %d spatial streams, and %s guard interval in operation mode '%s'", + minBitrate.get(), maxBitrate.get(), bandwidth.get(), numSpatialStreams, guardInterval.str().c_str(), getName()); else return mode; } @@ -715,19 +775,25 @@ const IIeee80211Mode *Ieee80211ModeSet::getFastestMode() const const IIeee80211Mode *Ieee80211ModeSet::getSlowerMode(const IIeee80211Mode *mode) const { int index = findModeIndex(mode); - if (index > 0) - return entries[index - 1].mode; - else - return nullptr; + if (index > 0) { + auto bitrate = mode->getDataMode()->getNetBitrate(); + for (int i = index - 1; i >= 0; i--) + if (entries[i].mode->getDataMode()->getNetBitrate() < bitrate) + return entries[i].mode; + } + return nullptr; } const IIeee80211Mode *Ieee80211ModeSet::getFasterMode(const IIeee80211Mode *mode) const { int index = findModeIndex(mode); - if (index >= 0 && index < (int)entries.size() - 1) - return entries[index + 1].mode; - else - return nullptr; + if (index >= 0) { + auto bitrate = mode->getDataMode()->getNetBitrate(); + for (size_t i = index + 1; i < entries.size(); i++) + if (entries[i].mode->getDataMode()->getNetBitrate() > bitrate) + return entries[i].mode; + } + return nullptr; } const IIeee80211Mode *Ieee80211ModeSet::getSlowestMandatoryMode() const @@ -758,24 +824,47 @@ const IIeee80211Mode *Ieee80211ModeSet::getFastestLegacyOperationalMode() const return legacyOperationalModes.empty() ? nullptr : legacyOperationalModes.back(); } +const IIeee80211Mode *Ieee80211ModeSet::getMandatoryModeAtOrBelow(const IIeee80211Mode *mode) const +{ + // Returns the highest-bitrate mandatory mode whose bitrate is <= the given mode's bitrate. + // For equal-bitrate mandatory modes, returns the first-encountered entry (strict > comparison). + // This may return a different mode object than the input when the input is mandatory and + // shares bitrate with another mandatory mode, but the resulting rate is behavior-equivalent. + const auto bitrate = mode->getDataMode()->getNetBitrate(); + const IIeee80211Mode *result = nullptr; + for (const auto& entry : entries) { + const auto entryBitrate = entry.mode->getDataMode()->getNetBitrate(); + if (entry.isMandatory && entryBitrate <= bitrate && + (result == nullptr || entryBitrate > result->getDataMode()->getNetBitrate())) + result = entry.mode; + } + return result; +} + const IIeee80211Mode *Ieee80211ModeSet::getSlowerMandatoryMode(const IIeee80211Mode *mode) const { - int index = findModeIndex(mode); - if (index > 0) - for (int i = index - 1; i >= 0; i--) - if (entries[i].isMandatory) - return entries[i].mode; - return nullptr; + const auto bitrate = mode->getDataMode()->getNetBitrate(); + const IIeee80211Mode *result = nullptr; + for (const auto& entry : entries) { + const auto entryBitrate = entry.mode->getDataMode()->getNetBitrate(); + if (entry.isMandatory && entryBitrate < bitrate && + (result == nullptr || entryBitrate > result->getDataMode()->getNetBitrate())) + result = entry.mode; + } + return result; } const IIeee80211Mode *Ieee80211ModeSet::getFasterMandatoryMode(const IIeee80211Mode *mode) const { - int index = findModeIndex(mode); - if (index >= 0) - for (size_t i = index + 1; i < entries.size(); i++) - if (entries[i].isMandatory) - return entries[i].mode; - return nullptr; + const auto bitrate = mode->getDataMode()->getNetBitrate(); + const IIeee80211Mode *result = nullptr; + for (const auto& entry : entries) { + const auto entryBitrate = entry.mode->getDataMode()->getNetBitrate(); + if (entry.isMandatory && entryBitrate > bitrate && + (result == nullptr || entryBitrate < result->getDataMode()->getNetBitrate())) + result = entry.mode; + } + return result; } const Ieee80211ModeSet *Ieee80211ModeSet::findModeSet(const char *mode) diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h index e14627dbcbe..b7b769105fe 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h @@ -34,6 +34,8 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject bool isMandatory; const IIeee80211Mode *mode; bool isLegacyOperational = false; + // Prefer historical catalog entries when no GI qualifier is supplied. + bool isPreferredForUnqualifiedLookup = true; }; struct EntryNetBitrateComparator { @@ -88,17 +90,21 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject bool containsMode(const IIeee80211Mode *mode) const { return findModeIndex(mode) != -1; } bool getIsMandatory(const IIeee80211Mode *mode) const; - - const IIeee80211Mode *findMode(bps bitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1) const; - const IIeee80211Mode *findMode(bps minBitrate, bps maxBitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1) const; - const IIeee80211Mode *getMode(bps bitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1) const; - const IIeee80211Mode *getMode(bps minBitrate, bps maxBitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1) const; + // Finds a mode with the same PHY tuple as mode. Unlike findMode(), this + // treats an absent guard interval (negative value) as an exact value. + const IIeee80211Mode *findCompatibleMode(const IIeee80211Mode *mode) const; + + const IIeee80211Mode *findMode(bps bitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1, simtime_t guardInterval = -1) const; + const IIeee80211Mode *findMode(bps minBitrate, bps maxBitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1, simtime_t guardInterval = -1) const; + const IIeee80211Mode *getMode(bps bitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1, simtime_t guardInterval = -1) const; + const IIeee80211Mode *getMode(bps minBitrate, bps maxBitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1, simtime_t guardInterval = -1) const; const IIeee80211Mode *getSlowestMode() const; const IIeee80211Mode *getFastestMode() const; const IIeee80211Mode *getSlowerMode(const IIeee80211Mode *mode) const; const IIeee80211Mode *getFasterMode(const IIeee80211Mode *mode) const; const IIeee80211Mode *getSlowestMandatoryMode() const; const IIeee80211Mode *getFastestMandatoryMode() const; + const IIeee80211Mode *getMandatoryModeAtOrBelow(const IIeee80211Mode *mode) const; const IIeee80211Mode *getSlowerMandatoryMode(const IIeee80211Mode *mode) const; const IIeee80211Mode *getFasterMandatoryMode(const IIeee80211Mode *mode) const; diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211OfdmMode.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211OfdmMode.h index 6ca84f780f3..608943efba3 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211OfdmMode.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211OfdmMode.h @@ -124,6 +124,7 @@ class INET_API Ieee80211OfdmDataMode : public IIeee80211DataMode, public Ieee802 virtual b getPaddingLength(b dataLength) const override; virtual b getCompleteLength(b dataLength) const override; virtual const simtime_t getDuration(b dataLength) const override; + virtual const simtime_t getGuardInterval() const override { return getGIDuration(); } const Ieee80211OfdmCode *getCode() const { return code; } virtual const simtime_t getSymbolInterval() const override { return Ieee80211OfdmTimingRelatedParametersBase::getSymbolInterval(); } @@ -265,4 +266,3 @@ class INET_API Ieee80211OfdmCompliantModes } // namespace inet #endif - diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.cc b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.cc index 89b142e37cb..bd553b35dae 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.cc +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.cc @@ -241,6 +241,13 @@ unsigned int Ieee80211VhtPreambleMode::computeNumberOfHTLongTrainings(unsigned i return numberOfSpaceTimeStreams == 3 ? 4 : numberOfSpaceTimeStreams; } +const simtime_t Ieee80211VhtPreambleMode::getDurationBeforeHeader() const +{ + // IEEE Std 802.11-2024, 21.3.2: the L-SIG duration is part of the + // pre-header timing of the supported VHT mixed format. + return getNonHTShortTrainingSequenceDuration() + getNonHTLongTrainingFieldDuration() + getLSIGDuration(); +} + const simtime_t Ieee80211VhtPreambleMode::getDuration() const { // 21.3.4 Mathematical description of signals @@ -251,12 +258,9 @@ const simtime_t Ieee80211VhtPreambleMode::getDuration() const bps Ieee80211VhtSignalMode::computeGrossBitrate() const { unsigned int numberOfCodedBitsPerSymbol = modulation->getSubcarrierModulation()->getCodeWordSize() * getNumberOfDataSubcarriers(); - if (guardIntervalType == HT_GUARD_INTERVAL_LONG) - return bps(numberOfCodedBitsPerSymbol / getSymbolInterval()); - else if (guardIntervalType == HT_GUARD_INTERVAL_SHORT) - return bps(numberOfCodedBitsPerSymbol / getShortGISymbolInterval()); - else - throw cRuntimeError("Unknown guard interval type"); + // IEEE Std 802.11-2024, Table 21-5: VHT-SIG fields use TSYML even + // when the Data field uses short GI; their signaling rate is GI-independent. + return bps(numberOfCodedBitsPerSymbol / getSymbolInterval()); } bps Ieee80211VhtSignalMode::computeNetBitrate() const @@ -312,6 +316,16 @@ bps Ieee80211VhtModeBase::getGrossBitrate() const return grossBitrate; } +const simtime_t Ieee80211VhtDataMode::getGuardInterval() const +{ + if (guardIntervalType == HT_GUARD_INTERVAL_LONG) + return getGIDuration(); + else if (guardIntervalType == HT_GUARD_INTERVAL_SHORT) + return getShortGIDuration(); + else + throw cRuntimeError("Unknown guard interval type"); +} + int Ieee80211VhtModeBase::getNumberOfDataSubcarriers() const { if (bandwidth == MHz(20)) @@ -639,6 +653,20 @@ const simtime_t Ieee80211VhtDataMode::getDuration(b dataLength) const return numberOfSymbols * getSymbolInterval(); } +const simtime_t Ieee80211VhtMode::getDataDuration(b dataBitLength) const +{ + auto dataDuration = dataMode->getDuration(dataBitLength); + if (dataMode->getGuardInterval() == dataMode->getShortGIDuration()) { + // IEEE Std 802.11-2024, 21.4.3, Eq. (21-109): short-GI VHT data + // airtime is the raw TSYMS train rounded up to a TSYML boundary. + // This corrects the previous implementation that used the raw short-GI symbol train. + const auto longGiSymbolInterval = dataMode->getDFTPeriod() + dataMode->getGIDuration(); + const auto numberOfLongGiSymbols = (dataDuration.raw() + longGiSymbolInterval.raw() - 1) / longGiSymbolInterval.raw(); + dataDuration = SimTime::fromRaw(numberOfLongGiSymbols * longGiSymbolInterval.raw()); + } + return dataDuration; +} + const simtime_t Ieee80211VhtMode::getSlotTime() const { if (centerFrequencyMode == BAND_5GHZ) @@ -672,9 +700,13 @@ Ieee80211VhtCompliantModes::~Ieee80211VhtCompliantModes() const Ieee80211VhtMode *Ieee80211VhtCompliantModes::getCompliantMode(const Ieee80211Vhtmcs *mcsMode, Ieee80211VhtMode::BandMode centerFrequencyMode, Ieee80211VhtPreambleMode::HighTroughputPreambleFormat preambleFormat, Ieee80211VhtModeBase::GuardIntervalType guardIntervalType) { + // IEEE Std 802.11-2024, 21.3.2 permits VHT PPDUs only in the mixed + // preamble format represented by this mode implementation. + if (preambleFormat != Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED) + throw cRuntimeError("Unsupported VHT preamble format: only HT_PREAMBLE_MIXED is supported (IEEE Std 802.11-2024, 21.3.2)"); const char *name = ""; // TODO unsigned int nss = mcsMode->getNumNss(); - auto htModeId = std::make_tuple(mcsMode->getBandwidth(), mcsMode->getMcsIndex(), guardIntervalType, nss); + auto htModeId = std::make_tuple(mcsMode->getBandwidth(), mcsMode->getMcsIndex(), guardIntervalType, nss, centerFrequencyMode, preambleFormat); auto mode = singleton.modeCache.find(htModeId); if (mode == singleton.modeCache.end()) { const Ieee80211OfdmSignalMode *legacySignal = nullptr; @@ -693,7 +725,7 @@ const Ieee80211VhtMode *Ieee80211VhtCompliantModes::getCompliantMode(const Ieee8 const Ieee80211VhtDataMode *dataMode = new Ieee80211VhtDataMode(mcsMode, mcsMode->getBandwidth(), guardIntervalType); const Ieee80211VhtPreambleMode *preambleMode = new Ieee80211VhtPreambleMode(htSignal, legacySignal, preambleFormat, dataMode->getNumberOfSpatialStreams()); const Ieee80211VhtMode *htMode = new Ieee80211VhtMode(name, preambleMode, dataMode, centerFrequencyMode); - singleton.modeCache.insert(std::pair, const Ieee80211VhtMode *>(htModeId, htMode)); + singleton.modeCache.insert(std::pair(htModeId, htMode)); return htMode; } return mode->second; @@ -1072,4 +1104,3 @@ const DI Ieee80211VhtmcsTable::vhtMcs9BW160MHzNss8([](){ return } /* namespace physicallayer */ } /* namespace inet */ - diff --git a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.h b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.h index aa4aa3639b0..d1a820e8eca 100644 --- a/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.h +++ b/src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.h @@ -96,6 +96,8 @@ class INET_API Ieee80211VhtSignalMode : public IIeee80211HeaderMode, public Ieee virtual b getLength() const override; virtual bps getNetBitrate() const override { return Ieee80211VhtModeBase::getNetBitrate(); } virtual bps getGrossBitrate() const override { return Ieee80211VhtModeBase::getGrossBitrate(); } + // IEEE Std 802.11-2024, Table 21-5: VHT-SIG uses the long-GI symbol + // interval independently of the data field's selected guard interval. virtual const simtime_t getSymbolInterval() const override { return Ieee80211HtTimingRelatedParametersBase::getSymbolInterval(); } virtual const Ieee80211OfdmModulation *getModulation() const override { return modulation; } virtual const Ieee80211VhtCode *getCode() const { return code; } @@ -148,6 +150,7 @@ class INET_API Ieee80211VhtPreambleMode : public IIeee80211PreambleMode, public virtual const simtime_t getSecondAndSubsequentHTLongTrainingFielDuration() const { return 4E-6; } // HT-LTFs, s = 2,3,..,n virtual unsigned int getNumberOfHtLongTrainings() const { return numberOfHTLongTrainings; } + virtual const simtime_t getDurationBeforeHeader() const; virtual const simtime_t getDuration() const override; virtual Ptr createPreamble() const override { return makeShared(); } @@ -237,13 +240,17 @@ class INET_API Ieee80211VhtDataMode : public IIeee80211DataMode, public Ieee8021 virtual bps getGrossBitrate() const override { return Ieee80211VhtModeBase::getGrossBitrate(); } virtual const Ieee80211Vhtmcs *getModulationAndCodingScheme() const { return modulationAndCodingScheme; } virtual const Ieee80211VhtCode *getCode() const { return modulationAndCodingScheme->getCode(); } - virtual const simtime_t getSymbolInterval() const override { return Ieee80211HtTimingRelatedParametersBase::getSymbolInterval(); } + virtual const simtime_t getGuardInterval() const override; + // IEEE Std 802.11-2024, Tables 21-5 and 21-8: the VHT Data symbol + // interval is TSYML for long GI and TSYMS for short GI. + virtual const simtime_t getSymbolInterval() const override { return getDFTPeriod() + getGuardInterval(); } virtual const Ieee80211OfdmModulation *getModulation() const override { return modulationAndCodingScheme->getModulation(); } }; class INET_API Ieee80211VhtMode : public Ieee80211ModeBase { public: + virtual int getVhtMcsIndex() const override { return dataMode->getMcsIndex(); } enum BandMode { BAND_2_4GHZ, BAND_5GHZ @@ -280,7 +287,10 @@ class INET_API Ieee80211VhtMode : public Ieee80211ModeBase virtual int getMpduMaxLength() const override { return 65535; } // in octets virtual BandMode getCenterFrequencyMode() const { return centerFrequencyMode; } - virtual const simtime_t getDuration(b dataBitLength) const override { return preambleMode->getDuration() + dataMode->getDuration(dataBitLength); } + virtual const simtime_t getDuration(b dataBitLength) const override { return preambleMode->getDuration() + getDataDuration(dataBitLength); } + virtual const simtime_t getPreambleDuration() const override { return preambleMode->getDurationBeforeHeader(); } + virtual const simtime_t getHeaderDuration() const override { return preambleMode->getDuration() - getPreambleDuration(); } + virtual const simtime_t getDataDuration(b dataBitLength) const override; }; // A specification of the high-throughput (HT) physical layer (PHY) @@ -679,7 +689,7 @@ class INET_API Ieee80211VhtCompliantModes protected: static OPP_THREAD_LOCAL const Ieee80211VhtCompliantModes singleton; - mutable std::map, const Ieee80211VhtMode *> modeCache; + mutable std::map, const Ieee80211VhtMode *> modeCache; public: Ieee80211VhtCompliantModes(); diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211ControlInfo.msg b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211ControlInfo.msg index 84d97310414..b819d14e951 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211ControlInfo.msg +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211ControlInfo.msg @@ -17,9 +17,9 @@ namespace inet::physicallayer; // class Ieee80211ConfigureRadioCommand extends ConfigureRadioCommand { - string opMode; // new default operation mode or "" if not set. - const Ieee80211ModeSet *modeSet; // new default mode set or nullptr if not set. - const IIeee80211Mode *mode; // new default transmission mode or nullptr if not set. + string opMode; // new default operation mode or "" if not set; ignored when modeSet is set. + const Ieee80211ModeSet *modeSet; // new default mode set or nullptr if not set; takes precedence over opMode. + const IIeee80211Mode *mode; // new default transmission mode or nullptr if not set; atomically validated against the resolved mode set. IIeee80211Band *band; // new default band or nullptr if not set. Ieee80211Channel *channel; // new default band and channel or nullptr if not set. int channelNumber = -1; // new default channel number in the range [0, numChannels] or -1 if not set. diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc index 000551f3cfa..ad9748e6094 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.cc @@ -8,8 +8,15 @@ #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" #include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211RadioChannelChangedDetails.h" +#include +#include + +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.h" + #include "inet/common/packet/chunk/BitCountChunk.h" #include "inet/common/ProtocolTag_m.h" +#include "inet/common/Simsignals.h" +#include "inet/physicallayer/wireless/common/signal/WirelessSignal.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211DsssOfdmMode.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ErpOfdmMode.h" @@ -44,6 +51,7 @@ void Ieee80211Radio::initialize(int stage) FlatRadioBase::initialize(stage); if (stage == INITSTAGE_LOCAL) { + modeSetCoordinator.reference(this, "modeSetCoordinatorModule", false); const char *fcsModeString = par("fcsMode"); fcsMode = parseFcsMode(fcsModeString, true); } @@ -68,13 +76,17 @@ void Ieee80211Radio::handleUpperCommand(cMessage *message) Ieee80211ConfigureRadioCommand *configureCommand = dynamic_cast(message->getControlInfo()); if (configureCommand != nullptr) { const char *opMode = configureCommand->getOpMode(); - if (*opMode) - setModeSet(Ieee80211ModeSet::getModeSet(opMode)); const Ieee80211ModeSet *modeSet = configureCommand->getModeSet(); - if (modeSet != nullptr) - setModeSet(modeSet); + // NOTE: When both modeSet and opMode are present, modeSet takes precedence + // and opMode is silently ignored. This differs from the previous behavior + // where both were applied sequentially (with modeSet as final state). + const Ieee80211ModeSet *newModeSet = modeSet != nullptr ? modeSet : (*opMode ? Ieee80211ModeSet::getModeSet(opMode) : nullptr); const IIeee80211Mode *mode = configureCommand->getMode(); - if (mode != nullptr) + if (newModeSet != nullptr && mode != nullptr) + setModeSetAndMode(newModeSet, mode); + else if (newModeSet != nullptr) + setModeSet(newModeSet); + else if (mode != nullptr) setMode(mode); const IIeee80211Band *band = configureCommand->getBand(); if (band != nullptr) @@ -92,13 +104,72 @@ void Ieee80211Radio::handleUpperCommand(cMessage *message) void Ieee80211Radio::setModeSet(const Ieee80211ModeSet *modeSet) { - Ieee80211Transmitter *ieee80211Transmitter = const_cast(check_and_cast(transmitter)); - Ieee80211Receiver *ieee80211Receiver = const_cast(check_and_cast(receiver)); - ieee80211Transmitter->setModeSet(modeSet); - ieee80211Receiver->setModeSet(modeSet); - EV << "Changing radio mode set to " << modeSet << endl; - receptionTimer = nullptr; + Enter_Method("setModeSet"); + changeModeSet(modeSet, nullptr, false); +} + +void Ieee80211Radio::setModeSetAndMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) +{ + Enter_Method("setModeSetAndMode"); + changeModeSet(modeSet, mode, true); +} + +void Ieee80211Radio::changeModeSet(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode, bool explicitMode) +{ + if (changingModeSet) + throw cRuntimeError("Reentrant radio mode-set change"); + if (modeSet != nullptr && mode != nullptr && !modeSet->containsMode(mode)) + throw cRuntimeError("Invalid mode"); + auto transmitter = const_cast(check_and_cast(this->transmitter)); + auto receiver = const_cast(check_and_cast(this->receiver)); + // Reject incompatible catalog-only requests before either transition guard is set. + // Keep the setter dispatch below; failures after PHY mutation remain fatal. + if (!explicitMode) + transmitter->computeModeForModeSet(modeSet); + if (modeSetCoordinator != nullptr) + modeSetCoordinator->beginModeSetChange(modeSet); + changingModeSet = true; + if (explicitMode) + transmitter->setModeSetAndMode(modeSet, mode); + else + transmitter->setModeSet(modeSet); + receiver->setModeSet(modeSet); + abortIncompatibleReception(); + if (modeSetCoordinator != nullptr) + modeSetCoordinator->completeModeSetChange(modeSet); + else if (modeSet != nullptr) + emit(modesetChangedSignal, const_cast(modeSet)); emit(listeningChangedSignal, 0); + changingModeSet = false; + EV << "Changing radio mode set to " << modeSet << " and mode to " << transmitter->getMode() << endl; +} + +const Ieee80211Channel *Ieee80211Radio::getChannel() const +{ + return check_and_cast(transmitter)->getChannel(); +} + +void Ieee80211Radio::abortIncompatibleReception() +{ + if (receptionTimer == nullptr) + return; + auto signal = check_and_cast(receptionTimer->getControlInfo()); + auto reception = signal->getReception(); + // Use the new receiver configuration, not the medium's cached listening or + // the local transmit mode. Possibility does not draw another error decision. + std::unique_ptr listening(receiver->createListening(this, + reception->getStartTime(), reception->getEndTime(), + reception->getStartPosition(), reception->getEndPosition())); + auto ieee80211Receiver = check_and_cast(receiver); + if (ieee80211Receiver->getModeSet() == nullptr || + !receiver->computeIsReceptionPossible(listening.get(), reception, (IRadioSignal::SignalPart)receptionTimer->getKind())) + abortReception(receptionTimer); +} + +bool Ieee80211Radio::isHtChannelWidthSupported(Hz channelWidth) const +{ + return check_and_cast(transmitter)->isHtChannelWidthSupported(channelWidth) && + check_and_cast(receiver)->isHtChannelWidthSupported(channelWidth); } void Ieee80211Radio::setMode(const IIeee80211Mode *mode) @@ -106,7 +177,6 @@ void Ieee80211Radio::setMode(const IIeee80211Mode *mode) Ieee80211Transmitter *ieee80211Transmitter = const_cast(check_and_cast(transmitter)); ieee80211Transmitter->setMode(mode); EV << "Changing radio mode to " << mode << endl; - receptionTimer = nullptr; emit(listeningChangedSignal, 0); } @@ -117,7 +187,7 @@ void Ieee80211Radio::setBand(const IIeee80211Band *band) ieee80211Transmitter->setBand(band); ieee80211Receiver->setBand(band); EV << "Changing radio band to " << band << endl; - receptionTimer = nullptr; + abortIncompatibleReception(); const auto *channel = ieee80211Transmitter->getChannel(); if (channel != nullptr) { Ieee80211RadioChannelChangedDetails details(channel->getBand()); @@ -135,7 +205,7 @@ void Ieee80211Radio::setChannel(const Ieee80211Channel *channel) ieee80211Transmitter->setChannel(channel); ieee80211Receiver->setChannel(new Ieee80211Channel(channel->getBand(), channel->getChannelNumber())); EV << "Changing radio channel to " << channel->getChannelNumber() << endl; - receptionTimer = nullptr; + abortIncompatibleReception(); Ieee80211RadioChannelChangedDetails details(channel->getBand()); emit(radioChannelChangedSignal, channel->getChannelNumber(), &details); emit(listeningChangedSignal, 0); @@ -148,7 +218,7 @@ void Ieee80211Radio::setChannelNumber(int newChannelNumber) ieee80211Transmitter->setChannelNumber(newChannelNumber); ieee80211Receiver->setChannelNumber(newChannelNumber); EV << "Changing radio channel to " << newChannelNumber << ".\n"; - receptionTimer = nullptr; + abortIncompatibleReception(); Ieee80211RadioChannelChangedDetails details(ieee80211Transmitter->getChannel()->getBand()); emit(radioChannelChangedSignal, newChannelNumber, &details); emit(listeningChangedSignal, 0); diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h index 087703e87f5..3d526363aea 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h @@ -9,6 +9,9 @@ #define __INET_IEEE80211RADIO_H #include "inet/physicallayer/wireless/common/base/packetlevel/FlatRadioBase.h" +#include "inet/common/ModuleRefByPar.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211Radio.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Band.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Channel.h" #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" @@ -18,7 +21,7 @@ namespace inet { namespace physicallayer { -class INET_API Ieee80211Radio : public FlatRadioBase +class INET_API Ieee80211Radio : public FlatRadioBase, public IIeee80211Radio { public: /** @@ -32,11 +35,16 @@ class INET_API Ieee80211Radio : public FlatRadioBase static const Ptr peekIeee80211PhyHeaderAtFront(const Packet *packet, b length = b(-1), int flags = 0); protected: + ModuleRefByPar modeSetCoordinator; + bool changingModeSet = false; FcsMode fcsMode = FCS_MODE_UNDEFINED; protected: virtual void initialize(int stage) override; + void changeModeSet(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode, bool explicitMode); + void abortIncompatibleReception(); + virtual void handleUpperCommand(cMessage *message) override; virtual void insertFcs(const Ptr& phyHeader) const; @@ -48,7 +56,13 @@ class INET_API Ieee80211Radio : public FlatRadioBase public: Ieee80211Radio(); + // Update behavioral consumers before publishing the new mode set. + // Failures are fatal simulation errors; these setters do not roll back. + // Behavioral consumers implement IIeee80211ModeSetListener. + virtual const Ieee80211Channel *getChannel() const override; + virtual bool isHtChannelWidthSupported(Hz channelWidth) const override; virtual void setModeSet(const Ieee80211ModeSet *modeSet); + virtual void setModeSetAndMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode); virtual void setMode(const IIeee80211Mode *mode); virtual void setBand(const IIeee80211Band *band); virtual void setChannel(const Ieee80211Channel *channel); diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.ned b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.ned index 86b85675aa6..56e5e2c1a9a 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.ned +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.ned @@ -8,6 +8,7 @@ package inet.physicallayer.wireless.ieee80211.packetlevel; import inet.physicallayer.wireless.common.base.packetlevel.FlatRadioBase; +import inet.physicallayer.wireless.ieee80211.contract.packetlevel.IIeee80211Radio; // // This radio model is part of the IEEE 802.11 physical layer model. It supports @@ -22,9 +23,11 @@ import inet.physicallayer.wireless.common.base.packetlevel.FlatRadioBase; // @see ~Ieee80211ScalarRadio, ~Ieee80211DimensionalRadio. // //# TODO check this Table 18-14—Receiver performance requirements -module Ieee80211Radio extends FlatRadioBase +module Ieee80211Radio extends FlatRadioBase like IIeee80211Radio { parameters: + string modeSetCoordinatorModule = default(""); // Optional interface transaction owner; empty for standalone PHY use + @signal[modesetChanged](type=inet::physicallayer::Ieee80211ModeSet); // Standalone radio only string opMode @enum("a", "b", "g(erp)", "g(mixed)", "n(mixed-2.4Ghz)", "p", "ac") = default("g(mixed)"); // Operation mode string bandName @enum("2.4 GHz", "5 GHz", "5 GHz (20 MHz)", "5 GHz (40 MHz)", "5 GHz (80 MHz)", "5 GHz (160 MHz)", "5.9 GHz") = default("2.4 GHz"); // Band name int channelNumber = default(0); // Initial channel number within the band (TODO this is offset by 1) diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.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..f195387a582 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; @@ -37,11 +38,13 @@ class INET_API Ieee80211Receiver : public FlatReceiverBase virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override; + const Ieee80211ModeSet *getModeSet() const { return modeSet; } virtual void setModeSet(const Ieee80211ModeSet *modeSet); virtual void setBand(const IIeee80211Band *band); virtual void setChannel(const Ieee80211Channel *channel); 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.cc b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc index 6c76381a790..bcbe608b091 100644 --- a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc +++ b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc @@ -72,19 +72,42 @@ const Ieee80211Channel *Ieee80211Transmitter::computeTransmissionChannel(const P return transmissionChannel; } +const IIeee80211Mode *Ieee80211Transmitter::computeModeForModeSet(const Ieee80211ModeSet *modeSet) const +{ + if (this->modeSet == modeSet) + return mode; + if (modeSet == nullptr) + return nullptr; + if (mode != nullptr && !modeSet->containsMode(mode)) { + auto newMode = modeSet->findCompatibleMode(mode); + if (newMode == nullptr) + throw cRuntimeError("Cannot map current mode to operation mode '%s' without changing bitrate, bandwidth, spatial streams, or guard interval", modeSet->getName()); + return newMode; + } + return mode; +} + void Ieee80211Transmitter::setModeSet(const Ieee80211ModeSet *modeSet) { if (this->modeSet != modeSet) { + auto newMode = computeModeForModeSet(modeSet); this->modeSet = modeSet; - if (mode != nullptr) - mode = modeSet != nullptr ? modeSet->getMode(mode->getDataMode()->getNetBitrate()) : nullptr; + mode = newMode; } } +void Ieee80211Transmitter::setModeSetAndMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode) +{ + if (modeSet != nullptr && mode != nullptr && !modeSet->containsMode(mode)) + throw cRuntimeError("Invalid mode"); + this->modeSet = modeSet; + this->mode = mode; +} + void Ieee80211Transmitter::setMode(const IIeee80211Mode *mode) { if (this->mode != mode) { - if (modeSet->findMode(mode->getDataMode()->getNetBitrate(), mode->getDataMode()->getBandwidth()) == nullptr) + if (modeSet != nullptr && mode != nullptr && !modeSet->containsMode(mode)) throw cRuntimeError("Invalid mode"); this->mode = mode; } @@ -151,9 +174,9 @@ const ITransmission *Ieee80211Transmitter::createTransmission(const IRadio *tran const Coord& endPosition = mobility->getCurrentPosition(); const Quaternion& startOrientation = mobility->getCurrentAngularPosition(); const Quaternion& endOrientation = mobility->getCurrentAngularPosition(); - const simtime_t preambleDuration = transmissionMode->getPreambleMode()->getDuration(); - const simtime_t headerDuration = transmissionMode->getHeaderMode()->getDuration(); - const simtime_t dataDuration = duration - headerDuration - preambleDuration; + const simtime_t preambleDuration = transmissionMode->getPreambleDuration(); + const simtime_t headerDuration = transmissionMode->getHeaderDuration(); + const simtime_t dataDuration = transmissionMode->getDataDuration(B(phyHeader->getLengthField())); auto analogModel = getAnalogModel()->createAnalogModel(preambleDuration, headerDuration, dataDuration, centerFrequency, transmissionBandwidth, transmissionPower); return new Ieee80211Transmission(transmitter, packet, startTime, endTime, preambleDuration, headerDuration, dataDuration, startPosition, endPosition, startOrientation, endOrientation, nullptr, nullptr, nullptr, nullptr, analogModel, transmissionMode, transmissionChannel); } @@ -161,4 +184,3 @@ const ITransmission *Ieee80211Transmitter::createTransmission(const IRadio *tran } // namespace physicallayer } // namespace inet - diff --git a/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h b/src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h index 05b6f29194c..ce0e61090e5 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; @@ -38,14 +39,22 @@ class INET_API Ieee80211Transmitter : public FlatTransmitterBase virtual const IIeee80211Mode *computeTransmissionMode(const Packet *packet) const; virtual const Ieee80211Channel *computeTransmissionChannel(const Packet *packet) const; + // Re-selects the current mode only when bitrate, bandwidth, NSS, and GI + // remain compatible. Use setModeSetAndMode for an explicit transition. + const Ieee80211ModeSet *getModeSet() const { return modeSet; } + const IIeee80211Mode *getMode() const { return mode; } + // Resolves a catalog-only change without mutation; throws if no exact tuple exists. + virtual const IIeee80211Mode *computeModeForModeSet(const Ieee80211ModeSet *modeSet) const; virtual void setModeSet(const Ieee80211ModeSet *modeSet); + // Applies a mode set and an explicitly selected mode as one validated update. + virtual void setModeSetAndMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode); virtual void setMode(const IIeee80211Mode *mode); virtual void setBand(const IIeee80211Band *band); virtual void setChannel(const Ieee80211Channel *channel); 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/fingerprint/examples.csv b/tests/fingerprint/examples.csv index 64abe9fcac0..03ffa40c1b7 100644 --- a/tests/fingerprint/examples.csv +++ b/tests/fingerprint/examples.csv @@ -377,8 +377,8 @@ /examples/manetrouting/gpsr/, -f omnetpp.ini -c DynamicIPv6 -r 0, 20s, e618-be99/tplx;edb3-6679/~tNl;2653-aa28/tyf, PASS, wireless adhoc /examples/manetrouting/gpsr/, -f omnetpp.ini -c DynamicGeneric -r 0, 20s, 732c-c576/tplx;03c2-f3ec/~tNl;fb81-3dac/tyf, PASS, wireless adhoc -/examples/manetrouting/multiradio/, -f omnetpp.ini -c MultiRadio -r 0, 20s, ec17-5cc2/tplx;55f5-0894/~tNl, PASS, wireless adhoc Ipv4 -/examples/manetrouting/multiradio/, -f omnetpp.ini -c SingleRadio -r 0, 20s, 85a0-51b8/tplx;c07a-44e1/~tNl;3aa0-49ed/tyf, PASS, wireless adhoc Ipv4 +/examples/manetrouting/multiradio/, -f omnetpp.ini -c MultiRadio -r 0, 20s, a83a-1ece/tplx;f1d9-19c4/~tNl, PASS, wireless adhoc Ipv4 +/examples/manetrouting/multiradio/, -f omnetpp.ini -c SingleRadio -r 0, 20s, c2bd-7372/tplx;fc98-08d6/~tNl;3aa0-49ed/tyf, PASS, wireless adhoc Ipv4 /examples/ipv6/mipv6/, -f omnetpp.ini -c Handover -r 0, 70s, dca6-3751/tplx;a44c-17bf/~tNl;96ff-ed7c/~tND;44ef-1a45/tyf, PASS, wireless EthernetMac /examples/ipv6/mipv6/, -f omnetpp.ini -c RouteOptimizationTwoCNs -r 0, 60s, 1560-028f/tplx;e6db-28c0/~tNl;805b-8a25/~tND;ed3e-17fa/tyf, PASS, wireless EthernetMac @@ -596,7 +596,7 @@ /examples/wireless/lan80211/, -f omnetpp.ini -c Ping1 -r 0, 25s, 3785-bc39/tplx;18be-f36c/~tNl;c76d-5483/~tND, PASS, wireless Ipv4 # /examples/wireless/lan80211/, -f omnetpp.ini -c Ping2 -r 0, 100s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND;0000-0000/tyf, ERROR, wireless # [Config Ping2] # __interactive__ -/examples/wireless/lan80211ac/, -f omnetpp.ini -c Ping1 -r 0, 100s, bb14-f903/tplx;538b-6566/~tNl, PASS, Ipv4 +/examples/wireless/lan80211ac/, -f omnetpp.ini -c Ping1 -r 0, 100s, e382-0a32/tplx;45db-b12b/~tNl, PASS, Ipv4 # /examples/wireless/lan80211ac/, -f omnetpp.ini -c Ping2 -r 0, ---100s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND;0000-0000/tyf, PASS, # [Config Ping2] # __interactive__ /examples/wireless/layered80211/, -f omnetpp.ini -c LayeredCompliant80211Ping -r 0, 100s, 88dd-4b30/tplx;6264-75f5/~tNl;66de-a722/~tND;7a22-c289/tyf, PASS, wireless Ipv4 diff --git a/tests/fingerprint/showcases.csv b/tests/fingerprint/showcases.csv index 330453edd7b..94747bc82f5 100644 --- a/tests/fingerprint/showcases.csv +++ b/tests/fingerprint/showcases.csv @@ -6,7 +6,7 @@ # /showcases/general/dynamic/, -f omnetpp.ini -c General -r 0, 100s, aa91-2c95/tplx;0000-0000/~tNl;0000-0000/~tND, PASS, -/showcases/general/pcaprecording/, -f omnetpp.ini -c PcapRecording -r 0, 10s, 40e0-9ac8/tplx;96d0-d13f/~tNl;4893-f8fa/~tND;588a-7dd9/tyf, PASS, wireless EthernetMac Ipv4 +/showcases/general/pcaprecording/, -f omnetpp.ini -c PcapRecording -r 0, 10s, a1b9-ff80/tplx;cb8f-443a/~tNl;cb45-7a00/~tND;588a-7dd9/tyf, PASS, wireless EthernetMac Ipv4 /showcases/measurement/datarate/, -f omnetpp.ini -c General -r 0, 1s, 7af9-93b4/tplx;2d48-cf54/~tNl;ce00-3055/~tND;c848-3d2a/tyf, PASS, Ipv4 /showcases/measurement/endtoenddelay/, -f omnetpp.ini -c General -r 0, 1s, a6ec-9c0d/tplx;5632-17e6/~tNl;3b24-2f2d/~tND;1479-9b89/tyf, PASS, Ipv4 @@ -127,8 +127,8 @@ /showcases/visualizer/canvas/ieee80211/, -f omnetpp.ini -c VisualizingHandover -r 0, 250s, 7fb8-49dc/tplx;2f60-762d/~tNl;0437-f151/~tND;07bb-3973/tyf, PASS, wireless /showcases/visualizer/canvas/ieee80211/, -f omnetpp.ini -c SignalLevels -r 0, 100s, 51ec-d359/tplx;cec5-d464/~tNl;a0c6-b121/~tND;c099-e5c5/tyf, PASS, Ipv4 -/showcases/visualizer/canvas/submoduleinfo/, -f omnetpp.ini -c PacketCounts -r 0, 5s, 271c-6821/tplx;3c0c-db82/~tNl;8315-9723/~tND;b145-6576/tyf, PASS, wireless Ipv4 # VisualizingSubmoduleInformation extended -/showcases/visualizer/canvas/submoduleinfo/, -f omnetpp.ini -c MACStates -r 0, 5s, 271c-6821/tplx;3c0c-db82/~tNl;8315-9723/~tND;4677-fc97/tyf, PASS, wireless Ipv4 +/showcases/visualizer/canvas/submoduleinfo/, -f omnetpp.ini -c PacketCounts -r 0, 5s, e700-27fb/tplx;f16c-28a3/~tNl;57f0-e9ab/~tND;b145-6576/tyf, PASS, wireless Ipv4 # VisualizingSubmoduleInformation extended +/showcases/visualizer/canvas/submoduleinfo/, -f omnetpp.ini -c MACStates -r 0, 5s, e700-27fb/tplx;f16c-28a3/~tNl;57f0-e9ab/~tND;4677-fc97/tyf, PASS, wireless Ipv4 /showcases/visualizer/canvas/instrumentfigures/, -f omnetpp.ini -c General -r 0, 3s, 6622-adb9/tplx;28ca-9f8c/~tNl;85f0-a9c3/~tND;4266-0d42/tyf, PASS, wireless Ipv4 @@ -177,7 +177,7 @@ /showcases/visualizer/canvas/spectrum/, -f omnetpp.ini -c PowerDensityMap -r 0, 1s, 2622-bc42/tplx;6db5-38b6/~tNl;df7a-beae/~tND;88d9-8b3f/tyf, PASS, wireless Ipv4 /showcases/visualizer/canvas/statistic/, -f omnetpp.ini -c PingRtt -r 0, 500s, 1479-6398/tplx;9b09-5004/~tNl;87e2-d13d/~tND;71d6-de16/tyf, PASS, wireless Ipv4 -/showcases/visualizer/canvas/statistic/, -f omnetpp.ini -c PacketErrorRate -r 0, 25s, eb0f-ff32/tplx;025d-a600/~tNl;0a60-f71c/~tND;0b84-e1e1/tyf, PASS, wireless Ipv4 +/showcases/visualizer/canvas/statistic/, -f omnetpp.ini -c PacketErrorRate -r 0, 25s, 78cf-be58/tplx;a1d6-4bb6/~tNl;d600-2b73/~tND;0b84-e1e1/tyf, PASS, wireless Ipv4 /showcases/visualizer/canvas/styling/, -f omnetpp.ini -c Line -r 0, 25s, cb1d-a154/tplx;a565-1a98/~tNl;ee1b-5f7a/~tND;7b44-2fb8/tyf, PASS, wireless Ipv4 /showcases/visualizer/canvas/styling/, -f omnetpp.ini -c Font -r 0, 25s, cb1d-a154/tplx;a565-1a98/~tNl;ee1b-5f7a/~tND;5e90-6a92/tyf, PASS, wireless Ipv4 @@ -196,7 +196,7 @@ /showcases/wireless/aggregation/, -f omnetpp.ini -c VoicePriorityAggregation -r 0, 1s, 1dbe-6672/tplx;a253-dbe3/~tNl;3bb2-9797/~tND;bd88-5ba6/tyf, PASS, wireless Ipv4 /showcases/wireless/analogmodel/, -f omnetpp.ini -c Routing -r 0, 5s, 56b0-3510/tplx;baeb-0d2c/~tNl;3d4b-65fc/~tND;00a2-e4ee/tyf, PASS, wireless Ipv4 -/showcases/wireless/analogmodel/, -f omnetpp.ini -c Distance -r 0, 2.5s, 1e75-270e/tplx;6d7a-d84c/~tNl;7b7c-2bfb/~tND;5575-fd8f/tyf, PASS, wireless Ipv4 +/showcases/wireless/analogmodel/, -f omnetpp.ini -c Distance -r 0, 2.5s, 98d8-d5d1/tplx;ebe5-7cf5/~tNl;9e70-fbb5/~tND;5575-fd8f/tyf, PASS, wireless Ipv4 /showcases/wireless/analogmodel/, -f omnetpp.ini -c Noise -r 0, 0.1s, dd08-a63c/tplx;e167-9c84/~tNl;d680-b8ad/~tND;0d1f-df73/tyf, PASS, wireless Ipv4 /showcases/wireless/blockack/, -f omnetpp.ini -c NoFragmentation -r 0, 1s, 5b0b-f055/tplx;8511-cc76/~tNl;3402-f804/~tND, PASS, wireless Ipv4 @@ -311,10 +311,10 @@ /showcases/wireless/pathloss/, -f omnetpp.ini -c General -r 0, 100s, 171a-e6eb/tplx;3466-822d/~tNl;5909-6e87/~tND;929d-03d5/tyf, PASS, wireless Ipv4 -/showcases/wireless/power/, -f omnetpp.ini -c General -r 0, 100s, 498f-b665/tplx;6f50-5caf/~tNl;1403-6fb2/~tND, PASS, wireless Ipv4 +/showcases/wireless/power/, -f omnetpp.ini -c General -r 0, 100s, 21cb-ec85/tplx;30b0-ee3e/~tNl;ae94-6216/~tND, PASS, wireless Ipv4 -/showcases/wireless/qos/, -f omnetpp.ini -c NonQos -r 0, 10s, 26be-ff05/tplx;9c39-00a3/~tNl;03a9-2f02/~tND;093e-1ca4/tyf, PASS, wireless Ipv4 -/showcases/wireless/qos/, -f omnetpp.ini -c Qos -r 0, 10s, 8d7c-5091/tplx;84a6-b893/~tNl;cb18-5b4d/~tND;bda9-15d1/tyf, PASS, wireless Ipv4 +/showcases/wireless/qos/, -f omnetpp.ini -c NonQos -r 0, 10s, 7a6b-0096/tplx;cd89-6ad5/~tNl;3d32-d9e4/~tND;093e-1ca4/tyf, PASS, wireless Ipv4 +/showcases/wireless/qos/, -f omnetpp.ini -c Qos -r 0, 10s, 904d-389e/tplx;21aa-5c22/~tNl;6b58-22a3/~tND;bda9-15d1/tyf, PASS, wireless Ipv4 /showcases/wireless/ratecontrol/, -f omnetpp.ini -c NoRateControl -r 0, 14s, 7ee9-503a/tplx;0816-e58f/~tNl;ec30-e32d/~tND;dad3-7f89/tyf, PASS, wireless Ipv4 /showcases/wireless/ratecontrol/, -f omnetpp.ini -c AarfRateControl -r 0, 12s, a7bc-05bb/tplx;9de0-4dd3/~tNl;de90-6575/~tND;7539-d32d/tyf, PASS, wireless Ipv4 diff --git a/tests/fingerprint/store.json b/tests/fingerprint/store.json index 321b6b5c23a..072c6784b34 100644 --- a/tests/fingerprint/store.json +++ b/tests/fingerprint/store.json @@ -16771,7 +16771,7 @@ "sim_time_limit": "20s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "ec17-5cc2", + "fingerprint": "a83a-1ece", "timestamp": 1681992800.3349502, "itervars": "$repetition==0" }, @@ -16783,7 +16783,7 @@ "sim_time_limit": "20s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "55f5-0894", + "fingerprint": "f1d9-19c4", "timestamp": 1681992800.3352785, "itervars": "$repetition==0" }, @@ -16795,7 +16795,7 @@ "sim_time_limit": "20s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "85a0-51b8", + "fingerprint": "c2bd-7372", "timestamp": 1681992800.3365238, "itervars": "$repetition==0" }, @@ -16819,7 +16819,7 @@ "sim_time_limit": "20s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "c07a-44e1", + "fingerprint": "fc98-08d6", "timestamp": 1681992800.3368874, "itervars": "$repetition==0" }, @@ -35623,7 +35623,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "bb14-f903", + "fingerprint": "e382-0a32", "timestamp": 1681992800.58698, "itervars": "$repetition==0" }, @@ -35635,7 +35635,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "1108-9845", + "fingerprint": "45db-b12b", "timestamp": 1681992800.5873275, "itervars": "$repetition==0" }, @@ -38179,7 +38179,7 @@ "sim_time_limit": "10s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "40e0-9ac8", + "fingerprint": "a1b9-ff80", "timestamp": 1681992799.492428, "itervars": "$repetition==0" }, @@ -38203,7 +38203,7 @@ "sim_time_limit": "10s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "87f0-5b5f", + "fingerprint": "cb45-7a00", "timestamp": 1681992799.4924438, "itervars": "$repetition==0" }, @@ -38215,7 +38215,7 @@ "sim_time_limit": "10s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "96d0-d13f", + "fingerprint": "cb8f-443a", "timestamp": 1681992799.4924366, "itervars": "$repetition==0" }, @@ -43147,7 +43147,7 @@ "sim_time_limit": "25s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "eb0f-ff32", + "fingerprint": "78cf-be58", "timestamp": 1681992799.6480415, "itervars": "$repetition==0" }, @@ -43171,7 +43171,7 @@ "sim_time_limit": "25s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "8a0d-832f", + "fingerprint": "d600-2b73", "timestamp": 1681992799.6481428, "itervars": "$repetition==0" }, @@ -43183,7 +43183,7 @@ "sim_time_limit": "25s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "025d-a600", + "fingerprint": "a1d6-4bb6", "timestamp": 1681992799.6480927, "itervars": "$repetition==0" }, @@ -43435,7 +43435,7 @@ "sim_time_limit": "5s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "271c-6821", + "fingerprint": "e700-27fb", "timestamp": 1681992799.630568, "itervars": "$repetition==0" }, @@ -43459,7 +43459,7 @@ "sim_time_limit": "5s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "32ff-eec4", + "fingerprint": "57f0-e9ab", "timestamp": 1681992799.6306372, "itervars": "$repetition==0" }, @@ -43471,7 +43471,7 @@ "sim_time_limit": "5s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "3c0c-db82", + "fingerprint": "f16c-28a3", "timestamp": 1681992799.6306028, "itervars": "$repetition==0" }, @@ -43483,7 +43483,7 @@ "sim_time_limit": "5s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "271c-6821", + "fingerprint": "e700-27fb", "timestamp": 1681992799.6302016, "itervars": "$repetition==0" }, @@ -43507,7 +43507,7 @@ "sim_time_limit": "5s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "32ff-eec4", + "fingerprint": "57f0-e9ab", "timestamp": 1681992799.6302688, "itervars": "$repetition==0" }, @@ -43519,7 +43519,7 @@ "sim_time_limit": "5s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "3c0c-db82", + "fingerprint": "f16c-28a3", "timestamp": 1681992799.6302357, "itervars": "$repetition==0" }, @@ -44251,7 +44251,7 @@ "sim_time_limit": "2.5s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "1e75-270e", + "fingerprint": "98d8-d5d1", "timestamp": 1681992799.6558988, "itervars": "$repetition==0" }, @@ -44275,7 +44275,7 @@ "sim_time_limit": "2.5s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "9ccb-a57b", + "fingerprint": "9e70-fbb5", "timestamp": 1681992799.6560135, "itervars": "$repetition==0" }, @@ -44287,7 +44287,7 @@ "sim_time_limit": "2.5s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "6d7a-d84c", + "fingerprint": "ebe5-7cf5", "timestamp": 1681992799.6559565, "itervars": "$repetition==0" }, @@ -46267,7 +46267,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "498f-b665", + "fingerprint": "21cb-ec85", "timestamp": 1681992799.6967368, "itervars": "$repetition==0" }, @@ -46279,7 +46279,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "fb79-0fa3", + "fingerprint": "ae94-6216", "timestamp": 1681992799.6968684, "itervars": "$repetition==0" }, @@ -46291,7 +46291,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "6f50-5caf", + "fingerprint": "30b0-ee3e", "timestamp": 1681992799.696803, "itervars": "$repetition==0" }, @@ -46303,7 +46303,7 @@ "sim_time_limit": "10s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "37fd-5401", + "fingerprint": "7a6b-0096", "timestamp": 1681992799.6971657, "itervars": "$repetition==0" }, @@ -46327,7 +46327,7 @@ "sim_time_limit": "10s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "eb84-1083", + "fingerprint": "3d32-d9e4", "timestamp": 1681992799.697302, "itervars": "$repetition==0" }, @@ -46339,7 +46339,7 @@ "sim_time_limit": "10s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "1061-2f07", + "fingerprint": "cd89-6ad5", "timestamp": 1681992799.6972342, "itervars": "$repetition==0" }, @@ -46351,7 +46351,7 @@ "sim_time_limit": "10s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "1a49-72b3", + "fingerprint": "904d-389e", "timestamp": 1681992799.6976697, "itervars": "$repetition==0" }, @@ -46375,7 +46375,7 @@ "sim_time_limit": "10s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "83cc-bdf2", + "fingerprint": "6b58-22a3", "timestamp": 1681992799.697813, "itervars": "$repetition==0" }, @@ -46387,7 +46387,7 @@ "sim_time_limit": "10s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "9760-e96a", + "fingerprint": "21aa-5c22", "timestamp": 1681992799.6977413, "itervars": "$repetition==0" }, @@ -50035,7 +50035,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "71f5-b341", + "fingerprint": "e443-0781", "timestamp": 1681992799.813049, "itervars": "$repetition==0" }, @@ -50059,7 +50059,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "7612-50df", + "fingerprint": "02d8-d7df", "timestamp": 1681992799.8132813, "itervars": "$repetition==0" }, @@ -50071,7 +50071,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "4a61-20ba", + "fingerprint": "cc4d-d519", "timestamp": 1681992799.8131676, "itervars": "$repetition==0" }, @@ -50803,7 +50803,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "tplx", - "fingerprint": "7f72-228e", + "fingerprint": "3e25-000b", "timestamp": 1681992799.808365, "itervars": "$repetition==0" }, @@ -50827,7 +50827,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "~tND", - "fingerprint": "c423-d956", + "fingerprint": "82f2-2fe2", "timestamp": 1681992799.8087497, "itervars": "$repetition==0" }, @@ -50839,7 +50839,7 @@ "sim_time_limit": "100s", "test_result": "PASS", "ingredients": "~tNl", - "fingerprint": "1374-346a", + "fingerprint": "ca9f-eb38", "timestamp": 1681992799.8085785, "itervars": "$repetition==0" }, @@ -53747,4 +53747,4 @@ "timestamp": 1681992799.829868, "itervars": "$repetition==0" } -] \ No newline at end of file +] diff --git a/tests/fingerprint/tutorials.csv b/tests/fingerprint/tutorials.csv index 609c4b97949..9e0cfea3ea4 100644 --- a/tests/fingerprint/tutorials.csv +++ b/tests/fingerprint/tutorials.csv @@ -12,10 +12,10 @@ /tutorials/configurator/, -f omnetpp.ini -c Step7C -r 0, 500s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND;0000-0000/tyf, PASS, /tutorials/configurator/, -f omnetpp.ini -c Step8A -r 0, 100s, 0b7e-7adc/tplx;12a6-f004/~tNl;8b42-bf7b/~tND;d3f3-ed4c/tyf, PASS, wireless EthernetMac Ipv4 # Step8A extended /tutorials/configurator/, -f omnetpp.ini -c Step8B -r 0, 100s, b1ac-8912/tplx;12a6-f004/~tNl;96e2-030b/~tND;af16-1150/tyf, PASS, wireless EthernetMac Ipv4 -/tutorials/configurator/, -f omnetpp.ini -c Step9 -r 0, 100s, 70cf-424a/tplx;d39c-9f1f/~tNl;c4ee-f7ca/~tND;c38c-98ef/tyf, PASS, wireless EthernetMac Ipv4 +/tutorials/configurator/, -f omnetpp.ini -c Step9 -r 0, 100s, 3e25-000b/tplx;ca9f-eb38/~tNl;82f2-2fe2/~tND;c38c-98ef/tyf, PASS, wireless EthernetMac Ipv4 /tutorials/configurator/, -f omnetpp.ini -c Step10A -r 0, 100s, 4b6f-7cd9/tplx;0000-0000/~tNl;0000-0000/~tND;095d-75bd/tyf, PASS, wireless # Step10A extended /tutorials/configurator/, -f omnetpp.ini -c Step10B -r 0, 100s, 4b6f-7cd9/tplx;0000-0000/~tNl;0000-0000/~tND;4d60-fcf9/tyf, PASS, wireless # Step10B extended -/tutorials/configurator/, -f omnetpp.ini -c Step10C -r 0, 100s, 71f5-b341/tplx;4a61-20ba/~tNl;707b-bb40/~tND;f078-56fe/tyf, PASS, wireless Ipv4 +/tutorials/configurator/, -f omnetpp.ini -c Step10C -r 0, 100s, e443-0781/tplx;cc4d-d519/~tNl;02d8-d7df/~tND;f078-56fe/tyf, PASS, wireless Ipv4 /tutorials/configurator/, -f omnetpp.ini -c Step11A -r 0, 500s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND;0000-0000/tyf, PASS, /tutorials/configurator/, -f omnetpp.ini -c Step11B -r 0, 500s, 49cf-d241/tplx;725c-f075/~tNl;15a3-3f79/~tND;7082-ff49/tyf, PASS, EthernetMac Ipv4 /tutorials/configurator/, -f omnetpp.ini -c Step12 -r 0, 100s, e75f-2a09/tplx;d302-92c4/~tNl;b67b-3f1f/~tND;4b03-e5a1/tyf, PASS, wireless EthernetMac Ipv4 @@ -200,4 +200,3 @@ /tutorials/queueing/, -f omnetpp.ini -c RequestResponse -r 0, 100s, 7d97-5413/tplx;0000-0000/~tNl;0000-0000/~tND;1aeb-2f3a/tyf, PASS, queueing /tutorials/queueing/, -f omnetpp.ini -c Telnet -r 0, 100s, f176-070e/tplx;0000-0000/~tNl;0000-0000/~tND;43fd-14e6/tyf, PASS, queueing /tutorials/queueing/, -f omnetpp.ini -c ExampleNetwork -r 0, 10s, 3783-f5ed/tplx;0000-0000/~tNl;0000-0000/~tND;5a45-9aba/tyf, PASS, queueing - 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/Ieee80211AlternativeRadioStartup_1.test b/tests/module/Ieee80211AlternativeRadioStartup_1.test new file mode 100644 index 00000000000..278e6819616 --- /dev/null +++ b/tests/module/Ieee80211AlternativeRadioStartup_1.test @@ -0,0 +1,84 @@ +%description: +Ieee80211Interface initializes with the actual GenericRadio and Ieee80211OfdmRadio +replaceable types, neither of which declares modeSetCoordinatorModule. Compound +parameter patterns must not impose that parameter on those radio implementations. + +%file: AlternativeRadioStartup.cc +#include "inet/common/InitStages.h" +#include "inet/physicallayer/wireless/common/contract/packetlevel/IRadio.h" +using namespace inet; +class AlternativeRadioStartup : public cSimpleModule +{ + protected: + virtual void initialize() override { scheduleAt(SIMTIME_ZERO, new cMessage("check startup")); } + virtual void handleMessage(cMessage *message) override + { + delete message; + auto radio = getModuleByPath("^.host.wlan[0].radio"); + ASSERT(dynamic_cast(radio) != nullptr); + ASSERT(!radio->hasPar("modeSetCoordinatorModule")); + ASSERT(std::string(radio->getModuleType()->getName()) == par("radioType").stdstringValue()); + std::cout << "Alternative radio initialized: " << par("radioType").stdstringValue() << "\n"; + endSimulation(); + } +}; +Define_Module(AlternativeRadioStartup); + +%file: test.ned +import inet.node.inet.AdhocHost; +import inet.physicallayer.wireless.common.contract.packetlevel.IRadioMedium; +simple AlternativeRadioStartup +{ + parameters: + @class(::AlternativeRadioStartup); + string radioType; +} +network AlternativeRadioNetwork +{ + submodules: + radioMedium: <> like IRadioMedium; + host: AdhocHost; + test: AlternativeRadioStartup; +} + +%inifile: omnetpp.ini +[General] +network = AlternativeRadioNetwork +ned-path = .;../../../../src;../../lib +seed-set = 0 +sim-time-limit = 1ms +record-vector-results = false +record-scalar-results = false +*.test.radioType = ${radio="GenericRadio","Ieee80211OfdmRadio"} +*.host.wlan[*].radio.typename = ${radio} +*.radioMedium.typename = ${radio} == "GenericRadio" ? "UnitDiskRadioMedium" : "Ieee80211DimensionalRadioMedium" +*.host.mobility.initFromDisplayString = false +*.host.mobility.initialX = 10m +*.host.mobility.initialY = 10m +*.host.mobility.initialZ = 0m +*.host.wlan[*].typename = "Ieee80211Interface" +*.host.wlan[*].opMode = "g(mixed)" +*.host.wlan[*].bitrate = 24Mbps +*.host.wlan[*].radio.signalAnalogRepresentation = ${radio} == "GenericRadio" ? "unitDisk" : "dimensional" +*.host.wlan[*].radio.transmitter.bitrate = 2Mbps +*.host.wlan[*].radio.transmitter.preambleDuration = 0s +*.host.wlan[*].radio.transmitter.headerLength = 96b +*.host.wlan[*].radio.transmitter.analogModel.communicationRange = 100m +*.host.wlan[*].radio.transmitter.analogModel.interferenceRange = 0m +*.host.wlan[*].radio.transmitter.analogModel.detectionRange = 0m +*.host.wlan[*].radio.receiver.ignoreInterference = true +*.host.wlan[*].radio.centerFrequency = 2.4GHz +*.host.wlan[*].radio.bandwidth = 20MHz +*.host.wlan[*].radio.transmitter.power = 0.1mW +*.host.wlan[*].radio.receiver.sensitivity = -100dBm +*.host.wlan[*].radio.receiver.snirThreshold = 4dB +*.host.wlan[*].radio.receiver.energyDetection = -90dBm +*.host.wlan[*].radio.receiver.channelSpacing = 20MHz +**.levelOfDetail = "symbol" +**.isCompliant = true + +%contains: stdout +Alternative radio initialized: GenericRadio + +%contains: stdout +Alternative radio initialized: Ieee80211OfdmRadio diff --git a/tests/module/Ieee80211ConfigurationContracts_1.test b/tests/module/Ieee80211ConfigurationContracts_1.test new file mode 100644 index 00000000000..ae36857923f --- /dev/null +++ b/tests/module/Ieee80211ConfigurationContracts_1.test @@ -0,0 +1,201 @@ +%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 IIeee80211ModeSetProvider +{ + protected: + const Ieee80211ModeSet *catalog = nullptr; + virtual void initialize() override { catalog = Ieee80211ModeSet::getModeSet("g(mixed)"); } + public: + virtual const Ieee80211ModeSet *getConfiguredModeSet() const override { return catalog; } +}; +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.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 IIeee80211ModeSetProvider { 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/Ieee80211ContentionModeSet_1.test b/tests/module/Ieee80211ContentionModeSet_1.test new file mode 100644 index 00000000000..33f605972f5 --- /dev/null +++ b/tests/module/Ieee80211ContentionModeSet_1.test @@ -0,0 +1,210 @@ +%description: +Runtime radio mode-set changes refresh active DCF/EDCA contention, retaining +whole remaining slots without another random draw. Cover IFS, backoff, DEFER, +EIFS and unchanged timing, and observe the actual channel grant time. + +%file: ContentionModeSet.cc +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211OfdmMode.h" +#include "inet/linklayer/ieee80211/mac/contention/Contention.h" +#include "inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h" +#include "inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h" +#include "inet/linklayer/ieee80211/mac/contract/IChannelAccess.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" + +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +class BoundedCwMode : public Ieee80211OfdmMode +{ + int upper; + public: + BoundedCwMode(int upper) : Ieee80211OfdmMode(*check_and_cast(Ieee80211ModeSet::getModeSet("a")->getMode(Mbps(6)))), upper(upper) {} + virtual int getLegacyCwMin() const override { return 127; } + virtual int getLegacyCwMax() const override { return upper; } +}; + +class TimingProbeContention : public Contention +{ + public: + int slots() const { return backoffSlots; } + State state() const { return static_cast(fsm.getState()); } + simtime_t slot() const { return slotTime; } + simtime_t interframe() const { return ifs; } + simtime_t extendedInterframe() const { return eifs; } + simtime_t grantTime() const { return startTxEvent->getArrivalTime(); } +}; +Define_Module(TimingProbeContention); + +class ContentionModeSetTest : public cSimpleModule, public IChannelAccess::ICallback, public cListener +{ + TimingProbeContention *contention = nullptr; + IChannelAccess *access = nullptr; + cModule *accessModule = nullptr; + + int currentCw() { return par("qos").boolValue() ? check_and_cast(accessModule)->getCw() : check_and_cast(accessModule)->getCw(); } + void incrementCw() + { + if (par("qos").boolValue()) + check_and_cast(accessModule)->incrementCw(); + else + check_and_cast(accessModule)->incrementCw(); + } + Ieee80211Radio *radio = nullptr; + simtime_t expectedGrant; + simtime_t previousGrant; + int initialSlots = 0; + int draws = 0; + bool switched = false; + int modeNotifications = 0; + std::string phase; + + protected: + virtual void initialize() override { scheduleAt(SimTime(1, SIMTIME_US), new cMessage("start")); } + virtual void receiveSignal(cComponent *, simsignal_t, intval_t, cObject *) override { draws++; } + virtual void receiveSignal(cComponent *source, simsignal_t, cObject *value, cObject *) override + { + Enter_Method_Silent(); + ASSERT(source == radio->getParentModule()->getSubmodule("mac")); + auto target = check_and_cast(value); + ASSERT(contention->slot() == target->getSlotTime()); + ASSERT(draws == 1); + if (contention->state() == Contention::IFS_AND_BACKOFF) { + auto interval = phase == "eifs" ? contention->extendedInterframe() : contention->interframe(); + auto expected = phase == "unchanged" ? previousGrant : simTime() + interval + contention->slots() * target->getSlotTime(); + ASSERT(contention->grantTime() == expected); + } + modeNotifications++; + } + virtual void handleMessage(cMessage *message) override + { + delete message; + if (contention == nullptr) { + phase = par("phase").stdstringValue(); + auto nic = getParentModule()->getSubmodule("ap")->getSubmodule("wlan", 0); + radio = check_and_cast(nic->getSubmodule("radio")); + nic->subscribe(modesetChangedSignal, this); + accessModule = nic->getSubmodule("mac")->getModuleByPath(par("qos").boolValue() ? ".hcf.edca.edcaf[1]" : ".dcf.channelAccess"); + access = check_and_cast(accessModule); + contention = check_and_cast(accessModule->getSubmodule("contention")); + contention->subscribe(IContention::backoffPeriodGeneratedSignal, this); + contention->mediumStateChanged(phase != "defer"); + ASSERT(currentCw() == 31); + incrementCw(); + ASSERT(currentCw() == 63); + access->requestChannel(this); + initialSlots = contention->slots(); + ASSERT(initialSlots >= 2); + ASSERT(draws == 1); + if (phase == "eifs") + contention->corruptedFrameReceived(); + previousGrant = contention->grantTime(); + simtime_t delay = SimTime(1, SIMTIME_US); + if (phase == "backoff") + delay = contention->interframe() + contention->slot() * 1.5; + scheduleAfter(delay, new cMessage("switch")); + } + else { + ASSERT(!switched); + ASSERT(contention->state() == (phase == "defer" ? Contention::DEFER : Contention::IFS_AND_BACKOFF)); + const auto *target = Ieee80211ModeSet::getModeSet(phase == "unchanged" ? "g(mixed)" : "g(erp)"); + radio->setModeSetAndMode(target, target->getMode(Mbps(24))); + ASSERT(currentCw() == 63); + ASSERT(modeNotifications == (phase == "unchanged" ? 0 : 1)); + ASSERT(contention->slot() == target->getSlotTime()); + ASSERT(contention->interframe() == target->getSifsTime() + (par("qos").boolValue() ? 3 : 2) * target->getSlotTime()); + ASSERT(contention->slots() == initialSlots - (phase == "backoff" ? 1 : 0)); + ASSERT(draws == 1); + if (phase == "defer") + contention->mediumStateChanged(true); + auto interval = phase == "eifs" ? contention->extendedInterframe() : contention->interframe(); + expectedGrant = phase == "unchanged" ? previousGrant : simTime() + interval + contention->slots() * target->getSlotTime(); + ASSERT(contention->grantTime() == expectedGrant); + switched = true; + } + } + virtual void channelGranted(IChannelAccess *channelAccess) override + { + Enter_Method_Silent(); + ASSERT(switched); + ASSERT(channelAccess == access); + ASSERT(simTime() == expectedGrant); + ASSERT(draws == 1); + access->releaseChannel(this); + contention->unsubscribe(IContention::backoffPeriodGeneratedSignal, this); + radio->getParentModule()->unsubscribe(modesetChangedSignal, this); + // Exercise both clamp boundaries after retry-window growth. + auto participant = check_and_cast(accessModule); + static BoundedCwMode lowerMode(1023), upperMode(127); + static Ieee80211ModeSet lowerSet("lower bound", {{true, &lowerMode, true}}, &lowerMode, Ieee80211ModeSet::getModeSet("a")->getPhyType()); + static Ieee80211ModeSet upperSet("upper bound", {{true, &upperMode, true}}, &upperMode, Ieee80211ModeSet::getModeSet("a")->getPhyType()); + participant->applyModeSet(&lowerSet); + ASSERT(currentCw() == 127); + incrementCw(); + ASSERT(currentCw() == 255); + participant->applyModeSet(&upperSet); + ASSERT(currentCw() == 127); + std::cout << "Verified contention phase=" << phase << " qos=" << par("qos").boolValue() << "\n"; + endSimulation(); + } +}; +Define_Module(ContentionModeSetTest); + +%file: test.ned +import inet.linklayer.ieee80211.mac.contention.Contention; +import inet.node.wireless.AccessPoint; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; +simple TimingProbeContention extends Contention +{ + @class(::TimingProbeContention); +} +simple ContentionModeSetTest +{ + parameters: + string phase; + bool qos; + @class(::ContentionModeSetTest); +} +network ContentionModeSetNetwork +{ + submodules: + radioMedium: Ieee80211ScalarRadioMedium; + ap: AccessPoint; + test: ContentionModeSetTest; +} + +%inifile: omnetpp.ini +[General] +network = ContentionModeSetNetwork +ned-path = .;../../../../src;../../lib +sim-time-limit = 10ms +seed-set = 0 +cmdenv-express-mode = false +record-vector-results = false +record-scalar-results = false +*.test.phase = ${"ifs","backoff","defer","eifs","unchanged"} +*.test.qos = ${qos=false,true} +**.mobility.initFromDisplayString = false +**.mobility.initialX = 10m +**.mobility.initialY = 10m +**.wlan[*].opMode = "g(mixed)" +**.wlan[*].bitrate = 24Mbps +**.wlan[*].radio.bandName = "2.4 GHz" +**.wlan[*].radio.channelNumber = 0 +**.wlan[*].radio.transmitter.power = 100mW +**.wlan[*].mac.qosStation = ${qos} +**.mgmt.ssid = "contention" +**.mgmt.beaconInterval = 1s +**.contention.typename = "TimingProbeContention" +**.contention.backoffOptimization = false +**.cwMin = -1 +**.cwMax = -1 + +%file: check_results.py +from pathlib import Path +text = Path("test.out").read_text() +for phase in ("ifs", "backoff", "defer", "eifs", "unchanged"): + for qos in (0, 1): + assert text.count(f"Verified contention phase={phase} qos={qos}") == 1, (phase, qos) + +%postrun-command: python3 check_results.py diff --git a/tests/module/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..cd306703a90 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,12 +234,25 @@ 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)); std::cout << "Dynamic channel change to 11 downgraded to 20 MHz and updated peer state.\n"; + // Reapplying HT must not undo the band-derived fallback, including in + // existing peer state. Exercise the real coordinated radio setter. + auto capabilities = peerState->negotiatedCapabilities; + radio->setModeSetAndMode(modeSet, modeSet->getMode(Mbps(24))); + ASSERT(mib->getHtOperation().secondaryChannelOffset == 0); + ASSERT(mib->getHtOperation().operatingChannelWidth == MHz(20)); + peerState = mib->findPeerHtState(staAddress); + ASSERT(peerState->negotiatedCapabilities == capabilities); + ASSERT(mib->getHtOperation().operatingChannelWidth == MHz(20)); + mgmt->emitBeaconNow(); + ASSERT(!mgmt->sentBeacons.back()->getHtOperation().staChannelWidth40Mhz); + std::cout << "HT mode refresh preserves edge-channel fallback and peer state.\n"; + // 4. Dynamic runtime channel change back to channel index 0 (std channel 1) radio->setChannelNumber(0); @@ -253,9 +266,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 +278,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 +296,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 +328,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 +342,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; @@ -339,11 +354,27 @@ class Ieee80211MgmtApChannelChangeTest : public cSimpleModule std::cout << "Dynamic band change revalidated against new active band.\n"; + // Legacy-to-HT on an edge channel must also apply the configured + // secondary-channel policy before advertising HT operation again. + auto legacy = physicallayer::Ieee80211ModeSet::getModeSet("g(mixed)"); + radio->setModeSetAndMode(legacy, legacy->getMode(Mbps(24))); + radio->setChannelNumber(2); // last channel in the three-channel test band + radio->setModeSetAndMode(modeSet, modeSet->getMode(Mbps(24))); + ASSERT(mib->getHtOperation().primaryChannel == 2); + ASSERT(mib->getHtOperation().secondaryChannelOffset == 0); + ASSERT(mib->getHtOperation().operatingChannelWidth == MHz(20)); + mgmt->emitBeaconNow(); + ASSERT(mgmt->sentBeacons.back()->getHtOperation().primaryChannel == 44); + ASSERT(!mgmt->sentBeacons.back()->getHtOperation().staChannelWidth40Mhz); + radio->setChannelNumber(0); + std::cout << "Legacy-to-HT switch validates the active band before advertising.\n"; + // 7. Band notifications may arrive during initialization before the MAC // publishes whether HT operation is supported. A legacy/non-HT AP must // retain the radio's channel without applying HT-only band validation. - 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); @@ -426,7 +457,7 @@ record-scalar-results = false *.ap.wlan[0].radio.transmitter.typename = "TestHt40Transmitter" *.ap.wlan[0].radio.receiver.typename = "TestHt40Receiver" **.wlan[*].opMode = "n(mixed-2.4Ghz)" -**.wlan[*].bitrate = 65Mbps +**.wlan[*].bitrate = 24Mbps **.wlan[*].radio.bandName = "2.4 GHz" **.wlan[*].radio.centerFrequency = 2.4GHz **.mobility.initFromDisplayString = false @@ -459,3 +490,9 @@ Dynamic band change revalidated against new active band. %contains: stdout Non-HT band notification deferred HT-only validation. + +%contains: stdout +HT mode refresh preserves edge-channel fallback and peer state. + +%contains: stdout +Legacy-to-HT switch validates the active band before advertising. diff --git a/tests/module/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/Ieee80211MgmtApUnavailableChannel_1.test b/tests/module/Ieee80211MgmtApUnavailableChannel_1.test index 746235f952e..15349362752 100644 --- a/tests/module/Ieee80211MgmtApUnavailableChannel_1.test +++ b/tests/module/Ieee80211MgmtApUnavailableChannel_1.test @@ -1,8 +1,9 @@ %description: An HT-capable AP must not serialize a management frame before its radio has published a valid primary channel. A radio channel of -1 suppresses the -initial channel signal when the transmitter is also unconfigured, so initialization reports the -missing MIB-owned channel instead of serializing an invalid value. +initial channel signal when the transmitter is also unconfigured, so initial +mode-set application reports the missing MIB-owned channel before a notification +or Beacon can expose invalid state. %file: test.ned diff --git a/tests/module/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..4a4d1a69bf6 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,16 +504,16 @@ 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); ASSERT(dcfSparseMode->getHtMcsIndex() == 0); ASSERT(dcfSparseMode->getDataMode()->getBandwidth() == MHz(20)); - // The invalid/absent negotiated state is a legacy-only unicast - // condition, while group-addressed traffic keeps its existing HT - // selection policy. + // Invalid/absent negotiated state forces legacy unicast. Group traffic + // independently uses the nonempty basic legacy set (IEEE Std + // 802.11-2024, 10.6.5.4), regardless of this peer's negotiated state. staMib->removePeerHtCapabilities(address); const auto *dcfLegacyMode = dcfRateSelection->computeMode(&ratePacket, dataHeader); const auto *qosLegacyMode = qosRateSelection->computeMode(&ratePacket, dataHeader, nullptr); @@ -539,9 +543,11 @@ class Ieee80211MgmtStaDiscoveryTest : public cSimpleModule, public cListener multicastHeader->setReceiverAddress(MacAddress::BROADCAST_ADDRESS); const auto *dcfMulticastMode = dcfRateSelection->computeMode(&ratePacket, multicastHeader); const auto *qosMulticastMode = qosRateSelection->computeMode(&ratePacket, multicastHeader, nullptr); - ASSERT(dcfMulticastMode->getHtMcsIndex() >= 0); - ASSERT(qosMulticastMode->getHtMcsIndex() >= 0); - staMib->setPeerHtCapabilities(address, staMib->localHtCapabilities, staMib->getHtOperation()); + ASSERT(dcfMulticastMode == modeSet->getMode(Mbps(24))); + ASSERT(qosMulticastMode == dcfMulticastMode); + ASSERT(dcfMulticastMode->getHtMcsIndex() < 0); + ASSERT(modeSet->getIsMandatory(dcfMulticastMode)); + 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/module/Ieee80211MixedHtDiscovery_1.test b/tests/module/Ieee80211MixedHtDiscovery_1.test new file mode 100644 index 00000000000..5a2069e048d --- /dev/null +++ b/tests/module/Ieee80211MixedHtDiscovery_1.test @@ -0,0 +1,141 @@ +%description: +Two mixed HT APs (DCF and HCF) advertise legacy basic-rate Beacons over the +radio medium. Legacy STAs discover them by passive scanning and associate. +The observation includes selected transmit modes, actual Beacon reception, +and terminal association state, so an HT Beacon cannot pass this test. + +%file: Ieee80211MixedHtDiscovery.cc +#include "inet/linklayer/ieee80211/mac/Ieee80211Mac.h" +#include "inet/linklayer/ieee80211/mib/Ieee80211Mib.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" + +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +class DiscoveryMac : public Ieee80211Mac +{ + public: + int beaconsSent = 0; + int beaconsReceived = 0; + protected: + virtual void sendDownFrame(Packet *packet) override + { + const auto& header = packet->peekAtFront(); + if (header->getType() == ST_BEACON) { + auto mode = packet->getTag()->getMode(); + ASSERT(mode->getHtMcsIndex() < 0); + ASSERT(modeSet->containsMode(mode)); + ASSERT(modeSet->getIsMandatory(mode)); + beaconsSent++; + } + Ieee80211Mac::sendDownFrame(packet); + } + virtual void handleLowerPacket(Packet *packet) override + { + const auto& header = packet->peekAtFront(); + if (header->getType() == ST_BEACON) { + ASSERT(packet->getTag()->getMode()->getHtMcsIndex() < 0); + beaconsReceived++; + } + Ieee80211Mac::handleLowerPacket(packet); + } +}; +Define_Module(DiscoveryMac); + +class Ieee80211MixedHtDiscoveryTest : public cSimpleModule +{ + protected: + virtual void finish() override + { + for (int i = 0; i < 2; i++) { + auto apNic = getParentModule()->getSubmodule("ap", i)->getSubmodule("wlan", 0); + auto staNic = getParentModule()->getSubmodule("sta", i)->getSubmodule("wlan", 0); + auto ap = check_and_cast(apNic->getSubmodule("mib")); + auto sta = check_and_cast(staNic->getSubmodule("mib")); + ASSERT(ap->isLocalHtCapable()); + ASSERT(!sta->isLocalHtCapable()); + ASSERT(sta->getBssStationData().isAssociated); + ASSERT(sta->getBssData().bssid == ap->address); + ASSERT(ap->getBssAccessPointData().stations.at(sta->address) == Ieee80211Mib::ASSOCIATED); + ASSERT(ap->findPeerHtState(sta->address) == nullptr); + ASSERT(sta->findPeerHtState(ap->address) == nullptr); + ASSERT(check_and_cast(apNic->getSubmodule("mac"))->beaconsSent > 0); + ASSERT(check_and_cast(staNic->getSubmodule("mac"))->beaconsReceived > 0); + } + std::cout << "Legacy passive discovery and association with mixed HT DCF and HCF APs verified.\n"; + } +}; +Define_Module(Ieee80211MixedHtDiscoveryTest); + +%file: test.ned +import inet.common.SimpleModule; +import inet.linklayer.ieee80211.mac.Ieee80211Mac; +import inet.node.inet.WirelessHost; +import inet.node.wireless.AccessPoint; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; + +module DiscoveryMac extends Ieee80211Mac +{ + parameters: + @class(::DiscoveryMac); +} + +simple Ieee80211MixedHtDiscoveryTest extends SimpleModule +{ + parameters: + @class(::Ieee80211MixedHtDiscoveryTest); +} + +network DiscoveryNetwork +{ + submodules: + radioMedium: Ieee80211ScalarRadioMedium; + ap[2]: AccessPoint; + sta[2]: WirelessHost; + test: Ieee80211MixedHtDiscoveryTest; +} + +%inifile: omnetpp.ini +[General] +network = DiscoveryNetwork +ned-path = .;../../../../src;../../lib +sim-time-limit = 100ms +seed-set = 0 +cmdenv-express-mode = false +record-vector-results = false +record-scalar-results = false +**.mobility.initFromDisplayString = false +**.mobility.initialX = 10m +**.mobility.initialY = 10m +*.ap[*].wlan[*].opMode = "n(mixed-2.4Ghz)" +*.sta[*].wlan[*].opMode = "g(mixed)" +**.wlan[*].bitrate = -1bps +**.wlan[*].radio.bandName = "2.4 GHz" +**.wlan[*].radio.channelNumber = 6 +**.wlan[*].radio.transmitter.power = 100mW +**.wlan[*].radio.receiver.sensitivity = -85dBm +**.wlan[*].radio.receiver.snirThreshold = 4dB +**.wlan[*].mac.typename = "DiscoveryMac" +*.ap[0].wlan[0].mac.qosStation = false +*.ap[1].wlan[0].mac.qosStation = true +*.ap[*].wlan[0].mgmt.typename = "Ieee80211MgmtAp" +*.ap[0].wlan[0].mgmt.ssid = "mixed-dcf" +*.ap[1].wlan[0].mgmt.ssid = "mixed-hcf" +*.ap[*].wlan[0].mgmt.beaconInterval = 5ms +*.sta[*].wlan[0].mgmt.typename = "Ieee80211MgmtSta" +*.sta[*].wlan[0].mgmt.numChannels = 1 +*.sta[*].wlan[0].agent.typename = "Ieee80211AgentSta" +*.sta[*].wlan[0].agent.activeScan = false +*.sta[0].wlan[0].agent.startingTime = 0s +*.sta[1].wlan[0].agent.startingTime = 1ms +*.sta[0].wlan[0].agent.defaultSsid = "mixed-dcf" +*.sta[1].wlan[0].agent.defaultSsid = "mixed-hcf" +*.sta[*].wlan[0].agent.channelsToScan = "6" +*.sta[*].wlan[0].agent.minChannelTime = 10ms +*.sta[*].wlan[0].agent.maxChannelTime = 10ms +*.sta[*].wlan[0].agent.authenticationTimeout = 20ms +*.sta[*].wlan[0].agent.associationTimeout = 20ms + +%contains: stdout +Legacy passive discovery and association with mixed HT DCF and HCF APs verified. diff --git a/tests/module/Ieee80211ModeSetFailure_1.test b/tests/module/Ieee80211ModeSetFailure_1.test new file mode 100644 index 00000000000..b1059a8e9ed --- /dev/null +++ b/tests/module/Ieee80211ModeSetFailure_1.test @@ -0,0 +1,131 @@ +%description: +Invalid fixed rates in either selector and exceptions in either radio notification +terminate the simulation. Both radio setters are exercised in independent runs; +no run catches an error or continues using partially updated state. A band +without standard channel mapping also fails through the real radio setter. Reentrant +mode-set changes are rejected while the new state is being published. + +%file: ModeSetFailure.cc +#include "inet/common/Simsignals.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Band.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" + +using namespace inet; +using namespace inet::physicallayer; + +class ModeSetFailure : public cSimpleModule, public cListener +{ + protected: + virtual void initialize() override { scheduleAt(SimTime(1, SIMTIME_US), new cMessage("switch")); } + virtual void receiveSignal(cComponent *source, simsignal_t signal, cObject *value, cObject *details) override + { + Enter_Method_Silent(); + auto failure = par("failure").stdstringValue(); + // A participant exception must prevent completion publication. + ASSERT(failure != "dcf" && failure != "hcf"); + if (failure == "listening") + return; // Application completed; the later listening observer fails. + if (par("failure").stdstringValue() == "detach") + check_and_cast(source)->unregisterModeSetConsumer(check_and_cast(source)->getSubmodule("mac")); + if (par("failure").stdstringValue() == "reentrant") + check_and_cast(check_and_cast(source)->getParentModule()->getSubmodule("radio"))->setModeSet(check_and_cast(value)); + throw cRuntimeError("test observer rejects mode set"); + } + virtual void receiveSignal(cComponent *source, simsignal_t signal, intval_t value, cObject *details) override + { + Enter_Method_Silent(); + ASSERT(par("failure").stdstringValue() == "listening"); + throw cRuntimeError("test observer rejects listening change"); + } + virtual void handleMessage(cMessage *message) override + { + delete message; + auto radio = check_and_cast(getParentModule()->getSubmodule("ap")->getSubmodule("wlan", 0)->getSubmodule("radio")); + auto ht = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"); + auto legacy = Ieee80211ModeSet::getModeSet("g(mixed)"); + radio->setMode(ht->getMode(Mbps(24))); + auto failure = par("failure").stdstringValue(); + radio->getParentModule()->subscribe(modesetChangedSignal, this); + if (failure == "listening" || failure == "dcf" || failure == "hcf") + radio->subscribe(IRadio::listeningChangedSignal, this); + std::cout << "Attempting " << failure << " explicit=" << par("explicitMode").boolValue() << "\n"; + if (failure == "band") { + radio->setBand(&Ieee80211CompliantBands::band5GHz); + throw cRuntimeError("UNEXPECTED successful band change"); + } + if (par("explicitMode")) + radio->setModeSetAndMode(legacy, legacy->getMode(Mbps(24))); + else + radio->setModeSet(legacy); + throw cRuntimeError("UNEXPECTED successful mode-set change"); + } +}; +Define_Module(ModeSetFailure); + +%file: test.ned +import inet.node.wireless.AccessPoint; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; + +simple ModeSetFailure +{ + parameters: + @class(::ModeSetFailure); + string failure; + bool explicitMode; +} +network ModeSetFailureNetwork +{ + submodules: + radioMedium: Ieee80211ScalarRadioMedium; + ap: AccessPoint; + test: ModeSetFailure; +} + +%inifile: omnetpp.ini +[General] +network = ModeSetFailureNetwork +ned-path = .;../../../../src;../../lib +sim-time-limit = 1ms +seed-set = 0 +cmdenv-express-mode = false +cmdenv-stop-batch-on-error = false +record-vector-results = false +record-scalar-results = false +*.test.failure = ${failure="dcf","hcf","modeset","listening","reentrant","band","detach"} +*.test.explicitMode = ${false,true} +**.mobility.initFromDisplayString = false +**.mobility.initialX = 10m +**.mobility.initialY = 10m +**.wlan[*].opMode = "n(mixed-2.4Ghz)" +**.wlan[*].bitrate = -1bps +**.wlan[*].radio.bandName = "2.4 GHz" +**.wlan[*].radio.channelNumber = 6 +**.wlan[*].radio.transmitter.power = 100mW +**.wlan[*].mac.qosStation = true +**.mgmt.ssid = "failure" +**.mgmt.beaconInterval = 1s +**.mac.dcf.rateSelection.dataFrameBitrate = ${failure} == "dcf" ? 65Mbps : -1bps +**.mac.hcf.rateSelection.dataFrameBitrate = ${failure} == "hcf" ? 65Mbps : -1bps +**.rateSelection.dataFrameBandwidth = 20MHz +**.rateSelection.dataFrameNumSpatialStreams = 1 +**.rateSelection.dataFrameGuardInterval = 800ns + +%exitcode: 1 + +%file: check_errors.py +from pathlib import Path +text = Path("test.out").read_text() + Path("test.err").read_text() +for failure in ("dcf", "hcf", "modeset", "listening", "reentrant", "band", "detach"): + for explicit in (0, 1): + assert text.count(f"Attempting {failure} explicit={explicit}") == 1 +errors = [line for line in text.splitlines() if " Error:" in line] +assert len(errors) == 14, errors +assert sum("Unknown mode for bitrate" in line for line in errors) == 4, errors +assert sum("test observer rejects mode set" in line for line in errors) == 2, errors +assert sum("test observer rejects listening change" in line for line in errors) == 2, errors +assert sum("Reentrant radio mode-set change" in line for line in errors) == 2, errors +assert sum("has no standards channel-number mapping" in line for line in errors) == 2, errors +assert sum("Cannot detach a mode-set consumer during a transition" in line for line in errors) == 2, errors +assert "UNEXPECTED successful" not in text + +%postrun-command: python3 check_errors.py diff --git a/tests/module/Ieee80211ModeSetRegistration_1.test b/tests/module/Ieee80211ModeSetRegistration_1.test new file mode 100644 index 00000000000..1b09af9a1e3 --- /dev/null +++ b/tests/module/Ieee80211ModeSetRegistration_1.test @@ -0,0 +1,262 @@ +%description: +Explicit mode-set registration is independent of observer subscriptions and registration +order. Initialization queries the catalog; the MAC publishes after each runtime change, +with MAC state already applied. Cover interface isolation, duplicate registration, +unregistration/deletion, and rejection of late and cross-interface registration. + +%file: Registration.cc +#include "inet/physicallayer/wireless/generic/GenericRadio.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211Band.h" +#include "inet/common/ModuleAccess.h" +#include "inet/networklayer/common/NetworkInterface.h" +#include "inet/common/Simsignals.h" +#include "inet/linklayer/ieee80211/mac/contract/IIeee80211ModeSetProvider.h" +#include "inet/linklayer/ieee80211/mib/Ieee80211Mib.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetCoordinator.h" +#include "inet/physicallayer/wireless/ieee80211/contract/packetlevel/IIeee80211ModeSetListener.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211RadioChannelChangedDetails.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h" +#include "inet/physicallayer/wireless/generic/GenericTransmitter.h" +#include "inet/physicallayer/wireless/generic/GenericReceiver.h" +#include "inet/physicallayer/wireless/ieee80211/contract/IIeee80211TransmitterCapabilities.h" +#include "inet/physicallayer/wireless/ieee80211/contract/IIeee80211ReceiverCapabilities.h" + +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +class CapabilityTransmitter : public GenericTransmitter, public IIeee80211TransmitterCapabilities +{ + public: + bool isHtChannelWidthSupported(Hz width) const override { return width == MHz(20) || width == MHz(40); } +}; +Define_Module(CapabilityTransmitter); +class CapabilityReceiver : public GenericReceiver, public IIeee80211ReceiverCapabilities +{ + public: + bool isHtChannelWidthSupported(Hz width) const override { return width == MHz(20) || width == MHz(40); } + bool isHtShortGuardIntervalSupported(Hz) const override { return false; } +}; +Define_Module(CapabilityReceiver); + +// A capability provider with generic PHY roles proves that MAC/management use +// the radio contract, not concrete IEEE transmitter/receiver implementations. +class CapabilityRadio : public GenericRadio, public IIeee80211Radio +{ + Ieee80211Channel channel{&Ieee80211CompliantBands::band2_4GHz, 10}; + public: + virtual bool isHtChannelWidthSupported(Hz width) const override { return width == MHz(20) || width == MHz(40); } + virtual const Ieee80211Channel *getChannel() const override { return &channel; } + protected: + virtual void initialize(int stage) override + { + GenericRadio::initialize(stage); + if (stage == INITSTAGE_PHYSICAL_LAYER) { + Ieee80211RadioChannelChangedDetails details(channel.getBand()); + emit(cComponent::registerSignal("radioChannelChanged"), 10L, &details); + } + } +}; +Define_Module(CapabilityRadio); + +class RegisteredConsumer : public cSimpleModule, public IIeee80211ModeSetListener +{ + const Ieee80211ModeSet *modeSet = nullptr; + public: + int applications = 0; + virtual const Ieee80211ModeSet *getModeSet() const override { return modeSet; } + virtual void applyModeSet(const Ieee80211ModeSet *value) override + { + Enter_Method_Silent(); + auto nic = getContainingNicModule(this); + ASSERT(check_and_cast(nic->getSubmodule("mac"))->getModeSet() == value); + ASSERT(check_and_cast(nic->getSubmodule("mib"))->isLocalHtCapable() == value->isHtOperationSupported()); + modeSet = value; + applications++; + } + protected: + virtual int numInitStages() const override { return NUM_INIT_STAGES; } + virtual void initialize(int stage) override + { + bool late = (getIndex() == 0) == par("reverse").boolValue(); + if (stage == (late ? INITSTAGE_PHYSICAL_LAYER : INITSTAGE_LOCAL)) { + auto coordinator = check_and_cast(getContainingNicModule(this)->getSubmodule("mac")); + bool rejected = false; + try { coordinator->registerModeSetConsumer(this, static_cast(99)); } + catch (const cRuntimeError&) { rejected = true; } + ASSERT(rejected); + coordinator->registerModeSetConsumer(this, IIeee80211ModeSetCoordinator::DERIVED_STATE); + coordinator->registerModeSetConsumer(this, IIeee80211ModeSetCoordinator::DERIVED_STATE); + } + else if (stage == INITSTAGE_LINK_LAYER) + modeSet = check_and_cast(getContainingNicModule(this)->getSubmodule("mac"))->getConfiguredModeSet(); + } +}; +Define_Module(RegisteredConsumer); + +// Merely implementing the consumer interface and subscribing must never enlist it. +class UnregisteredObserver : public cListener, public IIeee80211ModeSetListener +{ + public: + int notifications = 0; + virtual const Ieee80211ModeSet *getModeSet() const override { return nullptr; } + virtual void applyModeSet(const Ieee80211ModeSet *) override { throw cRuntimeError("Observer enlisted as participant"); } + virtual void receiveSignal(cComponent *, simsignal_t, cObject *, cObject *) override { notifications++; } +}; + +class RegistrationTest : public cSimpleModule, public cListener +{ + UnregisteredObserver observer; + int notifications = 0; + RegisteredConsumer *probe(cModule *nic, int index) { return check_and_cast(nic->getSubmodule("probe", index)); } + cModule *nic(int index) { return getParentModule()->getSubmodule("ap", index)->getSubmodule("wlan", 0); } + protected: + virtual int numInitStages() const override { return NUM_INIT_STAGES; } + virtual void initialize(int stage) override + { + if (stage == INITSTAGE_LOCAL) { + getParentModule()->subscribe(modesetChangedSignal, this); + getParentModule()->subscribe(modesetChangedSignal, &observer); + } + else if (stage == INITSTAGE_LAST) + scheduleAfter(SimTime(1, SIMTIME_US), new cMessage("change")); + } + virtual void receiveSignal(cComponent *source, simsignal_t, cObject *value, cObject *) override + { + Enter_Method_Silent(); + ASSERT(source == nic(0)->getSubmodule("mac") || source == nic(1)->getSubmodule("mac")); + auto module = check_and_cast(source)->getParentModule(); + auto target = check_and_cast(value); + ASSERT(probe(module, 0)->getModeSet() == target); + ASSERT(probe(module, 1)->getModeSet() == target); + if (module == nic(1)) { + auto mib = check_and_cast(module->getSubmodule("mib")); + ASSERT(mib->getLocalHtCapabilities().supportedChannelWidths.count(MHz(40)) == 1); + ASSERT(mib->requirePrimaryChannel() == 10); + ASSERT(mib->getHtOperation().operatingChannelWidth == MHz(20)); + ASSERT(mib->getHtOperation().secondaryChannelOffset == 0); + } + notifications++; + } + virtual void handleMessage(cMessage *message) override + { + delete message; + ASSERT(notifications == 0 && observer.notifications == 0); + auto first = nic(0); + auto second = nic(1); + auto radio = check_and_cast(first->getSubmodule("radio")); + auto coordinator = check_and_cast(first->getSubmodule("mac")); + auto legacy = Ieee80211ModeSet::getModeSet("g(mixed)"); + auto ht = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"); + radio->setModeSetAndMode(legacy, legacy->getMode(Mbps(24))); + ASSERT(notifications == 1 && observer.notifications == 1); + ASSERT(probe(first, 0)->applications == 1 && probe(first, 1)->applications == 1); + ASSERT(probe(second, 0)->applications == 0 && probe(second, 1)->applications == 0); + getParentModule()->unsubscribe(modesetChangedSignal, this); + getParentModule()->unsubscribe(modesetChangedSignal, &observer); + radio->setModeSet(ht); + ASSERT(probe(first, 0)->applications == 2 && probe(first, 1)->applications == 2); + ASSERT(notifications == 1 && observer.notifications == 1); + bool rejected = false; + try { coordinator->registerModeSetConsumer(probe(first, 0), IIeee80211ModeSetCoordinator::DERIVED_STATE); } + catch (const cRuntimeError&) { rejected = true; } + ASSERT(rejected); + rejected = false; + try { coordinator->unregisterModeSetConsumer(probe(second, 0)); } + catch (const cRuntimeError&) { rejected = true; } + ASSERT(rejected); + coordinator->unregisterModeSetConsumer(probe(first, 1)); + probe(first, 1)->deleteModule(); + radio->setModeSetAndMode(legacy, legacy->getMode(Mbps(24))); + ASSERT(probe(first, 0)->applications == 3); + ASSERT(probe(second, 0)->applications == 0); + if (par("deleteRegistered").boolValue()) + probe(first, 0)->deleteModule(); // Deliberately omit unregistering. + else + coordinator->unregisterModeSetConsumer(first->getSubmodule("mgmt")); + rejected = false; + try { radio->setModeSetAndMode(ht, ht->getMode(Mbps(24))); } + catch (const cRuntimeError&) { rejected = true; } + ASSERT(rejected); + // Missing membership is rejected before the PHY is mutated. + ASSERT(check_and_cast(radio->getTransmitter())->getModeSet() == legacy); + std::cout << "Explicit registration, publication, observer neutrality, isolation and teardown verified.\n"; + endSimulation(); + } +}; +Define_Module(RegistrationTest); + +%file: test.ned +import inet.node.wireless.AccessPoint; +import inet.linklayer.ieee80211.Ieee80211Interface; +import inet.physicallayer.wireless.common.medium.UnitDiskRadioMedium; +import inet.physicallayer.wireless.generic.GenericUnitDiskRadio; +import inet.physicallayer.wireless.generic.GenericTransmitter; +import inet.physicallayer.wireless.generic.GenericReceiver; +import inet.physicallayer.wireless.ieee80211.contract.packetlevel.IIeee80211Radio; +module CapabilityRadio extends GenericUnitDiskRadio like IIeee80211Radio +{ + parameters: + @class(::CapabilityRadio); + transmitter.typename = "CapabilityTransmitter"; + receiver.typename = "CapabilityReceiver"; +} +module CapabilityTransmitter extends GenericTransmitter { parameters: @class(::CapabilityTransmitter); } +module CapabilityReceiver extends GenericReceiver { parameters: @class(::CapabilityReceiver); } +simple RegisteredConsumer +{ + parameters: + bool reverse; + @class(::RegisteredConsumer); +} +module RegisteredInterface extends Ieee80211Interface +{ + submodules: + probe[2]: RegisteredConsumer; +} +simple RegistrationTest +{ + parameters: + bool deleteRegistered; + @class(::RegistrationTest); +} +network RegistrationNetwork +{ + submodules: + radioMedium: UnitDiskRadioMedium; + test: RegistrationTest; + ap[2]: AccessPoint; +} + +%inifile: omnetpp.ini +[General] +network = RegistrationNetwork +ned-path = .;../../../../src;../../lib +sim-time-limit = 1ms +seed-set = 0 +cmdenv-express-mode = false +record-vector-results = false +record-scalar-results = false +*.ap[*].wlan[*].typename = "RegisteredInterface" +**.probe[*].reverse = ${false,true} +*.test.deleteRegistered = ${false,true} +**.mobility.initFromDisplayString = false +**.mobility.initialX = 10m +**.mobility.initialY = 10m +*.ap[0].wlan[*].radio.typename = "Ieee80211UnitDiskRadio" +*.ap[1].wlan[*].radio.typename = "CapabilityRadio" +*.ap[1].wlan[*].mib.htSecondaryChannelOffset = 1 +**.wlan[*].opMode = "n(mixed-2.4Ghz)" +**.wlan[*].bitrate = 24Mbps +**.wlan[*].radio.bandName = "2.4 GHz" +**.wlan[*].radio.channelNumber = 0 +**.wlan[*].radio.transmitter.power = 100mW +**.radio.transmitter.analogModel.communicationRange = 100m +**.radio.transmitter.analogModel.interferenceRange = 100m +**.radio.transmitter.analogModel.detectionRange = 100m +**.mgmt.ssid = "registration" +**.mgmt.beaconInterval = 1s + +%contains: stdout +Explicit registration, publication, observer neutrality, isolation and teardown verified. diff --git a/tests/module/Ieee80211ModeSetRetry_1.test b/tests/module/Ieee80211ModeSetRetry_1.test new file mode 100644 index 00000000000..f34eee71935 --- /dev/null +++ b/tests/module/Ieee80211ModeSetRetry_1.test @@ -0,0 +1,307 @@ +%description: +DCF and HCF recompute retained HT mode requests after a coordinated legacy +transition. Drop exactly one data frame or RTS at the recipient, retain the real +queued MPDU through the timeout, then transmit and acknowledge it in g(mixed). +Peer capabilities are seeded; this tests MAC reconfiguration, not association. + +%file: ModeSetRetry.cc +#include "inet/common/Simsignals.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Mac.h" +#include "inet/linklayer/ieee80211/mac/contract/IFrameSequenceHandler.h" +#include "inet/linklayer/ieee80211/mac/framesequence/FrameSequenceContext.h" +#include "inet/linklayer/ieee80211/mac/originator/NonQosRecoveryProcedure.h" +#include "inet/linklayer/ieee80211/mac/originator/QosRecoveryProcedure.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" + +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +class RetryProbeMac : public Ieee80211Mac +{ + public: + bool blocked = false; + bool switched = false; + int htDataAttempts = 0; + int legacyDataAttempts = 0; + int rtsAttempts = 0; + int delivered = 0; + long dataTreeId = -1; + int sequence = -1; + InProgressFrames *retainedQueue = nullptr; + Packet *retainedFrame = nullptr; + + virtual void sendDownFrame(Packet *packet) override + { + if (par("sender")) { + auto header = packet->peekAtFront(); + if (auto data = dynamicPtrCast(header)) { + auto mode = packet->getTag()->getMode(); + if (!switched) { + ASSERT(mode->getHtMcsIndex() >= 0); + ASSERT(!data->getRetry()); + htDataAttempts++; + } + else { + ASSERT(Ieee80211ModeSet::getModeSet("g(mixed)")->containsMode(mode)); + ASSERT(mode->getHtMcsIndex() < 0); + ASSERT(packet->getTreeId() == dataTreeId); + ASSERT(retainedQueue->getLength() == 1 && retainedQueue->getFrames(0) == retainedFrame); + ASSERT(retainedFrame->getTag()->getMode() == mode); + ASSERT(data->getSequenceNumber().get() == sequence); + if (!par("protection")) + ASSERT(data->getRetry()); + legacyDataAttempts++; + } + } + else if (header->getType() == ST_RTS) + rtsAttempts++; + } + Ieee80211Mac::sendDownFrame(packet); + } + + virtual void sendUpFrame(Packet *packet) override + { + // End the fixture at the MAC service boundary after normal recipient + // processing and ACK scheduling; no synthetic LLC payload is decoded. + delivered++; + delete packet; + } + + protected: + virtual void handleLowerPacket(Packet *packet) override + { + auto header = packet->peekAtFront(); + bool target = par("protection") ? header->getType() == ST_RTS : + dynamicPtrCast(header) != nullptr; + if (!par("sender") && !blocked && target) { + blocked = true; + std::cout << "Controlled first reception loss\n"; + delete packet; + } + else + Ieee80211Mac::handleLowerPacket(packet); + } +}; +Define_Module(RetryProbeMac); + +class ModeSetRetryTest : public cSimpleModule, public cListener +{ + using cListener::finish; + RetryProbeMac *sender = nullptr; + RetryProbeMac *receiver = nullptr; + InProgressFrames *queue = nullptr; + Packet *retained = nullptr; + opp_component_ptr coordination; + cMessage *action = nullptr; + int phase = 0; + int sequences = 0; + int acknowledgments = 0; + + int retryCount() + { + auto header = retained->peekAtFront(); + if (par("qos")) + return check_and_cast(queue->getParentModule()->getSubmodule("recoveryProcedure"))->getRetryCount(retained, header); + return check_and_cast(coordination->getSubmodule("recoveryProcedure"))->getRetryCount(retained, header); + } + + Ieee80211Radio *radio(RetryProbeMac *mac) + { + return check_and_cast(mac->getParentModule()->getSubmodule("radio")); + } + + protected: + virtual void initialize() override + { + sender = check_and_cast(getModuleByPath("^.host[0].wlan[0].mac")); + receiver = check_and_cast(getModuleByPath("^.host[1].wlan[0].mac")); + coordination = sender->getSubmodule(par("qos") ? "hcf" : "dcf"); + coordination->subscribe(IFrameSequenceHandler::frameSequenceFinishedSignal, this); + coordination->subscribe(packetReceivedFromPeerSignal, this); + action = new cMessage("fixture action"); + scheduleAt(SimTime(1, SIMTIME_MS), action); + } + + virtual void receiveSignal(cComponent *, simsignal_t signal, cObject *value, cObject *) override + { + Enter_Method_Silent(); + if (signal == packetReceivedFromPeerSignal) { + auto packet = check_and_cast(value); + if (packet->peekAtFront()->getType() == ST_ACK) + acknowledgments++; + return; + } + auto context = check_and_cast(value); + queue = context->getInProgressFrames(); + sequences++; + ASSERT(sequences <= 2); + if (phase == 1) { + ASSERT(receiver->blocked); + ASSERT(queue->getLength() == 1); + retained = queue->getFrames(0); + ASSERT(retained->getTag()->getMode()->getHtMcsIndex() >= 0); + ASSERT(sender->dataTreeId == retained->getTreeId()); + sender->retainedQueue = queue; + sender->retainedFrame = retained; + if (!par("protection")) + ASSERT(retryCount() == 1); + sender->sequence = retained->peekAtFront()->getSequenceNumber().get(); + ASSERT(sender->htDataAttempts == (par("protection") ? 0 : 1)); + ASSERT(sender->rtsAttempts == (par("protection") ? 1 : 0)); + phase = 2; + } + else { + ASSERT(phase == 3); + ASSERT(queue->getLength() == 0); // The real ACK handler removed it. + retained = nullptr; + sender->retainedFrame = nullptr; + phase = 4; + } + scheduleAt(simTime(), action); // Run after frame-sequence cleanup. + } + + virtual void handleMessage(cMessage *) override + { + if (phase == 0) { + auto senderMib = check_and_cast(sender->getParentModule()->getSubmodule("mib")); + auto receiverMib = check_and_cast(receiver->getParentModule()->getSubmodule("mib")); + // Install the explicit test relationship; ad hoc initialization itself + // intentionally does not invent an accepted HT operation or peer. + for (auto mac : {sender, receiver}) { + auto mib = check_and_cast(mac->getParentModule()->getSubmodule("mib")); + Ieee80211HtOperation operation; + operation.primaryChannel = 6; + operation.operatingChannelWidth = MHz(20); + mib->commitBss("retry-test", sender->getAddress(), radio(mac)->getChannel()->getBand(), 6, &operation); + } + for (auto mac : {sender, receiver}) + radio(mac)->setMode(Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)")->getMode(Mbps(24))); + senderMib->setPeerHtCapabilities(receiver->getAddress(), receiverMib->getLocalHtCapabilities()); + receiverMib->setPeerHtCapabilities(sender->getAddress(), senderMib->getLocalHtCapabilities()); + auto packet = new Packet("retained-data"); + auto header = makeShared(); + header->setType(par("qos") ? ST_DATA_WITH_QOS : ST_DATA); + header->setChunkLength(DATAFRAME_HEADER_MINLENGTH + (par("qos") ? QOSCONTROL_PART_LENGTH : b(0))); + header->setTid(0); + header->setReceiverAddress(receiver->getAddress()); + header->setTransmitterAddress(sender->getAddress()); + header->setAddress3(receiver->getAddress()); + packet->insertAtBack(header); + packet->insertAtBack(makeShared(B(100))); + auto trailer = makeShared(); + trailer->setFcsMode(FCS_DECLARED_CORRECT); + packet->insertAtBack(trailer); + sender->dataTreeId = packet->getTreeId(); + phase = 1; + sender->processUpperFrame(packet, header); + } + else if (phase == 2) { + ASSERT(queue->getLength() == 1 && queue->getFrames(0) == retained); + auto oldMode = retained->getTag()->getMode(); + for (auto mac : {sender, receiver}) { + ASSERT(radio(mac)->getTransmissionState() != IRadio::TRANSMISSION_STATE_TRANSMITTING); + ASSERT(radio(mac)->getReceptionState() == IRadio::RECEPTION_STATE_IDLE); + radio(mac)->setModeSet(Ieee80211ModeSet::getModeSet("g(mixed)")); + } + ASSERT(retained->getTag()->getMode() == oldMode); + if (!par("protection")) + ASSERT(retryCount() == 1); + sender->switched = true; + phase = 3; + std::cout << "Retained HT request survives transition until reselection\n"; + } + else { + ASSERT(phase == 4 && sequences == 2); + ASSERT(receiver->delivered == 1 && acknowledgments == 1); + ASSERT(sender->legacyDataAttempts == 1); + ASSERT(sender->rtsAttempts == (par("protection") ? 2 : 0)); + std::cout << "Retained frame transmitted and acknowledged in new mode set: qos=" + << par("qos").boolValue() << " protection=" << par("protection").boolValue() << "\n"; + endSimulation(); + } + } + + virtual void finish() override { ASSERT(phase == 4); } + + public: + virtual ~ModeSetRetryTest() + { + if (coordination) { + coordination->unsubscribe(IFrameSequenceHandler::frameSequenceFinishedSignal, this); + coordination->unsubscribe(packetReceivedFromPeerSignal, this); + } + cancelAndDelete(action); + } +}; +Define_Module(ModeSetRetryTest); + +%file: test.ned +import inet.node.inet.WirelessHost; +import inet.linklayer.ieee80211.mac.Ieee80211Mac; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; +module RetryProbeMac extends Ieee80211Mac +{ + parameters: + @class(::RetryProbeMac); + bool sender; + bool protection; +} +simple ModeSetRetryTest +{ + parameters: + @class(::ModeSetRetryTest); + bool qos; + bool protection; +} +network RetryNetwork +{ + submodules: + radioMedium: Ieee80211ScalarRadioMedium; + host[2]: WirelessHost; + test: ModeSetRetryTest; +} + +%inifile: omnetpp.ini +[General] +network = RetryNetwork +ned-path = .;../../../../src;../../lib +sim-time-limit = 20ms +seed-set = 0 +cmdenv-express-mode = false +record-vector-results = false +record-scalar-results = false +*.test.qos = ${qos=false,true} +*.test.protection = ${protection=false,true} +**.mobility.initFromDisplayString = false +*.host[0].mobility.initialX = 10m +*.host[1].mobility.initialX = 11m +**.mobility.initialY = 10m +**.mobility.initialZ = 0m +**.wlan[*].opMode = "n(mixed-2.4Ghz)" +**.wlan[*].bitrate = -1bps +**.wlan[*].mgmt.typename = "Ieee80211MgmtAdhoc" +**.wlan[*].agent.typename = "" +**.wlan[*].radio.bandName = "2.4 GHz" +**.wlan[*].radio.channelNumber = 6 +**.wlan[*].radio.transmitter.power = 100mW +**.wlan[*].radio.antenna.numAntennas = 1 +**.wlan[*].mac.typename = "RetryProbeMac" +*.host[0].wlan[*].mac.sender = true +*.host[1].wlan[*].mac.sender = false +**.mac.protection = ${protection} +**.mac.qosStation = ${qos} +**.mac.*.rateControl.typename = "AarfRateControl" +**.mac.*.rtsPolicy.rtsThreshold = ${protection} ? 0B : 10000B + +%file: check.py +from pathlib import Path +text = Path("test.out").read_text() +for qos in (0, 1): + for protection in (0, 1): + assert text.count(f"Retained frame transmitted and acknowledged in new mode set: qos={qos} protection={protection}") == 1 +assert text.count("Controlled first reception loss") == 4 +assert text.count("Retained HT request survives transition until reselection") == 4 + +%postrun-command: python3 check.py diff --git a/tests/module/Ieee80211ModeSetTransition_1.test b/tests/module/Ieee80211ModeSetTransition_1.test new file mode 100644 index 00000000000..65f100de451 --- /dev/null +++ b/tests/module/Ieee80211ModeSetTransition_1.test @@ -0,0 +1,314 @@ +%description: +Exercise both radio mode-set setters with DCF and HCF selectors. Successful +HT/legacy transitions refresh PHY-limited capabilities, actual Beacon bodies, +peer state and adaptive rates before observers run, and notify the radio medium. + +%file: Ieee80211ModeSetTransition.cc + +#include "inet/common/Simsignals.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" +#include "inet/physicallayer/wireless/common/medium/RadioMedium.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Mac.h" +#include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h" +#include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h" + +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +class TransitionRadioMedium : public RadioMedium +{ + public: + int listeningNotifications = 0; + cComponent *lastListeningSource = nullptr; + protected: + virtual void receiveSignal(cComponent *source, simsignal_t signal, intval_t value, cObject *details) override + { + Enter_Method_Silent(); + RadioMedium::receiveSignal(source, signal, value, details); + if (signal == IRadio::listeningChangedSignal) { + listeningNotifications++; + lastListeningSource = source; + } + } +}; +Define_Module(TransitionRadioMedium); + +class TransitionMgmtAp : public Ieee80211MgmtAp +{ + bool probing = false; + public: + void checkAdvertisement() + { + Enter_Method_Silent(); + probing = true; + sendBeacon(); + probing = false; + } + protected: + virtual void sendDown(Packet *packet) override + { + if (!probing) { + Ieee80211MgmtAp::sendDown(packet); + return; + } + const auto& body = packet->peekAtFront(); + ASSERT(body->getHtCapabilitiesPresent() == modeSet->isHtOperationSupported()); + ASSERT(body->getHtOperationPresent() == modeSet->isHtOperationSupported()); + const auto& rates = body->getSupportedRates(); + const auto& extended = body->getExtendedSupportedRates(); + const auto& modes = modeSet->getLegacyOperationalModes(); + ASSERT(rates.numRates + extended.numRates == (int)modes.size()); + for (int i = 0; i < (int)modes.size(); i++) { + double rate = i < rates.numRates ? rates.rate[i] : extended.rate[i - rates.numRates]; + bool basic = i < rates.numRates ? rates.basicRate[i] : extended.basicRate[i - rates.numRates]; + ASSERT(rate == modes[i]->getDataMode()->getNetBitrate().get()); + ASSERT(basic == modeSet->getIsMandatory(modes[i])); + } + if (modeSet->isHtOperationSupported()) { + for (int i = 0; i < 77; i++) + ASSERT(body->getHtCapabilities().rxMcsSupported[i] == (i < 8)); + ASSERT(!body->getHtCapabilities().supportedChannelWidth40Mhz); + ASSERT(!body->getHtCapabilities().shortGi40); + } + delete packet; + } +}; +Define_Module(TransitionMgmtAp); + +class CachingModeSetObserver : public cListener +{ + public: + const Ieee80211ModeSet *modeSet = nullptr; + int notifications = 0; + virtual void receiveSignal(cComponent *source, simsignal_t signal, cObject *value, cObject *details) override + { + modeSet = check_and_cast(value); + notifications++; + } +}; + +class Ieee80211ModeSetTransitionTest : public cSimpleModule, public cListener +{ + int notifications = 0; + const Ieee80211ModeSet *observedModeSet = nullptr; + CachingModeSetObserver cachingObserver; + MacAddress peer = MacAddress("02:00:00:00:01:ff"); + + std::vector participants(cModule *module) + { + std::vector result; + if (auto participant = dynamic_cast(module)) + result.push_back(participant); + for (cModule::SubmoduleIterator it(module); !it.end(); ++it) { + auto nested = participants(*it); + result.insert(result.end(), nested.begin(), nested.end()); + } + return result; + } + + void check(cModule *nic, const Ieee80211ModeSet *expected, bool peerExpected) + { + auto radio = check_and_cast(nic->getSubmodule("radio")); + ASSERT(check_and_cast(radio->getTransmitter())->getModeSet() == expected); + ASSERT(check_and_cast(radio->getReceiver())->getModeSet() == expected); + for (auto participant : participants(nic)) + ASSERT(participant->getModeSet() == expected); + auto mib = check_and_cast(nic->getSubmodule("mib")); + ASSERT(mib->isLocalHtCapable() == expected->isHtOperationSupported()); + ASSERT(mib->requirePrimaryChannel() == 6); + ASSERT((mib->findPeerHtState(peer) != nullptr) == peerExpected); + for (int i = 0; i < 77; i++) { + ASSERT(mib->getLocalHtCapabilities().rxMcsSupported[i] == (expected->isHtOperationSupported() && i < 8)); + if (mib->hasHtOperation()) + ASSERT(mib->getHtOperation().basicMcsSupported[i] == (i < 8)); + } + if (peerExpected) { + auto state = mib->findPeerHtState(peer); + ASSERT(state->negotiatedCapabilities->localTxPeerRx.valid); + for (int i = 8; i < 77; i++) + ASSERT(!state->negotiatedCapabilities->localTxPeerRx.supportedMcs[i]); + } + check_and_cast(nic->getSubmodule("mgmt"))->checkAdvertisement(); + auto data = makeShared(); + auto beacon = makeShared(); + beacon->setType(ST_BEACON); + for (auto address : {MacAddress::BROADCAST_ADDRESS, MacAddress("01:00:5e:00:00:01"), peer}) { + data->setReceiverAddress(address); + beacon->setReceiverAddress(address); + Packet packet("selection probe"); + // A retry or protection-prepared frame may still carry an old HT mode. + auto staleMode = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)")->getMode(Mbps(65), MHz(20), 1, 800e-9); + packet.addTag()->setMode(staleMode); + auto mac = nic->getSubmodule("mac"); + auto dcf = check_and_cast(mac->getSubmodule("dcf")->getSubmodule("rateSelection")); + auto qos = check_and_cast(mac->getSubmodule("hcf")->getSubmodule("rateSelection")); + for (const auto& header : {Ptr(data), Ptr(beacon)}) { + auto nonQosMode = dcf->computeMode(&packet, header); + auto qosMode = qos->computeMode(&packet, header, nullptr); + ASSERT(expected->containsMode(nonQosMode)); + ASSERT(expected->containsMode(qosMode)); + if (address.isMulticast()) { + ASSERT(nonQosMode->getHtMcsIndex() < 0); + ASSERT(qosMode->getHtMcsIndex() < 0); + ASSERT(expected->getIsMandatory(nonQosMode)); + ASSERT(expected->getIsMandatory(qosMode)); + } + } + } + } + + protected: + virtual void initialize() override { scheduleAt(SimTime(1, SIMTIME_US), new cMessage("switch")); } + virtual void receiveSignal(cComponent *source, simsignal_t signal, cObject *value, cObject *details) override + { + Enter_Method_Silent(); + notifications++; + auto nic = check_and_cast(source)->getParentModule(); + ASSERT(nic->getSubmodule("mac") != nullptr); + auto target = check_and_cast(value); + observedModeSet = target; + // Publication must follow *all* internal updates, irrespective of where + // this observer appears in the subscriber list. + for (auto participant : participants(nic)) + ASSERT(participant->getModeSet() == target); + ASSERT(check_and_cast(nic->getSubmodule("mib"))->isLocalHtCapable() == target->isHtOperationSupported()); + check_and_cast(nic->getSubmodule("mgmt"))->checkAdvertisement(); + } + + virtual void handleMessage(cMessage *message) override + { + delete message; + auto nic = getParentModule()->getSubmodule("ap", 0)->getSubmodule("wlan", 0); + auto radio = check_and_cast(nic->getSubmodule("radio")); + auto mib = check_and_cast(nic->getSubmodule("mib")); + auto medium = check_and_cast(getParentModule()->getSubmodule("radioMedium")); + auto ht = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"); + auto legacy = Ieee80211ModeSet::getModeSet("g(mixed)"); + radio->setMode(ht->getMode(Mbps(24))); + cachingObserver.modeSet = ht; + nic->subscribe(modesetChangedSignal, &cachingObserver); + nic->subscribe(modesetChangedSignal, this); + mib->setPeerHtCapabilities(peer, mib->getLocalHtCapabilities()); + check(nic, ht, true); + auto originalCapabilities = mib->findPeerCapabilities(peer)->negotiatedCapabilities; + radio->setModeSet(ht); + ASSERT(mib->findPeerCapabilities(peer)->negotiatedCapabilities == originalCapabilities); + ASSERT(notifications == 0 && cachingObserver.notifications == 0); + auto mac = nic->getSubmodule("mac"); + auto arf = check_and_cast(mac->getSubmodule("dcf")->getSubmodule("rateControl")); + auto onoe = check_and_cast(mac->getSubmodule("hcf")->getSubmodule("rateControl")); + arf->getRate(peer); + onoe->getRate(peer); + int oldListening = medium->listeningNotifications; + radio->setModeSet(legacy); + check(nic, legacy, false); + ASSERT(mib->findPeerCapabilities(peer) != nullptr); + ASSERT(mib->findPeerCapabilities(peer)->negotiatedCapabilities != originalCapabilities); + ASSERT(legacy->containsMode(arf->getRate(peer))); + ASSERT(legacy->containsMode(onoe->getRate(peer))); + radio->setModeSetAndMode(ht, ht->getMode(Mbps(65), MHz(20), 1, 800e-9)); + check(nic, ht, true); + ASSERT(ht->containsMode(arf->getRate(peer))); + ASSERT(ht->containsMode(onoe->getRate(peer))); + mib->setPeerHtCapabilities(peer, mib->getLocalHtCapabilities()); + check(nic, ht, true); + ASSERT(notifications == 2); + ASSERT(medium->listeningNotifications == oldListening + 2); + ASSERT(medium->lastListeningSource == radio); + ASSERT(cachingObserver.modeSet == ht); + ASSERT(observedModeSet == ht); + // A failed catalog-only preflight must leave both PHY endpoints, MAC + // consumers and observations untouched, and allow an explicit retry. + auto incompatibleMode = ht->getMode(Mbps(135), MHz(40), 1, 800e-9); + radio->setMode(incompatibleMode); + auto retainedCapabilities = mib->findPeerCapabilities(peer)->negotiatedCapabilities; + int beforeRejectedListening = medium->listeningNotifications; + for (int attempt = 0; attempt < 2; attempt++) { + bool rejected = false; + try { radio->setModeSet(legacy); } + catch (const cRuntimeError& error) { + rejected = std::string(error.what()).find("Cannot map current mode") != std::string::npos; + } + ASSERT(rejected); + check(nic, ht, true); + ASSERT(check_and_cast(radio->getTransmitter())->getMode() == incompatibleMode); + ASSERT(mib->findPeerCapabilities(peer)->negotiatedCapabilities == retainedCapabilities); + ASSERT(notifications == 2 && cachingObserver.notifications == 2); + ASSERT(medium->listeningNotifications == beforeRejectedListening); + } + radio->setModeSetAndMode(legacy, legacy->getMode(Mbps(24))); + check(nic, legacy, false); + ASSERT(notifications == 3 && cachingObserver.notifications == 3); + ASSERT(medium->listeningNotifications == beforeRejectedListening + 1); + ASSERT(cachingObserver.modeSet == legacy && observedModeSet == legacy); + nic->unsubscribe(modesetChangedSignal, this); + nic->unsubscribe(modesetChangedSignal, &cachingObserver); + std::cout << "Mode-set switching, advertisements, peer state, both selectors, and notifications verified.\n"; + endSimulation(); + } +}; +Define_Module(Ieee80211ModeSetTransitionTest); + +%file: test.ned +import inet.common.SimpleModule; +import inet.node.wireless.AccessPoint; +import inet.linklayer.ieee80211.mgmt.Ieee80211MgmtAp; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; + +simple TransitionMgmtAp extends Ieee80211MgmtAp +{ + parameters: + @class(::TransitionMgmtAp); +} + +module TransitionRadioMedium extends Ieee80211ScalarRadioMedium +{ + parameters: + @class(::TransitionRadioMedium); +} + +simple Ieee80211ModeSetTransitionTest extends SimpleModule +{ + parameters: + @class(::Ieee80211ModeSetTransitionTest); +} + +network TransitionNetwork +{ + submodules: + radioMedium: TransitionRadioMedium; + ap[1]: AccessPoint; + test: Ieee80211ModeSetTransitionTest; +} + +%inifile: omnetpp.ini +[General] +network = TransitionNetwork +ned-path = .;../../../../src;../../lib +sim-time-limit = 1ms +seed-set = 0 +cmdenv-express-mode = false +record-vector-results = false +record-scalar-results = false +*.radioMedium.listeningFilter = true +**.mobility.initFromDisplayString = false +**.mobility.initialX = 10m +**.mobility.initialY = 10m +**.wlan[*].opMode = "n(mixed-2.4Ghz)" +**.wlan[*].bitrate = -1bps +**.wlan[*].radio.bandName = "2.4 GHz" +**.wlan[*].radio.channelNumber = 6 +**.wlan[*].radio.transmitter.power = 100mW +**.wlan[*].mac.qosStation = true +*.ap[*].wlan[0].mgmt.typename = "TransitionMgmtAp" +*.ap[*].wlan[0].mgmt.ssid = "transition" +*.ap[*].wlan[0].mgmt.beaconInterval = 1s +*.ap[*].wlan[0].mac.dcf.rateControl.typename = "AarfRateControl" +*.ap[*].wlan[0].mac.hcf.rateControl.typename = "OnoeRateControl" + +%contains: stdout +Mode-set switching, advertisements, peer state, both selectors, and notifications verified. diff --git a/tests/module/Ieee80211ReceptionReconfiguration_1.test b/tests/module/Ieee80211ReceptionReconfiguration_1.test new file mode 100644 index 00000000000..cda32204264 --- /dev/null +++ b/tests/module/Ieee80211ReceptionReconfiguration_1.test @@ -0,0 +1,254 @@ +%description: +Reconfigure during HT and legacy receptions through each IEEE 802.11 radio setter. +Compatible changes retain decoding and deliver the original packet exactly once. +Removing the incoming mode or retuning interrupts decoding, retains arrival timers +until cleanup, and suppresses the original packet. Both paths receive a later packet. +Exercise whole-signal and separate-part reception with seed 0. + +%file: ReceptionReconfiguration.cc +#include "inet/common/Simsignals.h" +#include "inet/common/packet/chunk/ByteCountChunk.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" + +using namespace inet; +using namespace inet::physicallayer; + +class ReconfigurationRadio : public Ieee80211Radio +{ + public: + bool hasActiveReception() const { return receptionTimer != nullptr; } + size_t getArrivalTimerCount() const { return allReceptionTimers.size(); } + void reconfigure(int operation) + { + Enter_Method_Silent(); + const auto *legacy = Ieee80211ModeSet::getModeSet("g(mixed)"); + switch (operation) { + case 0: setModeSet(legacy); break; + case 1: setModeSetAndMode(legacy, legacy->getMode(Mbps(6))); break; + case 2: setMode(legacy->getMode(Mbps(6))); break; + case 3: setBand(getChannel()->getBand()); break; + case 4: { + setChannel(new Ieee80211Channel(getChannel()->getBand(), 11)); + break; + } + case 5: setChannelNumber(11); break; + case 6: setModeSet(Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)")); break; + case 7: setMode(legacy->getMode(Mbps(12))); break; + case 8: setChannel(new Ieee80211Channel(getChannel()->getBand(), getChannel()->getChannelNumber())); break; + case 9: setChannelNumber(getChannel()->getChannelNumber()); break; + case 10: setBand(Ieee80211CompliantBands::getBand("5 GHz")); break; + default: throw cRuntimeError("Unknown operation"); + } + } + void prepareNextReception() + { + Enter_Method_Silent(); + // Reusing the radio-owned channel must also preserve distinct PHY owners. + setChannel(getChannel()); + setModeSet(Ieee80211ModeSet::getModeSet("g(mixed)")); + setBand(Ieee80211CompliantBands::getBand("2.4 GHz")); + setChannelNumber(6); + } +}; +Define_Module(ReconfigurationRadio); + +class ReceptionReconfigurationTest : public cSimpleModule, public cListener +{ + int delivered = 0; + int stateNotifications = 0; + int partNotifications = 0; + int configurationNotifications = 0; + bool switching = false; + + ReconfigurationRadio *receiverRadio() const + { + return check_and_cast(getParentModule()->getSubmodule("rx")); + } + bool expectAbort() const + { + int operation = par("operation"); + return operation == 4 || operation == 5 || operation == 10 || + ((operation == 0 || operation == 1) && par("incomingHt").boolValue()); + } + void checkReconfigured() const + { + if (expectAbort()) + checkAborted(); + else { + auto radio = receiverRadio(); + ASSERT(radio->hasActiveReception()); + ASSERT(radio->getReceptionState() == IRadio::RECEPTION_STATE_RECEIVING); + ASSERT(radio->getReceivedSignalPart() != IRadioSignal::SIGNAL_PART_NONE); + ASSERT(radio->getArrivalTimerCount() == 1); + } + } + void checkAborted() const + { + auto radio = receiverRadio(); + ASSERT(!radio->hasActiveReception()); + ASSERT(radio->getReceptionState() != IRadio::RECEPTION_STATE_RECEIVING); + ASSERT(radio->getReceivedSignalPart() == IRadioSignal::SIGNAL_PART_NONE); + ASSERT(radio->getArrivalTimerCount() == 1); + } + void transmit(bool first) + { + auto packet = new Packet(first ? "original" : "subsequent"); + packet->insertAtBack(makeShared(B(1500))); + bool ht = first && par("incomingHt").boolValue(); + const auto *catalog = Ieee80211ModeSet::getModeSet(ht ? "n(mixed-2.4Ghz)" : "g(mixed)"); + const auto *mode = ht ? catalog->getMode(Mbps(6.5), MHz(20), 1, 800e-9) : catalog->getMode(Mbps(12)); + packet->addTag()->setMode(mode); + send(packet, "out"); + } + protected: + virtual void initialize() override + { + scheduleAt(SimTime(1, SIMTIME_MS), new cMessage("first", 0)); + scheduleAt(SimTime(1100, SIMTIME_US), new cMessage("change", 1)); + scheduleAt(SimTime(4, SIMTIME_MS), new cMessage("second", 2)); + scheduleAt(SimTime(7, SIMTIME_MS), new cMessage("check", 3)); + } + virtual void receiveSignal(cComponent *, simsignal_t signal, intval_t, cObject *) override + { + if (!switching) + return; + if (signal == IRadio::receptionStateChangedSignal) + stateNotifications++; + else if (signal == IRadio::receivedSignalPartChangedSignal) + partNotifications++; + else { + checkReconfigured(); + configurationNotifications++; + } + } + virtual void receiveSignal(cComponent *, simsignal_t, cObject *, cObject *) override + { + if (switching) { + checkReconfigured(); + configurationNotifications++; + } + } + virtual void handleMessage(cMessage *message) override + { + if (!message->isSelfMessage()) { + auto packet = check_and_cast(message); + if (delivered == 0 && !expectAbort()) + ASSERT(!strcmp(packet->getName(), "original")); + else + ASSERT(!strcmp(packet->getName(), "subsequent")); + ASSERT(!packet->hasBitError()); + delivered++; + delete packet; + return; + } + int phase = message->getKind(); + delete message; + auto radio = receiverRadio(); + if (phase == 0) { + check_and_cast(getParentModule()->getSubmodule("tx"))->setRadioMode(IRadio::RADIO_MODE_TRANSMITTER); + radio->setRadioMode(IRadio::RADIO_MODE_RECEIVER); + for (auto signal : {IRadio::receptionStateChangedSignal, IRadio::receivedSignalPartChangedSignal, + IRadio::listeningChangedSignal, Ieee80211Radio::radioChannelChangedSignal, modesetChangedSignal}) + radio->subscribe(signal, this); + transmit(true); + } + else if (phase == 1) { + ASSERT(radio->hasActiveReception()); + ASSERT(radio->getReceptionState() == IRadio::RECEPTION_STATE_RECEIVING); + ASSERT(radio->getReceivedSignalPart() == (radio->par("separateReceptionParts").boolValue() ? + IRadioSignal::SIGNAL_PART_DATA : IRadioSignal::SIGNAL_PART_WHOLE)); + switching = true; + radio->reconfigure(par("operation")); + switching = false; + checkReconfigured(); + ASSERT(stateNotifications == (expectAbort() ? 1 : 0)); + ASSERT(partNotifications == (expectAbort() ? 1 : 0)); + ASSERT(configurationNotifications >= 1); + } + else if (phase == 2) { + ASSERT(radio->getArrivalTimerCount() == 0); + ASSERT(delivered == (expectAbort() ? 0 : 1)); + radio->prepareNextReception(); + transmit(false); + } + else { + ASSERT(delivered == (expectAbort() ? 1 : 2)); + ASSERT(!radio->hasActiveReception()); + ASSERT(radio->getArrivalTimerCount() == 0); + ASSERT(radio->getReceptionState() == IRadio::RECEPTION_STATE_IDLE); + for (auto signal : {IRadio::receptionStateChangedSignal, IRadio::receivedSignalPartChangedSignal, + IRadio::listeningChangedSignal, Ieee80211Radio::radioChannelChangedSignal, modesetChangedSignal}) + radio->unsubscribe(signal, this); + std::cout << "Reception compatibility, timer cleanup and subsequent delivery verified.\n"; + endSimulation(); + } + } +}; +Define_Module(ReceptionReconfigurationTest); + +%file: test.ned +import inet.common.SimpleModule; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadio; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; + +module ReconfigurationRadio extends Ieee80211ScalarRadio +{ + parameters: + @class(::ReconfigurationRadio); +} + +simple ReceptionReconfigurationTest extends SimpleModule +{ + parameters: + int operation; + bool incomingHt; + @class(::ReceptionReconfigurationTest); + gates: + input in; + output out; +} + +network ReceptionNetwork +{ + submodules: + radioMedium: Ieee80211ScalarRadioMedium; + tx: Ieee80211ScalarRadio; + rx: ReconfigurationRadio; + test: ReceptionReconfigurationTest; + connections allowunconnected: + test.out --> tx.upperLayerIn; + rx.upperLayerOut --> test.in; +} + +%inifile: omnetpp.ini +[General] +network = ReceptionNetwork +ned-path = .;../../../../src;../../lib +sim-time-limit = 8ms +seed-set = 0 +cmdenv-express-mode = false +record-vector-results = false +record-scalar-results = false +*.test.operation = ${operation=0,1,2,3,4,5,6,7,8,9,10} +*.test.incomingHt = ${incomingHt=false,true} +*.rx.separateReceptionParts = ${parts=false,true} +*.tx.separateTransmissionParts = ${parts} +*.tx.initialRadioMode = "off" +*.rx.initialRadioMode = "off" +*.radioMedium.listeningFilter = true +*.tx.opMode = "n(mixed-2.4Ghz)" +*.rx.opMode = "n(mixed-2.4Ghz)" +**.channelNumber = 6 +**.bandName = "2.4 GHz" +**.transmitter.bitrate = 6Mbps +**.transmitter.power = 100mW +**.antenna.mobilityModule = "" +**.antenna.mobility.typename = "StationaryMobility" +**.antenna.mobility.initFromDisplayString = false +**.antenna.mobility.initialX = 10m +**.antenna.mobility.initialY = 10m +**.antenna.mobility.initialZ = 0m + +%contains: stdout +Reception compatibility, timer cleanup and subsequent delivery verified. diff --git a/tests/module/Ieee80211TxopModeSet_1.test b/tests/module/Ieee80211TxopModeSet_1.test new file mode 100644 index 00000000000..39ea8d97409 --- /dev/null +++ b/tests/module/Ieee80211TxopModeSet_1.test @@ -0,0 +1,98 @@ +%description: +A runtime radio mode-set transition updates default TXOP limits for the next +TXOP, preserving an active TXOP and explicit overrides. Exercise every AC, +both b/g transition directions, and default, zero and positive overrides. + +%file: TxopModeSet.cc +#include "inet/linklayer/ieee80211/mac/originator/TxopProcedure.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" + +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +class TxopModeSetTest : public cSimpleModule +{ + protected: + virtual void initialize() override { scheduleAt(SimTime(1, SIMTIME_MS), new cMessage("check")); } + virtual void handleMessage(cMessage *message) override + { + delete message; + auto nic = getParentModule()->getSubmodule("ap")->getSubmodule("wlan", 0); + auto radio = check_and_cast(nic->getSubmodule("radio")); + auto edca = nic->getModuleByPath(".mac.hcf.edca"); + auto bModes = Ieee80211ModeSet::getModeSet("b"); + auto gModes = Ieee80211ModeSet::getModeSet("g(mixed)"); + // Existing modeled defaults (IEEE Std 802.11-2024, Table 9-194; + // TxopProcedure documents the retained legacy table values). + const int bLimitsUs[] = {0, 0, 6016, 3264}; + const int gLimitsUs[] = {0, 0, 3008, 1504}; + for (int index = 0; index < 4; index++) { + auto txop = check_and_cast(edca->getSubmodule("edcaf", index)->getSubmodule("txopProcedure")); + auto ac = static_cast(index); + simtime_t overrideLimit = txop->par("txopLimit"); + auto bLimit = overrideLimit == -1 ? SimTime(bLimitsUs[index], SIMTIME_US) : overrideLimit; + auto gLimit = overrideLimit == -1 ? SimTime(gLimitsUs[index], SIMTIME_US) : overrideLimit; + txop->startTxop(ac); + ASSERT(txop->getLimit() == bLimit); + radio->setModeSetAndMode(gModes, gModes->getMode(Mbps(24))); + ASSERT(txop->getModeSet() == gModes); + ASSERT(txop->getLimit() == bLimit); + ASSERT(txop->getRemaining() == bLimit); + txop->endTxop(); + txop->startTxop(ac); + ASSERT(txop->getLimit() == gLimit); + ASSERT(txop->getRemaining() == gLimit); + txop->endTxop(); + radio->setModeSetAndMode(bModes, bModes->getMode(Mbps(11))); + txop->startTxop(ac); + ASSERT(txop->getLimit() == bLimit); + ASSERT(txop->getRemaining() == bLimit); + txop->endTxop(); + } + std::cout << "Verified TXOP limits across mode transitions" << std::endl; + endSimulation(); + } +}; +Define_Module(TxopModeSetTest); + +%file: test.ned +import inet.node.wireless.AccessPoint; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; +simple TxopModeSetTest +{ + parameters: + @class(::TxopModeSetTest); +} +network TxopModeSetNetwork +{ + submodules: + radioMedium: Ieee80211ScalarRadioMedium; + ap: AccessPoint; + test: TxopModeSetTest; +} + +%inifile: omnetpp.ini +[General] +network = TxopModeSetNetwork +ned-path = .;../../../../src;../../lib +seed-set = 0 +sim-time-limit = 2ms +record-scalar-results = false +record-vector-results = false +**.mobility.initFromDisplayString = false +**.mobility.initialX = 10m +**.mobility.initialY = 10m +**.wlan[*].opMode = "b" +**.wlan[*].bitrate = -1bps +**.wlan[*].radio.bandName = "2.4 GHz" +**.wlan[*].radio.channelNumber = 6 +**.wlan[*].mac.qosStation = true +**.txopProcedure.txopLimit = ${-1s,0s,1ms} +*.ap.wlan[0].mgmt.typename = "Ieee80211MgmtApSimplified" + +%file: check_results.py +from pathlib import Path +assert Path("test.out").read_text().count("Verified TXOP limits across mode transitions") == 3 + +%postrun-command: python3 check_results.py diff --git a/tests/module/Ieee80211VhtAssociation_1.test b/tests/module/Ieee80211VhtAssociation_1.test new file mode 100644 index 00000000000..d3af71d2ea5 --- /dev/null +++ b/tests/module/Ieee80211VhtAssociation_1.test @@ -0,0 +1,323 @@ +%description: +Detailed AP/STA VHT association and reassociation with two asymmetric peers. +Exercise DCF and HCF, serialized IEs, negotiated data transmissions, absent-IE +fallback, disassociation isolation and coordinated mode-set invalidation. + +%file: VhtAssociation.cc +#include "inet/common/Simsignals.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Mac.h" +#include "inet/linklayer/ieee80211/mac/ratecontrol/AarfRateControl.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtSta.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211VhtMgmtElements.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h" +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +// Request the catalog maximum so peer filtering must narrow width/NSS/MCS/GI. +class VhtProbeRateControl : public AarfRateControl +{ + protected: + virtual const IIeee80211Mode *getInitialMode() override { return modeSet->getFastestMode(); } +}; +Define_Module(VhtProbeRateControl); + +class VhtProbeMac : public Ieee80211Mac +{ + public: + int delivered = 0; + int vhtSent = 0; + int legacySent = 0; + int requests = 0; + int responses = 0; + int reRequests = 0; + int reResponses = 0; + virtual void sendDownFrame(Packet *packet) override + { + auto header = packet->peekAtFront(); + if (dynamicPtrCast(header)) { + auto mode = packet->getTag()->getMode(); + auto peer = mib->findPeerVhtState(header->getReceiverAddress()); + if (peer == nullptr) { + ASSERT(mode->getVhtMcsIndex() < 0); + legacySent++; + } + else { + ASSERT(mode->getVhtMcsIndex() >= 0); + auto data = mode->getDataMode(); + int nss = data->getNumberOfSpatialStreams(); + ASSERT(mode->getVhtMcsIndex() <= peer->advertisedCapabilities.rxMaxMcs[nss - 1]); + ASSERT(mode->getVhtMcsIndex() <= mib->getLocalVhtCapabilities().txMaxMcs[nss - 1]); + ASSERT(data->getBandwidth() == MHz(20)); + ASSERT(data->getGuardInterval() == SimTime(800, SIMTIME_NS)); + vhtSent++; + } + } + else if (header->getType() == ST_ASSOCIATIONREQUEST || header->getType() == ST_REASSOCIATIONREQUEST || + header->getType() == ST_ASSOCIATIONRESPONSE || header->getType() == ST_REASSOCIATIONRESPONSE) { + auto body = packet->peekAt(header->getChunkLength()); + bool response = header->getType() == ST_ASSOCIATIONRESPONSE || header->getType() == ST_REASSOCIATIONRESPONSE; + ASSERT(body->getVhtCapabilitiesPresent() == mib->isVhtOperationSupported()); + ASSERT(body->getVhtOperationPresent() == (response && mib->isVhtOperationSupported())); + Packet wire("actual-management-body", body); + auto bytes = wire.peekAllAsBytes(); + Packet encoded("encoded", bytes); + Ptr decoded; + if (header->getType() == ST_ASSOCIATIONREQUEST) { + decoded = encoded.peekAtFront(); + requests++; + } + else if (header->getType() == ST_REASSOCIATIONREQUEST) { + decoded = encoded.peekAtFront(); + reRequests++; + } + else if (header->getType() == ST_ASSOCIATIONRESPONSE) { + decoded = encoded.peekAtFront(); + responses++; + } + else { + decoded = encoded.peekAtFront(); + reResponses++; + } + ASSERT(!decoded->isIncorrect() && !decoded->isIncomplete()); + ASSERT(decoded->getVhtCapabilitiesPresent() == body->getVhtCapabilitiesPresent()); + if (body->getVhtCapabilitiesPresent()) + for (int i = 0; i < 8; i++) { + ASSERT(decoded->getVhtCapabilities().rxMaxMcs[i] == body->getVhtCapabilities().rxMaxMcs[i]); + ASSERT(decoded->getVhtCapabilities().txMaxMcs[i] == body->getVhtCapabilities().txMaxMcs[i]); + } + } + Ieee80211Mac::sendDownFrame(packet); + } + virtual void sendUpFrame(Packet *packet) override + { + if (dynamicPtrCast(packet->peekAtFront())) { + Enter_Method("sendUpFrame"); + take(packet); + delivered++; + delete packet; // End synthetic data at the MAC service boundary. + } + else + Ieee80211Mac::sendUpFrame(packet); + } +}; +Define_Module(VhtProbeMac); + +// Inherited AP frame builders must dispatch through the advertisement hooks. +class VhtProbeAp : public Ieee80211MgmtAp +{ + public: + mutable int capabilitiesAdded = 0; + mutable int operationAdded = 0; + protected: + virtual void addVhtCapabilities(const Ptr& frame) const override + { + capabilitiesAdded++; + Ieee80211MgmtAp::addVhtCapabilities(frame); + } + virtual void addVhtOperation(const Ptr& frame) const override + { + operationAdded++; + Ieee80211MgmtAp::addVhtOperation(frame); + } +}; +Define_Module(VhtProbeAp); + +class VhtProbeSta : public Ieee80211MgmtSta +{ + public: + void reassociate(const MacAddress& ap) + { + Enter_Method("reassociate"); + Ieee80211Prim_ReassociateRequest request; + request.setAddress(ap); + request.setTimeout(SimTime(20, SIMTIME_MS)); + processReassociateCommand(&request); + } + void disconnect(const MacAddress& ap) + { + Enter_Method("disconnect"); + Ieee80211Prim_DisassociateRequest request; + request.setAddress(ap); + request.setReasonCode(RC_DIASS_MS_LEAVING); + processDisassociateCommand(&request); + } +}; +Define_Module(VhtProbeSta); + +class VhtAssociationTest : public cSimpleModule +{ + cMessage *action = nullptr; + int phase = 0; + Ieee80211Mib *mib(const char *host) { return check_and_cast(getModuleByPath((std::string("^.") + host + ".wlan[0].mib").c_str())); } + VhtProbeMac *mac(const char *host) { return check_and_cast(getModuleByPath((std::string("^.") + host + ".wlan[0].mac").c_str())); } + void sendData(const char *from, const char *to) + { + auto sender = mac(from); + auto receiver = mac(to); + auto packet = new Packet("negotiated-vht-data"); + auto header = makeShared(); + header->setType(par("qos") ? ST_DATA_WITH_QOS : ST_DATA); + header->setChunkLength(DATAFRAME_HEADER_MINLENGTH + (par("qos") ? QOSCONTROL_PART_LENGTH : b(0))); + header->setTid(0); + header->setReceiverAddress(receiver->getAddress()); + header->setTransmitterAddress(sender->getAddress()); + header->setAddress3(receiver->getAddress()); + packet->insertAtBack(header); + packet->insertAtBack(makeShared(B(100))); + auto trailer = makeShared(); + trailer->setFcsMode(FCS_DECLARED_CORRECT); + packet->insertAtBack(trailer); + sender->processUpperFrame(packet, header); + } + protected: + virtual void initialize() override { action = new cMessage("phase"); scheduleAt(SimTime(30, SIMTIME_MS), action); } + virtual void handleMessage(cMessage *) override + { + auto ap = mib("ap"); + auto first = mib("sta[0]"); + auto second = mib("sta[1]"); + bool missing = par("missing"); + if (phase == 0) { + ASSERT(first->getBssStationData().isAssociated && second->getBssStationData().isAssociated); + ASSERT(ap->findPeerVhtState(first->address)->advertisedCapabilities.rxMaxMcs[0] == 7); + ASSERT((ap->findPeerVhtState(second->address) == nullptr) == missing); + ASSERT((second->findPeerVhtState(ap->address) == nullptr) == missing); + if (!missing) { + ASSERT(ap->findPeerVhtState(second->address)->advertisedCapabilities.rxMaxMcs[0] == 8); + ASSERT(ap->findPeerVhtState(second->address)->advertisedCapabilities.rxMaxMcs[1] == 8); + } + sendData("ap", "sta[0]"); + sendData("ap", "sta[1]"); + sendData("sta[0]", "ap"); + sendData("sta[1]", "ap"); + } + else if (phase == 1) { + ASSERT(mac("ap")->delivered == 2 && mac("sta[0]")->delivered == 1 && mac("sta[1]")->delivered == 1); + ASSERT(mac("sta[0]")->vhtSent >= 1); + ASSERT(missing ? mac("sta[1]")->legacySent >= 1 : mac("sta[1]")->vhtSent >= 1); + // Change only STA0's receiver limit before a real same-AP reassociation. + auto capabilities = first->getLocalVhtCapabilities(); + capabilities.rxMaxMcs[0] = 8; + first->installLocalVhtCapabilities(capabilities, true); + check_and_cast(getModuleByPath("^.sta[0].wlan[0].mgmt"))->reassociate(ap->address); + } + else if (phase == 2) { + ASSERT(first->getBssStationData().isAssociated); + ASSERT(ap->findPeerVhtState(first->address)->advertisedCapabilities.rxMaxMcs[0] == 8); + ASSERT((ap->findPeerVhtState(second->address) == nullptr) == missing); + ASSERT(mac("sta[0]")->reRequests > 0 && mac("ap")->reResponses > 0); + sendData("ap", "sta[0]"); + } + else if (phase == 3) { + ASSERT(mac("sta[0]")->delivered == 2); + check_and_cast(getModuleByPath("^.sta[0].wlan[0].mgmt"))->disconnect(ap->address); + ASSERT(first->findPeerVhtState(ap->address) == nullptr); + } + else if (phase == 4) { + ASSERT(ap->findPeerVhtState(first->address) == nullptr); + ASSERT((ap->findPeerVhtState(second->address) == nullptr) == missing); + auto radio = check_and_cast(getModuleByPath("^.ap.wlan[0].radio")); + ASSERT(radio->getTransmissionState() != IRadio::TRANSMISSION_STATE_TRANSMITTING); + radio->setMode(Ieee80211ModeSet::getModeSet("ac")->getMode(Mbps(24))); + radio->setModeSet(Ieee80211ModeSet::getModeSet("a")); + radio->setModeSet(Ieee80211ModeSet::getModeSet("ac")); + ASSERT(ap->findPeerVhtState(second->address) == nullptr); + sendData("ap", "sta[1]"); + } + else { + ASSERT(mac("sta[1]")->delivered == 2); + ASSERT(mac("ap")->legacySent >= 1); + ASSERT(mac("ap")->responses >= 2 && mac("sta[0]")->requests >= 1 && mac("sta[1]")->requests >= 1); + auto mgmt = check_and_cast(getModuleByPath("^.ap.wlan[0].mgmt")); + ASSERT(mgmt->capabilitiesAdded >= mac("ap")->responses); + ASSERT(mgmt->operationAdded >= mac("ap")->responses); + std::cout << "VHT exchange verified: qos=" << par("qos").boolValue() << " missing=" << missing << "\n"; + endSimulation(); + return; + } + phase++; + scheduleAfter(SimTime(10, SIMTIME_MS), action); + } + virtual void finish() override { ASSERT(phase == 5); } + public: + virtual ~VhtAssociationTest() { cancelAndDelete(action); } +}; +Define_Module(VhtAssociationTest); + +%file: test.ned +import inet.node.inet.WirelessHost; +import inet.node.wireless.AccessPoint; +import inet.linklayer.ieee80211.mac.Ieee80211Mac; +import inet.linklayer.ieee80211.mac.ratecontrol.AarfRateControl; +import inet.linklayer.ieee80211.mgmt.Ieee80211MgmtSta; +import inet.linklayer.ieee80211.mgmt.Ieee80211MgmtAp; +import inet.physicallayer.wireless.ieee80211.packetlevel.Ieee80211ScalarRadioMedium; +simple VhtProbeRateControl extends AarfRateControl { parameters: @class(::VhtProbeRateControl); } +module VhtProbeMac extends Ieee80211Mac { parameters: @class(::VhtProbeMac); } +simple VhtProbeAp extends Ieee80211MgmtAp { parameters: @class(::VhtProbeAp); } +simple VhtProbeSta extends Ieee80211MgmtSta { parameters: @class(::VhtProbeSta); } +simple VhtAssociationTest { parameters: @class(::VhtAssociationTest); bool qos; bool missing; } +network VhtNetwork +{ + submodules: + radioMedium: Ieee80211ScalarRadioMedium; + ap: AccessPoint; + sta[2]: WirelessHost; + test: VhtAssociationTest; +} + +%inifile: omnetpp.ini +[General] +network = VhtNetwork +ned-path = .;../../../../src;../../lib +sim-time-limit = 100ms +seed-set = 0 +*.radioMedium.sameTransmissionStartTimeCheck = "ignore" # Deterministic simultaneous data submissions exercise contention. +cmdenv-express-mode = false +record-vector-results = false +record-scalar-results = false +*.test.qos = ${qos=false,true} +*.test.missing = ${missing=false,true} +**.mobility.initFromDisplayString = false +*.ap.mobility.initialX = 10m +*.sta[0].mobility.initialX = 11m +*.sta[1].mobility.initialX = 12m +**.mobility.initialY = 10m +**.mobility.initialZ = 0m +**.wlan[*].opMode = "ac" +**.wlan[*].bitrate = -1bps +**.wlan[*].radio.bandName = "5 GHz (20 MHz)" +**.wlan[*].radio.channelNumber = 0 +**.wlan[*].radio.antenna.numAntennas = 2 +**.wlan[*].radio.transmitter.power = 100mW +**.wlan[*].mac.typename = "VhtProbeMac" +**.mac.qosStation = ${qos} +**.mac.*.rateControl.typename = "VhtProbeRateControl" +*.sta[*].wlan[*].mgmt.typename = "VhtProbeSta" +*.sta[*].wlan[*].agent.typename = "Ieee80211AgentSta" +*.ap.wlan[*].mgmt.typename = "VhtProbeAp" +*.ap.wlan[*].mgmt.beaconInterval = 1s +*.sta[0].wlan[*].mib.vhtRxMcsMap = [7,-1,-1,-1,-1,-1,-1,-1] +*.sta[0].wlan[*].mib.vhtTxMcsMap = [8,8,-1,-1,-1,-1,-1,-1] +*.sta[1].wlan[*].mib.vhtRxMcsMap = [8,8,-1,-1,-1,-1,-1,-1] +*.sta[1].wlan[*].mib.vhtTxMcsMap = [7,-1,-1,-1,-1,-1,-1,-1] +*.sta[1].wlan[*].mib.vhtSupported = !${missing} +*.sta[0].wlan[*].agent.startingTime = 0s +*.sta[1].wlan[*].agent.startingTime = 10ms +*.sta[*].wlan[*].agent.channelsToScan = "0" +*.sta[*].wlan[*].agent.probeDelay = 1ms +*.sta[*].wlan[*].agent.minChannelTime = 5ms +*.sta[*].wlan[*].agent.maxChannelTime = 5ms +*.sta[*].wlan[*].agent.authenticationTimeout = 20ms +*.sta[*].wlan[*].agent.associationTimeout = 20ms + +%file: check.py +from pathlib import Path +text = Path('test.out').read_text() +for qos in (0, 1): + for missing in (0, 1): + assert text.count(f'VHT exchange verified: qos={qos} missing={missing}') == 1 +%postrun-command: python3 check.py 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/Ieee80211GroupModeSelection_1.test b/tests/unit/Ieee80211GroupModeSelection_1.test new file mode 100644 index 00000000000..8a0697e76ad --- /dev/null +++ b/tests/unit/Ieee80211GroupModeSelection_1.test @@ -0,0 +1,52 @@ +%description: +Group selection preserves an equivalent external basic legacy mode, while +retaining active-catalog identity and the existing fastest-basic fallback. + +%includes: +#include "inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211OfdmMode.h" + +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +%global: +class GroupModeSet : public Ieee80211ModeSet +{ + public: + GroupModeSet(const std::vector& entries, const IIeee80211Mode *reference) : + Ieee80211ModeSet("group", entries, reference, PhyType::OFDM, false) {} +}; + +%activity: +const auto *catalog = Ieee80211ModeSet::getModeSet("a"); +const auto *six = check_and_cast(catalog->getMode(Mbps(6))); +const auto *twelve = catalog->getMode(Mbps(12)); +const auto *twentyFour = catalog->getMode(Mbps(24)); +const auto *nine = catalog->getMode(Mbps(9)); +// Copy the mode wrapper only; its immutable constituent modes are borrowed. +Ieee80211OfdmMode externalSix(*six); +GroupModeSet basic({{true, six, true}, {true, twelve, true}, {true, twentyFour, true}, {false, nine, true}}, six); +ASSERT(!basic.containsMode(&externalSix)); +ASSERT(basic.findCompatibleMode(&externalSix) == six); +ASSERT(selectGroupAddressedMode(&basic, &externalSix) == six); +ASSERT(selectGroupAddressedMode(&basic, six) == six); +ASSERT(selectGroupAddressedMode(&basic, twelve) == twelve); +ASSERT(selectGroupAddressedMode(&basic, nine) == twentyFour); +ASSERT(selectGroupAddressedMode(&basic, catalog->getMode(Mbps(54))) == twentyFour); +ASSERT(selectGroupAddressedMode(&basic, nullptr) == twentyFour); +// A compatible non-basic or non-operational entry must not become a basic rate. +GroupModeSet optional({{false, six, true}, {true, twentyFour, true}}, twentyFour); +ASSERT(selectGroupAddressedMode(&optional, &externalSix) == twentyFour); +GroupModeSet nonOperational({{true, six, false}, {true, twentyFour, true}}, twentyFour); +ASSERT(selectGroupAddressedMode(&nonOperational, &externalSix) == twentyFour); +// Exact membership wins even if an earlier equal tuple has different eligibility. +GroupModeSet duplicate({{false, &externalSix, true}, {true, six, true}, {true, twentyFour, true}}, six); +ASSERT(selectGroupAddressedMode(&duplicate, six) == six); +GroupModeSet noBasic({{false, six, true}}, six); +ASSERT(selectGroupAddressedMode(&noBasic, &externalSix) == &externalSix); +ASSERT(selectGroupAddressedMode(&noBasic, nullptr) == nullptr); +std::cout << "Group mode compatibility and fallback verified.\n"; + +%contains: stdout +Group mode compatibility and fallback verified. 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/Ieee80211HtGuardInterval_1.test b/tests/unit/Ieee80211HtGuardInterval_1.test new file mode 100644 index 00000000000..8c5cec83e78 --- /dev/null +++ b/tests/unit/Ieee80211HtGuardInterval_1.test @@ -0,0 +1,575 @@ +%description: +Validate complete IEEE 802.11 HT long/short guard-interval catalog, lookup, and airtime. + +%includes: +#include +#include +#include + +#include "inet/common/Simsignals.h" +#include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h" +#include "inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtBase.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Receiver.h" +#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h" + +%global: +using namespace inet; +using namespace inet::physicallayer; + +// Same OFDM tuple except for an absent GI, to isolate compatibility semantics. +class AbsentGiDataMode : public Ieee80211OfdmDataMode +{ + public: + AbsentGiDataMode(const Ieee80211OfdmDataMode& source) : Ieee80211OfdmDataMode(source) {} + virtual const simtime_t getGuardInterval() const override { return -1; } +}; + +class AbsentGiMode : public Ieee80211OfdmMode +{ + AbsentGiDataMode data; + public: + AbsentGiMode(const Ieee80211OfdmMode& source) : Ieee80211OfdmMode(source), data(*source.getDataMode()) {} + virtual const Ieee80211OfdmDataMode *getDataMode() const override { return &data; } +}; + +class TestIeee80211Transmitter : public Ieee80211Transmitter +{ + public: + const IIeee80211Mode *getSelectedMode() const { return mode; } + const Ieee80211ModeSet *getSelectedModeSet() const { return modeSet; } +}; + +class TestIeee80211Receiver : public Ieee80211Receiver +{ + public: + const Ieee80211ModeSet *getSelectedModeSet() const { return modeSet; } +}; + +class TestIeee80211Radio : public Ieee80211Radio +{ + public: + void setup(ITransmitter *tx, IReceiver *rx) + { + transmitter = tx; + receiver = rx; + } +}; + +class TestModeSetListener : public cListener +{ + public: + const Ieee80211ModeSet *receivedModeSet = nullptr; + int notifications = 0; + + virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj, cObject *details) override + { + if (signalID == modesetChangedSignal) { + receivedModeSet = dynamic_cast(obj); + notifications++; + } + } +}; + +class TestRateSelectionAccessor : public ieee80211::RateSelection +{ + public: + static const IIeee80211Mode *getDataFrameMode(const ieee80211::RateSelection *rs) { return static_cast(rs)->dataFrameMode; } + static const IIeee80211Mode *getMulticastFrameMode(const ieee80211::RateSelection *rs) { return static_cast(rs)->multicastFrameMode; } + static const IIeee80211Mode *getMgmtFrameMode(const ieee80211::RateSelection *rs) { return static_cast(rs)->mgmtFrameMode; } + static const IIeee80211Mode *getControlFrameMode(const ieee80211::RateSelection *rs) { return static_cast(rs)->controlFrameMode; } + static const IIeee80211Mode *getResponseAckFrameMode(const ieee80211::RateSelection *rs) { return static_cast(rs)->responseAckFrameMode; } + static const IIeee80211Mode *getResponseCtsFrameMode(const ieee80211::RateSelection *rs) { return static_cast(rs)->responseCtsFrameMode; } + static const IIeee80211Mode *getFastestMandatoryMode(const ieee80211::RateSelection *rs) { return static_cast(rs)->fastestMandatoryMode; } + static const Ieee80211ModeSet *readModeSet(const ieee80211::RateSelection *rs) { return static_cast(rs)->modeSet; } + static void notifyModeSet(ieee80211::RateSelection *rs, const Ieee80211ModeSet *ms) { + rs->applyModeSet(ms); + } +}; + +class TestQosRateSelectionAccessor : public ieee80211::QosRateSelection +{ + public: + static const IIeee80211Mode *getDataFrameMode(const ieee80211::QosRateSelection *rs) { return static_cast(rs)->dataFrameMode; } + static const IIeee80211Mode *getResponseBlockAckFrameMode(const ieee80211::QosRateSelection *rs) { return static_cast(rs)->responseBlockAckFrameMode; } + static const IIeee80211Mode *getFastestMandatoryMode(const ieee80211::QosRateSelection *rs) { return static_cast(rs)->fastestMandatoryMode; } + static const Ieee80211ModeSet *readModeSet(const ieee80211::QosRateSelection *rs) { return static_cast(rs)->modeSet; } + static void notifyModeSet(ieee80211::QosRateSelection *rs, const Ieee80211ModeSet *ms) { + rs->applyModeSet(ms); + } +}; + +class TestIeee80211Mgmt : public ieee80211::Ieee80211MgmtBase +{ + protected: + virtual void handleTimer(cMessage *frame) override {} + virtual void handleCommand(int msgkind, cObject *ctrl) override {} + virtual void handleAuthenticationFrame(Packet *packet, const Ptr& header) override {} + virtual void handleDeauthenticationFrame(Packet *packet, const Ptr& header) override {} + virtual void handleAssociationRequestFrame(Packet *packet, const Ptr& header) override {} + virtual void handleAssociationResponseFrame(Packet *packet, const Ptr& header) override {} + virtual void handleReassociationRequestFrame(Packet *packet, const Ptr& header) override {} + virtual void handleReassociationResponseFrame(Packet *packet, const Ptr& header) override {} + virtual void handleDisassociationFrame(Packet *packet, const Ptr& header) override {} + virtual void handleBeaconFrame(Packet *packet, const Ptr& header) override {} + virtual void handleProbeRequestFrame(Packet *packet, const Ptr& header) override {} + virtual void handleProbeResponseFrame(Packet *packet, const Ptr& header) override {} + + public: + void notifyModeSet(const Ieee80211ModeSet *modeSet) + { + applyModeSet(modeSet); + } + + const ieee80211::Ieee80211SupportedRatesElement& getSupportedRates() const { return supportedRates; } +}; + +%activity: +// Reject unsupported VHT greenfield before any mixed-format cache request. +bool rejectedVhtGreenfieldBeforeMixed = false; +try { + Ieee80211VhtCompliantModes::getCompliantMode(&Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, + Ieee80211VhtMode::BAND_5GHZ, Ieee80211VhtPreambleMode::HT_PREAMBLE_GREENFIELD, + Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG); +} +catch (cRuntimeError&) { + rejectedVhtGreenfieldBeforeMixed = true; +} +ASSERT(rejectedVhtGreenfieldBeforeMixed); + +const auto modeSet = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"); +ASSERT(modeSet->getNumModes() == 135); + +TestIeee80211Mgmt mgmt; +mgmt.notifyModeSet(modeSet); +const auto& htSupportedRates = mgmt.getSupportedRates(); +const double expectedHtSupportedRates[] = {1, 2, 5.5, 6, 11, 12, 24}; +ASSERT(htSupportedRates.numRates == 7); +for (int i = 0; i < htSupportedRates.numRates; i++) { + ASSERT(htSupportedRates.rate[i] == expectedHtSupportedRates[i]); + if (i > 0) + ASSERT(htSupportedRates.rate[i - 1] < htSupportedRates.rate[i]); +} + +mgmt.notifyModeSet(Ieee80211ModeSet::getModeSet("a")); +const auto& legacySupportedRates = mgmt.getSupportedRates(); +const double expectedLegacySupportedRates[] = {6, 12, 24, 9, 18, 36, 48, 54}; +ASSERT(legacySupportedRates.numRates == 8); +for (int i = 0; i < legacySupportedRates.numRates; i++) { + ASSERT(legacySupportedRates.rate[i] == expectedLegacySupportedRates[i]); + ASSERT(legacySupportedRates.basicRate[i] == (i < 3)); +} + +using Key = std::tuple; +std::map modes; +int mandatoryCount = 0; +for (int index = 0; index < modeSet->getNumModes(); index++) { + auto mode = dynamic_cast(modeSet->getMode(index)); + if (mode == nullptr) + continue; + auto dataMode = mode->getDataMode(); + int bandwidth = dataMode->getBandwidth() == MHz(20) ? 20 : dataMode->getBandwidth() == MHz(40) ? 40 : 0; + int mcs = dataMode->getMcsIndex(); + auto guardIntervalType = dataMode->getGuardIntervalType(); + ASSERT(bandwidth != 0); + ASSERT((bandwidth == 20 || bandwidth == 40) && 0 <= mcs && mcs <= 31); + ASSERT(modes.emplace(Key(bandwidth, mcs, guardIntervalType), mode).second); + + bool mustBeMandatory = bandwidth == 20 && mcs <= 7 && + guardIntervalType == Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG; + ASSERT(modeSet->isMandatory(index) == mustBeMandatory); + if (modeSet->isMandatory(index)) + mandatoryCount++; +} +ASSERT(mandatoryCount == 8); + +for (int index = 0; index < modeSet->getNumModes(); index++) { + auto mode = modeSet->getMode(index); + auto slowerMode = modeSet->getSlowerMode(mode); + auto fasterMode = modeSet->getFasterMode(mode); + ASSERT(slowerMode == nullptr || slowerMode->getDataMode()->getNetBitrate() < mode->getDataMode()->getNetBitrate()); + ASSERT(fasterMode == nullptr || fasterMode->getDataMode()->getNetBitrate() > mode->getDataMode()->getNetBitrate()); +} + +for (int bandwidth : {20, 40}) { + for (int mcs = 0; mcs <= 31; mcs++) { + auto longMode = modes.at(Key(bandwidth, mcs, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG)); + auto shortMode = modes.at(Key(bandwidth, mcs, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT)); + auto longData = longMode->getDataMode(); + auto shortData = shortMode->getDataMode(); + ASSERT(longData->getGuardInterval() == SimTime(800, SIMTIME_NS)); + ASSERT(shortData->getGuardInterval() == SimTime(400, SIMTIME_NS)); + ASSERT(longData->getSymbolInterval() == SimTime(4, SIMTIME_US)); + ASSERT(shortData->getSymbolInterval() == SimTime(3600, SIMTIME_NS)); + ASSERT(std::fabs(shortData->getNetBitrate().get() * 9 - longData->getNetBitrate().get() * 10) < 1); + + // IEEE Std 802.11-2024, 19.3.11.11.6: short GI is Data-only. + auto longSignal = longMode->getHeaderMode(); + auto shortSignal = shortMode->getHeaderMode(); + ASSERT(longSignal->getSymbolInterval() == SimTime(4, SIMTIME_US)); + ASSERT(shortSignal->getSymbolInterval() == SimTime(4, SIMTIME_US)); + ASSERT(longSignal->getDuration() == SimTime(8, SIMTIME_US)); + ASSERT(shortSignal->getDuration() == SimTime(8, SIMTIME_US)); + ASSERT(longSignal->getNetBitrate() == shortSignal->getNetBitrate()); + ASSERT(longSignal->getGrossBitrate() == shortSignal->getGrossBitrate()); + } +} +ASSERT(modes.find(Key(20, 32, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG)) == modes.end()); + +auto mixedLong = modes.at(Key(20, 0, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG)); +auto mixedShort = modes.at(Key(20, 0, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT)); +ASSERT(mixedLong->getDataMode()->getDuration(B(24)) == SimTime(36, SIMTIME_US)); +ASSERT(mixedShort->getDataMode()->getDuration(B(24)) == SimTime(32400, SIMTIME_NS)); +ASSERT(mixedShort->getDataMode()->getDuration(B(27)) == SimTime(36, SIMTIME_US)); +ASSERT(mixedLong->getDataMode()->getDuration(B(30)) == SimTime(44, SIMTIME_US)); +ASSERT(mixedShort->getDataMode()->getDuration(B(30)) == SimTime(39600, SIMTIME_NS)); + +// IEEE Std 802.11-2024, 19.4.3: Eq. (19-90) rounds mixed-format +// short-GI Data to 4 us, while Eq. (19-92) keeps greenfield Data raw. +ASSERT(mixedShort->getDataDuration(B(24)) == SimTime(36, SIMTIME_US)); +ASSERT(mixedShort->getDataDuration(B(27)) == SimTime(36, SIMTIME_US)); +ASSERT(mixedShort->getDataDuration(B(30)) == SimTime(40, SIMTIME_US)); + +// Preserve representative historical optional-rate timing and bitrate values. +auto mixedShortMcs8 = modes.at(Key(20, 8, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT)); +auto mixedShortMcs0Bw40 = modes.at(Key(40, 0, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT)); +ASSERT(std::fabs(mixedShortMcs8->getDataMode()->getNetBitrate().get() - 14.4444444444444e6) < 1); +ASSERT(mixedShortMcs8->getDataMode()->getDuration(B(24)) == SimTime(18, SIMTIME_US)); +ASSERT(mixedShortMcs8->getDataDuration(B(24)) == SimTime(20, SIMTIME_US)); +ASSERT(mixedShortMcs8->getDuration(B(24)) == SimTime(60, SIMTIME_US)); +ASSERT(mixedShortMcs0Bw40->getDataMode()->getNetBitrate() == Mbps(15)); +ASSERT(mixedShortMcs0Bw40->getDataMode()->getDuration(B(24)) == SimTime(14400, SIMTIME_NS)); +ASSERT(mixedShortMcs0Bw40->getDataDuration(B(24)) == SimTime(16, SIMTIME_US)); +ASSERT(mixedShortMcs0Bw40->getDuration(B(24)) == SimTime(52, SIMTIME_US)); +auto greenfieldShort = Ieee80211HtCompliantModes::getCompliantMode( + &Ieee80211HtmcsTable::htMcs8BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, + Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, + Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT); +ASSERT(greenfieldShort->getPreambleMode()->getPreambleFormat() == Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD); +ASSERT(greenfieldShort->getDataMode()->getNumberOfSpatialStreams() == 2); +ASSERT(greenfieldShort->getDataDuration(B(24)) == SimTime(18, SIMTIME_US)); +ASSERT(greenfieldShort->getDataDuration(B(30)) == SimTime(21600, SIMTIME_NS)); + +auto mixedLongOneSymbol = modes.at(Key(20, 15, Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG)); +auto mixedShortOneSymbol = modes.at(Key(20, 15, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT)); +auto greenfieldShortOneSymbol = Ieee80211HtCompliantModes::getCompliantMode( + &Ieee80211HtmcsTable::htMcs15BW20MHz, Ieee80211HtMode::BAND_2_4GHZ, + Ieee80211HtPreambleMode::HT_PREAMBLE_GREENFIELD, + Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT); +for (auto mode : {mixedLongOneSymbol, mixedShortOneSymbol, greenfieldShortOneSymbol}) { + auto dataDuration = mode->getDataDuration(B(24)); + ASSERT(dataDuration >= SIMTIME_ZERO); + ASSERT(mode->getDuration(B(24)) == mode->getPreambleDuration() + mode->getHeaderDuration() + dataDuration); +} +ASSERT(mixedLongOneSymbol->getDataDuration(B(24)) == SimTime(4, SIMTIME_US)); +ASSERT(mixedShortOneSymbol->getDataDuration(B(24)) == SimTime(4, SIMTIME_US)); +ASSERT(greenfieldShortOneSymbol->getDataDuration(B(24)) == SimTime(3600, SIMTIME_NS)); +ASSERT(mixedShortOneSymbol->getPreambleDuration() == SimTime(20, SIMTIME_US)); +ASSERT(mixedShortOneSymbol->getHeaderDuration() == SimTime(20, SIMTIME_US)); +ASSERT(greenfieldShortOneSymbol->getPreambleDuration() == SimTime(16, SIMTIME_US)); +ASSERT(greenfieldShortOneSymbol->getHeaderDuration() == SimTime(12, SIMTIME_US)); + +auto unspecified65 = dynamic_cast(modeSet->getMode(Mbps(65), MHz(20), 1)); +auto long65 = dynamic_cast(modeSet->getMode(Mbps(65), MHz(20), 1, SimTime(800, SIMTIME_NS))); +auto short65 = dynamic_cast(modeSet->getMode(Mbps(65), MHz(20), 1, SimTime(400, SIMTIME_NS))); +ASSERT(unspecified65->getDataMode()->getMcsIndex() == 7); +ASSERT(unspecified65->getDataMode()->getGuardIntervalType() == Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG); +ASSERT(long65->getDataMode()->getMcsIndex() == 7); +ASSERT(short65->getDataMode()->getMcsIndex() == 6); + +TestIeee80211Transmitter transmitter; +transmitter.setModeSet(modeSet); +transmitter.setMode(short65); +ASSERT(transmitter.getSelectedMode() == short65); +bool rejectedModeOutsideSet = false; +try { + transmitter.setMode(greenfieldShortOneSymbol); +} +catch (cRuntimeError&) { + rejectedModeOutsideSet = true; +} +ASSERT(rejectedModeOutsideSet); +ASSERT(transmitter.getSelectedMode() == short65); + +auto unspecified135 = dynamic_cast(modeSet->getMode(Mbps(135), MHz(40), 1)); +ASSERT(unspecified135->getDataMode()->getMcsIndex() == 6); +ASSERT(unspecified135->getDataMode()->getGuardIntervalType() == Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT); +ASSERT(modeSet->findMode(Mbps(65), MHz(20), 1, SimTime(600, SIMTIME_NS)) == nullptr); +const auto legacyOfdmModeSet = Ieee80211ModeSet::getModeSet("a"); +ASSERT(legacyOfdmModeSet->findMode(Mbps(6), Hz(NaN), -1, SimTime(800, SIMTIME_NS)) != nullptr); +ASSERT(legacyOfdmModeSet->findMode(Mbps(6), Hz(NaN), -1, SimTime(400, SIMTIME_NS)) == nullptr); + +// Mode-set remapping preserves the complete modeled PHY tuple. In +// particular, a negative GI is an actual absence for compatibility lookup, +// not findMode()'s public wildcard. +const auto erpModeSet = Ieee80211ModeSet::getModeSet("g(erp)"); +const auto pModeSet = Ieee80211ModeSet::getModeSet("p"); +const auto aMode = legacyOfdmModeSet->getMode(Mbps(6), MHz(20), 1, SimTime(800, SIMTIME_NS)); +const auto erpMode = erpModeSet->findCompatibleMode(aMode); +ASSERT(erpMode != nullptr); +AbsentGiMode absentGiMode(*check_and_cast(aMode)); +Ieee80211ModeSet absentGiSet("absent GI", {{true, &absentGiMode, true}}, &absentGiMode, legacyOfdmModeSet->getPhyType()); +ASSERT(legacyOfdmModeSet->findCompatibleMode(&absentGiMode) == nullptr); +ASSERT(absentGiSet.findCompatibleMode(aMode) == nullptr); +ASSERT(absentGiSet.findCompatibleMode(&absentGiMode) == &absentGiMode); +// Public lookup retains its unspecified-GI wildcard. +ASSERT(legacyOfdmModeSet->findMode(Mbps(6), MHz(20), 1, -1) == aMode); + +ASSERT(erpMode->getDataMode()->getNetBitrate() == aMode->getDataMode()->getNetBitrate()); +ASSERT(erpMode->getDataMode()->getBandwidth() == aMode->getDataMode()->getBandwidth()); +ASSERT(erpMode->getDataMode()->getGuardInterval() == aMode->getDataMode()->getGuardInterval()); + +const auto dsssModeSet = Ieee80211ModeSet::getModeSet("b"); +const auto mixedModeSet = Ieee80211ModeSet::getModeSet("g(mixed)"); +const auto dsssMode = dsssModeSet->getMode(Mbps(1)); +ASSERT(dsssMode->getDataMode()->getGuardInterval() < SIMTIME_ZERO); +ASSERT(mixedModeSet->findCompatibleMode(dsssMode) != nullptr); + +TestIeee80211Transmitter legacyTransmitter; +legacyTransmitter.setModeSet(legacyOfdmModeSet); +legacyTransmitter.setMode(aMode); +legacyTransmitter.setModeSet(erpModeSet); +ASSERT(legacyTransmitter.getSelectedModeSet() == erpModeSet); +ASSERT(legacyTransmitter.getSelectedMode() == erpMode); +const auto modeBeforeRejectedTransition = legacyTransmitter.getSelectedMode(); +bool rejectedIncompatibleModeSet = false; +try { + legacyTransmitter.setModeSet(pModeSet); +} +catch (cRuntimeError&) { + rejectedIncompatibleModeSet = true; +} +ASSERT(rejectedIncompatibleModeSet); +ASSERT(legacyTransmitter.getSelectedModeSet() == erpModeSet); +ASSERT(legacyTransmitter.getSelectedMode() == modeBeforeRejectedTransition); +const auto pMode = pModeSet->getMode(2); // the declared 6 Mbps 10 MHz mode +legacyTransmitter.setModeSetAndMode(pModeSet, pMode); +ASSERT(legacyTransmitter.getSelectedModeSet() == pModeSet); +ASSERT(legacyTransmitter.getSelectedMode() == pMode); + +Ieee80211VhtCompliantModes::getCompliantMode(&Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, + Ieee80211VhtMode::BAND_5GHZ, Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, + Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG); +bool rejectedVhtGreenfieldAfterMixed = false; +try { + Ieee80211VhtCompliantModes::getCompliantMode(&Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, + Ieee80211VhtMode::BAND_5GHZ, Ieee80211VhtPreambleMode::HT_PREAMBLE_GREENFIELD, + Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG); +} +catch (cRuntimeError&) { + rejectedVhtGreenfieldAfterMixed = true; +} +ASSERT(rejectedVhtGreenfieldAfterMixed); +const auto vhtModeSet = Ieee80211ModeSet::getModeSet("ac"); +const auto vhtLongMcs0 = Ieee80211VhtCompliantModes::getCompliantMode( + &Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, Ieee80211VhtMode::BAND_5GHZ, + Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG); +const auto vhtShortMcs0 = Ieee80211VhtCompliantModes::getCompliantMode( + &Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, Ieee80211VhtMode::BAND_5GHZ, + Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211VhtModeBase::HT_GUARD_INTERVAL_SHORT); +// IEEE Std 802.11-2024, Tables 21-5/21-8 and 21.4.3, Eqs. (21-109)/(21-110). +ASSERT(vhtLongMcs0->getDataMode()->getSymbolInterval() == SimTime(4, SIMTIME_US)); +ASSERT(vhtShortMcs0->getDataMode()->getSymbolInterval() == SimTime(3600, SIMTIME_NS)); +ASSERT(vhtLongMcs0->getHeaderMode()->getSymbolInterval() == SimTime(4, SIMTIME_US)); +ASSERT(vhtShortMcs0->getHeaderMode()->getSymbolInterval() == SimTime(4, SIMTIME_US)); +ASSERT(vhtLongMcs0->getHeaderMode()->getNetBitrate() == vhtShortMcs0->getHeaderMode()->getNetBitrate()); +ASSERT(vhtLongMcs0->getHeaderMode()->getGrossBitrate() == vhtShortMcs0->getHeaderMode()->getGrossBitrate()); +ASSERT(vhtShortMcs0->getDataMode()->getDuration(B(24)) == SimTime(32400, SIMTIME_NS)); +ASSERT(vhtShortMcs0->getDataDuration(B(24)) == SimTime(36, SIMTIME_US)); +ASSERT(vhtShortMcs0->getDataMode()->getDuration(B(27)) == SimTime(36, SIMTIME_US)); +ASSERT(vhtShortMcs0->getDataDuration(B(27)) == SimTime(36, SIMTIME_US)); +ASSERT(vhtShortMcs0->getDataMode()->getDuration(B(30)) == SimTime(39600, SIMTIME_NS)); +ASSERT(vhtShortMcs0->getDataDuration(B(30)) == SimTime(40, SIMTIME_US)); +ASSERT(vhtLongMcs0->getDataMode()->getDuration(B(24)) == SimTime(36, SIMTIME_US)); +ASSERT(vhtLongMcs0->getDataDuration(B(24)) == SimTime(36, SIMTIME_US)); +for (auto vhtMode : {vhtLongMcs0, vhtShortMcs0}) + for (auto dataLength : {B(24), B(27), B(30)}) + ASSERT(vhtMode->getDuration(dataLength) == vhtMode->getPreambleDuration() + vhtMode->getHeaderDuration() + vhtMode->getDataDuration(dataLength)); +bool foundVhtLongGuardInterval = false; +bool foundVhtShortGuardInterval = false; +for (int index = 0; index < vhtModeSet->getNumModes(); index++) { + auto vhtMode = dynamic_cast(vhtModeSet->getMode(index)); + if (vhtMode == nullptr) + continue; + auto vhtDataMode = vhtMode->getDataMode(); + auto guardInterval = vhtDataMode->getGuardInterval(); + auto resolvedMode = vhtModeSet->findMode(vhtDataMode->getNetBitrate(), vhtDataMode->getBandwidth(), vhtDataMode->getNumberOfSpatialStreams(), guardInterval); + ASSERT(resolvedMode != nullptr); + ASSERT(resolvedMode->getDataMode()->getGuardInterval() == guardInterval); + foundVhtLongGuardInterval |= guardInterval == SimTime(800, SIMTIME_NS); + foundVhtShortGuardInterval |= guardInterval == SimTime(400, SIMTIME_NS); +} +ASSERT(foundVhtLongGuardInterval); +ASSERT(foundVhtShortGuardInterval); + +auto vhtOneSymbol = dynamic_cast(vhtModeSet->getFastestMode()); +ASSERT(vhtOneSymbol != nullptr); +ASSERT(vhtOneSymbol->getDataDuration(B(24)) == SimTime(4, SIMTIME_US)); +ASSERT(vhtOneSymbol->getPreambleDuration() == SimTime(20, SIMTIME_US)); +ASSERT(vhtOneSymbol->getHeaderDuration() >= SIMTIME_ZERO); +ASSERT(vhtOneSymbol->getDuration(B(24)) == vhtOneSymbol->getPreambleDuration() + vhtOneSymbol->getHeaderDuration() + vhtOneSymbol->getDataDuration(B(24))); + +for (const auto candidateSet : {modeSet, vhtModeSet}) { + for (int index = 0; index < candidateSet->getNumModes(); index++) { + auto mode = candidateSet->getMode(index); + auto slowerMode = candidateSet->getSlowerMode(mode); + auto fasterMode = candidateSet->getFasterMode(mode); + auto slowerMandatoryMode = candidateSet->getSlowerMandatoryMode(mode); + auto fasterMandatoryMode = candidateSet->getFasterMandatoryMode(mode); + ASSERT(slowerMode == nullptr || slowerMode->getDataMode()->getNetBitrate() < mode->getDataMode()->getNetBitrate()); + ASSERT(fasterMode == nullptr || fasterMode->getDataMode()->getNetBitrate() > mode->getDataMode()->getNetBitrate()); + ASSERT(slowerMandatoryMode == nullptr || slowerMandatoryMode->getDataMode()->getNetBitrate() < mode->getDataMode()->getNetBitrate()); + ASSERT(fasterMandatoryMode == nullptr || fasterMandatoryMode->getDataMode()->getNetBitrate() > mode->getDataMode()->getNetBitrate()); + } +} + +// The equal-rate mandatory candidate is selected by bitrate, not entry index. +auto equalRateVhtMode = Ieee80211VhtCompliantModes::getCompliantMode( + &Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss2, Ieee80211VhtMode::BAND_5GHZ, + Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG); +auto mandatoryAtEqualRate = vhtModeSet->getMandatoryModeAtOrBelow(equalRateVhtMode); +ASSERT(mandatoryAtEqualRate != nullptr); +ASSERT(mandatoryAtEqualRate->getDataMode()->getNetBitrate() == equalRateVhtMode->getDataMode()->getNetBitrate()); +ASSERT(vhtModeSet->getIsMandatory(mandatoryAtEqualRate)); + +transmitter.setModeSetAndMode(vhtModeSet, vhtOneSymbol); +ASSERT(transmitter.getSelectedModeSet() == vhtModeSet); +ASSERT(transmitter.getSelectedMode() == vhtOneSymbol); +bool rejectedCombinedModeUpdate = false; +try { + transmitter.setModeSetAndMode(modeSet, greenfieldShortOneSymbol); +} +catch (cRuntimeError&) { + rejectedCombinedModeUpdate = true; +} +ASSERT(rejectedCombinedModeUpdate); +ASSERT(transmitter.getSelectedModeSet() == vhtModeSet); +ASSERT(transmitter.getSelectedMode() == vhtOneSymbol); + +// Verify Ieee80211Radio setModeSet and setModeSetAndMode publish modesetChangedSignal +TestIeee80211Transmitter radioTx; +TestIeee80211Receiver radioRx; +TestIeee80211Radio radio; +radio.setup(&radioTx, &radioRx); + +TestModeSetListener radioListener; +radio.subscribe(modesetChangedSignal, &radioListener); + +radio.setModeSet(vhtModeSet); +ASSERT(radioTx.getSelectedModeSet() == vhtModeSet); +ASSERT(radioRx.getSelectedModeSet() == vhtModeSet); +ASSERT(radioListener.notifications == 1); +ASSERT(radioListener.receivedModeSet == vhtModeSet); + +radio.setModeSetAndMode(modeSet, modeSet->getMode(0)); +ASSERT(radioTx.getSelectedModeSet() == modeSet); +ASSERT(radioTx.getSelectedMode() == modeSet->getMode(0)); +ASSERT(radioRx.getSelectedModeSet() == modeSet); +ASSERT(radioListener.notifications == 2); +ASSERT(radioListener.receivedModeSet == modeSet); + +bool radioRejectedInvalidMode = false; +try { + radio.setModeSetAndMode(modeSet, vhtOneSymbol); +} +catch (cRuntimeError&) { + radioRejectedInvalidMode = true; +} +ASSERT(radioRejectedInvalidMode); +ASSERT(radioTx.getSelectedModeSet() == modeSet); +ASSERT(radioRx.getSelectedModeSet() == modeSet); +ASSERT(radioListener.notifications == 2); + +// Verify RateSelection and QosRateSelection dynamic mode-set updates +auto *rateSelection = check_and_cast(cModuleType::get("inet.linklayer.ieee80211.mac.rateselection.RateSelection")->create("rateSelection", this)); +rateSelection->par("rateControlModule").setStringValue(""); +rateSelection->par("mibModule").setStringValue(""); +rateSelection->par("dataFrameBitrate").setDoubleValue(54e6); +rateSelection->par("multicastFrameBitrate").setDoubleValue(6e6); +rateSelection->par("mgmtFrameBitrate").setDoubleValue(6e6); +rateSelection->par("controlFrameBitrate").setDoubleValue(6e6); +rateSelection->par("responseAckFrameBitrate").setDoubleValue(6e6); +rateSelection->par("responseCtsFrameBitrate").setDoubleValue(6e6); +rateSelection->finalizeParameters(); +rateSelection->buildInside(); + +TestRateSelectionAccessor::notifyModeSet(rateSelection, erpModeSet); +ASSERT(TestRateSelectionAccessor::readModeSet(rateSelection) == erpModeSet); +ASSERT(erpModeSet->containsMode(TestRateSelectionAccessor::getDataFrameMode(rateSelection))); +ASSERT(erpModeSet->containsMode(TestRateSelectionAccessor::getMulticastFrameMode(rateSelection))); +ASSERT(erpModeSet->containsMode(TestRateSelectionAccessor::getMgmtFrameMode(rateSelection))); +ASSERT(erpModeSet->containsMode(TestRateSelectionAccessor::getControlFrameMode(rateSelection))); +ASSERT(erpModeSet->containsMode(TestRateSelectionAccessor::getResponseAckFrameMode(rateSelection))); +ASSERT(erpModeSet->containsMode(TestRateSelectionAccessor::getResponseCtsFrameMode(rateSelection))); +ASSERT(erpModeSet->containsMode(TestRateSelectionAccessor::getFastestMandatoryMode(rateSelection))); +ASSERT(TestRateSelectionAccessor::getFastestMandatoryMode(rateSelection) == erpModeSet->getMode(Mbps(24))); + +// Dynamically switch rateSelection to HT mode set +TestRateSelectionAccessor::notifyModeSet(rateSelection, modeSet); +ASSERT(TestRateSelectionAccessor::readModeSet(rateSelection) == modeSet); +ASSERT(modeSet->containsMode(TestRateSelectionAccessor::getDataFrameMode(rateSelection))); +ASSERT(!erpModeSet->containsMode(TestRateSelectionAccessor::getDataFrameMode(rateSelection))); +ASSERT(modeSet->containsMode(TestRateSelectionAccessor::getMulticastFrameMode(rateSelection))); +ASSERT(!erpModeSet->containsMode(TestRateSelectionAccessor::getMulticastFrameMode(rateSelection))); +ASSERT(modeSet->containsMode(TestRateSelectionAccessor::getMgmtFrameMode(rateSelection))); +ASSERT(modeSet->containsMode(TestRateSelectionAccessor::getControlFrameMode(rateSelection))); +ASSERT(modeSet->containsMode(TestRateSelectionAccessor::getResponseAckFrameMode(rateSelection))); +ASSERT(modeSet->containsMode(TestRateSelectionAccessor::getResponseCtsFrameMode(rateSelection))); +ASSERT(modeSet->containsMode(TestRateSelectionAccessor::getFastestMandatoryMode(rateSelection))); + +// Also verify HT-specific fixed mode configured with bandwidth, stream, and guard interval +auto *rateSelectionHt = check_and_cast(cModuleType::get("inet.linklayer.ieee80211.mac.rateselection.RateSelection")->create("rateSelectionHt", this)); +rateSelectionHt->par("rateControlModule").setStringValue(""); +rateSelectionHt->par("mibModule").setStringValue(""); +rateSelectionHt->par("dataFrameBitrate").setDoubleValue(65e6); +rateSelectionHt->par("dataFrameBandwidth").setDoubleValue(20e6); +rateSelectionHt->par("dataFrameNumSpatialStreams").setIntValue(1); +rateSelectionHt->par("dataFrameGuardInterval").setDoubleValue(400e-9); +rateSelectionHt->par("multicastFrameBitrate").setDoubleValue(6e6); +rateSelectionHt->par("mgmtFrameBitrate").setDoubleValue(6e6); +rateSelectionHt->par("controlFrameBitrate").setDoubleValue(6e6); +rateSelectionHt->par("responseAckFrameBitrate").setDoubleValue(6e6); +rateSelectionHt->par("responseCtsFrameBitrate").setDoubleValue(6e6); +rateSelectionHt->finalizeParameters(); +rateSelectionHt->buildInside(); + +TestRateSelectionAccessor::notifyModeSet(rateSelectionHt, modeSet); +ASSERT(TestRateSelectionAccessor::readModeSet(rateSelectionHt) == modeSet); +ASSERT(modeSet->containsMode(TestRateSelectionAccessor::getDataFrameMode(rateSelectionHt))); +ASSERT(TestRateSelectionAccessor::getDataFrameMode(rateSelectionHt) == short65); + +auto *qosRateSelection = check_and_cast(cModuleType::get("inet.linklayer.ieee80211.mac.rateselection.QosRateSelection")->create("qosRateSelection", this)); +qosRateSelection->par("rateControlModule").setStringValue(""); +qosRateSelection->par("mibModule").setStringValue(""); +qosRateSelection->par("dataFrameBitrate").setDoubleValue(54e6); +qosRateSelection->par("multicastFrameBitrate").setDoubleValue(6e6); +qosRateSelection->par("mgmtFrameBitrate").setDoubleValue(6e6); +qosRateSelection->par("controlFrameBitrate").setDoubleValue(6e6); +qosRateSelection->par("responseAckFrameBitrate").setDoubleValue(6e6); +qosRateSelection->par("responseBlockAckFrameBitrate").setDoubleValue(6e6); +qosRateSelection->par("responseCtsFrameBitrate").setDoubleValue(6e6); +qosRateSelection->finalizeParameters(); +qosRateSelection->buildInside(); + +TestQosRateSelectionAccessor::notifyModeSet(qosRateSelection, erpModeSet); +ASSERT(TestQosRateSelectionAccessor::readModeSet(qosRateSelection) == erpModeSet); +ASSERT(erpModeSet->containsMode(TestQosRateSelectionAccessor::getDataFrameMode(qosRateSelection))); +ASSERT(erpModeSet->containsMode(TestQosRateSelectionAccessor::getResponseBlockAckFrameMode(qosRateSelection))); +ASSERT(erpModeSet->containsMode(TestQosRateSelectionAccessor::getFastestMandatoryMode(qosRateSelection))); + +TestQosRateSelectionAccessor::notifyModeSet(qosRateSelection, modeSet); +ASSERT(TestQosRateSelectionAccessor::readModeSet(qosRateSelection) == modeSet); +ASSERT(modeSet->containsMode(TestQosRateSelectionAccessor::getDataFrameMode(qosRateSelection))); +ASSERT(!erpModeSet->containsMode(TestQosRateSelectionAccessor::getDataFrameMode(qosRateSelection))); +ASSERT(modeSet->containsMode(TestQosRateSelectionAccessor::getResponseBlockAckFrameMode(qosRateSelection))); +ASSERT(!erpModeSet->containsMode(TestQosRateSelectionAccessor::getResponseBlockAckFrameMode(qosRateSelection))); +ASSERT(modeSet->containsMode(TestQosRateSelectionAccessor::getFastestMandatoryMode(qosRateSelection))); + +EV << "HT guard interval catalog, timing, and lookup checks passed.\n"; + +%contains: stdout +HT guard interval catalog, timing, and lookup checks passed. diff --git a/tests/unit/Ieee80211HtModeSet_1.test b/tests/unit/Ieee80211HtModeSet_1.test index 44877fd9d68..00a6bc4967c 100644 --- a/tests/unit/Ieee80211HtModeSet_1.test +++ b/tests/unit/Ieee80211HtModeSet_1.test @@ -5,6 +5,7 @@ Also verify exact sparse HT MCS and width derivation from typed mode entries. %includes: #include +#include #include #include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h" @@ -38,8 +39,51 @@ static const IIeee80211Mode *findHtMode(const Ieee80211ModeSet *modeSet, int mcs return nullptr; } +// Synthetic data rates isolate exact tuple matching from public lookup tolerance. +class CustomBitrateDataMode : public Ieee80211HtDataMode +{ + bps bitrate; + public: + CustomBitrateDataMode(const Ieee80211HtDataMode& base, bps bitrate) : Ieee80211HtDataMode(base), bitrate(bitrate) {} + virtual bps getNetBitrate() const override { return bitrate; } +}; + %activity: +// IEEE Std 802.11-2024, Tables 19-35, 19-38 and 19-41. These optional +// table entries are not part of the selectable MCS 0-31 catalog below. +struct OptionalMcsExpectation { + const Ieee80211Htmcs *mcs; + int streams; + int bitsPerSubcarrier[4]; + double grossMbps; + double netMbps; + int symbolsFor1000Bits; +}; +const OptionalMcsExpectation optionalMcs[] = { + {&Ieee80211HtmcsTable::htMcs32BW40MHz, 1, {1, 0, 0, 0}, 12, 6, 43}, + {&Ieee80211HtmcsTable::htMcs73BW40MHz, 4, {6, 6, 4, 2}, 486, 364.5, 1}, + {&Ieee80211HtmcsTable::htMcs76BW20MHz, 4, {6, 6, 6, 4}, 286, 214.5, 2}, +}; +for (const auto& expected : optionalMcs) { + const Ieee80211OfdmModulation *streams[] = {expected.mcs->getModulation(), + expected.mcs->getStreamExtension1Modulation(), expected.mcs->getStreamExtension2Modulation(), + expected.mcs->getStreamExtension3Modulation()}; + for (int i = 0; i < 4; i++) + ASSERT((streams[i] ? streams[i]->getSubcarrierModulation()->getCodeWordSize() : 0) == expected.bitsPerSubcarrier[i]); + for (auto gi : {Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG, Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT}) { + Ieee80211HtDataMode data(expected.mcs, expected.mcs->getBandwidth(), gi); + bool shortGi = gi == Ieee80211HtModeBase::HT_GUARD_INTERVAL_SHORT; + auto symbol = SimTime(shortGi ? 3600 : 4000, SIMTIME_NS); + double rateScale = shortGi ? 10.0 / 9 : 1; + ASSERT(data.getNumberOfSpatialStreams() == expected.streams); + ASSERT(std::abs(data.getGrossBitrate().get() - expected.grossMbps * 1e6 * rateScale) < 1); + ASSERT(std::abs(data.getNetBitrate().get() - expected.netMbps * 1e6 * rateScale) < 1); + ASSERT(data.getSymbolInterval() == symbol); + ASSERT(data.getDuration(b(1000)) == expected.symbolsFor1000Bits * symbol); + } +} + const auto *htModeSet = Ieee80211ModeSet::getModeSet("n(mixed-2.4Ghz)"); const auto *vhtOnlyModeSet = Ieee80211ModeSet::getModeSet("ac"); @@ -69,6 +113,18 @@ for (int mcsIndex = 0; mcsIndex < 32; mcsIndex++) { } } } +// No preamble is needed: these synthetic modes exercise only tuple lookup. +const auto *baseData = check_and_cast( + findHtMode(htModeSet, 0, MHz(20), Ieee80211HtModeBase::HT_GUARD_INTERVAL_LONG)->getDataMode()); +Ieee80211HtMode source("source", nullptr, new CustomBitrateDataMode(*baseData, Mbps(6)), Ieee80211HtMode::BAND_2_4GHZ); +Ieee80211HtMode nearby("nearby", nullptr, new CustomBitrateDataMode(*baseData, Mbps(6.04)), Ieee80211HtMode::BAND_2_4GHZ); +Ieee80211HtMode equal("equal", nullptr, new CustomBitrateDataMode(*baseData, Mbps(6)), Ieee80211HtMode::BAND_2_4GHZ); +SparseModeSet nearbySet({{true, &nearby}, {true, htModeSet->getMode(Mbps(1)), true}}, &nearby, false); +ASSERT(nearbySet.findCompatibleMode(&source) == nullptr); +ASSERT(nearbySet.findMode(Mbps(6), MHz(20), 1, 800e-9) == &nearby); +SparseModeSet equalSet({{true, &nearby}, {true, &equal}, {true, htModeSet->getMode(Mbps(1)), true}}, &equal, false); +ASSERT(equalSet.findCompatibleMode(&source) == &equal); +ASSERT(equalSet.findCompatibleMode(nullptr) == nullptr); ASSERT(htModeSet->getHtSupportedChannelWidths().size() == 2); ASSERT(htModeSet->isHtShortGuardIntervalSupported(MHz(20))); ASSERT(htModeSet->isHtShortGuardIntervalSupported(MHz(40))); diff --git a/tests/unit/Ieee80211MgmtFrameSerializer_1.test b/tests/unit/Ieee80211MgmtFrameSerializer_1.test index 18e3638f495..439fce74a40 100644 --- a/tests/unit/Ieee80211MgmtFrameSerializer_1.test +++ b/tests/unit/Ieee80211MgmtFrameSerializer_1.test @@ -172,6 +172,36 @@ static void checkMalformedSupportedRates(const std::vector& prefix) ASSERT(zeroLength->getSupportedRates().numRates == 0); } +template +static void checkMalformedAssociationIds() +{ + for (uint16_t status : {0, 0x1234}) { + const std::vector invalidAids = status == 0 ? + std::vector{0x0001, 0x4001, 0x8001, 0xC000, 0xC7D8, 0xFFFF} : + std::vector{0x0001, 0xC000, 0xC001}; + for (auto aid : invalidAids) { + auto bytes = appendTrailingInformationElement({ + 0x00, 0x00, uint8_t(status), uint8_t(status >> 8), uint8_t(aid), uint8_t(aid >> 8), + 0x01, 0x01, 0x0C, 0x32, 0x01, 0x18 + }); + auto frame = deserializeMalformedBody(bytes); + MemoryInputStream stream(bytes); + Ieee80211MgmtFrameSerializer serializer; + auto decoded = serializer.deserialize(stream, typeid(T)); + ASSERT(decoded->isIncorrect()); + ASSERT(stream.getPosition() == B(bytes.size())); + ASSERT(frame->isIncorrect()); + ASSERT(frame->isComplete()); + ASSERT(frame->getStatusCode() == status); + ASSERT(frame->getAid() == 0); + ASSERT(frame->getSupportedRates().numRates == 1); + ASSERT(frame->getSupportedRates().rate[0] == 6); + ASSERT(frame->getExtendedSupportedRates().numRates == 1); + ASSERT(frame->getExtendedSupportedRates().rate[0] == 12); + } + } +} + %activity: { @@ -267,17 +297,8 @@ static void checkMalformedSupportedRates(const std::vector& prefix) ASSERT(frame->getAid() == 0x0245); } -bool missingAssociationIdMarkerRejected = false; -try { - deserializeBody({ - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x01, 0x01, 0x0C - }); -} -catch (const cRuntimeError&) { - missingAssociationIdMarkerRejected = true; -} -ASSERT(missingAssociationIdMarkerRejected); +checkMalformedAssociationIds(); +checkMalformedAssociationIds(); bool successfulZeroAssociationIdRejected = false; try { diff --git a/tests/unit/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..aa7a8ea4b89 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,65 @@ 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 shortGiDisabledOnlyMcs0Peer = makePeerState({0}, {MHz(20)}, MHz(20)); +const auto *shortGiMcs0Fallback = selectForTest(modeSet, &shortGiDisabledOnlyMcs0Peer, mcs8Short, peer); +ASSERT(shortGiMcs0Fallback == mcs0Long); + 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()); diff --git a/tests/unit/Ieee80211SupportedRates_1.test b/tests/unit/Ieee80211SupportedRates_1.test index a0e82cec048..e25270baab5 100644 --- a/tests/unit/Ieee80211SupportedRates_1.test +++ b/tests/unit/Ieee80211SupportedRates_1.test @@ -412,6 +412,84 @@ assertTiming(pModeSet, SimTime(32, SIMTIME_US), SimTime(13, SIMTIME_US), SimTime assertTiming(nModeSet, SimTime(10, SIMTIME_US), SimTime(20, SIMTIME_US), SimTime(24, SIMTIME_US), 15, 1023); assertTiming(acModeSet, SimTime(16, SIMTIME_US), SimTime(9, SIMTIME_US), SimTime(24, SIMTIME_US), 15, 1023); +// IEEE Std 802.11-2024, 9.4.2.2: SSID length verification (0 to 32 octets) +for (size_t len : {size_t(0), size_t(1), size_t(32)}) { + std::string ssid(len, 'x'); + auto probeReq = makeShared(); + probeReq->setSSID(ssid.c_str()); + Ieee80211SupportedRatesElement rates; + rates.numRates = 1; + rates.rate[0] = 1.0; + rates.basicRate[0] = true; + probeReq->setSupportedRates(rates); + probeReq->setChunkLength(B(2 + len + 3)); + + auto bytes = serializeFrame(probeReq); + auto decoded = deserializeFrame(bytes); + ASSERT(std::string(decoded->getSSID()) == ssid); +} + +// 33-octet SSID exceeds standard maximum and must be rejected on serialization +{ + std::string overlengthSsid(33, 'x'); + auto probeReq = makeShared(); + probeReq->setSSID(overlengthSsid.c_str()); + Ieee80211SupportedRatesElement rates; + rates.numRates = 1; + rates.rate[0] = 1.0; + rates.basicRate[0] = true; + probeReq->setSupportedRates(rates); + probeReq->setChunkLength(B(2 + 33 + 3)); + + bool overlengthRejected = false; + try { + serializeFrame(probeReq); + } + catch (const cRuntimeError&) { + overlengthRejected = true; + } + ASSERT(overlengthRejected); +} + +// Deserializing a frame with element length = 33 must throw +{ + std::vector rawProbeReq = { + 0x00, 33 // Element 0 (SSID), length 33 + }; + for (int i = 0; i < 33; i++) + rawProbeReq.push_back('x'); + rawProbeReq.push_back(0x01); // Element 1 (Supported Rates) + rawProbeReq.push_back(1); // Length 1 + rawProbeReq.push_back(0x82); // 1.0 Mbps basic + + bool deserializationRejected = false; + try { + deserializeFrame(rawProbeReq); + } + catch (const cRuntimeError&) { + deserializationRejected = true; + } + ASSERT(deserializationRejected); +} + +// Deserializing a truncated SSID element must throw +{ + std::vector truncatedProbeReq = { + 0x00, 10 // Element 0 (SSID), length 10 but only 3 payload bytes follow + }; + for (int i = 0; i < 3; i++) + truncatedProbeReq.push_back('a'); + + bool truncatedRejected = false; + try { + deserializeFrame(truncatedProbeReq); + } + catch (const cRuntimeError&) { + truncatedRejected = true; + } + ASSERT(truncatedRejected); +} + EV << "Supported Rates and AID validation checks passed.\n"; %contains: stdout diff --git a/tests/unit/Ieee80211VhtMgmtElements_1.test b/tests/unit/Ieee80211VhtMgmtElements_1.test new file mode 100644 index 00000000000..6ac5dd04684 --- /dev/null +++ b/tests/unit/Ieee80211VhtMgmtElements_1.test @@ -0,0 +1,176 @@ +%description: +Verify IEEE 802.11-2024 9.4.2.156/157 byte encodings, directional MCS maps, +operation decoding, duplicate/truncated elements and subtype restrictions. + +%includes: +#include "inet/common/packet/Packet.h" +#include "inet/common/packet/chunk/BytesChunk.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211VhtMgmtElements.h" +using namespace inet; +using namespace inet::ieee80211; + +%global: +static Ptr response() +{ + auto frame = makeShared(); + frame->setAid(1); + frame->setStatusCode(SC_SUCCESSFUL); + Ieee80211SupportedRatesElement rates; + rates.numRates = 1; + rates.rate[0] = 6; + frame->setSupportedRates(rates); + Ieee80211VhtCapabilities cap; + cap.rxMaxMcs = {9, 8, 7, -1, -1, -1, -1, -1}; + cap.txMaxMcs = {7, -1, -1, -1, -1, -1, -1, -1}; + cap.supported160Mhz = true; + cap.shortGi80 = true; + cap.rxHighestLongGiRateMbps = 1234; + cap.txHighestLongGiRateMbps = 567; + setVhtCapabilities(frame, cap); + Ieee80211VhtOperation op; + setVhtOperation(frame, op); + frame->setChunkLength(B(30)); + return frame; +} +static std::vector bytes(const Ptr& frame) +{ + Packet packet("wire", frame); + return packet.peekAllAsBytes()->getBytes(); +} +static Ptr decode(const std::vector& data) +{ + Packet packet("wire", makeShared(data)); + return packet.popAtFront(B(data.size()), Chunk::PF_ALLOW_INCORRECT | Chunk::PF_ALLOW_INCOMPLETE); +} + +template +static void checkSubtype(int baseLength, bool operationAllowed) +{ + // IEEE Std 802.11-2024, Tables 9-62 and 9-64 through 9-69. + // Exercise the codec's subtype permissions independently of advertisement policy. + auto reference = response(); + for (bool ht : {false, true}) { + for (bool vht : {false, true}) { + auto frame = makeShared(); + Ieee80211SupportedRatesElement rates; + rates.numRates = 1; + rates.rate[0] = 6; + frame->setSupportedRates(rates); + if (auto response = dynamicPtrCast(frame)) { + response->setStatusCode(SC_SUCCESSFUL); + response->setAid(1); + } + if (auto beacon = dynamicPtrCast(frame)) + beacon->setBeaconInterval(SimTime(102400, SIMTIME_US)); + frame->setHtCapabilitiesPresent(ht); + frame->setVhtCapabilities(reference->getVhtCapabilities()); + frame->setVhtOperation(reference->getVhtOperation()); + frame->setVhtCapabilitiesPresent(vht); + frame->setVhtOperationPresent(vht && operationAllowed); + frame->setChunkLength(B(baseLength + (ht ? 28 : 0) + (vht ? 14 : 0) + (vht && operationAllowed ? 7 : 0))); + auto encoded = bytes(frame); + Packet packet("subtype", makeShared(encoded)); + auto decoded = packet.popAtFront(B(encoded.size())); + ASSERT(!decoded->isIncorrect()); + ASSERT(decoded->getHtCapabilitiesPresent() == ht); + ASSERT(decoded->getVhtCapabilitiesPresent() == vht); + ASSERT(decoded->getVhtOperationPresent() == (vht && operationAllowed)); + if (!operationAllowed) { + // A well-formed VHT Operation IE is forbidden in a request, + // even when the same request accepts VHT Capabilities. + encoded.insert(encoded.end(), {192, 5, 0, 0, 0, 0xfc, 0xff}); + Packet malformed("forbidden-operation", makeShared(encoded)); + ASSERT(malformed.popAtFront(B(encoded.size()), Chunk::PF_ALLOW_INCORRECT)->isIncorrect()); + } + } + } + if (!operationAllowed) { + auto forbidden = makeShared(); + Ieee80211SupportedRatesElement rates; + rates.numRates = 1; + rates.rate[0] = 6; + forbidden->setSupportedRates(rates); + forbidden->setVhtOperation(reference->getVhtOperation()); + forbidden->setVhtOperationPresent(true); + forbidden->setChunkLength(B(baseLength + 7)); + bool rejected = false; + try { bytes(forbidden); } catch (const cRuntimeError&) { rejected = true; } + ASSERT(rejected); + } +} +%activity: +checkSubtype(17, true); +checkSubtype(17, true); +checkSubtype(9, true); +checkSubtype(9, true); +checkSubtype(5, false); +checkSubtype(9, false); +checkSubtype(15, false); +auto original = response(); +auto data = bytes(original); +ASSERT(data.size() == 30); +ASSERT(data[9] == 191 && data[10] == 12); +ASSERT(data[11] == 0x24 && data[12] == 0 && data[13] == 0 && data[14] == 0); +ASSERT(data[15] == 0xc6 && data[16] == 0xff); // Rx: MCS9,8,7,unsupported +ASSERT(data[17] == 0xd2 && data[18] == 0x04); +ASSERT(data[19] == 0xfc && data[20] == 0xff); // Tx: MCS7,unsupported +ASSERT(data[21] == 0x37 && data[22] == 0x02); +ASSERT(data[23] == 192 && data[24] == 5); +ASSERT(data[25] == 0 && data[26] == 0 && data[27] == 0); +ASSERT(data[28] == 0xfc && data[29] == 0xff); +auto decoded = decode(data); +ASSERT(!decoded->isIncorrect()); +Ieee80211VhtCapabilities cap; +Ieee80211VhtOperation op; +ASSERT(decodeVhtCapabilities(decoded, cap)); +ASSERT(cap.rxMaxMcs[0] == 9 && cap.rxMaxMcs[1] == 8 && cap.rxMaxMcs[2] == 7); +ASSERT(cap.txMaxMcs[0] == 7 && cap.txMaxMcs[1] == -1); +ASSERT(cap.rxHighestLongGiRateMbps == 1234 && cap.txHighestLongGiRateMbps == 567); +ASSERT(cap.shortGi80 && !cap.shortGi20 && !cap.shortGi40 && !cap.shortGi160); +ASSERT(decodeVhtOperation(decoded, op) && op.channelWidth == MHz(20)); +auto fresh = response(); +fresh->setVhtCapabilities(decoded->getVhtCapabilities()); +fresh->setVhtOperation(decoded->getVhtOperation()); +ASSERT(bytes(fresh) == data); +for (int idOffset : {9, 23}) { + auto duplicate = data; + duplicate.insert(duplicate.end(), data.begin() + idOffset, data.begin() + idOffset + data[idOffset + 1] + 2); + ASSERT(decode(duplicate)->isIncorrect()); + auto wrongLength = data; + wrongLength[idOffset + 1]--; + ASSERT(decode(wrongLength)->isIncorrect()); +} +for (size_t length = 10; length < data.size(); length++) { + if (length == 23) + continue; // complete capability IE without optional operation + auto truncated = data; + truncated.resize(length); + ASSERT(decode(truncated)->isIncomplete()); +} +auto invalidWidth = data; +invalidWidth[11] |= 12; +ASSERT(decode(invalidWidth)->isIncorrect()); +auto frame = response(); +auto element = frame->getVhtOperation(); +element.channelWidth = 1; +element.centerFrequencySegment0 = 42; +frame->setVhtOperation(element); +ASSERT(decodeVhtOperation(frame, op) && op.channelWidth == MHz(80)); +element.centerFrequencySegment1 = 50; +frame->setVhtOperation(element); +ASSERT(decodeVhtOperation(frame, op) && op.channelWidth == MHz(160)); +element.centerFrequencySegment1 = 106; +frame->setVhtOperation(element); +ASSERT(!decodeVhtOperation(frame, op)); // 80+80 is outside this implementation + +auto forbidden = makeShared(); +forbidden->setVhtCapabilitiesPresent(true); +forbidden->setVhtCapabilities(original->getVhtCapabilities()); +forbidden->setChunkLength(B(16)); +bool rejected = false; +try { bytes(forbidden); } catch (const cRuntimeError&) { rejected = true; } +ASSERT(rejected); +std::cout << "VHT management wire contract verified.\n"; + +%contains: stdout +VHT management wire contract verified. diff --git a/tests/unit/Ieee80211VhtModeSet_1.test b/tests/unit/Ieee80211VhtModeSet_1.test new file mode 100644 index 00000000000..80fc8544fea --- /dev/null +++ b/tests/unit/Ieee80211VhtModeSet_1.test @@ -0,0 +1,101 @@ +%description: +Every valid VHT tuple in the ac catalog has both guard intervals. Existing +unqualified bitrate lookup and mandatory/reference selections retain their +historical choices. The validity matrix follows IEEE 802.11-2024, 21.5, +Tables 21-29 through 21-60, including the ten unavailable tuples. + +%includes: +#include +#include +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h" +#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.h" +using namespace inet; +using namespace inet::physicallayer; + +%global: +class HistoricalVhtCatalog : public Ieee80211ModeSet +{ + static std::vector historicalEntries(const Ieee80211ModeSet *catalog) + { + std::vector result; + for (int i = 0; i < catalog->getNumModes(); i++) { + auto mode = catalog->getMode(i); + auto data = dynamic_cast(mode->getDataMode()); + if (data == nullptr) + result.push_back({true, mode, true}); + else { + bool originalLong = data->getBandwidth() == MHz(20) && data->getNumberOfSpatialStreams() == 1; + if ((data->getGuardInterval() == SimTime(800, SIMTIME_NS)) == originalLong) + result.push_back({originalLong, mode}); + } + } + return result; + } + public: + HistoricalVhtCatalog(const Ieee80211ModeSet *catalog) : + Ieee80211ModeSet("historical-ac", historicalEntries(catalog), catalog->getReferenceMode(), PhyType::VHT) {} +}; + +%activity: +auto catalog = Ieee80211ModeSet::getModeSet("ac"); +HistoricalVhtCatalog historical(catalog); +std::map, std::array> tuples; +for (int i = 0; i < catalog->getNumModes(); i++) { + auto mode = catalog->getMode(i); + auto data = dynamic_cast(mode->getDataMode()); + if (data == nullptr) { + ASSERT(catalog->isMandatory(i)); + continue; + } + ASSERT(catalog->isMandatory(i) == (data->getBandwidth() == MHz(20) && + data->getNumberOfSpatialStreams() == 1 && data->getGuardInterval() == SimTime(800, SIMTIME_NS))); + int gi = data->getGuardInterval() == SimTime(400, SIMTIME_NS) ? 1 : 0; + ASSERT(data->getGuardInterval() == SimTime(gi ? 400 : 800, SIMTIME_NS)); + auto& pair = tuples[{(int)data->getBandwidth().get(), data->getNumberOfSpatialStreams(), (int)data->getMcsIndex()}]; + ASSERT(pair[gi] == nullptr); + pair[gi] = mode; + auto found = catalog->getMode(data->getNetBitrate(), data->getBandwidth(), data->getNumberOfSpatialStreams(), data->getGuardInterval()); + ASSERT(found == mode); +} +int valid = 0; +for (int width : {20, 40, 80, 160}) { + for (int nss = 1; nss <= 8; nss++) { + for (int mcs = 0; mcs <= 9; mcs++) { + bool legal = !(width == 20 && mcs == 9 && nss != 3 && nss != 6) && + !(width == 80 && mcs == 6 && (nss == 3 || nss == 7)) && + !(width == 160 && mcs == 9 && nss == 3) && + !(width == 80 && mcs == 9 && nss == 6); + auto it = tuples.find({width, nss, mcs}); + ASSERT((it != tuples.end()) == legal); + if (legal) { + valid++; + auto longMode = it->second[0]; + auto shortMode = it->second[1]; + ASSERT(longMode && shortMode); + ASSERT(longMode->getDataMode()->getDuration(B(1500)) > shortMode->getDataMode()->getDuration(B(1500))); + ASSERT(longMode->getHeaderMode()->getDuration() == shortMode->getHeaderMode()->getDuration()); + } + } + } +} +ASSERT(valid == 310 && catalog->getNumModes() == 623); +ASSERT(historical.getNumModes() == 313); +for (int i = 0; i < historical.getNumModes(); i++) { + auto data = historical.getMode(i)->getDataMode(); + auto rate = data->getNetBitrate(); + ASSERT(catalog->getMode(rate) == historical.getMode(rate)); + ASSERT(catalog->getMode(rate, data->getBandwidth(), data->getNumberOfSpatialStreams()) == + historical.getMode(rate, data->getBandwidth(), data->getNumberOfSpatialStreams())); +} +ASSERT(catalog->getReferenceMode() == Ieee80211VhtCompliantModes::getCompliantMode( + &Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, Ieee80211VhtMode::BAND_5GHZ, + Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG)); +ASSERT(catalog->getFastestMode() == historical.getFastestMode()); +ASSERT(catalog->getSlowestMode() == historical.getSlowestMode()); +ASSERT(catalog->getFastestMandatoryMode() == historical.getFastestMandatoryMode()); +ASSERT(catalog->getSlowestMandatoryMode() == historical.getSlowestMandatoryMode()); +ASSERT(catalog->getLegacyOperationalModes() == historical.getLegacyOperationalModes()); +std::cout << "VHT GI catalog: 310 legal tuples, both GIs, historical selection preserved\n"; + +%contains: stdout +VHT GI catalog: 310 legal tuples, both GIs, historical selection preserved diff --git a/tests/unit/Ieee80211VhtPeerModeSelection_1.test b/tests/unit/Ieee80211VhtPeerModeSelection_1.test new file mode 100644 index 00000000000..f2abc096a00 --- /dev/null +++ b/tests/unit/Ieee80211VhtPeerModeSelection_1.test @@ -0,0 +1,77 @@ +%description: +Verify local-Tx/peer-Rx VHT selection over every catalog mode with asymmetric +maps, all operational widths, GI restrictions, absent peer fallback and rate caps. + +%includes: +#include "inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h" +using namespace inet; +using namespace inet::ieee80211; +using namespace inet::physicallayer; + +%activity: +auto modes = Ieee80211ModeSet::getModeSet("ac"); +Ieee80211VhtCapabilities local; +local.txMaxMcs = {8, 7, -1, -1, -1, -1, -1, -1}; +local.rxMaxMcs = {7, -1, -1, -1, -1, -1, -1, -1}; +local.supported160Mhz = true; +local.shortGi20 = local.shortGi40 = local.shortGi80 = local.shortGi160 = true; +Ieee80211VhtOperation operation; +Ieee80211Mib::PeerVhtState peer; +peer.advertisedCapabilities.rxMaxMcs = {7, 8, -1, -1, -1, -1, -1, -1}; +peer.advertisedCapabilities.txMaxMcs = {9, -1, -1, -1, -1, -1, -1, -1}; +peer.advertisedCapabilities.supported160Mhz = true; +int checked = 0; +for (int width : {20, 40, 80, 160}) { + operation.channelWidth = MHz(width); + peer.operation.channelWidth = MHz(width); + for (bool shortGi : {false, true}) { + auto& remote = peer.advertisedCapabilities; + remote.shortGi20 = remote.shortGi40 = remote.shortGi80 = remote.shortGi160 = shortGi; + for (int i = 0; i < modes->getNumModes(); i++) { + auto requested = modes->getMode(i); + if (requested->getVhtMcsIndex() < 0) + continue; + auto selected = selectPeerCompatibleVhtMode(modes, local, operation, &peer, requested); + ASSERT(modes->containsMode(selected)); + ASSERT(selected->getVhtMcsIndex() >= 0); + int nss = selected->getDataMode()->getNumberOfSpatialStreams(); + ASSERT(nss <= 2); + ASSERT(selected->getVhtMcsIndex() <= local.txMaxMcs[nss - 1]); + ASSERT(selected->getVhtMcsIndex() <= remote.rxMaxMcs[nss - 1]); + ASSERT(selected->getDataMode()->getBandwidth() <= MHz(width)); + ASSERT(shortGi || selected->getDataMode()->getGuardInterval() == SimTime(800, SIMTIME_NS)); + ASSERT(selected->getDataMode()->getNetBitrate() <= requested->getDataMode()->getNetBitrate()); + auto fallback = selectPeerCompatibleVhtMode(modes, local, operation, nullptr, requested); + ASSERT(fallback->getVhtMcsIndex() < 0 && fallback->getHtMcsIndex() < 0); + checked++; + } + } +} +ASSERT(checked == 4960); +operation.channelWidth = MHz(160); +peer.operation.channelWidth = MHz(20); +auto requested = modes->getFastestMode(); +auto selected = selectPeerCompatibleVhtMode(modes, local, operation, &peer, requested); +ASSERT(selected->getDataMode()->getBandwidth() == MHz(20)); +peer.advertisedCapabilities.rxHighestLongGiRateMbps = 20; +local.txHighestLongGiRateMbps = 10; +peer.advertisedCapabilities.shortGi20 = false; +selected = selectPeerCompatibleVhtMode(modes, local, operation, &peer, requested); +ASSERT(selected->getDataMode()->getNetBitrate() <= Mbps(10)); +// Table 9-315 encodes 58.5 Mb/s as 58; it must not reject that mode. +local.txHighestLongGiRateMbps = 58; +peer.advertisedCapabilities.rxHighestLongGiRateMbps = 58; +const IIeee80211Mode *fractional = nullptr; +for (int i = 0; i < modes->getNumModes(); i++) { + auto mode = modes->getMode(i); + auto data = mode->getDataMode(); + if (mode->getVhtMcsIndex() == 6 && data->getBandwidth() == MHz(20) && + data->getNumberOfSpatialStreams() == 1 && data->getGuardInterval() == SimTime(800, SIMTIME_NS)) + fractional = mode; +} +ASSERT(fractional != nullptr); +ASSERT(selectPeerCompatibleVhtMode(modes, local, operation, &peer, fractional) == fractional); +std::cout << "VHT directional selection verified for 4960 requests.\n"; + +%contains: stdout +VHT directional selection verified for 4960 requests.