Skip to content

Commit f30548f

Browse files
ieee80211: add HT capability and operation signaling
Add a model-backed subset of the IEEE 802.11 HT Capabilities and HT Operation state to the MIB. Derive local capability limits from the authoritative mode set, preserve the standard Tx/Rx MCS representation, negotiate directional peer capabilities conservatively, and retain exact bitmap holes for equal Tx/Rx sets. Carry typed HT Capabilities and HT Operation elements in beacon, probe, association, and reassociation management frames. Serialize and deserialize the standard element layouts with subtype presence validation, malformed-element rejection, and concise protocol-printer output. Install peer HT state during detailed and simplified management flows. At the AP, reserve association IDs while a response is pending and commit the AID, station state, and peer capabilities only after the response is acknowledged. Preserve state across retries, clean up final failures, and downgrade an associated station after an acknowledged refusal when management-frame protection is not modeled. Add focused unit coverage for directional negotiation, unequal and undefined Tx MCS advertisements, non-contiguous MCS bitmaps, byte-level management element encoding, malformed inputs, and subtype policy. Add a detailed 802.11n association module test that verifies the request/response element matrix and peer-state installation.
1 parent fa3c69f commit f30548f

21 files changed

Lines changed: 1396 additions & 68 deletions
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//
2+
// Copyright (C) 2026 INET Framework contributors
3+
//
4+
// SPDX-License-Identifier: LGPL-3.0-or-later
5+
//
6+
7+
#ifndef __INET_IEEE80211HTMGMTELEMENTS_H
8+
#define __INET_IEEE80211HTMGMTELEMENTS_H
9+
10+
#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame_m.h"
11+
#include "inet/linklayer/ieee80211/mib/Ieee80211HtCapabilities.h"
12+
13+
namespace inet {
14+
namespace ieee80211 {
15+
16+
inline Ieee80211HtCapabilitiesElement makeHtCapabilitiesElement(const Ieee80211HtCapabilities& capabilities)
17+
{
18+
Ieee80211HtCapabilitiesElement element;
19+
element.ldpc = capabilities.ldpc;
20+
element.supportedChannelWidth40Mhz = capabilities.supportedChannelWidths.count(MHz(40)) != 0;
21+
element.greenfield = capabilities.greenfield;
22+
element.shortGi20 = capabilities.shortGi20;
23+
element.shortGi40 = capabilities.shortGi40;
24+
element.maxAmpduLengthExponent = capabilities.maxAmpduLengthExponent;
25+
for (int i = 0; i < 77; i++)
26+
element.rxMcsSupported[i] = capabilities.rxMcsSupported[i];
27+
element.txMcsSetDefined = capabilities.txMcsSetDefined;
28+
element.txRxMcsSetNotEqual = capabilities.txRxMcsSetNotEqual;
29+
element.txMaxNss = capabilities.txMaxNss;
30+
element.txUnequalModulation = capabilities.txUnequalModulation;
31+
if (element.txMcsSetDefined && !element.txRxMcsSetNotEqual) {
32+
for (int nss = 0; nss < 4; nss++) {
33+
int rxMaximum = -1;
34+
for (int mcs = 0; mcs < 8; mcs++)
35+
if (capabilities.rxMcsSupported[nss * 8 + mcs])
36+
rxMaximum = mcs;
37+
if (capabilities.txMcsNss.maxMcsPerNss[nss] != rxMaximum)
38+
throw cRuntimeError("Equal HT Tx/Rx MCS Set does not match the Rx MCS bitmap");
39+
}
40+
}
41+
return element;
42+
}
43+
44+
inline Ieee80211HtCapabilities makeHtCapabilities(const Ieee80211HtCapabilitiesElement& element)
45+
{
46+
if (element.maxAmpduLengthExponent < 0 || element.maxAmpduLengthExponent > 3)
47+
throw cRuntimeError("Invalid Maximum A-MPDU Length Exponent: %d", element.maxAmpduLengthExponent);
48+
Ieee80211HtCapabilities capabilities;
49+
capabilities.supportedChannelWidths.insert(MHz(20));
50+
if (element.supportedChannelWidth40Mhz)
51+
capabilities.supportedChannelWidths.insert(MHz(40));
52+
capabilities.ldpc = element.ldpc;
53+
capabilities.greenfield = element.greenfield;
54+
capabilities.shortGi20 = element.shortGi20;
55+
capabilities.shortGi40 = element.shortGi40;
56+
capabilities.maxAmpduLengthExponent = element.maxAmpduLengthExponent;
57+
capabilities.txMcsSetDefined = element.txMcsSetDefined;
58+
capabilities.txRxMcsSetNotEqual = element.txRxMcsSetNotEqual;
59+
capabilities.txMaxNss = element.txMaxNss;
60+
capabilities.txUnequalModulation = element.txUnequalModulation;
61+
for (int i = 0; i < 77; i++)
62+
capabilities.rxMcsSupported[i] = element.rxMcsSupported[i];
63+
for (int nss = 0; nss < 4; nss++) {
64+
int rxMaximum = -1;
65+
for (int mcs = 0; mcs < 8; mcs++)
66+
if (element.rxMcsSupported[nss * 8 + mcs])
67+
rxMaximum = mcs;
68+
capabilities.txMcsNss.maxMcsPerNss[nss] = element.txMcsSetDefined && !element.txRxMcsSetNotEqual ? rxMaximum : -1;
69+
}
70+
return capabilities;
71+
}
72+
73+
inline Ieee80211HtOperationElement makeHtOperationElement(const Ieee80211HtOperation& operation)
74+
{
75+
Ieee80211HtOperationElement element;
76+
element.primaryChannel = operation.primaryChannel;
77+
element.secondaryChannelOffset = operation.secondaryChannelOffset;
78+
element.staChannelWidth40Mhz = operation.operatingChannelWidth == MHz(40);
79+
element.protectionMode = static_cast<int>(operation.protectionMode);
80+
for (int i = 0; i < 77; i++)
81+
element.basicMcsSupported[i] = operation.basicMcsSupported[i];
82+
return element;
83+
}
84+
85+
inline Ieee80211HtOperation makeHtOperation(const Ieee80211HtOperationElement& element)
86+
{
87+
if (element.secondaryChannelOffset == 2 || element.secondaryChannelOffset < 0 || element.secondaryChannelOffset > 3)
88+
throw cRuntimeError("Invalid HT Secondary Channel Offset: %d", element.secondaryChannelOffset);
89+
if (element.protectionMode < 0 || element.protectionMode > 3)
90+
throw cRuntimeError("Invalid HT Protection field: %d", element.protectionMode);
91+
Ieee80211HtOperation operation;
92+
operation.primaryChannel = element.primaryChannel;
93+
operation.secondaryChannelOffset = element.secondaryChannelOffset;
94+
operation.operatingChannelWidth = element.staChannelWidth40Mhz ? MHz(40) : MHz(20);
95+
operation.protectionMode = static_cast<Ieee80211HtProtectionMode>(element.protectionMode);
96+
for (int i = 0; i < 77; i++)
97+
operation.basicMcsSupported[i] = element.basicMcsSupported[i];
98+
return operation;
99+
}
100+
101+
inline B getHtMgmtElementsLength(const Ptr<const Ieee80211MgmtFrame>& frame)
102+
{
103+
B length(0);
104+
if (frame->getHtCapabilitiesPresent())
105+
length += B(28);
106+
if (frame->getHtOperationPresent())
107+
length += B(24);
108+
return length;
109+
}
110+
111+
inline void setHtCapabilities(const Ptr<Ieee80211MgmtFrame>& frame, const Ieee80211HtCapabilities& capabilities)
112+
{
113+
frame->setHtCapabilitiesPresent(true);
114+
frame->setHtCapabilities(makeHtCapabilitiesElement(capabilities));
115+
}
116+
117+
inline void setHtOperation(const Ptr<Ieee80211MgmtFrame>& frame, const Ieee80211HtOperation& operation)
118+
{
119+
frame->setHtOperationPresent(true);
120+
frame->setHtOperation(makeHtOperationElement(operation));
121+
}
122+
123+
} // namespace ieee80211
124+
} // namespace inet
125+
126+
#endif

