wifi: Add tests for GCR-BA

This commit is contained in:
Sébastien Deronne
2025-01-16 13:07:15 +01:00
parent e6d3feeb34
commit ea45b8052d
2 changed files with 915 additions and 17 deletions

View File

@@ -353,9 +353,13 @@ GcrTestBase::Transmit(std::string context,
? it->second.at(0)
: ((it->second.count(staId + 1) != 0) ? it->second.at(staId + 1)
: std::set<uint8_t>{});
auto corruptIndex = (m_apWifiMac->GetGcrManager()->GetRetransmissionPolicy() ==
GroupAddressRetransmissionPolicy::GCR_BLOCK_ACK)
? psdu->GetHeader(i).GetSequenceNumber()
: i;
if (std::find(corruptedMpdusForSta.cbegin(),
corruptedMpdusForSta.cend(),
i + 1) != std::end(corruptedMpdusForSta))
corruptIndex + 1) != std::end(corruptedMpdusForSta))
{
NS_LOG_INFO("STA " << staId + 1 << ": corrupted MPDU #" << i + 1 << " (seq="
<< psdu->GetHeader(i).GetSequenceNumber() << ")"
@@ -710,7 +714,9 @@ GcrTestBase::CheckResults()
std::count_if(m_params.stas.cbegin(), m_params.stas.cend(), [](const auto& staInfo) {
return staInfo.gcrCapable;
});
if (m_params.numGroupcastPackets > 0 && (m_params.maxNumMpdusInPsdu > 1))
const auto isGcrBa = m_apWifiMac->GetGcrManager()->GetRetransmissionPolicy() ==
GroupAddressRetransmissionPolicy::GCR_BLOCK_ACK;
if (m_params.numGroupcastPackets > 0 && (isGcrBa || (m_params.maxNumMpdusInPsdu > 1)))
{
NS_TEST_EXPECT_MSG_EQ(+m_nTxGcrAddbaReq,
gcrCapableStas + m_params.addbaReqsToCorrupt.size(),
@@ -904,14 +910,15 @@ GcrTestBase::DoSetup()
auto staHtConfiguration = CreateObject<HtConfiguration>();
staHtConfiguration->m_40MHzSupported =
(m_params.stas.at(i).standard >= WIFI_STANDARD_80211ac ||
m_params.stas.at(i).maxChannelWidth >= 40);
m_params.stas.at(i).maxChannelWidth >= MHz_u{40});
staHtConfiguration->m_sgiSupported = (m_params.stas.at(i).minGi == NanoSeconds(400));
staNetDevice->SetHtConfiguration(staHtConfiguration);
}
if (m_params.stas.at(i).standard >= WIFI_STANDARD_80211ac)
{
auto staVhtConfiguration = CreateObject<VhtConfiguration>();
staVhtConfiguration->m_160MHzSupported = (m_params.stas.at(i).maxChannelWidth >= 160);
staVhtConfiguration->m_160MHzSupported =
(m_params.stas.at(i).maxChannelWidth >= MHz_u{160});
staNetDevice->SetVhtConfiguration(staVhtConfiguration);
}
if (m_params.stas.at(i).standard >= WIFI_STANDARD_80211ax)
@@ -1354,6 +1361,444 @@ GcrUrTest::CheckResults()
}
}
GcrBaTest::GcrBaTest(const std::string& testName,
const GcrParameters& commonParams,
const GcrBaParameters& gcrBaParams)
: GcrTestBase(testName, commonParams),
m_gcrBaParams{gcrBaParams},
m_nTxGcrBar{0},
m_nTxGcrBlockAck{0},
m_nTxBlockAck{0},
m_firstTxSeq{0},
m_lastTxSeq{-1},
m_nTxGcrBarsInCurrentTxop{0}
{
}
void
GcrBaTest::PacketGenerated(std::string context, Ptr<const Packet> p, const Address& addr)
{
if (m_params.rtsFramesToCorrupt.empty() && m_params.ctsFramesToCorrupt.empty())
{
return;
}
GcrTestBase::PacketGenerated(context, p, addr);
}
void
GcrBaTest::Transmit(std::string context,
WifiConstPsduMap psduMap,
WifiTxVector txVector,
double txPowerW)
{
auto psdu = psduMap.cbegin()->second;
auto mpdu = *psdu->begin();
const auto nodeId = ConvertContextToNodeId(context);
auto addr1 = mpdu->GetHeader().GetAddr1();
if (addr1.IsGroup() && !addr1.IsBroadcast() && mpdu->GetHeader().IsQosData())
{
NS_TEST_EXPECT_MSG_EQ(nodeId, 0, "Groupcast transmission from unexpected node");
NS_LOG_INFO("AP: groupcast transmission (#MPDUs=" << psdu->GetNMpdus() << ")");
const auto txopLimitAllowsAggregation =
(m_params.txopLimit.IsZero() || m_params.txopLimit > MicroSeconds(320));
const uint16_t prevTxMpdus = m_totalTx * m_expectedMaxNumMpdusInPsdu;
const uint16_t remainingMpdus = m_params.numGroupcastPackets - prevTxMpdus;
const auto expectedNumAggregates =
(GetNumNonHtStas(m_params.stas) == 0) && txopLimitAllowsAggregation
? (((m_totalTx == 0) || m_params.mpdusToCorruptPerPsdu.empty() ||
(!m_params.mpdusToCorruptPerPsdu.empty() &&
m_params.mpdusToCorruptPerPsdu.cbegin()->second.size() > 1))
? ((m_params.mpdusToCorruptPerPsdu.empty() &&
(GetNumNonGcrStas(m_params.stas) == 0))
? std::min(m_expectedMaxNumMpdusInPsdu, remainingMpdus)
: m_expectedMaxNumMpdusInPsdu)
: ((!m_params.expectedDroppedGroupcastMpdus.empty() &&
m_totalTx <= m_expectedMaxNumMpdusInPsdu)
? m_expectedMaxNumMpdusInPsdu
: m_params.mpdusToCorruptPerPsdu.cbegin()
->second.cbegin()
->second.size()))
: 1;
NS_TEST_EXPECT_MSG_EQ(psdu->GetNMpdus(),
expectedNumAggregates,
"Incorrect number of aggregated MPDUs");
const uint16_t maxLastSeqNum = (((m_totalTx + 1) * m_expectedMaxNumMpdusInPsdu) - 1);
const uint16_t limitLastSeqNum = (m_params.numGroupcastPackets - 1);
uint16_t expectedLastSeqNum =
(m_expectGcrUsed && (GetNumNonHtStas(m_params.stas) > 0))
? (m_totalTx / 2)
: (((GetNumNonHtStas(m_params.stas) == 0) && txopLimitAllowsAggregation)
? std::min(maxLastSeqNum, limitLastSeqNum)
: m_totalTx);
for (std::size_t i = 0; i < psdu->GetNMpdus(); ++i)
{
const auto isNewTx = (m_lastTxSeq < psdu->GetHeader(i).GetSequenceNumber());
NS_TEST_EXPECT_MSG_EQ(
psdu->GetHeader(i).IsRetry(),
!isNewTx,
"retry flag should not be set for the first groupcast transmission");
}
if (m_expectGcrUsed)
{
auto expectedStartSeq = std::min_element(m_rxGroupcastPerSta.cbegin(),
m_rxGroupcastPerSta.cend(),
[](const auto& v1, const auto& v2) {
return v1.size() < v2.size();
})
->size();
if (psdu->GetHeader(0).IsRetry() && GetNumNonGcrStas(m_params.stas) > 0)
{
expectedStartSeq -= psdu->GetNMpdus();
}
m_firstTxSeq = psdu->GetHeader(0).GetSequenceNumber();
NS_TEST_EXPECT_MSG_EQ(m_firstTxSeq,
expectedStartSeq,
"Incorrect starting sequence number");
m_lastTxSeq = psdu->GetHeader(psdu->GetNMpdus() - 1).GetSequenceNumber();
if (m_totalTx > 0)
{
if (!m_params.mpdusToCorruptPerPsdu.empty())
{
expectedLastSeqNum = 0;
for (const auto& mpduNumToCorruptPerSta :
m_params.mpdusToCorruptPerPsdu.cbegin()->second)
{
for (const auto mpduNumToCorrupt : mpduNumToCorruptPerSta.second)
{
const uint16_t mpduSeqNum = mpduNumToCorrupt - 1;
expectedLastSeqNum = std::max(mpduSeqNum, expectedLastSeqNum);
}
}
if (!m_params.expectedDroppedGroupcastMpdus.empty() &&
m_totalTx <= m_expectedMaxNumMpdusInPsdu)
{
expectedLastSeqNum += m_totalTx;
}
}
}
NS_TEST_EXPECT_MSG_EQ(m_lastTxSeq,
expectedLastSeqNum,
"Incorrect last sequence number");
}
}
else if (!mpdu->GetHeader().GetAddr1().IsBroadcast() && mpdu->GetHeader().IsQosData())
{
NS_TEST_EXPECT_MSG_EQ(nodeId, 0, "Unicast transmission from unexpected node");
NS_LOG_INFO("AP: unicast transmission (#MPDUs=" << psdu->GetNMpdus() << ")");
}
else if (mpdu->GetHeader().IsBlockAckReq())
{
CtrlBAckRequestHeader blockAckReq;
mpdu->GetPacket()->PeekHeader(blockAckReq);
NS_TEST_EXPECT_MSG_EQ(nodeId, 0, "Groupcast transmission from unexpected node");
uint8_t staId = 0;
uint8_t numStas = m_params.stas.size();
for (uint8_t i = 0; i < numStas; ++i)
{
if (mpdu->GetHeader().GetAddr1() == m_stasWifiMac.at(i)->GetAddress())
{
staId = i + 1;
break;
}
}
NS_ASSERT(staId != 0);
NS_LOG_INFO("AP: send " << (blockAckReq.IsGcr() ? "GCR " : "") << "BAR to STA " << +staId);
m_nTxGcrBar++;
m_nTxGcrBarsInCurrentTxop++;
const auto expectedGcr =
m_expectGcrUsed && ((m_params.numUnicastPackets == 0) ||
((m_params.startUnicast < m_params.startGroupcast) &&
(Simulator::Now() > m_params.startGroupcast)) ||
((m_params.startGroupcast < m_params.startUnicast) &&
(Simulator::Now() < m_params.startUnicast)));
NS_ASSERT(blockAckReq.IsGcr() == expectedGcr);
NS_TEST_EXPECT_MSG_EQ(blockAckReq.IsGcr(),
expectedGcr,
"Expected GCR Block Ack request type sent to STA " << +staId);
if (blockAckReq.IsGcr())
{
const auto expectedStartingSequence =
((!m_params.mpdusToCorruptPerPsdu.empty() &&
!m_params.expectedDroppedGroupcastMpdus.empty() &&
m_nTxGcrBar > m_params.mpdusToCorruptPerPsdu.size())
? m_params.numGroupcastPackets
: m_firstTxSeq);
NS_ASSERT(blockAckReq.GetStartingSequence() == expectedStartingSequence);
NS_TEST_EXPECT_MSG_EQ(
blockAckReq.GetStartingSequence(),
expectedStartingSequence,
"Incorrect starting sequence in GCR Block Ack request sent to STA " << +staId);
bool isBarRetry = (m_gcrBaParams.barsToCorrupt.count(m_nTxGcrBar - 1) != 0) ||
(m_gcrBaParams.blockAcksToCorrupt.count(m_nTxGcrBlockAck) != 0);
NS_TEST_EXPECT_MSG_EQ(mpdu->GetHeader().IsRetry(),
isBarRetry,
"Incorrect retry flag set for GCR Block Ack Request");
if (const auto it = m_gcrBaParams.barsToCorrupt.find(m_nTxGcrBar);
it != m_gcrBaParams.barsToCorrupt.cend())
{
NS_LOG_INFO("Corrupt BAR #" << +m_nTxGcrBar);
const auto uid = mpdu->GetPacket()->GetUid();
for (auto& errorModel : m_errorModels)
{
errorModel->SetList({uid});
}
}
else
{
NS_LOG_INFO("Do not corrupt BAR #" << +m_nTxGcrBar);
for (auto& errorModel : m_errorModels)
{
errorModel->SetList({});
}
}
}
}
else if (mpdu->GetHeader().IsBlockAck())
{
CtrlBAckResponseHeader blockAck;
mpdu->GetPacket()->PeekHeader(blockAck);
NS_TEST_EXPECT_MSG_NE(nodeId, 0, "BlockAck transmission from unexpected node");
NS_LOG_INFO("STA" << nodeId << ": send " << (blockAck.IsGcr() ? "GCR " : "")
<< "Block ACK");
const auto expectedGcr = ((m_params.numUnicastPackets == 0) ||
((m_params.startUnicast < m_params.startGroupcast) &&
(Simulator::Now() > m_params.startGroupcast)) ||
((m_params.startGroupcast < m_params.startUnicast) &&
(Simulator::Now() < m_params.startUnicast)));
NS_TEST_EXPECT_MSG_EQ(blockAck.IsGcr(),
expectedGcr,
"Expected " << (expectedGcr ? "GCR " : "")
<< "Block Ack type sent from STA " << nodeId);
if (expectedGcr)
{
m_nTxGcrBlockAck++;
const auto& corruptedMpdusForSta =
(m_params.mpdusToCorruptPerPsdu.empty() ||
(m_params.mpdusToCorruptPerPsdu.size() < m_totalTx))
? std::set<uint8_t>{}
: ((m_params.mpdusToCorruptPerPsdu.at(m_totalTx).count(0) != 0)
? m_params.mpdusToCorruptPerPsdu.at(m_totalTx).at(0)
: ((m_params.mpdusToCorruptPerPsdu.at(m_totalTx).count(nodeId) != 0)
? m_params.mpdusToCorruptPerPsdu.at(m_totalTx).at(nodeId)
: std::set<uint8_t>{}));
for (int seq = m_firstTxSeq; seq <= m_lastTxSeq; ++seq)
{
auto expectedReceived =
(corruptedMpdusForSta.empty() || corruptedMpdusForSta.count(seq + 1) == 0);
NS_TEST_EXPECT_MSG_EQ(
blockAck.IsPacketReceived(seq, 0),
expectedReceived,
"Incorrect bitmap filled in GCR Block Ack response sent from STA " << nodeId);
}
}
else
{
m_nTxBlockAck++;
}
if (blockAck.IsGcr())
{
if (m_gcrBaParams.blockAcksToCorrupt.count(m_nTxGcrBlockAck))
{
NS_LOG_INFO("Corrupt Block ACK #" << +m_nTxGcrBlockAck);
const auto uid = mpdu->GetPacket()->GetUid();
m_apErrorModel->SetList({uid});
}
else
{
NS_LOG_INFO("Do not corrupt Block ACK #" << +m_nTxGcrBlockAck);
m_apErrorModel->SetList({});
}
}
}
GcrTestBase::Transmit(context, psduMap, txVector, txPowerW);
}
void
GcrBaTest::NotifyTxopTerminated(Time startTime, Time duration, uint8_t linkId)
{
GcrTestBase::NotifyTxopTerminated(startTime, duration, linkId);
if (m_nTxGcrBarsInCurrentTxop > 0)
{
m_nTxGcrBarsPerTxop.push_back(m_nTxGcrBarsInCurrentTxop);
}
m_nTxGcrBarsInCurrentTxop = 0;
}
void
GcrBaTest::Receive(std::string context, Ptr<const Packet> p, const Address& adr)
{
const auto staId = ConvertContextToNodeId(context) - 1;
const auto socketAddress = PacketSocketAddress::ConvertFrom(adr);
if (socketAddress.GetProtocol() == MULTICAST_PROTOCOL)
{
NS_LOG_INFO("STA" << staId + 1 << ": multicast packet forwarded up");
const auto txopLimitAllowsAggregation =
(m_params.txopLimit.IsZero() || m_params.txopLimit > MicroSeconds(320));
m_rxGroupcastPerSta.at(staId).push_back(
(GetNumNonHtStas(m_params.stas) == 0) && txopLimitAllowsAggregation
? (m_totalTx - (m_lastTxSeq / m_expectedMaxNumMpdusInPsdu))
: 1);
}
else if (socketAddress.GetProtocol() == UNICAST_PROTOCOL)
{
NS_LOG_INFO("STA" << staId + 1 << ": unicast packet forwarded up");
m_rxUnicastPerSta.at(staId)++;
}
}
void
GcrBaTest::ConfigureGcrManager(WifiMacHelper& macHelper)
{
macHelper.SetGcrManager("ns3::WifiDefaultGcrManager",
"RetransmissionPolicy",
StringValue("GCR_BA"),
"GcrProtectionMode",
EnumValue(m_params.gcrProtectionMode));
}
void
GcrBaTest::CheckResults()
{
GcrTestBase::CheckResults();
if (m_params.numUnicastPackets > 0)
{
NS_TEST_EXPECT_MSG_EQ(+m_nTxBlockAck,
((m_params.numUnicastPackets > 1) ? GetNumGcrStas(m_params.stas) : 0),
"Incorrect number of transmitted BlockAck frames");
}
const auto txopLimitAllowsAggregation =
(m_params.txopLimit.IsZero() || m_params.txopLimit > MicroSeconds(320));
auto expectedTotalTx =
m_expectGcrUsed && txopLimitAllowsAggregation && (GetNumNonHtStas(m_params.stas) == 0)
? m_params.mpdusToCorruptPerPsdu.empty()
? std::ceil(static_cast<double>(m_params.numGroupcastPackets -
m_params.expectedDroppedGroupcastMpdus.size()) /
m_expectedMaxNumMpdusInPsdu)
: (std::ceil(static_cast<double>(m_params.numGroupcastPackets) /
m_expectedMaxNumMpdusInPsdu) +
std::ceil(static_cast<double>(m_params.mpdusToCorruptPerPsdu.size()) /
m_expectedMaxNumMpdusInPsdu))
: m_params.numGroupcastPackets;
const uint8_t numExpectedBars =
m_expectGcrUsed
? (m_params.mpdusToCorruptPerPsdu.empty()
? ((GetNumGcrStas(m_params.stas) * expectedTotalTx) +
m_gcrBaParams.barsToCorrupt.size() + m_gcrBaParams.blockAcksToCorrupt.size())
: ((GetNumGcrStas(m_params.stas) * expectedTotalTx) +
m_gcrBaParams.barsToCorrupt.size() + m_gcrBaParams.blockAcksToCorrupt.size() +
m_params.expectedDroppedGroupcastMpdus.size()))
: 0;
const uint8_t numExpectedBlockAcks =
m_expectGcrUsed ? (m_params.mpdusToCorruptPerPsdu.empty()
? ((GetNumGcrStas(m_params.stas) * expectedTotalTx) +
m_gcrBaParams.blockAcksToCorrupt.size())
: ((GetNumGcrStas(m_params.stas) * expectedTotalTx) +
m_gcrBaParams.blockAcksToCorrupt.size() +
m_params.expectedDroppedGroupcastMpdus.size()))
: 0;
uint8_t numNonConcealedTx = 0;
if (m_expectGcrUsed && (GetNumNonHtStas(m_params.stas) > 0))
{
numNonConcealedTx = expectedTotalTx;
}
else if (m_expectGcrUsed && (GetNumNonGcrStas(m_params.stas) > 0))
{
numNonConcealedTx = 1;
}
NS_TEST_EXPECT_MSG_EQ(+m_totalTx,
expectedTotalTx + numNonConcealedTx,
"Incorrect number of transmitted packets");
NS_TEST_EXPECT_MSG_EQ(+m_nTxGcrBar,
+numExpectedBars,
"Incorrect number of transmitted GCR BARs");
NS_TEST_EXPECT_MSG_EQ(+m_nTxGcrBlockAck,
+numExpectedBlockAcks,
"Incorrect number of transmitted GCR Block ACKs");
if (!m_gcrBaParams.expectedNTxBarsPerTxop.empty())
{
NS_TEST_EXPECT_MSG_EQ(m_nTxGcrBarsPerTxop.size(),
m_gcrBaParams.expectedNTxBarsPerTxop.size(),
"Incorrect number of TXOPs containing transmission of BAR frame(s)");
for (std::size_t i = 0; i < m_gcrBaParams.expectedNTxBarsPerTxop.size(); ++i)
{
NS_TEST_EXPECT_MSG_EQ(+m_nTxGcrBarsPerTxop.at(i),
+m_gcrBaParams.expectedNTxBarsPerTxop.at(i),
"Incorrect number of BAR(s) transmitted in TXOP");
}
}
uint8_t numStas = m_params.stas.size();
for (uint8_t i = 0; i < numStas; ++i)
{
// calculate the amount of corrupted PSDUs and the expected number of retransmission per
// MPDU
uint8_t prevExpectedNumAttempt = 1;
uint8_t prevPsduNum = 1;
uint8_t droppedPsdus = 0;
auto prevDropped = false;
for (uint16_t j = 0; j < m_params.numGroupcastPackets; ++j)
{
uint8_t expectedNumAttempt = 1;
const auto psduNum = ((j / m_params.maxNumMpdusInPsdu) + 1);
const auto packetInAmpdu =
(m_params.maxNumMpdusInPsdu > 1) ? ((j % m_params.maxNumMpdusInPsdu) + 1) : 1;
if (psduNum > prevPsduNum)
{
prevExpectedNumAttempt = 1;
}
prevPsduNum = psduNum;
for (auto& mpduToCorruptPerPsdu : m_params.mpdusToCorruptPerPsdu)
{
if (mpduToCorruptPerPsdu.first <= (psduNum - 1))
{
continue;
}
const auto& corruptedMpdusForSta =
(mpduToCorruptPerPsdu.second.count(0) != 0)
? mpduToCorruptPerPsdu.second.at(0)
: ((mpduToCorruptPerPsdu.second.count(i + 1) != 0)
? mpduToCorruptPerPsdu.second.at(i + 1)
: std::set<uint8_t>{});
if (corruptedMpdusForSta.count(packetInAmpdu) == 0)
{
break;
}
expectedNumAttempt++;
}
if ((!m_expectGcrUsed && (expectedNumAttempt > 1)) ||
(m_params.expectedDroppedGroupcastMpdus.count(j + 1) != 0))
{
droppedPsdus++;
prevDropped = true;
continue;
}
expectedNumAttempt = (prevDropped && !m_params.mpdusToCorruptPerPsdu.empty())
? m_params.mpdusToCorruptPerPsdu.size()
: std::max(expectedNumAttempt, prevExpectedNumAttempt);
prevExpectedNumAttempt = expectedNumAttempt;
const std::size_t rxPsdus = (j - droppedPsdus);
NS_TEST_EXPECT_MSG_EQ(+m_rxGroupcastPerSta.at(i).at(rxPsdus),
+expectedNumAttempt,
"Packet has not been forwarded up at the expected TX attempt");
}
const std::size_t rxPackets = (m_params.numGroupcastPackets - droppedPsdus);
NS_TEST_EXPECT_MSG_EQ(+m_rxGroupcastPerSta.at(i).size(),
rxPackets,
"STA" + std::to_string(i + 1) +
" did not receive the expected number of groupcast packets");
}
auto rsm = DynamicCast<IdealWifiManagerForGcrTest>(m_apWifiMac->GetWifiRemoteStationManager());
NS_ASSERT(rsm);
NS_TEST_EXPECT_MSG_EQ(rsm->m_blockAckSenders.size(),
GetNumGcrStas(m_params.stas),
"RSM have not received Block ACK from all members");
}
WifiGcrTestSuite::WifiGcrTestSuite()
: TestSuite("wifi-gcr", Type::UNIT)
{
@@ -1372,15 +1817,15 @@ WifiGcrTestSuite::WifiGcrTestSuite()
{
for (const auto& stasInfo : StationsScenarios{
{{{GCR_INCAPABLE_STA, WIFI_STANDARD_80211a}}},
{{{GCR_CAPABLE_STA, WIFI_STANDARD_80211n, 40, 2, NanoSeconds(400)}}},
{{{GCR_CAPABLE_STA, WIFI_STANDARD_80211n, MHz_u{40}, 2, NanoSeconds(400)}}},
{{{GCR_CAPABLE_STA, WIFI_STANDARD_80211ac}}},
{{{GCR_CAPABLE_STA, WIFI_STANDARD_80211ax}}},
{{{GCR_CAPABLE_STA, WIFI_STANDARD_80211be}}},
{{{GCR_CAPABLE_STA, WIFI_STANDARD_80211ax, 80, 1, NanoSeconds(800)},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211be, 80, 1, NanoSeconds(3200)}}},
{{{GCR_CAPABLE_STA, WIFI_STANDARD_80211n, 20, 1},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211ac, 80, 2},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211ax, 160, 3}}},
{{{GCR_CAPABLE_STA, WIFI_STANDARD_80211ax, MHz_u{80}, 1, NanoSeconds(800)},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211be, MHz_u{80}, 1, NanoSeconds(3200)}}},
{{{GCR_CAPABLE_STA, WIFI_STANDARD_80211n, MHz_u{20}, 1},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211ac, MHz_u{80}, 2},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211ax, MHz_u{160}, 3}}},
{{{GCR_INCAPABLE_STA, WIFI_STANDARD_80211a},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211n}}},
{{{GCR_INCAPABLE_STA, WIFI_STANDARD_80211n},
@@ -1695,10 +2140,10 @@ WifiGcrTestSuite::WifiGcrTestSuite()
{}),
TestCase::Duration::QUICK);
AddTestCase(new GcrUrTest("GCR-UR with buffer size limit to 1024 MPDUs",
{.stas = {{GCR_CAPABLE_STA, WIFI_STANDARD_80211be, 40},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211be, 40},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211be, 40},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211be, 40}},
{.stas = {{GCR_CAPABLE_STA, WIFI_STANDARD_80211be, MHz_u{40}},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211be, MHz_u{40}},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211be, MHz_u{40}},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211be, MHz_u{40}}},
.numGroupcastPackets = 1200,
.packetSize = 100,
.maxNumMpdusInPsdu = 1024,
@@ -1744,6 +2189,383 @@ WifiGcrTestSuite::WifiGcrTestSuite()
.packetsPauzeAggregation = 4,
.packetsResumeAggregation = 100}),
TestCase::Duration::QUICK);
// GCR Block ACKs
for (auto& [groupcastPackets, groupcastStartTime, unicastPackets, unicastStartTime] :
std::vector<std::tuple<uint16_t, Time, uint16_t, Time>>{
{2, Seconds(1.0), 0, Seconds(0.0)}, // no unicast
{2, Seconds(0.5), 1, Seconds(1.0)}, // groupcast then unicast
{2, Seconds(1.0), 1, Seconds(0.5)}}) // unicast then groupcast
{
for (auto& [corruptedBars, corruptedBlockAcks] :
std::vector<std::pair<std::set<uint8_t>, std::set<uint8_t>>>{{{}, {}},
{{1}, {}},
{{}, {1}},
{{1}, {1}}})
{
for (auto& [rtsThreshold, gcrPotection, protectionName] :
std::vector<std::tuple<uint32_t, GroupcastProtectionMode, std::string>>{
{maxRtsCtsThreshold, GroupcastProtectionMode::RTS_CTS, "no protection"},
{500, GroupcastProtectionMode::RTS_CTS, "RTS-CTS"},
{1500, GroupcastProtectionMode::CTS_TO_SELF, "CTS-TO-SELF"}})
{
for (const auto& stasInfo : StationsScenarios{
{{{GCR_INCAPABLE_STA, WIFI_STANDARD_80211a}}},
{{{GCR_CAPABLE_STA,
WIFI_STANDARD_80211n,
MHz_u{40},
2,
NanoSeconds(400)}}},
{{{GCR_CAPABLE_STA, WIFI_STANDARD_80211ac}}},
{{{GCR_CAPABLE_STA, WIFI_STANDARD_80211ax}}},
{{{GCR_CAPABLE_STA, WIFI_STANDARD_80211be}}},
{{{GCR_CAPABLE_STA, WIFI_STANDARD_80211ax, MHz_u{80}, 1, NanoSeconds(800)},
{GCR_CAPABLE_STA,
WIFI_STANDARD_80211be,
MHz_u{80},
1,
NanoSeconds(3200)}}},
{{{GCR_CAPABLE_STA, WIFI_STANDARD_80211n, MHz_u{20}, 1},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211ac, MHz_u{80}, 2},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211ax, MHz_u{160}, 3}}},
{{{GCR_INCAPABLE_STA, WIFI_STANDARD_80211a},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211n}}},
{{{GCR_INCAPABLE_STA, WIFI_STANDARD_80211n},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211n}}}})
{
const auto maxChannelWidth =
std::max_element(stasInfo.cbegin(),
stasInfo.cend(),
[](const auto& lhs, const auto& rhs) {
return lhs.maxChannelWidth < rhs.maxChannelWidth;
})
->maxChannelWidth;
const auto useSpectrum =
std::any_of(stasInfo.cbegin(),
stasInfo.cend(),
[maxChannelWidth](const auto& staInfo) {
return (staInfo.maxChannelWidth != maxChannelWidth);
});
std::string scenario =
"STAs=" + printStasInfo(stasInfo) + ", protection=" + protectionName +
", corruptBARs=" + std::to_string(!corruptedBars.empty()) +
", corruptBACKs=" + std::to_string(!corruptedBlockAcks.empty());
if (unicastPackets > 0)
{
scenario += ", mixedGroupcastUnicast";
if (unicastStartTime > groupcastStartTime)
{
scenario += " (groupcast before unicast)";
}
else
{
scenario += " (unicast before groupcast)";
}
}
AddTestCase(new GcrBaTest("GCR-BA without any corrupted MPDUs: " + scenario,
{.stas = stasInfo,
.numGroupcastPackets = groupcastPackets,
.numUnicastPackets = unicastPackets,
.maxNumMpdusInPsdu = 2,
.startGroupcast = groupcastStartTime,
.startUnicast = unicastStartTime,
.rtsThreshold = rtsThreshold,
.gcrProtectionMode = gcrPotection},
{corruptedBars, corruptedBlockAcks}),
useSpectrum ? TestCase::Duration::EXTENSIVE
: TestCase::Duration::QUICK);
if (GetNumNonGcrStas(stasInfo) == 0)
{
AddTestCase(new GcrBaTest("GCR-BA with second MPDU corrupted: " + scenario,
{.stas = stasInfo,
.numGroupcastPackets = groupcastPackets,
.numUnicastPackets = unicastPackets,
.maxNumMpdusInPsdu = 2,
.startGroupcast = groupcastStartTime,
.startUnicast = unicastStartTime,
.rtsThreshold = rtsThreshold,
.gcrProtectionMode = gcrPotection,
.mpdusToCorruptPerPsdu = {{1, {{0, {2}}}}}},
{corruptedBars, corruptedBlockAcks}),
useSpectrum ? TestCase::Duration::EXTENSIVE
: TestCase::Duration::QUICK);
AddTestCase(new GcrBaTest("GCR-BA with first MPDU corrupted: " + scenario,
{.stas = stasInfo,
.numGroupcastPackets = groupcastPackets,
.numUnicastPackets = unicastPackets,
.maxNumMpdusInPsdu = 2,
.startGroupcast = groupcastStartTime,
.startUnicast = unicastStartTime,
.rtsThreshold = rtsThreshold,
.gcrProtectionMode = gcrPotection,
.mpdusToCorruptPerPsdu = {{1, {{0, {1}}}}}},
{corruptedBars, corruptedBlockAcks}),
useSpectrum ? TestCase::Duration::EXTENSIVE
: TestCase::Duration::QUICK);
AddTestCase(new GcrBaTest("GCR-BA with both MPDUs corrupted: " + scenario,
{.stas = stasInfo,
.numGroupcastPackets = groupcastPackets,
.numUnicastPackets = unicastPackets,
.maxNumMpdusInPsdu = 2,
.startGroupcast = groupcastStartTime,
.startUnicast = unicastStartTime,
.rtsThreshold = rtsThreshold,
.gcrProtectionMode = gcrPotection,
.mpdusToCorruptPerPsdu = {{1, {{0, {1, 2}}}}}},
{corruptedBars, corruptedBlockAcks}),
useSpectrum ? TestCase::Duration::EXTENSIVE
: TestCase::Duration::QUICK);
if (GetNumGcrStas(stasInfo) > 1)
{
AddTestCase(
new GcrBaTest("GCR-BA with second MPDU corrupted for first STA: " +
scenario,
{.stas = stasInfo,
.numGroupcastPackets = groupcastPackets,
.numUnicastPackets = unicastPackets,
.maxNumMpdusInPsdu = 2,
.startGroupcast = groupcastStartTime,
.startUnicast = unicastStartTime,
.rtsThreshold = rtsThreshold,
.gcrProtectionMode = gcrPotection,
.mpdusToCorruptPerPsdu = {{1, {{1, {2}}}}}},
{corruptedBars, corruptedBlockAcks}),
useSpectrum ? TestCase::Duration::EXTENSIVE
: TestCase::Duration::QUICK);
AddTestCase(
new GcrBaTest("GCR-BA with first MPDU corrupted for first STA: " +
scenario,
{.stas = stasInfo,
.numGroupcastPackets = groupcastPackets,
.numUnicastPackets = unicastPackets,
.maxNumMpdusInPsdu = 2,
.startGroupcast = groupcastStartTime,
.startUnicast = unicastStartTime,
.rtsThreshold = rtsThreshold,
.gcrProtectionMode = gcrPotection,
.mpdusToCorruptPerPsdu = {{1, {{1, {1}}}}}},
{corruptedBars, corruptedBlockAcks}),
useSpectrum ? TestCase::Duration::EXTENSIVE
: TestCase::Duration::QUICK);
AddTestCase(
new GcrBaTest(
"GCR-BA with first different MPDUs corrupted for each STA: " +
scenario,
{.stas = stasInfo,
.numGroupcastPackets = groupcastPackets,
.numUnicastPackets = unicastPackets,
.maxNumMpdusInPsdu = 2,
.startGroupcast = groupcastStartTime,
.startUnicast = unicastStartTime,
.rtsThreshold = rtsThreshold,
.gcrProtectionMode = gcrPotection,
.mpdusToCorruptPerPsdu = {{1, {{1, {1}}, {2, {2}}}}}},
{corruptedBars, corruptedBlockAcks}),
useSpectrum ? TestCase::Duration::EXTENSIVE
: TestCase::Duration::QUICK);
AddTestCase(new GcrBaTest(
"GCR-BA with first different MPDUs corrupted for each "
"STA with different order: " +
scenario,
{.stas = stasInfo,
.numGroupcastPackets = groupcastPackets,
.numUnicastPackets = unicastPackets,
.maxNumMpdusInPsdu = 2,
.startGroupcast = groupcastStartTime,
.startUnicast = unicastStartTime,
.rtsThreshold = rtsThreshold,
.gcrProtectionMode = gcrPotection,
.mpdusToCorruptPerPsdu = {{1, {{1, {2}}, {2, {1}}}}}},
{corruptedBars, corruptedBlockAcks}),
useSpectrum ? TestCase::Duration::EXTENSIVE
: TestCase::Duration::QUICK);
}
}
}
}
}
std::string scenario = "GCR-BA with dropped MPDU because of lifetime expiry";
if (unicastPackets > 0)
{
scenario += ", mixedGroupcastUnicast";
if (unicastStartTime > groupcastStartTime)
{
scenario += " (groupcast before unicast)";
}
else
{
scenario += " (unicast before groupcast)";
}
}
AddTestCase(
new GcrBaTest(scenario,
{.stas = {{GCR_CAPABLE_STA, WIFI_STANDARD_80211n}},
.numGroupcastPackets =
uint16_t(groupcastPackets *
2), // consider more packets to verify TX window is advanced
.numUnicastPackets = unicastPackets,
.maxNumMpdusInPsdu = 2,
.startGroupcast = groupcastStartTime,
.startUnicast = unicastStartTime,
.maxLifetime = MilliSeconds(2),
.rtsThreshold = maxRtsCtsThreshold,
.mpdusToCorruptPerPsdu =
{{1, {{0, {2}}}}, {2, {{0, {2}}}}, {3, {{0, {2}}}}, {4, {{0, {2}}}}},
.expectedDroppedGroupcastMpdus = {2}},
{}),
TestCase::Duration::QUICK);
scenario = "";
if (unicastPackets > 0)
{
if (unicastStartTime > groupcastStartTime)
{
scenario += "Groupcast followed by unicast";
}
else
{
scenario += "Unicast followed by groupcast";
}
}
else
{
scenario += "GCR-BA";
}
scenario += " with ";
AddTestCase(new GcrBaTest(scenario + "ADDBA request corrupted",
{.stas = {{GCR_CAPABLE_STA, WIFI_STANDARD_80211n}},
.numGroupcastPackets = groupcastPackets,
.numUnicastPackets = unicastPackets,
.maxNumMpdusInPsdu = 2,
.startGroupcast = groupcastStartTime,
.startUnicast = unicastStartTime,
.rtsThreshold = maxRtsCtsThreshold,
.addbaReqsToCorrupt = {1}},
{}),
TestCase::Duration::QUICK);
AddTestCase(new GcrBaTest(scenario + "ADDBA response corrupted",
{.stas = {{GCR_CAPABLE_STA, WIFI_STANDARD_80211n}},
.numGroupcastPackets = groupcastPackets,
.numUnicastPackets = unicastPackets,
.maxNumMpdusInPsdu = 2,
.startGroupcast = groupcastStartTime,
.startUnicast = unicastStartTime,
.rtsThreshold = maxRtsCtsThreshold,
.addbaRespsToCorrupt = {1}},
{}),
TestCase::Duration::QUICK);
AddTestCase(new GcrBaTest(scenario + "ADDBA timeout",
{.stas = {{GCR_CAPABLE_STA, WIFI_STANDARD_80211n}},
.numGroupcastPackets = groupcastPackets,
.numUnicastPackets = unicastPackets,
.maxNumMpdusInPsdu = 2,
.startGroupcast = groupcastStartTime,
.startUnicast = unicastStartTime,
.rtsThreshold = maxRtsCtsThreshold,
.addbaReqsToCorrupt = {1, 2, 3, 4, 5, 6, 7, 8}},
{}),
TestCase::Duration::QUICK);
AddTestCase(new GcrBaTest(scenario + "DELBA frames after timeout expires",
{.stas = {{GCR_CAPABLE_STA, WIFI_STANDARD_80211n}},
.numGroupcastPackets = groupcastPackets,
.numUnicastPackets = uint16_t(unicastPackets * 2),
.maxNumMpdusInPsdu = 2,
.startGroupcast = groupcastStartTime,
.startUnicast = unicastStartTime,
.rtsThreshold = maxRtsCtsThreshold,
.baInactivityTimeout = 10},
{}),
TestCase::Duration::QUICK);
}
AddTestCase(new GcrBaTest(
"GCR-BA with BARs sent over 2 TXOPs because of TXOP limit",
{.stas = {{GCR_CAPABLE_STA, WIFI_STANDARD_80211n},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211ac},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211ax}},
.numGroupcastPackets = 2,
.maxNumMpdusInPsdu = 2,
.maxLifetime = Seconds(1.0),
.rtsThreshold = maxRtsCtsThreshold,
.txopLimit = MicroSeconds(480)},
{.expectedNTxBarsPerTxop = {1, 2}}), // 1 BAR in first TXOP, 2 BARs in next TXOP
TestCase::Duration::QUICK);
AddTestCase(new GcrBaTest("GCR-BA with TXOP limit not allowing aggregation",
{.stas = {{GCR_CAPABLE_STA, WIFI_STANDARD_80211n},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211ac},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211ax}},
.numGroupcastPackets = 2,
.maxNumMpdusInPsdu = 2,
.maxLifetime = Seconds(1.0),
.rtsThreshold = maxRtsCtsThreshold,
.txopLimit = MicroSeconds(320)},
{.expectedNTxBarsPerTxop = {1, 2, 1, 2}}),
TestCase::Duration::QUICK);
AddTestCase(new GcrBaTest("GCR-BA with number of packets larger than MPDU buffer size",
{.stas = {{GCR_CAPABLE_STA, WIFI_STANDARD_80211n}},
.numGroupcastPackets = 300,
.maxNumMpdusInPsdu = 2,
.rtsThreshold = maxRtsCtsThreshold},
{}),
TestCase::Duration::QUICK);
AddTestCase(new GcrBaTest("GCR-BA with buffer size limit to 64 MPDUs",
{.stas = {{GCR_CAPABLE_STA, WIFI_STANDARD_80211n},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211ac},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211ax},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211be}},
.numGroupcastPackets = 300,
.packetSize = 500,
.maxNumMpdusInPsdu = 1024, // capped to 64 because not lowest is HT
.rtsThreshold = maxRtsCtsThreshold},
{}),
TestCase::Duration::QUICK);
AddTestCase(new GcrBaTest("GCR-BA with buffer size limit to 256 MPDUs",
{.stas = {{GCR_CAPABLE_STA, WIFI_STANDARD_80211ax},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211ax},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211be},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211be}},
.numGroupcastPackets = 300,
.packetSize = 150,
.maxNumMpdusInPsdu = 1024, // capped to 256 because not lowest is HE
.rtsThreshold = maxRtsCtsThreshold},
{}),
TestCase::Duration::QUICK);
AddTestCase(new GcrBaTest("GCR-BA with buffer size limit to 1024 MPDUs",
{.stas = {{GCR_CAPABLE_STA, WIFI_STANDARD_80211be, MHz_u{40}},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211be, MHz_u{40}},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211be, MHz_u{40}},
{GCR_CAPABLE_STA, WIFI_STANDARD_80211be, MHz_u{40}}},
.numGroupcastPackets = 1200,
.packetSize = 100,
.maxNumMpdusInPsdu = 1024,
.rtsThreshold = maxRtsCtsThreshold},
{}),
TestCase::Duration::QUICK);
AddTestCase(new GcrBaTest("GCR-BA with corrupted RTS frames to verify previously assigned "
"sequence numbers are properly released",
{.stas = {{GCR_CAPABLE_STA, WIFI_STANDARD_80211n}},
.numGroupcastPackets = 6,
.packetSize = 500,
.maxNumMpdusInPsdu = 2,
.maxLifetime = MilliSeconds(
1), // reduce lifetime to make sure packets get dropped
.rtsThreshold = 500,
.rtsFramesToCorrupt = {2, 3, 4},
.expectedDroppedGroupcastMpdus = {3, 4}},
{}),
TestCase::Duration::QUICK);
AddTestCase(new GcrBaTest("GCR-BA with corrupted CTS frames to verify previously assigned "
"sequence numbers are properly released",
{.stas = {{GCR_CAPABLE_STA, WIFI_STANDARD_80211n}},
.numGroupcastPackets = 6,
.packetSize = 500,
.maxNumMpdusInPsdu = 2,
.maxLifetime = MilliSeconds(
1), // reduce lifetime to make sure packets get dropped
.rtsThreshold = 500,
.ctsFramesToCorrupt = {2, 3, 4},
.expectedDroppedGroupcastMpdus = {3, 4}},
{}),
TestCase::Duration::QUICK);
}
static WifiGcrTestSuite g_wifiGcrTestSuite; ///< the test suite

