diff --git a/src/wifi/test/channel-access-manager-test.cc b/src/wifi/test/channel-access-manager-test.cc index 695b4b29f..b06ef1c41 100644 --- a/src/wifi/test/channel-access-manager-test.cc +++ b/src/wifi/test/channel-access-manager-test.cc @@ -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 #include #include using namespace ns3; +NS_LOG_COMPONENT_DEFINE("WifiChannelAccessManagerTest"); + template 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 m_apMac; ///< AP wifi MAC + Ptr 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 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(); + auto staNode = CreateObject(); + + 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()); + + WifiMacHelper mac; + mac.SetType("ns3::ApWifiMac", + "Ssid", + SsidValue(Ssid("ns-3-ssid")), + "BeaconGeneration", + BooleanValue(true)); + + auto apDevice = DynamicCast(wifi.Install(phyHelper, mac, apNode).Get(0)); + + mac.SetType("ns3::StaWifiMac", + "Ssid", + SsidValue(Ssid("ns-3-ssid")), + "ActiveProbing", + BooleanValue(false)); + + auto staDevice = DynamicCast(wifi.Install(phyHelper, mac, staNode).Get(0)); + + m_apMac = DynamicCast(apDevice->GetMac()); + m_staMac = DynamicCast(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 positionAlloc = CreateObject(); + 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(); + 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(); + 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(m_apMac->GetWifiPhy(SINGLE_LINK_OP_ID)); + auto psd = Create(phy->GetCurrentInterface()->GetRxSpectrumModel()); + *psd = DbmToW(20) / 80e6; // PSD spread across 80 MHz to generate some noise + + auto spectrumSignalParams = Create(); + 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;