src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.cc

Lines changed: 120 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h"
1818
#include "inet/linklayer/ieee80211/mac/Ieee80211SubtypeTag_m.h"
1919
#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtAp.h"
20+
#include "inet/linklayer/ieee80211/mgmt/Ieee80211HtMgmtElements.h"
2021
#include "inet/networklayer/common/NetworkInterface.h"
2122
#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Radio.h"
2223

@@ -93,6 +94,7 @@ void Ieee80211MgmtAp::receiveSignal(cComponent *source, simsignal_t signalID, in
9394
if (signalID == Ieee80211Radio::radioChannelChangedSignal) {
9495
EV << "updating channel number\n";
9596
channelNumber = value;
97+
mib->htOperation.primaryChannel = channelNumber;
9698
}
9799
}
98100

@@ -105,16 +107,50 @@ void Ieee80211MgmtAp::receiveSignal(cComponent *source, simsignal_t signalID, cO
105107
if (context->getNumSteps() >= 2) {
106108
auto transmitStep = dynamic_cast<ITransmitStep *>(context->getStepBeforeLast());
107109
auto receiveStep = dynamic_cast<IReceiveStep *>(context->getLastStep());
108-
if (transmitStep && receiveStep &&
109-
transmitStep->getCompletion() == IFrameSequenceStep::Completion::ACCEPTED &&
110-
receiveStep->getCompletion() == IFrameSequenceStep::Completion::ACCEPTED) {
110+
if (transmitStep && receiveStep) {
111111
auto responseHeader = dynamicPtrCast<const Ieee80211MgmtHeader>(transmitStep->getFrameToTransmit()->peekAtFront<Ieee80211MacHeader>());
112112
if (responseHeader != nullptr && (responseHeader->getType() == ST_ASSOCIATIONRESPONSE || responseHeader->getType() == ST_REASSOCIATIONRESPONSE)) {
113-
auto ackHeader = receiveStep->getReceivedFrame()->peekAtFront<Ieee80211MacHeader>();
114-
if (ackHeader->getType() == ST_ACK) {
115-
if (responseHeader->getType() == ST_ASSOCIATIONRESPONSE && mib->bssAccessPointData.stations[responseHeader->getReceiverAddress()] != Ieee80211Mib::ASSOCIATED)
116-
sendAssocNotification(responseHeader->getReceiverAddress());
117-
mib->bssAccessPointData.stations[responseHeader->getReceiverAddress()] = Ieee80211Mib::ASSOCIATED;
113+
const auto& address = responseHeader->getReceiverAddress();
114+
auto sta = staList.find(address);
115+
bool exchangeSucceeded = transmitStep->getCompletion() == IFrameSequenceStep::Completion::ACCEPTED &&
116+
receiveStep->getCompletion() == IFrameSequenceStep::Completion::ACCEPTED &&
117+
receiveStep->getReceivedFrame()->peekAtFront<Ieee80211MacHeader>()->getType() == ST_ACK;
118+
bool isPendingResponse = sta != staList.end() && sta->second.pendingAssociationResponse == transmitStep->getFrameToTransmit();
119+
if (isPendingResponse && sta->second.pendingAssociationSuccessful && exchangeSucceeded) {
120+
bool wasAssociated = mib->bssAccessPointData.stations[address] == Ieee80211Mib::ASSOCIATED;
121+
mib->bssAccessPointData.associationIds[address] = sta->second.pendingAssociationId;
122+
mib->bssAccessPointData.stations[address] = Ieee80211Mib::ASSOCIATED;
123+
if (sta->second.pendingHtStateAvailable) {
124+
// IEEE Std 802.11-2024 association state becomes effective only after the successful response exchange.
125+
if (sta->second.pendingHtCapabilitiesValid)
126+
mib->setPeerHtCapabilities(address, sta->second.pendingHtCapabilities, mib->htOperation);
127+
else
128+
mib->removePeerHtCapabilities(address);
129+
}
130+
// Signal delivery is synchronous; observers must see committed station and peer state.
131+
if (responseHeader->getType() == ST_ASSOCIATIONRESPONSE && !wasAssociated)
132+
sendAssocNotification(address);
133+
}
134+
else if (isPendingResponse && !sta->second.pendingAssociationSuccessful && exchangeSucceeded &&
135+
mib->bssAccessPointData.stations[address] == Ieee80211Mib::ASSOCIATED)
136+
{
137+
// This model does not implement negotiated management-frame protection.
138+
// IEEE Std 802.11-2024, 11.3.5.3(p): a refused (re)association
139+
// therefore moves the STA from State 4 back to State 3.
140+
mib->releaseAssociationId(address);
141+
mib->bssAccessPointData.stations[address] = Ieee80211Mib::AUTHENTICATED;
142+
// Signal delivery is synchronous; observers must see the downgraded state.
143+
sendDisAssocNotification(address);
144+
}
145+
if (isPendingResponse) {
146+
bool retryPending = false;
147+
if (!exchangeSucceeded) {
148+
auto inProgressFrames = context->getInProgressFrames();
149+
for (int i = 0; i < inProgressFrames->getLength(); i++)
150+
retryPending |= inProgressFrames->getFrames(i) == transmitStep->getFrameToTransmit();
151+
}
152+
if (exchangeSucceeded || !retryPending)
153+
clearPendingAssociation(&sta->second);
118154
}
119155
}
120156
}
@@ -130,13 +166,42 @@ Ieee80211MgmtAp::StaInfo *Ieee80211MgmtAp::lookupSenderSTA(const Ptr<const Ieee8
130166
return it == staList.end() ? nullptr : &(it->second);
131167
}
132168

133-
void Ieee80211MgmtAp::sendManagementFrame(const char *name, const Ptr<Ieee80211MgmtFrame>& body, int subtype, const MacAddress& destAddr)
169+
Packet *Ieee80211MgmtAp::sendManagementFrame(const char *name, const Ptr<Ieee80211MgmtFrame>& body, int subtype, const MacAddress& destAddr)
134170
{
135171
auto packet = new Packet(name);
136172
packet->addTag<MacAddressReq>()->setDestAddress(destAddr);
137173
packet->addTag<Ieee80211SubtypeReq>()->setSubtype(subtype);
138174
packet->insertAtBack(body);
139175
sendDown(packet);
176+
return packet;
177+
}
178+
179+
short Ieee80211MgmtAp::reserveAssociationId(StaInfo *sta) const
180+
{
181+
auto existing = mib->bssAccessPointData.associationIds.find(sta->address);
182+
if (existing != mib->bssAccessPointData.associationIds.end())
183+
return existing->second;
184+
if (sta->pendingAssociationId != 0)
185+
return sta->pendingAssociationId;
186+
for (short aid = 1; aid <= 2007; aid++) {
187+
bool used = false;
188+
for (const auto& entry : mib->bssAccessPointData.associationIds)
189+
used |= entry.second == aid;
190+
for (const auto& entry : staList)
191+
used |= entry.second.pendingAssociationId == aid;
192+
if (!used)
193+
return aid;
194+
}
195+
throw cRuntimeError("No IEEE 802.11 association ID is available");
196+
}
197+
198+
void Ieee80211MgmtAp::clearPendingAssociation(StaInfo *sta)
199+
{
200+
sta->pendingAssociationSuccessful = false;
201+
sta->pendingAssociationId = 0;
202+
sta->pendingAssociationResponse = nullptr;
203+
sta->pendingHtStateAvailable = false;
204+
sta->pendingHtCapabilitiesValid = false;
140205
}
141206

142207
void Ieee80211MgmtAp::sendBeacon()
@@ -147,7 +212,9 @@ void Ieee80211MgmtAp::sendBeacon()
147212
body->setSupportedRates(supportedRates);
148213
body->setBeaconInterval(beaconInterval);
149214
body->setChannelNumber(channelNumber);
150-
body->setChunkLength(B(8 + 2 + 2 + (2 + ssid.length()) + (2 + supportedRates.numRates)));
215+
addHtCapabilities(body);
216+
addHtOperation(body);
217+
body->setChunkLength(B(8 + 2 + 2 + (2 + ssid.length()) + (2 + supportedRates.numRates)) + getHtMgmtElementsLength(body));
151218
sendManagementFrame("Beacon", body, ST_BEACON, MacAddress::BROADCAST_ADDRESS);
152219
}
153220

@@ -165,6 +232,7 @@ void Ieee80211MgmtAp::handleAuthenticationFrame(Packet *packet, const Ptr<const
165232
sta->address = staAddress;
166233
mib->bssAccessPointData.stations[staAddress] = Ieee80211Mib::NOT_AUTHENTICATED;
167234
sta->authSeqExpected = 1;
235+
clearPendingAssociation(sta);
168236
}
169237

170238
// reset authentication status, when starting a new auth sequence
@@ -180,6 +248,8 @@ void Ieee80211MgmtAp::handleAuthenticationFrame(Packet *packet, const Ptr<const
180248
mib->releaseAssociationId(sta->address);
181249
}
182250
mib->bssAccessPointData.stations[sta->address] = Ieee80211Mib::NOT_AUTHENTICATED;
251+
clearPendingAssociation(sta);
252+
mib->removePeerHtCapabilities(sta->address);
183253
sta->authSeqExpected = 1;
184254
}
185255

@@ -240,6 +310,8 @@ void Ieee80211MgmtAp::handleDeauthenticationFrame(Packet *packet, const Ptr<cons
240310
}
241311
mib->bssAccessPointData.stations[sta->address] = Ieee80211Mib::NOT_AUTHENTICATED;
242312
sta->authSeqExpected = 1;
313+
clearPendingAssociation(sta);
314+
mib->removePeerHtCapabilities(sta->address);
243315
}
244316
}
245317

