diff --git a/src/wifi/CMakeLists.txt b/src/wifi/CMakeLists.txt index f240633f7..1a600ab66 100644 --- a/src/wifi/CMakeLists.txt +++ b/src/wifi/CMakeLists.txt @@ -309,6 +309,7 @@ build_lib( test/spectrum-wifi-phy-test.cc test/tx-duration-test.cc test/wifi-aggregation-test.cc + test/wifi-dynamic-bw-op-test.cc test/wifi-error-rate-models-test.cc test/wifi-mac-ofdma-test.cc test/wifi-mac-queue-test.cc diff --git a/src/wifi/test/wifi-dynamic-bw-op-test.cc b/src/wifi/test/wifi-dynamic-bw-op-test.cc new file mode 100644 index 000000000..ca4f745d9 --- /dev/null +++ b/src/wifi/test/wifi-dynamic-bw-op-test.cc @@ -0,0 +1,459 @@ + /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2022 Universita' degli Studi di Napoli Federico II + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Stefano Avallone + */ + +#include "ns3/channel-access-manager.h" +#include "ns3/config.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/qos-txop.h" +#include "ns3/rng-seed-manager.h" +#include "ns3/spectrum-wifi-helper.h" +#include "ns3/string.h" +#include "ns3/test.h" +#include "ns3/wifi-mac.h" +#include "ns3/wifi-net-device.h" +#include "ns3/wifi-psdu.h" + +using namespace ns3; + +NS_LOG_COMPONENT_DEFINE ("WifiDynamicBwOpTestSuite"); + +/** + * \ingroup wifi-test + * \ingroup tests + * + * Two BSSes, each with one AP and one non-AP STA, are configured to operate on + * different channels. Specifically, the operating channel of BSS 1 is the secondary + * channel of BSS 0, where X is half the width of the channel used by BSS 0. + * This test demonstrates that, if a transmission is ongoing on BSS 1, we can have + * a transmission on BSS 0 on its primary channel. + */ +class WifiUseAvailBwTest : public TestCase +{ +public: + /** + * Constructor + * + * \param channelStr channel setting strings for BSS 0 and BSS 1 + * \param bss0Width width (MHz) of the transmission in BSS 0 started when BSS 1 is transmitting + */ + WifiUseAvailBwTest (std::initializer_list channelStr, uint16_t bss0Width); + virtual ~WifiUseAvailBwTest (); + + /** + * Function to trace packets received by the server application in the given BSS + * \param bss the given BSS + * \param p the packet + * \param addr the address + */ + void L7Receive (uint8_t bss, Ptr p, const Address &addr); + /** + * Callback invoked when a PHY receives a PSDU to transmit + * \param bss the BSS the PSDU belongs to + * \param psduMap the PSDU map + * \param txVector the TX vector + * \param txPowerW the tx power in Watts + */ + void Transmit (uint8_t bss, WifiConstPsduMap psduMap, WifiTxVector txVector, double txPowerW); + /** + * Check correctness of transmitted frames + */ + void CheckResults (void); + +private: + void DoRun (void) override; + + /// Information about transmitted frames + struct FrameInfo + { + Time txStart; ///< Frame start TX time + Time txDuration; ///< Frame TX duration + uint8_t bss; ///< BSS the frame belongs to + WifiMacHeader header; ///< Frame MAC header + std::size_t nMpdus; ///< number of MPDUs in the PSDU + WifiTxVector txVector; ///< TX vector used to transmit the frame + }; + + std::vector m_channelStr; ///< channel setting strings + uint16_t m_bss0Width; ///< width (MHz) of the transmission in BSS 0 + ///< started when BSS 1 is transmitting + NetDeviceContainer m_staDevices; ///< container for stations' NetDevices + NetDeviceContainer m_apDevices; ///< container for AP's NetDevice + std::array m_sockets; ///< packet sockets for the two BSSes + std::vector m_txPsdus; ///< transmitted PSDUs + uint8_t m_txPkts; ///< TX packets per BSS (in addition to the two + ///< required to establish BA agreement) + std::array m_rcvPkts; ///< number of packets received by the stations +}; + +WifiUseAvailBwTest::WifiUseAvailBwTest (std::initializer_list channelStr, + uint16_t bss0Width) + : TestCase ("Check transmission on available bandwidth"), + m_channelStr (channelStr), + m_bss0Width (bss0Width), + m_txPkts (bss0Width / 10), // so that they all fit in an A-MPDU + m_rcvPkts ({0, 0}) +{ +} + +WifiUseAvailBwTest::~WifiUseAvailBwTest () +{ +} + +void +WifiUseAvailBwTest::L7Receive (uint8_t bss, Ptr p, const Address &addr) +{ + NS_LOG_INFO ("Received " << p->GetSize () << " bytes in BSS " << +bss); + m_rcvPkts[bss]++; +} + +void +WifiUseAvailBwTest::Transmit (uint8_t bss, WifiConstPsduMap psduMap, WifiTxVector txVector, double txPowerW) +{ + auto psdu = psduMap.begin ()->second; + Time now = Simulator::Now (); + + // Log all frames that are not management frames (we are only interested in data + // frames and acknowledgments) and have been transmitted after 400ms (so as to + // skip association requests/responses) + if (!psdu->GetHeader (0).IsMgt () && now > MilliSeconds (400)) + { + m_txPsdus.push_back ({now, WifiPhy::CalculateTxDuration (psduMap, txVector, WIFI_PHY_BAND_5GHZ), + bss, psdu->GetHeader (0), psdu->GetNMpdus (), txVector}); + } + + NS_LOG_INFO (now << " BSS " << +bss + << " " << psdu->GetHeader (0).GetTypeString () + << " seq " << psdu->GetHeader (0).GetSequenceNumber () + << " to " << psdu->GetAddr1 () + << " #MPDUs " << psdu->GetNMpdus () + << " size " << psdu->GetSize () + << " TX duration " << WifiPhy::CalculateTxDuration (psduMap, txVector, WIFI_PHY_BAND_5GHZ) << "\n" + << "TXVECTOR " << txVector << "\n"); + + // when AP of BSS 1 starts transmitting (after 1.5 s), we generate packets + // for the AP of BSS 0 to transmit + if (bss == 1 && psdu->GetNMpdus () == m_txPkts && now >= Seconds (1.5)) + { + Ptr client = CreateObject (); + client->SetAttribute ("PacketSize", UintegerValue (2000)); + client->SetAttribute ("MaxPackets", UintegerValue (m_txPkts)); + client->SetAttribute ("Interval", TimeValue (MicroSeconds (0))); + client->SetRemote (m_sockets[0]); + m_apDevices.Get (0)->GetNode ()->AddApplication (client); + client->SetStartTime (Seconds (0)); // start now + client->SetStopTime (Seconds (1.0)); // stop in a second + client->Initialize (); + + // after 1us (to allow for propagation delay), the largest idle primary + // channel on the AP of BSS 0 should be the expected one + Simulator::Schedule ( + MicroSeconds (1), + [&]() + { + auto mac = StaticCast (m_apDevices.Get (0))->GetMac (); + auto cam = mac->GetChannelAccessManager (); + NS_TEST_EXPECT_MSG_EQ (cam->GetLargestIdlePrimaryChannel (MicroSeconds (1), + Simulator::Now ()), + m_bss0Width, + "Unexpected width of the largest idle primary channel"); + }); + } +} + +void +WifiUseAvailBwTest::DoRun (void) +{ + RngSeedManager::SetSeed (1); + RngSeedManager::SetRun (40); + int64_t streamNumber = 100; + + NodeContainer wifiApNodes (2); + NodeContainer wifiStaNodes (2); + + auto spectrumChannel = CreateObject (); + Ptr lossModel = CreateObject (); + spectrumChannel->AddPropagationLossModel (lossModel); + Ptr delayModel = CreateObject (); + spectrumChannel->SetPropagationDelayModel (delayModel); + + SpectrumWifiPhyHelper phy; + phy.SetChannel (spectrumChannel); + + WifiHelper wifi; + wifi.SetStandard (WIFI_STANDARD_80211ax); + wifi.SetRemoteStationManager ("ns3::ConstantRateWifiManager", + "DataMode", StringValue ("HeMcs0"), + "ControlMode", StringValue ("OfdmRate6Mbps")); + + WifiMacHelper apMac; + apMac.SetType ("ns3::ApWifiMac", + "Ssid", SsidValue (Ssid ("dynamic-bw-op-ssid"))); + + WifiMacHelper staMac; + staMac.SetType ("ns3::StaWifiMac", + "Ssid", SsidValue (Ssid ("dynamic-bw-op-ssid"))); + + // BSS 0 + phy.Set ("ChannelSettings", StringValue (m_channelStr.at (0))); + + m_apDevices = wifi.Install (phy, apMac, wifiApNodes.Get (0)); + m_staDevices = wifi.Install (phy, staMac, wifiStaNodes.Get (0)); + + // BSS 1 + phy.Set ("ChannelSettings", StringValue (m_channelStr.at (1))); + + m_apDevices.Add (wifi.Install (phy, apMac, wifiApNodes.Get (1))); + m_staDevices.Add (wifi.Install (phy, staMac, wifiStaNodes.Get (1))); + + // schedule association requests at different times + Ptr dev; + + // Assign fixed streams to random variables in use + streamNumber += wifi.AssignStreams (m_apDevices, streamNumber); + streamNumber += wifi.AssignStreams (m_staDevices, streamNumber); + + MobilityHelper mobility; + Ptr positionAlloc = CreateObject (); + + positionAlloc->Add (Vector (0.0, 0.0, 0.0)); + positionAlloc->Add (Vector (50.0, 0.0, 0.0)); + positionAlloc->Add (Vector (0.0, 50.0, 0.0)); + positionAlloc->Add (Vector (50.0, 50.0, 0.0)); + mobility.SetPositionAllocator (positionAlloc); + + mobility.SetMobilityModel ("ns3::ConstantPositionMobilityModel"); + mobility.Install (wifiApNodes); + mobility.Install (wifiStaNodes); + + NS_LOG_INFO ("Position of AP (BSS 0) = " << wifiApNodes.Get (0)->GetObject ()->GetPosition ()); + NS_LOG_INFO ("Position of AP (BSS 1) = " << wifiApNodes.Get (1)->GetObject ()->GetPosition ()); + NS_LOG_INFO ("Position of STA (BSS 0) = " << wifiStaNodes.Get (0)->GetObject ()->GetPosition ()); + NS_LOG_INFO ("Position of STA (BSS 1) = " << wifiStaNodes.Get (1)->GetObject ()->GetPosition ()); + + PacketSocketHelper packetSocket; + packetSocket.Install (wifiApNodes); + packetSocket.Install (wifiStaNodes); + + // DL frames + for (uint8_t bss : {0, 1}) + { + m_sockets[bss].SetSingleDevice (m_apDevices.Get (bss)->GetIfIndex ()); + m_sockets[bss].SetPhysicalAddress (m_staDevices.Get (bss)->GetAddress ()); + m_sockets[bss].SetProtocol (1); + + // the first client application generates two packets in order + // to trigger the establishment of a Block Ack agreement + Ptr client1 = CreateObject (); + client1->SetAttribute ("PacketSize", UintegerValue (500)); + client1->SetAttribute ("MaxPackets", UintegerValue (2)); + client1->SetAttribute ("Interval", TimeValue (MicroSeconds (0))); + client1->SetRemote (m_sockets[bss]); + wifiApNodes.Get (bss)->AddApplication (client1); + client1->SetStartTime (Seconds (0.5) + bss * MilliSeconds (500)); + client1->SetStopTime (Seconds (2.0)); + + // At time 1.5, start a transmission in BSS 1 + if (bss == 1) + { + Ptr client2 = CreateObject (); + client2->SetAttribute ("PacketSize", UintegerValue (2000)); + client2->SetAttribute ("MaxPackets", UintegerValue (m_txPkts)); + client2->SetAttribute ("Interval", TimeValue (MicroSeconds (0))); + client2->SetRemote (m_sockets[bss]); + wifiApNodes.Get (bss)->AddApplication (client2); + client2->SetStartTime (Seconds (1.5)); + client2->SetStopTime (Seconds (2.0)); + } + + Ptr server = CreateObject (); + server->SetLocal (m_sockets[bss]); + wifiStaNodes.Get (bss)->AddApplication (server); + server->SetStartTime (Seconds (0.0)); + server->SetStopTime (Seconds (2.0)); + + // Trace received packets on non-AP STAs + Config::ConnectWithoutContext ("/NodeList/" + std::to_string (2 + bss) + + "/ApplicationList/*/$ns3::PacketSocketServer/Rx", + MakeCallback (&WifiUseAvailBwTest::L7Receive, this).Bind (bss)); + // Trace PSDUs passed to the PHY of the AP + Config::ConnectWithoutContext ("/NodeList/" + std::to_string (bss) + + "/DeviceList/*/$ns3::WifiNetDevice/Phy/PhyTxPsduBegin", + MakeCallback (&WifiUseAvailBwTest::Transmit, this).Bind (bss)); + // Trace PSDUs passed to the PHY of the non-AP STA + Config::ConnectWithoutContext ("/NodeList/" + std::to_string (2 + bss) + + "/DeviceList/*/$ns3::WifiNetDevice/Phy/PhyTxPsduBegin", + MakeCallback (&WifiUseAvailBwTest::Transmit, this).Bind (bss)); + } + + Simulator::Stop (Seconds (2)); + Simulator::Run (); + + CheckResults (); + + Simulator::Destroy (); +} + +void +WifiUseAvailBwTest::CheckResults (void) +{ + NS_TEST_ASSERT_MSG_EQ (m_txPsdus.size (), 12, "Expected 12 transmitted frames"); + + // first logged frames are Acks after ADDBA Request/Response frames + auto psduIt = m_txPsdus.begin (); + NS_TEST_EXPECT_MSG_EQ (psduIt->header.IsAck (), true, "Expected Ack after ADDBA Request"); + NS_TEST_EXPECT_MSG_EQ (+psduIt->bss, 0, "Expected a transmission in BSS 0"); + psduIt++; + + NS_TEST_EXPECT_MSG_EQ (psduIt->header.IsAck (), true, "Expected Ack after ADDBA Response"); + NS_TEST_EXPECT_MSG_EQ (+psduIt->bss, 0, "Expected a transmission in BSS 0"); + psduIt++; + + // the first data frame is an A-MPDU sent by the AP of BSS 0 right after the + // establishment of the Block Ack agreement + NS_TEST_EXPECT_MSG_EQ (psduIt->header.IsQosData (), true, "Expected a QoS data frame"); + NS_TEST_EXPECT_MSG_EQ (+psduIt->bss, 0, "Expected a transmission in BSS 0"); + NS_TEST_EXPECT_MSG_EQ (psduIt->nMpdus, 2, "Expected an A-MPDU of 2 MPDUs after Block Ack"); + NS_TEST_EXPECT_MSG_EQ (psduIt->txVector.GetChannelWidth (), + StaticCast(m_apDevices.Get (0))->GetPhy ()->GetChannelWidth (), + "Expected a transmission on the whole channel width"); + psduIt++; + + NS_TEST_EXPECT_MSG_EQ (psduIt->header.IsBlockAck (), true, "Expected Block Ack after data frame"); + NS_TEST_EXPECT_MSG_EQ (+psduIt->bss, 0, "Expected a transmission in BSS 0"); + psduIt++; + + // same sequence for BSS 1 + NS_TEST_EXPECT_MSG_EQ (psduIt->header.IsAck (), true, "Expected Ack after ADDBA Request"); + NS_TEST_EXPECT_MSG_EQ (+psduIt->bss, 1, "Expected a transmission in BSS 1"); + psduIt++; + + NS_TEST_EXPECT_MSG_EQ (psduIt->header.IsAck (), true, "Expected Ack after ADDBA Response"); + NS_TEST_EXPECT_MSG_EQ (+psduIt->bss, 1, "Expected a transmission in BSS 1"); + psduIt++; + + NS_TEST_EXPECT_MSG_EQ (psduIt->header.IsQosData (), true, "Expected a QoS data frame"); + NS_TEST_EXPECT_MSG_EQ (+psduIt->bss, 1, "Expected a transmission in BSS 1"); + NS_TEST_EXPECT_MSG_EQ (psduIt->nMpdus, 2, "Expected an A-MPDU of 2 MPDUs after Block Ack"); + NS_TEST_EXPECT_MSG_EQ (psduIt->txVector.GetChannelWidth (), + StaticCast(m_apDevices.Get (1))->GetPhy ()->GetChannelWidth (), + "Expected a transmission on the whole channel width"); + psduIt++; + + NS_TEST_EXPECT_MSG_EQ (psduIt->header.IsBlockAck (), true, "Expected Block Ack after data frame"); + NS_TEST_EXPECT_MSG_EQ (+psduIt->bss, 1, "Expected a transmission in BSS 1"); + psduIt++; + + // after some time, we have another A-MPDU transmitted in BSS 1 + NS_TEST_EXPECT_MSG_EQ (psduIt->header.IsQosData (), true, "Expected a QoS data frame"); + NS_TEST_EXPECT_MSG_EQ (+psduIt->bss, 1, "Expected a transmission in BSS 1"); + NS_TEST_EXPECT_MSG_EQ (psduIt->nMpdus, +m_txPkts, "Expected an A-MPDU of " << +m_txPkts << " MPDUs"); + NS_TEST_EXPECT_MSG_EQ (psduIt->txVector.GetChannelWidth (), + StaticCast(m_apDevices.Get (1))->GetPhy ()->GetChannelWidth (), + "Expected a transmission on the whole channel width"); + psduIt++; + + // we expect that the AP of BSS 0 starts transmitting while the AP of BSS 1 is transmitting + NS_TEST_EXPECT_MSG_EQ (psduIt->header.IsQosData (), true, "Expected a QoS data frame"); + NS_TEST_EXPECT_MSG_EQ (+psduIt->bss, 0, "Expected a transmission in BSS 0"); + NS_TEST_EXPECT_MSG_EQ (psduIt->nMpdus, +m_txPkts, "Expected an A-MPDU of " << +m_txPkts << " MPDUs"); + NS_TEST_EXPECT_MSG_EQ (psduIt->txVector.GetChannelWidth (), m_bss0Width, + "Unexpected transmission width"); + NS_TEST_EXPECT_MSG_LT (psduIt->txStart, std::prev (psduIt)->txStart + std::prev (psduIt)->txDuration, + "AP 0 is expected to transmit before the end of transmission of AP 1"); + psduIt++; + + // receive a Block Ack in BSS 1 and then a Block Ack in BSS 0 + NS_TEST_EXPECT_MSG_EQ (psduIt->header.IsBlockAck (), true, "Expected Block Ack after data frame"); + NS_TEST_EXPECT_MSG_EQ (+psduIt->bss, 1, "Expected a transmission in BSS 1"); + psduIt++; + + NS_TEST_EXPECT_MSG_EQ (psduIt->header.IsBlockAck (), true, "Expected Block Ack after data frame"); + NS_TEST_EXPECT_MSG_EQ (+psduIt->bss, 0, "Expected a transmission in BSS 0"); + psduIt++; + + // each application server (on STAs) received 2 packets right after Block Ack + // agreement establishment and m_txPkts packets afterwards + NS_TEST_EXPECT_MSG_EQ (+m_rcvPkts[0], 2 + m_txPkts, "Unexpected number of packets received by STA 0"); + NS_TEST_EXPECT_MSG_EQ (+m_rcvPkts[1], 2 + m_txPkts, "Unexpected number of packets received by STA 1"); +} + + +/** + * \ingroup wifi-test + * \ingroup tests + * + * \brief wifi dynamic bandwidth operation Test Suite + */ +class WifiDynamicBwOpTestSuite : public TestSuite +{ +public: + WifiDynamicBwOpTestSuite (); +}; + +WifiDynamicBwOpTestSuite::WifiDynamicBwOpTestSuite () + : TestSuite ("wifi-dynamic-bw-op", UNIT) +{ + /** + * primary20 + * ┌────────┬────────┐ + * BSS 0 │ 52 │ 56 │ + * └────────┴────────┘ + * + * ┌────────┐ + * BSS 1 │ 52 │ + * └────────┘ + */ + AddTestCase (new WifiUseAvailBwTest ({"{54, 40, BAND_5GHZ, 1}", "{52, 20, BAND_5GHZ, 0}"}, 20), + TestCase::QUICK); + /** + * ─── primary 40 ─── + * primary20 + * ┌────────┬────────┬────────┬────────┐ + * BSS 0 │ 52 │ 56 │ 60 │ 64 │ + * └────────┴────────┴────────┴────────┘ + * + * ┌────────┬────────┐ + * BSS 1 │ 60 │ 64 │ + * └────────┴────────┘ + * primary20 + */ + AddTestCase (new WifiUseAvailBwTest ({"{58, 80, BAND_5GHZ, 0}", "{62, 40, BAND_5GHZ, 1}"}, 40), + TestCase::QUICK); + /** + * ─────────── primary 80 ─────────── + * primary20 + * ┌────────┬────────┬────────┬────────┬───────┬────────┬────────┬────────┐ + * BSS 0 │ 36 │ 40 │ 44 │ 48 │ 52 │ 56 │ 60 │ 64 │ + * └────────┴────────┴────────┴────────┴───────┴────────┴────────┴────────┘ + * + * ┌────────┬────────┬────────┬────────┐ + * BSS 1 │ 36 │ 40 │ 44 │ 48 │ + * └────────┴────────┴────────┴────────┘ + * primary20 + */ + AddTestCase (new WifiUseAvailBwTest ({"{50, 160, BAND_5GHZ, 5}", "{42, 80, BAND_5GHZ, 2}"}, 80), + TestCase::QUICK); +} + +static WifiDynamicBwOpTestSuite g_wifiDynamicBwOpTestSuite; ///< the test suite