Skip to content

Commit 52fb0b8

Browse files
Fix VHT short-GI timing and mode-set remapping
Use 3.6 us raw VHT data symbols for short guard intervals and round the aggregate data duration to a 4 us boundary according to IEEE 802.11-2024. Keep VHT signaling intervals and rates independent of the data guard interval. Add an exact PHY compatibility lookup for mode-set transitions. Preserve bitrate, bandwidth, spatial-stream count, and modeled guard-interval presence, and reject incompatible transitions before mutating transmitter state. Document radio command precedence and rate-selection guard-interval qualifiers. Extend the focused IEEE 802.11 guard-interval test with VHT rounding boundaries, signaling checks, compatible and rejected mode-set transitions, and atomic state preservation. Validated with the debug build, the filtered Ieee80211HtGuardInterval_1 unit test, and the lan80211ac Ping1 fingerprint.
1 parent 4aebf17 commit 52fb0b8

10 files changed

Lines changed: 127 additions & 16 deletions

File tree

src/inet/linklayer/ieee80211/mac/rateselection/QosRateSelection.ned

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ simple QosRateSelection extends SimpleModule
3333
double dataFrameBitrate @unit(bps) = default(-1bps); // Fastest
3434
double dataFrameBandwidth @unit(Hz) = default(nan Hz); // Explicit mode qualifier when dataFrameBitrate is specified
3535
int dataFrameNumSpatialStreams = default(-1); // Explicit mode qualifier when dataFrameBitrate is specified
36-
double dataFrameGuardInterval @unit(s) = default(-1s); // Explicit mode qualifier when dataFrameBitrate is specified
36+
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
3737

3838
double mgmtFrameBitrate @unit(bps) = default(-1bps); // Fastest
3939
double controlFrameBitrate @unit(bps) = default(-1bps);

src/inet/linklayer/ieee80211/mac/rateselection/RateSelection.ned

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ simple RateSelection extends SimpleModule like IRateSelection
2828
double dataFrameBitrate @unit(bps) = default(-1bps); // Fastest
2929
double dataFrameBandwidth @unit(Hz) = default(nan Hz); // Explicit mode qualifier when dataFrameBitrate is specified
3030
int dataFrameNumSpatialStreams = default(-1); // Explicit mode qualifier when dataFrameBitrate is specified
31-
double dataFrameGuardInterval @unit(s) = default(-1s); // Explicit mode qualifier when dataFrameBitrate is specified
31+
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
3232

3333
double mgmtFrameBitrate @unit(bps) = default(-1bps); // Fastest
3434
double controlFrameBitrate @unit(bps) = default(-1bps);

src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.cc

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,33 @@ bool Ieee80211ModeSet::getIsMandatory(const IIeee80211Mode *mode) const
520520
return entries[getModeIndex(mode)].isMandatory;
521521
}
522522