@@ -258,15 +330,27 @@ void Ieee80211MgmtAp::handleAssociationRequestFrame(Packet *packet, const Ptr<co
258330
return;
259331
}
260332

333+
const auto& requestBody = packet->peekData<Ieee80211AssociationRequestFrame>();
334+
sta->pendingAssociationSuccessful = false;
335+
sta->pendingHtStateAvailable = true;
336+
sta->pendingHtCapabilitiesValid = mib->isHtOperationSupported() && requestBody->getHtCapabilitiesPresent();
337+
if (sta->pendingHtCapabilitiesValid)
338+
sta->pendingHtCapabilities = makeHtCapabilities(requestBody->getHtCapabilities());
339+
bool basicHtMcsSupported = !sta->pendingHtCapabilitiesValid ||
340+
supportsBasicHtMcsSet(sta->pendingHtCapabilities, mib->htOperation);
261341
delete packet;
262342

263-
// send OK response
343+
// IEEE Std 802.11-2024, 11.3.5.3 g): an HT STA must support every Basic HT-MCS.
264344
const auto& body = makeShared<Ieee80211AssociationResponseFrame>();
265-
body->setStatusCode(SC_SUCCESSFUL);
266-
body->setAid(mib->allocateAssociationId(sta->address));
345+
body->setStatusCode(basicHtMcsSupported ? SC_SUCCESSFUL : SC_DATARATE_UNSUP);
346+
sta->pendingAssociationId = basicHtMcsSupported ? reserveAssociationId(sta) : 0;
347+
body->setAid(sta->pendingAssociationId);
348+
sta->pendingAssociationSuccessful = basicHtMcsSupported;
267349
body->setSupportedRates(supportedRates);
268-
body->setChunkLength(B(2 + 2 + 2 + body->getSupportedRates().numRates + 2));
269-
sendManagementFrame("AssocResp-OK", body, ST_ASSOCIATIONRESPONSE, sta->address);
350+
addHtCapabilities(body);
351+
addHtOperation(body);
352+
body->setChunkLength(B(2 + 2 + 2 + body->getSupportedRates().numRates + 2) + getHtMgmtElementsLength(body));
353+
sta->pendingAssociationResponse = sendManagementFrame(basicHtMcsSupported ? "AssocResp-OK" : "AssocResp-UnsupportedHtMcs", body, ST_ASSOCIATIONRESPONSE, sta->address);
270354
}
271355