View File

@@ -38,8 +38,8 @@ using namespace ns3;
* The test eventually corrupts some MPDUs based on a provided list of groupcast MPDUs in a given
* PSDU (indices are starting from 1) that should not be successfully received by a given STA or by
* all STA (s). It may also corrupts specific frames, such as RTS/CTS or action frames that are used
* to establish or teardown Block Ack agreements. The latter is needed is needed when A-MPDU is
* used.
* to establish or teardown Block Ack agreements. The latter is needed is needed for GCR-BA or for
* GCR-UR when A-MPDU is used.
*
* It is checked that:
*
@@ -56,6 +56,8 @@ using namespace ns3;
* frames has been received and they all contain the GCR group address
* - when Block Ack agreement timeout is used, the expected amount of DELBA frames has been received
* and they all contain the GCR group address
* - The expected buffer size is being selected for the GCR Block Ack agreement, depending on what
* is supported by each member
*/
class GcrTestBase : public TestCase
{
@@ -65,7 +67,7 @@ class GcrTestBase : public TestCase
{
bool gcrCapable{false}; ///< flag whether the STA is GCR capable
WifiStandard standard{WIFI_STANDARD_UNSPECIFIED}; ///< standard configured for the STA
MHz_u maxChannelWidth{20}; ///< maximum channel width in MHz supported by the STA
MHz_u maxChannelWidth{20}; ///< maximum channel width supported by the STA
uint8_t maxNumStreams{1}; ///< maximum number of spatial streams supported by the STA
Time minGi{NanoSeconds(800)}; ///< minimum guard interval duration supported by the STA
};
@@ -304,6 +306,80 @@ class GcrUrTest : public GcrTestBase
uint64_t m_currentUid; ///< current UID
};
/**
* @ingroup wifi-test
* @ingroup tests
*
* @brief Test the implementation of GCR Block Ack.
*
* GCR-BA tests consider an AP and multiple STAs (with different capabilities) using GCR-BA.
* During tests, besides frames that can be corrupted by the base class, transmitted MPDUs
* can be corrupted, either for all STAs or for a particular STA. These tests eventually corrupt
* Block Ack Requests and Block Acks frames.
*
* Besides what is verified in the base class, it is checked that:
* - The expected amount of packets has been forwarded up to upper layer
* - When the GCR-BA service is used, the expected amount of Block Ack request and Block Acks frames
* have been received and they all contain the GCR group address
* - MPDUs are properly discarded when their lifetime expires, and TX window as well as receiver
* scoreboard are properly advanced if this occurs
* - When the GCR-BA service is used, the exchange of GCR Block Ack Request and GCR Block Acks
* frames might be spread over multiple TXOPs
* - A-MPDU is only used if TXOP limit duration permits it
*/
class GcrBaTest : public GcrTestBase
{
public:
/// Parameters for GCR-BA tests
struct GcrBaParameters
{
std::set<uint8_t> barsToCorrupt{}; ///< list of GCR BARs (starting from 1) to corrupt
std::set<uint8_t>
blockAcksToCorrupt{}; ///< list of GCR Block ACKs (starting from 1) to corrupt
std::vector<uint8_t>
expectedNTxBarsPerTxop{}; ///< the expected number of BAR frames transmitted by the AP
///< per TXOP (only takes into account TXOPs with BARs
///< transmitted)
};
/**
* Constructor
*
* @param testName the name of the test
* @param commonParams the common GCR parameters for the test to run
* @param gcrBaParams the GCR-BA parameters for the test to run
*/
GcrBaTest(const std::string& testName,
const GcrParameters& commonParams,
const GcrBaParameters& gcrBaParams);
~GcrBaTest() override = default;
private:
void ConfigureGcrManager(WifiMacHelper& macHelper) override;
void CheckResults() override;
void PacketGenerated(std::string context, Ptr<const Packet> p, const Address& adr) override;
void Transmit(std::string context,
WifiConstPsduMap psduMap,
WifiTxVector txVector,
double txPowerW) override;
void Receive(std::string context, Ptr<const Packet> p, const Address& adr) override;
void NotifyTxopTerminated(Time startTime, Time duration, uint8_t linkId) override;
GcrBaParameters m_gcrBaParams; ///< GCR-BA parameters for the test to run
uint8_t m_nTxGcrBar; ///< number of GCR Block Ack Request frames sent by the AP
uint8_t m_nTxGcrBlockAck; ///< number of GCR Block Ack Response frames sent to the AP
uint8_t m_nTxBlockAck; ///< number of Block Ack Response frames sent to the AP
uint16_t m_firstTxSeq; ///< sequence number of the first in-flight groupcast MPDU
int m_lastTxSeq; ///< sequence number of the last in-flight groupcast MPDU
std::vector<uint8_t>
m_nTxGcrBarsPerTxop; ///< number of GCR BAR frames transmitted by the AP per TXOP (only
///< takes into account TXOPs with BARs transmitted)
uint8_t m_nTxGcrBarsInCurrentTxop; ///< number of GCR BAR frames transmitted by the AP in the
///< current TXOP
};
/**
* @ingroup wifi-test
* @ingroup tests