523+
const IIeee80211Mode *Ieee80211ModeSet::findCompatibleMode(const IIeee80211Mode *mode) const
524+
{
525+
if (mode == nullptr)
526+
return nullptr;
527+
528+
const auto sourceDataMode = mode->getDataMode();
529+
const auto sourceBitrate = sourceDataMode->getNetBitrate();
530+
const auto sourceBandwidth = sourceDataMode->getBandwidth();
531+
const auto sourceGuardInterval = sourceDataMode->getGuardInterval();
532+
const auto minBitrate = sourceBitrate - Mbps(0.05);
533+
const auto maxBitrate = sourceBitrate + Mbps(0.05);
534+
for (const auto& entry : entries) {
535+
const auto candidateDataMode = entry.mode->getDataMode();
536+
const auto candidateBandwidth = candidateDataMode->getBandwidth();
537+
const auto candidateGuardInterval = candidateDataMode->getGuardInterval();
538+
const bool bandwidthMatches = (std::isnan(sourceBandwidth.get()) && std::isnan(candidateBandwidth.get())) ||
539+
(!std::isnan(sourceBandwidth.get()) && !std::isnan(candidateBandwidth.get()) && sourceBandwidth == candidateBandwidth);
540+
const bool guardIntervalMatches = (sourceGuardInterval < SIMTIME_ZERO && candidateGuardInterval < SIMTIME_ZERO) ||
541+
(sourceGuardInterval >= SIMTIME_ZERO && candidateGuardInterval == sourceGuardInterval);
542+
if (minBitrate <= candidateDataMode->getNetBitrate() && candidateDataMode->getNetBitrate() <= maxBitrate &&
543+
bandwidthMatches && candidateDataMode->getNumberOfSpatialStreams() == sourceDataMode->getNumberOfSpatialStreams() &&
544+
guardIntervalMatches)
545+
return entry.mode;
546+
}
547+
return nullptr;
548+
}
549+
523550
const IIeee80211Mode *Ieee80211ModeSet::findMode(bps bitrate, Hz bandwidth, int numSpatialStreams, simtime_t guardInterval) const
524551
{
525552
return findMode(bitrate - Mbps(0.05), bitrate + Mbps(0.05), bandwidth, numSpatialStreams, guardInterval);

src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211ModeSet.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ class INET_API Ieee80211ModeSet : public IPrintableObject, public cObject
5252

5353
bool containsMode(const IIeee80211Mode *mode) const { return findModeIndex(mode) != -1; }
5454
bool getIsMandatory(const IIeee80211Mode *mode) const;
55+
// Finds a mode with the same PHY tuple as mode. Unlike findMode(), this
56+
// treats an absent guard interval (negative value) as an exact value.
57+
const IIeee80211Mode *findCompatibleMode(const IIeee80211Mode *mode) const;
5558

5659
const IIeee80211Mode *findMode(bps bitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1, simtime_t guardInterval = -1) const;
5760
const IIeee80211Mode *findMode(bps minBitrate, bps maxBitrate, Hz bandwidth = Hz(NaN), int numSpatialStreams = -1, simtime_t guardInterval = -1) const;

src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.cc

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -258,12 +258,9 @@ const simtime_t Ieee80211VhtPreambleMode::getDuration() const
258258
bps Ieee80211VhtSignalMode::computeGrossBitrate() const
259259
{
260260
unsigned int numberOfCodedBitsPerSymbol = modulation->getSubcarrierModulation()->getCodeWordSize() * getNumberOfDataSubcarriers();
261-
if (guardIntervalType == HT_GUARD_INTERVAL_LONG)
262-
return bps(numberOfCodedBitsPerSymbol / getSymbolInterval());
263-
else if (guardIntervalType == HT_GUARD_INTERVAL_SHORT)
264-
return bps(numberOfCodedBitsPerSymbol / getShortGISymbolInterval());
265-
else
266-
throw cRuntimeError("Unknown guard interval type");
261+
// IEEE Std 802.11-2024, Table 21-5: VHT-SIG fields use TSYML even
262+
// when the Data field uses short GI; their signaling rate is GI-independent.
263+
return bps(numberOfCodedBitsPerSymbol / getSymbolInterval());
267264
}
268265

269266
bps Ieee80211VhtSignalMode::computeNetBitrate() const
@@ -646,6 +643,19 @@ const simtime_t Ieee80211VhtDataMode::getDuration(b dataLength) const
646643
return numberOfSymbols * getSymbolInterval();
647644
}
648645