272356
void Ieee80211MgmtAp::handleAssociationResponseFrame(Packet *packet, const Ptr<const Ieee80211MgmtHeader>& header)
@@ -289,15 +373,27 @@ void Ieee80211MgmtAp::handleReassociationRequestFrame(Packet *packet, const Ptr<
289373
return;
290374
}
291375

376+
const auto& requestBody = packet->peekData<Ieee80211ReassociationRequestFrame>();
377+
sta->pendingAssociationSuccessful = false;
378+
sta->pendingHtStateAvailable = true;
379+
sta->pendingHtCapabilitiesValid = mib->isHtOperationSupported() && requestBody->getHtCapabilitiesPresent();
380+
if (sta->pendingHtCapabilitiesValid)
381+
sta->pendingHtCapabilities = makeHtCapabilities(requestBody->getHtCapabilities());
382+
bool basicHtMcsSupported = !sta->pendingHtCapabilitiesValid ||
383+
supportsBasicHtMcsSet(sta->pendingHtCapabilities, mib->htOperation);
292384
delete packet;
293385

294386
// send OK response
295387
const auto& body = makeShared<Ieee80211ReassociationResponseFrame>();
296-
body->setStatusCode(SC_SUCCESSFUL);
297-
body->setAid(mib->allocateAssociationId(sta->address));
388+
body->setStatusCode(basicHtMcsSupported ? SC_SUCCESSFUL : SC_DATARATE_UNSUP);
389+
sta->pendingAssociationId = basicHtMcsSupported ? reserveAssociationId(sta) : 0;
390+
body->setAid(sta->pendingAssociationId);
391+
sta->pendingAssociationSuccessful = basicHtMcsSupported;
298392
body->setSupportedRates(supportedRates);
299-
body->setChunkLength(B(2 + (2 + ssid.length()) + (2 + supportedRates.numRates) + 6));
300-
sendManagementFrame("ReassocResp-OK", body, ST_REASSOCIATIONRESPONSE, sta->address);
393+
addHtCapabilities(body);
394+
addHtOperation(body);
395+
body->setChunkLength(B(2 + 2 + 2 + (2 + supportedRates.numRates)) + getHtMgmtElementsLength(body));
396+
sta->pendingAssociationResponse = sendManagementFrame(basicHtMcsSupported ? "ReassocResp-OK" : "ReassocResp-UnsupportedHtMcs", body, ST_REASSOCIATIONRESPONSE, sta->address);
301397
}
302398

