wifi: Add unit test to check ChannelAccessManager attributes

This commit is contained in:
Stefano Avallone
2023-12-28 12:23:09 +01:00
parent 7b31f77674
commit f78be0a507

View File

@@ -18,22 +18,37 @@
*/
#include "ns3/adhoc-wifi-mac.h"
#include "ns3/ap-wifi-mac.h"
#include "ns3/channel-access-manager.h"
#include "ns3/config.h"
#include "ns3/frame-exchange-manager.h"
#include "ns3/interference-helper.h"
#include "ns3/mgt-action-headers.h"
#include "ns3/mobility-helper.h"
#include "ns3/multi-model-spectrum-channel.h"
#include "ns3/packet-socket-client.h"
#include "ns3/packet-socket-helper.h"
#include "ns3/packet-socket-server.h"
#include "ns3/pointer.h"
#include "ns3/qos-txop.h"
#include "ns3/rng-seed-manager.h"
#include "ns3/simulator.h"
#include "ns3/spectrum-wifi-helper.h"
#include "ns3/spectrum-wifi-phy.h"
#include "ns3/sta-wifi-mac.h"
#include "ns3/string.h"
#include "ns3/test.h"
#include "ns3/wifi-net-device.h"
#include "ns3/wifi-spectrum-phy-interface.h"
#include <iomanip>
#include <list>
#include <numeric>
using namespace ns3;
NS_LOG_COMPONENT_DEFINE("WifiChannelAccessManagerTest");
template <typename TxopType>
class ChannelAccessManagerTest;
@@ -1529,6 +1544,481 @@ LargestIdlePrimaryChannelTest::DoRun()
Simulator::Destroy();
}
/**
* \ingroup wifi-test
* \ingroup tests
*
* \brief Test the GenerateBackoffIfTxopWithoutTx and ProactiveBackoff attributes of the
* ChannelAccessManager. The backoff values generated by the VO AC of the AP are checked.
*
* The GenerateBackoffIfTxopWithoutTx test checks the generation of backoff values when the
* attribute is set to true. A QoS data frame is queued at the AP but the queue is blocked so
* that the frame is not transmitted. A backoff value is kept being generated as long as the
* frame is kept in the queue.
*
* Backoff Last
* Backoff Backoff Backoff value #3, backoff
* value #0 value #1 value #2 unblock queue value
* | ┌─────┐ | | | ┌─────┐ ┌────┐ |
* | ┌───┐ │Assoc│ | |Decrement| |Decrement| |Decrement│ADDBA│ │QoS │ |
* | │ACK│ │Resp │ |AIFS| backoff |slot| backoff |slot| backoff │ Req │. .│data│ |
* ──┬─────┬┴───┴──┴─────┴┬───┬────────────────────────────────────────────┴─────┴───┴────┴┬───┬──
* │Assoc│ │ACK│ │ACK│
* │ Req │ └───┘ └───┘
* └─────┘
*
* The ProactiveBackoff test checks the generation of backoff values when the attribute is set
* to true. A noise is generated to trigger the generation of a new backoff value, provided
* that the backoff counter is zero.
*
*
* Backoff Backoff Backoff Backoff
* value #0 value #1 value #2 value #3
* | | ┌─────┐ | |
* | | ┌───┐ │Assoc│ | |
* | | │ACK│ │Resp │ |SIFS| noise | AIFS+backoff | noise |
* ─────────────┬─────┬┴───┴──┴─────┴┬───┬──────────────────────────────────────────────────
* │Assoc│ │ACK│
* │ Req │ └───┘
* └─────┘
*/
class BackoffGenerationTest : public TestCase
{
public:
/**
* Tested attributes
*/
enum TestType : uint8_t
{
GEN_BACKOFF_IF_TXOP_NO_TX = 0,
PROACTIVE_BACKOFF
};
/**
* Constructor
*
* \param type the test type
*/
BackoffGenerationTest(TestType type);
private:
void DoSetup() override;
void DoRun() override;
/**
* Callback invoked when a FEM passes PSDUs to the PHY.
*
* \param psduMap the PSDU map
* \param txVector the TX vector
* \param txPowerW the tx power in Watts
*/
void Transmit(WifiConstPsduMap psduMap, WifiTxVector txVector, double txPowerW);
/**
* Callback invoked when a new backoff value is generated by the given AC on the station.
*
* \param ac the AC index
* \param backoff the generated backoff value
* \param linkId the ID of the link for which the backoff value has been generated
*/
void BackoffGenerated(AcIndex ac, uint32_t backoff, uint8_t linkId);
/**
* Indicate that a new backoff value has not been generated as expected.
*/
void MissedBackoff();
/**
* Generate interference to make CCA busy.
*/
void GenerateInterference();
Ptr<ApWifiMac> m_apMac; ///< AP wifi MAC
Ptr<StaWifiMac> m_staMac; ///< MAC of the non-AP STA
bool m_generateBackoffIfTxopWithoutTx; ///< whether the GenerateBackoffIfTxopWithoutTx
///< attribute is set to true
bool m_proactiveBackoff; ///< whether the ProactiveBackoff attribute is set to true
static constexpr uint8_t m_tid{6}; ///< TID of generated packet
std::size_t m_nGenBackoff{0}; ///< number of generated backoff values
std::size_t m_nExpectedGenBackoff{0}; ///< expected total number of generated backoff values
EventId m_nextBackoffGen; ///< timer elapsing when next backoff value is expected
///< to be generated
Time m_assocReqStartTxTime{0}; ///< Association Request start TX time
Time m_assocReqPpduHdrDuration{0}; ///< Association Request PPDU header TX duration
std::size_t m_nAcks{0}; ///< number of transmitted Ack frames
const Time m_interferenceDuration{MicroSeconds(10)}; ///< interference duration
Ptr<PacketSocketClient> m_client; ///< client to be installed on the AP after association
};
BackoffGenerationTest::BackoffGenerationTest(TestType type)
: TestCase("Check attributes impacting the generation of backoff values"),
m_generateBackoffIfTxopWithoutTx(type == GEN_BACKOFF_IF_TXOP_NO_TX),
m_proactiveBackoff(type == PROACTIVE_BACKOFF)
{
if (m_proactiveBackoff)
{
m_nExpectedGenBackoff = 4;
}
}
void
BackoffGenerationTest::DoSetup()
{
RngSeedManager::SetSeed(1);
RngSeedManager::SetRun(1);
int64_t streamNumber = 10;
Config::SetDefault("ns3::ChannelAccessManager::GenerateBackoffIfTxopWithoutTx",
BooleanValue(m_generateBackoffIfTxopWithoutTx));
Config::SetDefault("ns3::ChannelAccessManager::ProactiveBackoff",
BooleanValue(m_proactiveBackoff));
auto apNode = CreateObject<Node>();
auto staNode = CreateObject<Node>();
WifiHelper wifi;
wifi.SetStandard(WIFI_STANDARD_80211be);
wifi.SetRemoteStationManager("ns3::ConstantRateWifiManager",
"DataMode",
StringValue("EhtMcs0"),
"ControlMode",
StringValue("HtMcs0"));
// MLDs are configured with three links
SpectrumWifiPhyHelper phyHelper;
phyHelper.SetPcapDataLinkType(WifiPhyHelper::DLT_IEEE802_11_RADIO);
phyHelper.Set("ChannelSettings", StringValue("{36, 0, BAND_5GHZ, 0}"));
phyHelper.AddChannel(CreateObject<MultiModelSpectrumChannel>());
WifiMacHelper mac;
mac.SetType("ns3::ApWifiMac",
"Ssid",
SsidValue(Ssid("ns-3-ssid")),
"BeaconGeneration",
BooleanValue(true));
auto apDevice = DynamicCast<WifiNetDevice>(wifi.Install(phyHelper, mac, apNode).Get(0));
mac.SetType("ns3::StaWifiMac",
"Ssid",
SsidValue(Ssid("ns-3-ssid")),
"ActiveProbing",
BooleanValue(false));
auto staDevice = DynamicCast<WifiNetDevice>(wifi.Install(phyHelper, mac, staNode).Get(0));
m_apMac = DynamicCast<ApWifiMac>(apDevice->GetMac());
m_staMac = DynamicCast<StaWifiMac>(staDevice->GetMac());
// Trace PSDUs passed to the PHY
apDevice->GetPhy(SINGLE_LINK_OP_ID)
->TraceConnectWithoutContext("PhyTxPsduBegin",
MakeCallback(&BackoffGenerationTest::Transmit, this));
staDevice->GetPhy(SINGLE_LINK_OP_ID)
->TraceConnectWithoutContext("PhyTxPsduBegin",
MakeCallback(&BackoffGenerationTest::Transmit, this));
// Trace backoff generation
m_apMac->GetQosTxop(AC_VO)->TraceConnectWithoutContext(
"BackoffTrace",
MakeCallback(&BackoffGenerationTest::BackoffGenerated, this).Bind(AC_VO));
// Assign fixed streams to random variables in use
streamNumber += WifiHelper::AssignStreams(NetDeviceContainer(apDevice), streamNumber);
streamNumber += WifiHelper::AssignStreams(NetDeviceContainer(staDevice), streamNumber);
Ptr<ListPositionAllocator> positionAlloc = CreateObject<ListPositionAllocator>();
positionAlloc->Add(Vector(0.0, 0.0, 0.0));
positionAlloc->Add(Vector(1.0, 0.0, 0.0));
MobilityHelper mobility;
mobility.SetPositionAllocator(positionAlloc);
mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
mobility.Install(apNode);
mobility.Install(staNode);
// // install packet socket on all nodes
PacketSocketHelper packetSocket;
packetSocket.Install(apNode);
packetSocket.Install(staNode);
// install a packet socket server on the non-AP station
PacketSocketAddress srvAddr;
srvAddr.SetSingleDevice(staDevice->GetIfIndex());
srvAddr.SetProtocol(1);
auto server = CreateObject<PacketSocketServer>();
server->SetLocal(srvAddr);
server->SetStartTime(Seconds(0));
server->SetStopTime(Seconds(1));
staNode->AddApplication(server);
// Prepare a packet socket client that generates one packet at the AP. This client will be
// installed as soon as association is completed
PacketSocketAddress remoteAddr;
remoteAddr.SetSingleDevice(apDevice->GetIfIndex());
remoteAddr.SetPhysicalAddress(staDevice->GetAddress());
remoteAddr.SetProtocol(1);
m_client = CreateObject<PacketSocketClient>();
m_client->SetAttribute("PacketSize", UintegerValue(1000));
m_client->SetAttribute("MaxPackets", UintegerValue(1));
m_client->SetAttribute("Interval", TimeValue(Time{0}));
m_client->SetAttribute("Priority", UintegerValue(m_tid)); // AC VO
m_client->SetRemote(remoteAddr);
m_client->SetStartTime(Seconds(0));
m_client->SetStopTime(Seconds(1));
// Block VO queue so that the AP does not send QoS data frames
m_apMac->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
AC_VO,
{WIFI_QOSDATA_QUEUE},
m_staMac->GetAddress(),
m_apMac->GetAddress(),
{m_tid},
{SINGLE_LINK_OP_ID});
}
void
BackoffGenerationTest::DoRun()
{
Simulator::Stop(Seconds(1));
Simulator::Run();
NS_TEST_EXPECT_MSG_EQ(m_nExpectedGenBackoff,
m_nGenBackoff,
"Unexpected total number of generated backoff values");
Simulator::Destroy();
}
void
BackoffGenerationTest::Transmit(WifiConstPsduMap psduMap, WifiTxVector txVector, double txPowerW)
{
auto txDuration =
WifiPhy::CalculateTxDuration(psduMap,
txVector,
m_apMac->GetWifiPhy(SINGLE_LINK_OP_ID)->GetPhyBand());
for (const auto& [aid, psdu] : psduMap)
{
std::stringstream ss;
ss << std::setprecision(10) << psdu->GetHeader(0).GetTypeString();
if (psdu->GetHeader(0).IsAction())
{
ss << " ";
WifiActionHeader actionHdr;
psdu->GetPayload(0)->PeekHeader(actionHdr);
actionHdr.Print(ss);
}
ss << " #MPDUs " << psdu->GetNMpdus() << " duration/ID " << psdu->GetHeader(0).GetDuration()
<< " RA = " << psdu->GetAddr1() << " TA = " << psdu->GetAddr2()
<< " ADDR3 = " << psdu->GetHeader(0).GetAddr3()
<< " ToDS = " << psdu->GetHeader(0).IsToDs()
<< " FromDS = " << psdu->GetHeader(0).IsFromDs();
if (psdu->GetHeader(0).IsAssocReq())
{
m_assocReqStartTxTime = Simulator::Now();
m_assocReqPpduHdrDuration = WifiPhy::CalculatePhyPreambleAndHeaderDuration(txVector);
}
else if (psdu->GetHeader(0).IsAck())
{
m_nAcks++;
if (m_nAcks == 2)
{
// generate a packet destined to the non-AP station (this packet is held because
// the queue is blocked) as soon as association is completed
Simulator::Schedule(txDuration,
&Node::AddApplication,
m_apMac->GetDevice()->GetNode(),
m_client);
}
}
else if (psdu->GetHeader(0).IsQosData())
{
ss << " seqNo = {";
for (auto& mpdu : *PeekPointer(psdu))
{
ss << mpdu->GetHeader().GetSequenceNumber() << ",";
}
ss << "} TID = " << +psdu->GetHeader(0).GetQosTid();
// after sending the QoS data frame, we expect one more backoff value to be generated
// (at the end of the TXOP)
m_nExpectedGenBackoff = m_nGenBackoff + 1;
}
NS_LOG_INFO(ss.str());
}
NS_LOG_INFO("TX duration = " << txDuration.As(Time::MS) << " TXVECTOR = " << txVector << "\n");
}
void
BackoffGenerationTest::BackoffGenerated(AcIndex ac, uint32_t backoff, uint8_t linkId)
{
NS_LOG_INFO("Backoff value " << backoff << " generated by AP on link " << +linkId << " for "
<< ac << "\n");
// number of backoff values to generate when the GenerateBackoffIfTxopWithoutTx attribute is
// set to true (can be any value >= 3)
const std::size_t nValues = 5;
switch (m_nGenBackoff)
{
case 0:
NS_TEST_EXPECT_MSG_EQ(Simulator::Now().IsZero(),
true,
"First backoff value should be generated at initialization time");
m_nGenBackoff++;
return;
case 1:
if (m_generateBackoffIfTxopWithoutTx)
{
NS_TEST_EXPECT_MSG_EQ(m_apMac->IsAssociated(m_staMac->GetAddress()).has_value(),
true,
"Second backoff value should be generated after association");
}
if (m_proactiveBackoff)
{
NS_TEST_ASSERT_MSG_GT(
Simulator::Now(),
m_assocReqStartTxTime,
"Second backoff value should be generated after AssocReq TX start time");
NS_TEST_EXPECT_MSG_LT(Simulator::Now(),
m_assocReqStartTxTime + m_assocReqPpduHdrDuration,
"Second backoff value should be generated right after AssocReq "
"PPDU payload starts");
}
break;
case 2:
if (m_proactiveBackoff)
{
NS_TEST_EXPECT_MSG_EQ(m_apMac->IsAssociated(m_staMac->GetAddress()).has_value(),
true,
"Third backoff value should be generated after association");
// after a SIFS:
Simulator::Schedule(m_apMac->GetWifiPhy(linkId)->GetSifs(), [=, this]() {
// generate interference (lasting 10 us)
GenerateInterference();
if (backoff == 0)
{
// backoff value is 0, thus a new backoff value is generated due to the
// interference
NS_TEST_EXPECT_MSG_EQ(m_nGenBackoff,
4,
"Unexpected number of generated backoff values");
}
else
{
// interference does not cause the generation of a new backoff value because
// the backoff counter is non-zero.
// At the end of the interference:
Simulator::Schedule(m_interferenceDuration, [=, this]() {
auto voEdcaf = m_apMac->GetQosTxop(AC_VO);
// update backoff (backoff info is only updated when some event occurs)
m_apMac->GetChannelAccessManager(linkId)->NeedBackoffUponAccess(voEdcaf,
true,
true);
auto delay =
m_apMac->GetChannelAccessManager(linkId)->GetBackoffEndFor(voEdcaf) -
Simulator::Now() + NanoSeconds(1);
// right after the backoff counts down to zero:
Simulator::Schedule(delay, [=, this]() {
// check that the number of generated backoff values is still 3
NS_TEST_EXPECT_MSG_EQ(m_nGenBackoff,
3,
"Unexpected number of generated backoff values");
GenerateInterference();
// check that a new backoff value is generated due to the interference
NS_TEST_EXPECT_MSG_EQ(m_nGenBackoff,
4,
"Unexpected number of generated backoff values");
});
});
}
});
}
break;
case nValues:
// Unblock VO queue so that the AP can send QoS data frames
m_apMac->GetMacQueueScheduler()->UnblockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
AC_VO,
{WIFI_QOSDATA_QUEUE},
m_staMac->GetAddress(),
m_apMac->GetAddress(),
{m_tid},
{SINGLE_LINK_OP_ID});
break;
}
if (m_generateBackoffIfTxopWithoutTx)
{
Time delay; // expected time until the generation of the next backoff value
const auto offset =
NanoSeconds(1); // offset between expected time and the time when check is made
if (m_nGenBackoff == 1)
{
// we have to wait an AIFS before invoking backoff
delay = m_apMac->GetWifiPhy(linkId)->GetSifs() +
m_apMac->GetQosTxop(AC_VO)->GetAifsn(linkId) *
m_apMac->GetWifiPhy(linkId)->GetSlot();
}
else if (m_nGenBackoff <= nValues)
{
NS_TEST_EXPECT_MSG_EQ(m_nextBackoffGen.IsPending(),
true,
"Expected a timer to be running");
NS_TEST_EXPECT_MSG_EQ(Simulator::GetDelayLeft(m_nextBackoffGen),
offset,
"Backoff value generated too early");
m_nextBackoffGen.Cancel();
// we get here when the backoff expired but no transmission occurred, thus we have
// generated a new backoff value and we will start decrementing the counter in a slot
delay = m_apMac->GetWifiPhy(linkId)->GetSlot();
}
if (m_nGenBackoff < nValues)
{
// add the time corresponding to the generated number of slots
delay += backoff * m_apMac->GetWifiPhy(linkId)->GetSlot();
m_nextBackoffGen =
Simulator::Schedule(delay + offset, &BackoffGenerationTest::MissedBackoff, this);
}
}
m_nGenBackoff++;
}
void
BackoffGenerationTest::MissedBackoff()
{
NS_TEST_EXPECT_MSG_EQ(true,
false,
"Expected a new backoff value to be generated at time "
<< Simulator::Now().As(Time::S));
}
void
BackoffGenerationTest::GenerateInterference()
{
NS_LOG_FUNCTION(this);
auto phy = DynamicCast<SpectrumWifiPhy>(m_apMac->GetWifiPhy(SINGLE_LINK_OP_ID));
auto psd = Create<SpectrumValue>(phy->GetCurrentInterface()->GetRxSpectrumModel());
*psd = DbmToW(20) / 80e6; // PSD spread across 80 MHz to generate some noise
auto spectrumSignalParams = Create<SpectrumSignalParameters>();
spectrumSignalParams->duration = m_interferenceDuration;
spectrumSignalParams->txPhy = phy->GetCurrentInterface();
spectrumSignalParams->txAntenna = phy->GetAntenna();
spectrumSignalParams->psd = psd;
phy->StartRx(spectrumSignalParams, phy->GetCurrentInterface());
}
/**
* \ingroup wifi-test
* \ingroup tests
@@ -1585,6 +2075,10 @@ ChannelAccessManagerTestSuite::ChannelAccessManagerTestSuite()
: TestSuite("wifi-channel-access-manager", Type::UNIT)
{
AddTestCase(new LargestIdlePrimaryChannelTest, TestCase::Duration::QUICK);
AddTestCase(new BackoffGenerationTest(BackoffGenerationTest::GEN_BACKOFF_IF_TXOP_NO_TX),
TestCase::Duration::QUICK);
AddTestCase(new BackoffGenerationTest(BackoffGenerationTest::PROACTIVE_BACKOFF),
TestCase::Duration::QUICK);
}
static ChannelAccessManagerTestSuite g_camTestSuite;