646+
const simtime_t Ieee80211VhtMode::getDataDuration(b dataBitLength) const
647+
{
648+
auto dataDuration = dataMode->getDuration(dataBitLength);
649+
if (dataMode->getGuardInterval() == dataMode->getShortGIDuration()) {
650+
// IEEE Std 802.11-2024, 21.4.3, Eq. (21-109): short-GI VHT data
651+
// airtime is the raw TSYMS train rounded up to a TSYML boundary.
652+
const auto longGiSymbolInterval = dataMode->getDFTPeriod() + dataMode->getGIDuration();
653+
const auto numberOfLongGiSymbols = (dataDuration.raw() + longGiSymbolInterval.raw() - 1) / longGiSymbolInterval.raw();
654+
dataDuration = SimTime::fromRaw(numberOfLongGiSymbols * longGiSymbolInterval.raw());
655+
}
656+
return dataDuration;
657+
}
658+
649659
const simtime_t Ieee80211VhtMode::getSlotTime() const
650660
{
651661
if (centerFrequencyMode == BAND_5GHZ)

src/inet/physicallayer/wireless/ieee80211/mode/Ieee80211VhtMode.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ class INET_API Ieee80211VhtSignalMode : public IIeee80211HeaderMode, public Ieee
9696
virtual b getLength() const override;
9797
virtual bps getNetBitrate() const override { return Ieee80211VhtModeBase::getNetBitrate(); }
9898
virtual bps getGrossBitrate() const override { return Ieee80211VhtModeBase::getGrossBitrate(); }
99+
// IEEE Std 802.11-2024, Table 21-5: VHT-SIG uses the long-GI symbol
100+
// interval independently of the data field's selected guard interval.
99101
virtual const simtime_t getSymbolInterval() const override { return Ieee80211HtTimingRelatedParametersBase::getSymbolInterval(); }
100102
virtual const Ieee80211OfdmModulation *getModulation() const override { return modulation; }
101103
virtual const Ieee80211VhtCode *getCode() const { return code; }
@@ -239,7 +241,9 @@ class INET_API Ieee80211VhtDataMode : public IIeee80211DataMode, public Ieee8021
239241
virtual const Ieee80211Vhtmcs *getModulationAndCodingScheme() const { return modulationAndCodingScheme; }
240242
virtual const Ieee80211VhtCode *getCode() const { return modulationAndCodingScheme->getCode(); }
241243
virtual const simtime_t getGuardInterval() const override { return guardIntervalType == HT_GUARD_INTERVAL_LONG ? getGIDuration() : getShortGIDuration(); }
242-
virtual const simtime_t getSymbolInterval() const override { return Ieee80211HtTimingRelatedParametersBase::getSymbolInterval(); }
244+
// IEEE Std 802.11-2024, Tables 21-5 and 21-8: the VHT Data symbol
245+
// interval is TSYML for long GI and TSYMS for short GI.
246+
virtual const simtime_t getSymbolInterval() const override { return getDFTPeriod() + getGuardInterval(); }
243247
virtual const Ieee80211OfdmModulation *getModulation() const override { return modulationAndCodingScheme->getModulation(); }
244248
};
245249

@@ -282,10 +286,10 @@ class INET_API Ieee80211VhtMode : public Ieee80211ModeBase
282286
virtual int getMpduMaxLength() const override { return 65535; } // in octets
283287
virtual BandMode getCenterFrequencyMode() const { return centerFrequencyMode; }
284288

285-
virtual const simtime_t getDuration(b dataBitLength) const override { return preambleMode->getDuration() + dataMode->getDuration(dataBitLength); }
289+
virtual const simtime_t getDuration(b dataBitLength) const override { return preambleMode->getDuration() + getDataDuration(dataBitLength); }
286290
virtual const simtime_t getPreambleDuration() const override { return preambleMode->getDurationBeforeHeader(); }
287291
virtual const simtime_t getHeaderDuration() const override { return preambleMode->getDuration() - getPreambleDuration(); }
288-
virtual const simtime_t getDataDuration(b dataBitLength) const override { return dataMode->getDuration(dataBitLength); }
292+
virtual const simtime_t getDataDuration(b dataBitLength) const override;
289293
};
290294

291295
// A specification of the high-throughput (HT) physical layer (PHY)