303399
void Ieee80211MgmtAp::handleReassociationResponseFrame(Packet *packet, const Ptr<const Ieee80211MgmtHeader>& header)
@@ -316,6 +412,8 @@ void Ieee80211MgmtAp::handleDisassociationFrame(Packet *packet, const Ptr<const
316412
mib->releaseAssociationId(sta->address);
317413
}
318414
mib->bssAccessPointData.stations[sta->address] = Ieee80211Mib::AUTHENTICATED;
415+
clearPendingAssociation(sta);
416+
mib->removePeerHtCapabilities(sta->address);
319417
}
320418
}
321419

@@ -344,7 +442,9 @@ void Ieee80211MgmtAp::handleProbeRequestFrame(Packet *packet, const Ptr<const Ie
344442
body->setSupportedRates(supportedRates);
345443
body->setBeaconInterval(beaconInterval);
346444
body->setChannelNumber(channelNumber);
347-
body->setChunkLength(B(8 + 2 + 2 + (2 + ssid.length()) + (2 + supportedRates.numRates)));
445+
addHtCapabilities(body);
446+
addHtOperation(body);
447+
body->setChunkLength(B(8 + 2 + 2 + (2 + ssid.length()) + (2 + supportedRates.numRates)) + getHtMgmtElementsLength(body));
348448
sendManagementFrame("ProbeResp", body, ST_PROBERESPONSE, staAddress);
349449
}
350450

0 commit comments

Comments
 (0)