wifi: Add tests to verify various combinations of channel settings and PHY capabilities

This commit is contained in:
Sébastien Deronne
2025-03-22 17:34:47 +01:00
parent a9965c13bf
commit 1a1d3cd4cb
3 changed files with 385 additions and 3 deletions

View File

@@ -376,6 +376,7 @@ build_lib(
test/spectrum-wifi-phy-test.cc
test/tx-duration-test.cc
test/wifi-aggregation-test.cc
test/wifi-channel-settings-test.cc
test/wifi-co-trace-helper-test.cc
test/wifi-dynamic-bw-op-test.cc
test/wifi-eht-info-elems-test.cc

View File

@@ -1278,9 +1278,10 @@ WifiPhy::DoChannelSwitch()
if (const auto chWidth = GetChannelWidth();
(m_maxRadioBw != MHz_u{0}) && (chWidth > m_maxRadioBw))
{
NS_ABORT_MSG("Attempting to set a "
<< chWidth << " MHz channel on a station only supporting " << m_maxRadioBw
<< " MHz operation");
// throw an exception instead of using NS_ABORT_MSG for unit testing this code
throw std::runtime_error("Attempting to set a " + std::to_string(chWidth) +
" MHz channel on a station only supporting " +
std::to_string(m_maxRadioBw) + " MHz operation");
}
if (changingPhyBand)

View File

@@ -0,0 +1,380 @@
/*
* Copyright (c) 2025 DERONNE SOFTWARE ENGINEERING
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author: Sébastien Deronne <sebastien.deronne@gmail.com>
*/
#include "ns3/ap-wifi-mac.h"
#include "ns3/boolean.h"
#include "ns3/config.h"
#include "ns3/double.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/rng-seed-manager.h"
#include "ns3/spectrum-wifi-helper.h"
#include "ns3/sta-wifi-mac.h"
#include "ns3/string.h"
#include "ns3/test.h"
#include "ns3/wifi-net-device.h"
#include "ns3/wifi-standards.h"
#include <map>
#include <sstream>
#include <string>
#include <vector>
using namespace ns3;
NS_LOG_COMPONENT_DEFINE("WifiChannelSettingsTest");
/**
* @ingroup wifi-test
* @ingroup tests
*
* @brief Test initial channel settings for AP and non-AP STAs when those are not necessarily
* identical
*
* This test verifies the channel width used by the non-AP STA is properly advertised to the AP STA,
* and that invalid combinations get rejected. It generates DL and UL traffic (limited to 1 packet
* for each direction) and checks that the number of received packets matches with the expectation.
*/
class WifiChannelSettingsTest : public TestCase
{
public:
/**
* Parameters for the test
*/
struct Params
{
WifiStandard apStandard; ///< wifi standard for AP STA
WifiStandard staStandard; ///< wifi standard for non-AP STA
std::string apChannelSettings; ///< channel setting string for AP STA
std::string staChannelSettings; ///< channel setting string for non-AP STA
MHz_u staLargestSupportedChWidth; ///< largest supported channel width by the non-AP STA
bool skipAssocIncompatibleChannelWidth{
false}; ///< flag to skip association when STA channel width is incompatible with AP
/**
* Print the parameters
*
* @return a string with the parameters
*/
std::string Print() const
{
std::ostringstream oss;
oss << "AP standard=" << apStandard << ", non-AP STA standard=" << staStandard
<< ", AP settings="
<< apChannelSettings + ", non-AP STA settings=" + staChannelSettings
<< ", staLargestSupportedChWidth=" << staLargestSupportedChWidth
<< " MHz, skipAssocIncompatibleChannelWidth=" << skipAssocIncompatibleChannelWidth;
return oss.str();
}
};
/**
* Constructor
*
* @param params parameters for the test
*/
WifiChannelSettingsTest(const Params& params);
~WifiChannelSettingsTest() override = default;
private:
void DoRun() override;
/**
* Callback invoked when a packet is received by the server application
* @param p the packet
* @param adr the address
*/
void AppRx(Ptr<const Packet> p, const Address& adr);
/**
* Check results
*/
void CheckResults();
Params m_params; ///< test parameters
Ptr<ApWifiMac> m_apWifiMac; ///< AP wifi MAC
Ptr<StaWifiMac> m_staWifiMac; ///< STA wifi MAC
const uint32_t m_dlPacketSize{1400}; ///< DL packet size (bytes)
const uint32_t m_ulPacketSize{600}; ///< UL packet size (bytes)
uint32_t m_receivedDl{0}; ///< received DL packets
uint32_t m_receivedUl{0}; ///< received UL packets
};
WifiChannelSettingsTest::WifiChannelSettingsTest(const Params& params)
: TestCase("Check correct behaviour for scenario: " + params.Print()),
m_params{params}
{
}
void
WifiChannelSettingsTest::AppRx(Ptr<const Packet> p, const Address& adr)
{
NS_LOG_INFO("Received " << p->GetSize() << " bytes");
if (p->GetSize() == m_dlPacketSize)
{
m_receivedDl++;
}
else if (p->GetSize() == m_ulPacketSize)
{
m_receivedUl++;
}
}
void
WifiChannelSettingsTest::DoRun()
{
RngSeedManager::SetSeed(1);
RngSeedManager::SetRun(10);
int64_t streamNumber = 100;
NodeContainer wifiApNode(1);
NodeContainer wifiStaNode(1);
auto spectrumChannel = CreateObject<MultiModelSpectrumChannel>();
auto lossModel = CreateObject<FriisPropagationLossModel>();
spectrumChannel->AddPropagationLossModel(lossModel);
auto delayModel = CreateObject<ConstantSpeedPropagationDelayModel>();
spectrumChannel->SetPropagationDelayModel(delayModel);
SpectrumWifiPhyHelper phy;
phy.SetChannel(spectrumChannel);
WifiHelper wifi;
wifi.SetRemoteStationManager("ns3::IdealWifiManager");
wifi.SetStandard(m_params.apStandard);
phy.Set("ChannelSettings", StringValue(m_params.apChannelSettings));
WifiMacHelper mac;
mac.SetType("ns3::ApWifiMac", "Ssid", SsidValue(Ssid("ns-3-ssid")));
auto apDevice = wifi.Install(phy, mac, wifiApNode);
wifi.SetStandard(m_params.staStandard);
phy.Set("MaxRadioBw", DoubleValue(m_params.staLargestSupportedChWidth));
phy.Set("ChannelSettings", StringValue(m_params.staChannelSettings));
NetDeviceContainer staDevice;
mac.SetType("ns3::StaWifiMac", "Ssid", SsidValue(Ssid("ns-3-ssid")));
mac.SetAssocManager(
"ns3::WifiDefaultAssocManager",
"AllowAssocAllChannelWidths",
BooleanValue(true), // avoid assert in test, it checks for received DL packets instead
"SkipAssocIncompatibleChannelWidth",
BooleanValue(m_params.skipAssocIncompatibleChannelWidth));
std::istringstream split(m_params.staChannelSettings);
std::vector<std::string> tmp;
for (std::string val; std::getline(split, val, ',');)
{
tmp.emplace_back(val);
}
const auto staBw = std::stold(tmp.at(1));
const auto expectInvalidConfig = m_params.staLargestSupportedChWidth < staBw;
auto invalidConfig{false};
try
{
staDevice = wifi.Install(phy, mac, wifiStaNode);
}
catch (const std::runtime_error&)
{
invalidConfig = true;
}
NS_TEST_ASSERT_MSG_EQ(invalidConfig,
expectInvalidConfig,
"Configuration should " << (expectInvalidConfig ? "not " : "")
<< "have been allowed");
if (invalidConfig)
{
Simulator::Destroy();
return;
}
m_apWifiMac = DynamicCast<ApWifiMac>(DynamicCast<WifiNetDevice>(apDevice.Get(0))->GetMac());
m_staWifiMac = DynamicCast<StaWifiMac>(DynamicCast<WifiNetDevice>(staDevice.Get(0))->GetMac());
streamNumber += WifiHelper::AssignStreams(apDevice, streamNumber);
streamNumber += WifiHelper::AssignStreams(staDevice, streamNumber);
MobilityHelper mobility;
auto positionAlloc = CreateObject<ListPositionAllocator>();
positionAlloc->Add(Vector(0.0, 0.0, 0.0));
positionAlloc->Add(Vector(10.0, 0.0, 0.0));
mobility.SetPositionAllocator(positionAlloc);
mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
mobility.Install(wifiApNode);
mobility.Install(wifiStaNode);
PacketSocketHelper packetSocket;
packetSocket.Install(wifiStaNode);
packetSocket.Install(wifiApNode);
// generate a single packet in DL direction
PacketSocketAddress dlSocket;
dlSocket.SetSingleDevice(apDevice.Get(0)->GetIfIndex());
dlSocket.SetPhysicalAddress(staDevice.Get(0)->GetAddress());
dlSocket.SetProtocol(0);
auto dlClient = CreateObject<PacketSocketClient>();
dlClient->SetAttribute("PacketSize", UintegerValue(m_dlPacketSize));
dlClient->SetAttribute("MaxPackets", UintegerValue(1));
dlClient->SetRemote(dlSocket);
wifiApNode.Get(0)->AddApplication(dlClient);
dlClient->SetStartTime(Seconds(0.15));
dlClient->SetStopTime(Seconds(0.25));
auto dlServer = CreateObject<PacketSocketServer>();
dlServer->SetLocal(dlSocket);
wifiStaNode.Get(0)->AddApplication(dlServer);
dlServer->SetStartTime(Seconds(0.0));
dlServer->SetStopTime(Seconds(0.25));
// generate a single packet in UL direction
PacketSocketAddress ulSocket;
ulSocket.SetSingleDevice(staDevice.Get(0)->GetIfIndex());
ulSocket.SetPhysicalAddress(apDevice.Get(0)->GetAddress());
ulSocket.SetProtocol(1);
auto ulClient = CreateObject<PacketSocketClient>();
ulClient->SetAttribute("PacketSize", UintegerValue(m_ulPacketSize));
ulClient->SetAttribute("MaxPackets", UintegerValue(1));
ulClient->SetRemote(ulSocket);
wifiStaNode.Get(0)->AddApplication(ulClient);
ulClient->SetStartTime(Seconds(0.2));
ulClient->SetStopTime(Seconds(0.25));
auto ulServer = CreateObject<PacketSocketServer>();
ulServer->SetLocal(ulSocket);
wifiApNode.Get(0)->AddApplication(ulServer);
ulServer->SetStartTime(Seconds(0.0));
ulServer->SetStopTime(Seconds(0.25));
Config::ConnectWithoutContext("/NodeList/*/ApplicationList/*/$ns3::PacketSocketServer/Rx",
MakeCallback(&WifiChannelSettingsTest::AppRx, this));
Simulator::Stop(Seconds(0.25));
Simulator::Run();
CheckResults();
Simulator::Destroy();
}
void
WifiChannelSettingsTest::CheckResults()
{
const auto staPhy = m_staWifiMac->GetDevice()->GetPhy();
const auto staBw = staPhy->GetChannelWidth();
const auto compatibleBw =
GetSupportedChannelWidthSet(staPhy->GetStandard(), staPhy->GetPhyBand()).contains(staBw) ||
(staBw >= m_apWifiMac->GetDevice()->GetPhy()->GetChannelWidth());
NS_TEST_EXPECT_MSG_EQ(m_staWifiMac->GetWifiRemoteStationManager()->GetChannelWidthSupported(
m_apWifiMac->GetAddress()),
m_apWifiMac->GetDevice()->GetPhy()->GetChannelWidth(),
"Incorrect AP channel width information received by STA");
// if BW is not compatible, STA should not be able to receive any DL packets (since they will
// use a larger BW than the one supported by its PHY)
const uint32_t expectedRxDl = compatibleBw ? 1 : 0;
NS_TEST_EXPECT_MSG_EQ(m_receivedDl,
expectedRxDl,
"Unexpected number of received packets in downlink direction");
// if BW is not compatible and skipAssocIncompatibleChannelWidth is true, STA should not be
// associated to AP and hence no traffic should be received
const uint32_t expectedRxUl =
(compatibleBw || !m_params.skipAssocIncompatibleChannelWidth) ? 1 : 0;
NS_TEST_EXPECT_MSG_EQ(m_receivedUl,
expectedRxUl,
"Unexpected number of received packets in uplink direction");
}
/**
* @ingroup wifi-test
* @ingroup tests
*
* @brief wifi channel settings test suite
*/
class WifiChannelSettingsTestSuite : public TestSuite
{
public:
WifiChannelSettingsTestSuite();
};
WifiChannelSettingsTestSuite::WifiChannelSettingsTestSuite()
: TestSuite("wifi-channel-settings", Type::UNIT)
{
const std::map<std::pair<MHz_u, WifiPhyBand>, std::string> channelSettingsMap{
{{MHz_u{20}, WIFI_PHY_BAND_2_4GHZ}, "{1, 20, BAND_2_4GHZ, 0}"},
{{MHz_u{40}, WIFI_PHY_BAND_2_4GHZ}, "{3, 40, BAND_2_4GHZ, 0}"},
{{MHz_u{20}, WIFI_PHY_BAND_5GHZ}, "{36, 20, BAND_5GHZ, 0}"},
{{MHz_u{40}, WIFI_PHY_BAND_5GHZ}, "{38, 40, BAND_5GHZ, 0}"},
{{MHz_u{80}, WIFI_PHY_BAND_5GHZ}, "{42, 80, BAND_5GHZ, 0}"},
{{MHz_u{160}, WIFI_PHY_BAND_5GHZ}, "{50, 160, BAND_5GHZ, 0}"},
{{MHz_u{20}, WIFI_PHY_BAND_6GHZ}, "{1, 20, BAND_6GHZ, 0}"},
{{MHz_u{40}, WIFI_PHY_BAND_6GHZ}, "{3, 40, BAND_6GHZ, 0}"},
{{MHz_u{80}, WIFI_PHY_BAND_6GHZ}, "{7, 80, BAND_6GHZ, 0}"},
{{MHz_u{160}, WIFI_PHY_BAND_6GHZ}, "{15, 160, BAND_6GHZ, 0}"},
};
for (const auto standard : {WIFI_STANDARD_80211n,
WIFI_STANDARD_80211ac,
WIFI_STANDARD_80211ax,
WIFI_STANDARD_80211be})
{
for (const auto maxSupportedBw : {MHz_u{20}, MHz_u{40}, MHz_u{80}, MHz_u{160}})
{
for (const auto& [apWidthBandPair, apChannel] : channelSettingsMap)
{
for (const auto& [staWidthBandPair, staChannel] : channelSettingsMap)
{
if (apWidthBandPair.second != staWidthBandPair.second)
{
continue; // different band
}
if (const auto& allowedBands = wifiStandards.at(standard);
std::find(allowedBands.cbegin(),
allowedBands.cend(),
apWidthBandPair.second) == allowedBands.cend())
{
continue; // standard does not operate on this band
}
if (const auto maxWidth =
std::max(apWidthBandPair.first, staWidthBandPair.first);
maxWidth > GetMaximumChannelWidth(GetModulationClassForStandard(standard)))
{
continue; // channel width(s) not supported by the standard
}
for (const auto skipAssocIfIncompatible : {false, true})
{
AddTestCase(
new WifiChannelSettingsTest(
{.apStandard = standard,
.staStandard = standard,
.apChannelSettings = apChannel,
.staChannelSettings = staChannel,
.staLargestSupportedChWidth = maxSupportedBw,
.skipAssocIncompatibleChannelWidth = skipAssocIfIncompatible}),
TestCase::Duration::QUICK);
}
}
}
}
}
}
static WifiChannelSettingsTestSuite g_wifiChannelSettingsTestSuite; ///< the test suite