diff --git a/tests/protocol/wifi/11n/N_HtCapabilitiesIe.test b/tests/protocol/wifi/11n/N_HtCapabilitiesIe.test index f4facfdbc33..c30376ebdf0 100644 --- a/tests/protocol/wifi/11n/N_HtCapabilitiesIe.test +++ b/tests/protocol/wifi/11n/N_HtCapabilitiesIe.test @@ -1,27 +1,69 @@ -%# expected-result: FAIL %description: -802.11n HT Capabilities information element in Association Request (type 0): an -Ieee80211HtCapabilities chunk is present in the frame. [NOT-MODELED -- INET's -Ieee80211MgmtFrame.msg defines AssociationRequestFrame with only SSID and -supportedRates fields; no HT Capabilities or HT Operation IE chunk is generated; -the classes Ieee80211HtCapabilities and Ieee80211HtOperation do not exist in INET] +802.11n HT Capabilities information element in an Association Request (type 0). +The n(mixed-2.4Ghz) fixture must advertise HT through the actual management-frame +body, include a meaningful mandatory MCS bit, and carry the HT Capabilities element +on the serialized wire. [Required -- IEEE 802.11-2024 Section 9.4.2.54] %file: N_HtCapabilitiesIe.cc #include "ProtocolTest.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame_m.h" + +#include +#include namespace inet { namespace protocoltest { -// Spec-required assertion: an Association Request from an HT STA must include the -// HT Capabilities element (IEEE 802.11-2020 Section 9.4.2.56). INET's mgmt layer -// never inserts this chunk; the filter will never match. +static bool hasHtCapabilitiesMcs0InModel(const Packet *packet) +{ + const auto& macHeader = packet->peekAtFront(); + const auto& body = packet->peekAt(macHeader->getChunkLength()); + return body->getHtCapabilitiesPresent() && body->getHtCapabilities().rxMcsSupported[0]; +} + +// The packetSentToLower event is still in the packet domain, so checking the typed +// management body alone would not prove the bytes produced by the serializer. Parse +// the serialized Association Request body as information elements. The +// EID (45) and fixed body length (26) are the HT Capabilities element; MCS 0 is +// bit 0 of the first byte of the 16-octet Supported MCS Set (body offset 3). +static bool hasHtCapabilitiesElementOnWire(const Packet *packet) +{ + const auto bytes = packet->peekAllAsBytes()->getBytes(); + const auto& macHeader = packet->peekAtFront(); + const size_t headerLength = macHeader->getChunkLength().get(); + constexpr size_t fixedAssociationRequestLength = 4; // Capability + Listen Interval + constexpr size_t fcsLength = 4; + if (headerLength + fixedAssociationRequestLength + fcsLength > bytes.size()) + return false; + size_t offset = headerLength + fixedAssociationRequestLength; + const size_t bodyEnd = bytes.size() - fcsLength; + while (offset + 2 <= bodyEnd) { + const uint8_t elementId = bytes[offset++]; + const size_t length = bytes[offset++]; + if (offset + length > bodyEnd) + return false; + if (elementId == 45) + return length == 26 && (bytes[offset + 3] & 1) != 0; + offset += length; + } + return false; +} + +// The association request must enter the real STA management path with the HT +// capability gate enabled. The assertion is deliberately separate from the +// selector: if the IE disappears, the first Association Request is selected and +// the test fails immediately instead of waiting for a later matching event. Define_ProtocolTest(wifi_11n_ht_capabilities_ie) { return ProtocolTest("wifi_11n_ht_capabilities_ie") .once(on("sta1.wlan[0].mac").signal("packetSentToLower") - .filterPacket("ieee80211mac.type == 0 && Ieee80211HtCapabilities.length >= 0") - .describe("an Association Request containing HT Capabilities IE").within(2.5)); + .filterPacket("ieee80211mac.type == 0") + .assertPacket("Ieee80211AssociationRequestFrame.htCapabilitiesPresent == true") + .assertEvent([](const MatchContext& c) { return hasHtCapabilitiesMcs0InModel(c.event.packet) && hasHtCapabilitiesElementOnWire(c.event.packet); }) + .describe("an HT Association Request with MCS 0 and a serialized HT Capabilities IE") + .within(2.5)); } } // namespace protocoltest @@ -33,6 +75,7 @@ Define_ProtocolTest(wifi_11n_ht_capabilities_ie) *.tester.testName = "wifi_11n_ht_capabilities_ie" include ../../ini/_n.ini include ../../ini/_base.ini +*.sta1.wlan[0].mac.fcsMode = "computed" %contains: stdout PROTOCOLTEST wifi_11n_ht_capabilities_ie: PASS diff --git a/tests/protocol/wifi/11n/N_HtCapabilitiesIeLegacy.test b/tests/protocol/wifi/11n/N_HtCapabilitiesIeLegacy.test new file mode 100644 index 00000000000..89679a026f5 --- /dev/null +++ b/tests/protocol/wifi/11n/N_HtCapabilitiesIeLegacy.test @@ -0,0 +1,76 @@ +%description: +An Association Request from a legacy STA must not carry an HT Capabilities +information element. The fixture keeps the AP and sta1 in n(mixed-2.4Ghz) mode, +while sta2 is explicitly g(mixed), so the negative observation reaches the same +production management serializer with the HT capability gate disabled. + +%file: N_HtCapabilitiesIeLegacy.cc + +#include "ProtocolTest.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame_m.h" + +#include +#include + +namespace inet { +namespace protocoltest { + +static bool hasHtCapabilitiesInModel(const Packet *packet) +{ + const auto& macHeader = packet->peekAtFront(); + const auto& body = packet->peekAt(macHeader->getChunkLength()); + return body->getHtCapabilitiesPresent(); +} + +static bool hasHtCapabilitiesElementOnWire(const Packet *packet) +{ + const auto bytes = packet->peekAllAsBytes()->getBytes(); + const auto& macHeader = packet->peekAtFront(); + const size_t headerLength = macHeader->getChunkLength().get(); + constexpr size_t fixedAssociationRequestLength = 4; // Capability + Listen Interval + constexpr size_t fcsLength = 4; + ASSERT(headerLength + fixedAssociationRequestLength + fcsLength <= bytes.size()); + size_t offset = headerLength + fixedAssociationRequestLength; + const size_t bodyEnd = bytes.size() - fcsLength; + while (offset + 2 <= bodyEnd) { + const uint8_t elementId = bytes[offset++]; + const size_t length = bytes[offset++]; + ASSERT(offset + length <= bodyEnd); + if (elementId == 45) + return true; + offset += length; + } + ASSERT(offset == bodyEnd); + return false; +} + +Define_ProtocolTest(wifi_11n_ht_capabilities_ie_legacy) +{ + return ProtocolTest("wifi_11n_ht_capabilities_ie_legacy") + .once(on("sta2.wlan[0].mac").signal("packetSentToLower") + .filterPacket("ieee80211mac.type == 0") + .assertPacket("Ieee80211AssociationRequestFrame.htCapabilitiesPresent == false") + .assertEvent([](const MatchContext& c) { return !hasHtCapabilitiesInModel(c.event.packet) && !hasHtCapabilitiesElementOnWire(c.event.packet); }) + .describe("a legacy Association Request without HT Capabilities") + .within(2.5)); +} + +} // namespace protocoltest +} // namespace inet + +%inifile: test.ini + +[General] +*.tester.testName = "wifi_11n_ht_capabilities_ie_legacy" +include ../../ini/_n.ini +include ../../ini/_base.ini +# Make only the observed STA legacy-capable. The AP and sta1 remain HT-capable, +# which makes this a negative fixture for the same Association Request path. +*.sta2.wlan[0].opMode = "g(mixed)" +*.sta2.wlan[0].mac.modeSet = "g(mixed)" +*.sta2.wlan[0].radio.opMode = "g(mixed)" +*.sta2.wlan[0].mac.fcsMode = "computed" + +%contains: stdout +PROTOCOLTEST wifi_11n_ht_capabilities_ie_legacy: PASS diff --git a/tests/protocol/wifi/11n/N_HtOperationIe.test b/tests/protocol/wifi/11n/N_HtOperationIe.test index b9243200069..e37305cb822 100644 --- a/tests/protocol/wifi/11n/N_HtOperationIe.test +++ b/tests/protocol/wifi/11n/N_HtOperationIe.test @@ -1,25 +1,54 @@ -%# expected-result: FAIL %description: -802.11n HT Operation information element in Beacon (type 8): an Ieee80211HtOperation -chunk is present in the Beacon frame. [NOT-MODELED -- INET's Ieee80211BeaconFrame -contains only SSID, supportedRates, beaconInterval, channelNumber, and -handoverParameters; no HT Operation IE is generated] +An HT AP's generated Beacon carries HT Operation in its management body and on +wire (IEEE 802.11-2024 Section 9.4.2.55). The fixture pins the primary channel +index to zero, corresponding to IEEE channel 1 in the 2.4 GHz band. %file: N_HtOperationIe.cc #include "ProtocolTest.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" +#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame_m.h" namespace inet { namespace protocoltest { -// Spec-required assertion: Beacons from an HT AP must include the HT Operation -// element (IEEE 802.11-2020 Section 9.4.2.57). INET omits this element entirely. +static bool hasExpectedHtOperation(const Packet *packet) +{ + const auto& header = packet->peekAtFront(); + const auto& body = packet->peekAt(header->getChunkLength()); + if (!body->getHtOperationPresent() || body->getHtOperation().primaryChannel != 1) + return false; + const auto bytes = packet->peekAllAsBytes()->getBytes(); + size_t offset = header->getChunkLength().get() + 12; // TSF, interval, capability + constexpr size_t fcsLength = 4; + if (offset + fcsLength > bytes.size()) + return false; + const size_t end = bytes.size() - fcsLength; + bool found = false; + while (offset + 2 <= end) { + const auto id = bytes[offset++]; + const size_t length = bytes[offset++]; + if (offset + length > end) + return false; + if (id == 61) { + if (found || length != 22 || bytes[offset] != 1) + return false; + found = true; + } + offset += length; + } + return found && offset == end; +} + Define_ProtocolTest(wifi_11n_ht_operation_ie) { return ProtocolTest("wifi_11n_ht_operation_ie") .once(on("ap.wlan[0].mac").signal("packetSentToLower") - .filterPacket("ieee80211mac.type == 8 && Ieee80211HtOperation.length >= 0") - .describe("a Beacon frame containing HT Operation IE").within(2.5)); + .filterPacket("ieee80211mac.type == 8") + .assertPacket("Ieee80211BeaconFrame.htOperationPresent == true") + .assertEvent([](const MatchContext& c) { return hasExpectedHtOperation(c.event.packet); }) + .describe("an HT Beacon advertising primary channel 1 in its body and wire IE") + .within(2.5)); } } // namespace protocoltest @@ -31,6 +60,8 @@ Define_ProtocolTest(wifi_11n_ht_operation_ie) *.tester.testName = "wifi_11n_ht_operation_ie" include ../../ini/_n.ini include ../../ini/_base.ini +*.ap.wlan[0].radio.channelNumber = 0 +*.ap.wlan[0].mac.fcsMode = "computed" %contains: stdout PROTOCOLTEST wifi_11n_ht_operation_ie: PASS diff --git a/tests/protocol/wifi/README.md b/tests/protocol/wifi/README.md index 64632f0e47f..326a66fcd98 100644 --- a/tests/protocol/wifi/README.md +++ b/tests/protocol/wifi/README.md @@ -30,7 +30,7 @@ Each test is one **`.test`** file: its program lives in a `%file: .cc` ```sh . /home/levy/workspace/omnetpp/setenv -q -./run-tests.sh # all 74 tests +./run-tests.sh # all 75 tests ./run-tests.sh 11n/N_BlockAck.test # a subset ``` @@ -64,7 +64,7 @@ ignores `%#` lines — the opp_repl wrapper reads it). The wrapper then reports Only `(unexpected)` outcomes fail the run (non-zero exit); expected PASS and expected FAIL are both green. -**Today: 39 CONFORMS (expected PASS) + 35 NOT-MODELED (expected FAIL), 0 DEVIATES across 74 +**Today: 42 CONFORMS (expected PASS) + 33 NOT-MODELED (expected FAIL), 0 DEVIATES across 75 tests — all results as expected, run exits 0.** ## Conformance matrix @@ -152,8 +152,9 @@ be satisfied without changing the shared harness. | `N_CompressedBa` | Compressed Block Ack bitmap | R | ✅ | | `N_RtsCts` | HCF RTS/CTS | R | ✅ | | `N_Ampdu` | A-MPDU aggregation | R | ⛔ policy returns nullptr | -| `N_HtCapabilitiesIe` | HT Capabilities IE in AssocReq | R | ⛔ no IE in mgmt frame | -| `N_HtOperationIe` | HT Operation IE in Beacon | R | ⛔ no IE in mgmt frame | +| `N_HtCapabilitiesIe` | HT Capabilities IE in AssocReq | R | ✅ typed body, MCS 0 and wire IE | +| `N_HtCapabilitiesIeLegacy` | Legacy AssocReq omits HT Capabilities | R | ✅ typed body and wire absence | +| `N_HtOperationIe` | HT Operation IE in Beacon | R | ✅ typed body and wire primary channel | | `N_Smps` | SM Power Save action frame | O | ⛔ | | `N_Greenfield` | HT greenfield preamble | O | ⛔ always MIXED | | `N_StbcCap` | HT STBC | O | ⛔ `getSTBC()` hardcoded 0 | @@ -161,6 +162,17 @@ be satisfied without changing the shared harness. | `N_2040Coex` | 20/40 BSS Coexistence | O | ⛔ | | `N_ReverseDirection` | Reverse Direction Grant (RDG) | O | ⛔ no HT Control field | +The three HT management-element cases above pass in debug mode on the audit implementation +based on `7287f347aaca36e557c708d6930a6f5a450b2833`: + +```sh +inet_run_protocol_tests -m debug -f '(N_HtCapabilitiesIe|N_HtCapabilitiesIeLegacy|N_HtOperationIe)\.test' +``` + +The selectors observe the first relevant production frame and assert the element separately, +so an omitted element fails the assertion instead of being filtered out. This verifies these +frames and fields, not every capability-negotiation procedure. + ### 11ac (WiFi 5 — VHT) | Test | Feature | R/O | | |------|---------|-----|--| @@ -185,9 +197,9 @@ be satisfied without changing the shared harness. aggregation-policy module exist, but `BasicMpduAggregationPolicy::computeAggregateFrames()` returns `nullptr`, so no A-MPDU is ever emitted — a *required* 802.11ac feature (all VHT data is meant to ride in A-MPDUs). The most significant gap. (A-MSDU *is* modeled.) -- **No HT/VHT management Information Elements** (`N_HtCapabilitiesIe`, `N_HtOperationIe`, - `Ac_VhtIe`): Beacon/AssociationRequest carry no HT/VHT Capabilities or Operation elements, so - capability negotiation is not modeled — notable for real-device interoperability. +- **VHT management Information Elements** (`Ac_VhtIe`) remain a separately tracked gap. + HT Capabilities and HT Operation are modeled and checked in generated management frames; + their test results do not establish VHT support. - **ERP protection absent** (`G_ErpProtection`): `g(mixed)` never emits CTS-to-self/RTS before ERP-OFDM data when legacy STAs are present. - **STBC hardcoded off** in both HT and VHT signal modes (`getSTBC()` ≡ 0); **LDPC absent**.