src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211ControlInfo.msg

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ namespace inet::physicallayer;
1717
//
1818
class Ieee80211ConfigureRadioCommand extends ConfigureRadioCommand
1919
{
20-
string opMode; // new default operation mode or "" if not set.
21-
const Ieee80211ModeSet *modeSet; // new default mode set or nullptr if not set.
22-
const IIeee80211Mode *mode; // new default transmission mode or nullptr if not set.
20+
string opMode; // new default operation mode or "" if not set; ignored when modeSet is set.
21+
const Ieee80211ModeSet *modeSet; // new default mode set or nullptr if not set; takes precedence over opMode.
22+
const IIeee80211Mode *mode; // new default transmission mode or nullptr if not set; atomically validated against the resolved mode set.
2323
IIeee80211Band *band; // new default band or nullptr if not set.
2424
Ieee80211Channel *channel; // new default band and channel or nullptr if not set.
2525
int channelNumber = -1; // new default channel number in the range [0, numChannels] or -1 if not set.

src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,9 @@ void Ieee80211Transmitter::setModeSet(const Ieee80211ModeSet *modeSet)
7777
if (this->modeSet != modeSet) {
7878
auto newMode = mode;
7979
if (mode != nullptr && modeSet != nullptr && !modeSet->containsMode(mode)) {
80-
auto dataMode = mode->getDataMode();
81-
newMode = modeSet->getMode(dataMode->getNetBitrate(), dataMode->getBandwidth(), dataMode->getNumberOfSpatialStreams(), dataMode->getGuardInterval());
80+
newMode = modeSet->findCompatibleMode(mode);
81+
if (newMode == nullptr)
82+
throw cRuntimeError("Cannot map current mode to operation mode '%s' without changing bitrate, bandwidth, spatial streams, or guard interval", modeSet->getName());
8283
}
8384
else if (modeSet == nullptr)
8485
newMode = nullptr;

src/inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmitter.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class INET_API Ieee80211Transmitter : public FlatTransmitterBase
3838
virtual const IIeee80211Mode *computeTransmissionMode(const Packet *packet) const;
3939
virtual const Ieee80211Channel *computeTransmissionChannel(const Packet *packet) const;
4040

41+
// Re-selects the current mode only when bitrate, bandwidth, NSS, and GI
42+
// remain compatible. Use setModeSetAndMode for an explicit transition.
4143
virtual void setModeSet(const Ieee80211ModeSet *modeSet);
4244
// Applies a mode set and an explicitly selected mode as one validated update.
4345
virtual void setModeSetAndMode(const Ieee80211ModeSet *modeSet, const IIeee80211Mode *mode);

tests/unit/Ieee80211HtGuardInterval_1.test

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,46 @@ const auto legacyOfdmModeSet = Ieee80211ModeSet::getModeSet("a");
226226
ASSERT(legacyOfdmModeSet->findMode(Mbps(6), Hz(NaN), -1, SimTime(800, SIMTIME_NS)) != nullptr);
227227
ASSERT(legacyOfdmModeSet->findMode(Mbps(6), Hz(NaN), -1, SimTime(400, SIMTIME_NS)) == nullptr);
228228

229+
// Mode-set remapping preserves the complete modeled PHY tuple. In
230+
// particular, a negative GI is an actual absence for compatibility lookup,
231+
// not findMode()'s public wildcard.
232+
const auto erpModeSet = Ieee80211ModeSet::getModeSet("g(erp)");
233+
const auto pModeSet = Ieee80211ModeSet::getModeSet("p");
234+
const auto aMode = legacyOfdmModeSet->getMode(Mbps(6), MHz(20), 1, SimTime(800, SIMTIME_NS));
235+
const auto erpMode = erpModeSet->findCompatibleMode(aMode);
236+
ASSERT(erpMode != nullptr);
237+
ASSERT(erpMode->getDataMode()->getNetBitrate() == aMode->getDataMode()->getNetBitrate());
238+
ASSERT(erpMode->getDataMode()->getBandwidth() == aMode->getDataMode()->getBandwidth());
239+
ASSERT(erpMode->getDataMode()->getGuardInterval() == aMode->getDataMode()->getGuardInterval());
240+
241+
const auto dsssModeSet = Ieee80211ModeSet::getModeSet("b");
242+
const auto mixedModeSet = Ieee80211ModeSet::getModeSet("g(mixed)");
243+
const auto dsssMode = dsssModeSet->getMode(Mbps(1));
244+
ASSERT(dsssMode->getDataMode()->getGuardInterval() < SIMTIME_ZERO);
245+
ASSERT(mixedModeSet->findCompatibleMode(dsssMode) != nullptr);
246+
247+
TestIeee80211Transmitter legacyTransmitter;
248+
legacyTransmitter.setModeSet(legacyOfdmModeSet);
249+
legacyTransmitter.setMode(aMode);
250+
legacyTransmitter.setModeSet(erpModeSet);
251+
ASSERT(legacyTransmitter.getSelectedModeSet() == erpModeSet);
252+
ASSERT(legacyTransmitter.getSelectedMode() == erpMode);
253+
const auto modeBeforeRejectedTransition = legacyTransmitter.getSelectedMode();
254+
bool rejectedIncompatibleModeSet = false;
255+
try {
256+
legacyTransmitter.setModeSet(pModeSet);
257+
}
258+
catch (cRuntimeError&) {
259+
rejectedIncompatibleModeSet = true;
260+
}
261+
ASSERT(rejectedIncompatibleModeSet);
262+
ASSERT(legacyTransmitter.getSelectedModeSet() == erpModeSet);
263+
ASSERT(legacyTransmitter.getSelectedMode() == modeBeforeRejectedTransition);
264+
const auto pMode = pModeSet->getMode(2); // the declared 6 Mbps 10 MHz mode
265+
legacyTransmitter.setModeSetAndMode(pModeSet, pMode);
266+
ASSERT(legacyTransmitter.getSelectedModeSet() == pModeSet);
267+
ASSERT(legacyTransmitter.getSelectedMode() == pMode);
268+
229269
Ieee80211VhtCompliantModes::getCompliantMode(&Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1,
230270
Ieee80211VhtMode::BAND_5GHZ, Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED,
231271
Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG);
@@ -240,6 +280,30 @@ catch (cRuntimeError&) {
240280
}
241281
ASSERT(rejectedVhtGreenfieldAfterMixed);
242282
const auto vhtModeSet = Ieee80211ModeSet::getModeSet("ac");
283+
const auto vhtLongMcs0 = Ieee80211VhtCompliantModes::getCompliantMode(
284+
&Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, Ieee80211VhtMode::BAND_5GHZ,
285+
Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211VhtModeBase::HT_GUARD_INTERVAL_LONG);
286+
const auto vhtShortMcs0 = Ieee80211VhtCompliantModes::getCompliantMode(
287+
&Ieee80211VhtmcsTable::vhtMcs0BW20MHzNss1, Ieee80211VhtMode::BAND_5GHZ,
288+
Ieee80211VhtPreambleMode::HT_PREAMBLE_MIXED, Ieee80211VhtModeBase::HT_GUARD_INTERVAL_SHORT);
289+
// IEEE Std 802.11-2024, Tables 21-5/21-8 and 21.4.3, Eqs. (21-109)/(21-110).
290+
ASSERT(vhtLongMcs0->getDataMode()->getSymbolInterval() == SimTime(4, SIMTIME_US));
291+
ASSERT(vhtShortMcs0->getDataMode()->getSymbolInterval() == SimTime(3600, SIMTIME_NS));
292+
ASSERT(vhtLongMcs0->getHeaderMode()->getSymbolInterval() == SimTime(4, SIMTIME_US));
293+
ASSERT(vhtShortMcs0->getHeaderMode()->getSymbolInterval() == SimTime(4, SIMTIME_US));
294+
ASSERT(vhtLongMcs0->getHeaderMode()->getNetBitrate() == vhtShortMcs0->getHeaderMode()->getNetBitrate());
295+
ASSERT(vhtLongMcs0->getHeaderMode()->getGrossBitrate() == vhtShortMcs0->getHeaderMode()->getGrossBitrate());
296+
ASSERT(vhtShortMcs0->getDataMode()->getDuration(B(24)) == SimTime(32400, SIMTIME_NS));
297+
ASSERT(vhtShortMcs0->getDataDuration(B(24)) == SimTime(36, SIMTIME_US));
298+
ASSERT(vhtShortMcs0->getDataMode()->getDuration(B(27)) == SimTime(36, SIMTIME_US));
299+
ASSERT(vhtShortMcs0->getDataDuration(B(27)) == SimTime(36, SIMTIME_US));
300+
ASSERT(vhtShortMcs0->getDataMode()->getDuration(B(30)) == SimTime(39600, SIMTIME_NS));
301+
ASSERT(vhtShortMcs0->getDataDuration(B(30)) == SimTime(40, SIMTIME_US));
302+
ASSERT(vhtLongMcs0->getDataMode()->getDuration(B(24)) == SimTime(36, SIMTIME_US));
303+
ASSERT(vhtLongMcs0->getDataDuration(B(24)) == SimTime(36, SIMTIME_US));
304+
for (auto vhtMode : {vhtLongMcs0, vhtShortMcs0})
305+
for (auto dataLength : {B(24), B(27), B(30)})
306+
ASSERT(vhtMode->getDuration(dataLength) == vhtMode->getPreambleDuration() + vhtMode->getHeaderDuration() + vhtMode->getDataDuration(dataLength));
243307
bool foundVhtLongGuardInterval = false;
244308
bool foundVhtShortGuardInterval = false;
245309
for (int index = 0; index < vhtModeSet->getNumModes(); index++) {

0 commit comments

Comments
 (0)