diff --git a/src/wifi/model/channel-access-manager.h b/src/wifi/model/channel-access-manager.h index 82ba1143f..5cbc57347 100644 --- a/src/wifi/model/channel-access-manager.h +++ b/src/wifi/model/channel-access-manager.h @@ -23,6 +23,7 @@ #include class EmlsrUlTxopTest; +class EmlsrCcaBusyTest; namespace ns3 { @@ -51,6 +52,7 @@ class ChannelAccessManager : public Object { /// Allow test cases to access private members friend class ::EmlsrUlTxopTest; + friend class ::EmlsrCcaBusyTest; public: ChannelAccessManager(); diff --git a/src/wifi/model/eht/emlsr-manager.h b/src/wifi/model/eht/emlsr-manager.h index 00c74bbb6..49f9a3bad 100644 --- a/src/wifi/model/eht/emlsr-manager.h +++ b/src/wifi/model/eht/emlsr-manager.h @@ -19,6 +19,8 @@ #include #include +class EmlsrCcaBusyTest; + namespace ns3 { @@ -34,6 +36,9 @@ class WifiMpdu; */ class EmlsrManager : public Object { + /// Allow test cases to access private members + friend class ::EmlsrCcaBusyTest; + public: /** * \brief Get the type ID. diff --git a/src/wifi/test/wifi-emlsr-test.cc b/src/wifi/test/wifi-emlsr-test.cc index 8bf87e14f..36328eff7 100644 --- a/src/wifi/test/wifi-emlsr-test.cc +++ b/src/wifi/test/wifi-emlsr-test.cc @@ -4056,6 +4056,221 @@ EmlsrLinkSwitchTest::CheckResults() } } +EmlsrCcaBusyTest::EmlsrCcaBusyTest(uint16_t auxPhyMaxChWidth) + : EmlsrOperationsTestBase(std::string("Check EMLSR link switching (auxPhyMaxChWidth=") + + std::to_string(auxPhyMaxChWidth) + "MHz )"), + m_auxPhyMaxChWidth(auxPhyMaxChWidth), + m_channelSwitchDelay(MicroSeconds(75)), + m_currMainPhyLinkId(0), + m_nextMainPhyLinkId(0) +{ + m_nEmlsrStations = 1; + m_nNonEmlsrStations = 1; + m_linksToEnableEmlsrOn = {0, 1, 2}; // enable EMLSR on all links right after association + m_mainPhyId = 1; + m_establishBaUl = true; + m_duration = Seconds(1.0); + m_transitionDelay = {MicroSeconds(128)}; +} + +void +EmlsrCcaBusyTest::DoRun() +{ + Simulator::Stop(m_duration); + Simulator::Run(); + + Simulator::Destroy(); +} + +void +EmlsrCcaBusyTest::DoSetup() +{ + Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(true)); + Config::SetDefault("ns3::EmlsrManager::AuxPhyChannelWidth", UintegerValue(m_auxPhyMaxChWidth)); + Config::SetDefault("ns3::EmlsrManager::AuxPhyMaxModClass", StringValue("EHT")); + Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(m_channelSwitchDelay)); + + EmlsrOperationsTestBase::DoSetup(); + + // use channels of different widths + for (auto mac : std::initializer_list>{m_apMac, m_staMacs[0], m_staMacs[1]}) + { + mac->GetWifiPhy(0)->SetOperatingChannel( + WifiPhy::ChannelTuple{4, 40, WIFI_PHY_BAND_2_4GHZ, 0}); + mac->GetWifiPhy(1)->SetOperatingChannel( + WifiPhy::ChannelTuple{58, 80, WIFI_PHY_BAND_5GHZ, 0}); + mac->GetWifiPhy(2)->SetOperatingChannel( + WifiPhy::ChannelTuple{79, 160, WIFI_PHY_BAND_6GHZ, 0}); + } +} + +void +EmlsrCcaBusyTest::TransmitPacketToAp(uint8_t linkId) +{ + m_staMacs[1]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 1, 1, 2000)); + + // force the transmission of the packet to happen now on the given link. + // Multiple ScheduleNow calls are needed because Node::AddApplication() schedules a call to + // Application::Initialize(), which schedules a call to Application::StartApplication(), which + // schedules a call to PacketSocketClient::Send(), which finally generates the packet + Simulator::ScheduleNow([=, this]() { + Simulator::ScheduleNow([=, this]() { + Simulator::ScheduleNow([=, this]() { + m_staMacs[1]->GetFrameExchangeManager(linkId)->StartTransmission( + m_staMacs[1]->GetQosTxop(AC_BE), + m_staMacs[1]->GetWifiPhy(linkId)->GetChannelWidth()); + }); + }); + }); + + // check that the other MLD started transmitting on the correct link + Simulator::Schedule(TimeStep(1), [=, this]() { + NS_TEST_EXPECT_MSG_EQ(m_staMacs[1]->GetWifiPhy(linkId)->IsStateTx(), + true, + "At time " << Simulator::Now().As(Time::NS) + << ", other MLD did not start transmitting on link " + << +linkId); + }); +} + +void +EmlsrCcaBusyTest::StartTraffic() +{ + auto currMainPhyLinkId = m_staMacs[0]->GetLinkForPhy(m_mainPhyId); + NS_TEST_ASSERT_MSG_EQ(currMainPhyLinkId.has_value(), + true, + "Main PHY is not operating on any link"); + m_currMainPhyLinkId = *currMainPhyLinkId; + m_nextMainPhyLinkId = (m_currMainPhyLinkId + 1) % 2; + + // request the main PHY to switch to another link + m_staMacs[0]->GetEmlsrManager()->SwitchMainPhy(m_nextMainPhyLinkId, + false, + EmlsrManager::DONT_RESET_BACKOFF, + EmlsrManager::DONT_REQUEST_ACCESS); + + // the other MLD transmits a packet to the AP + TransmitPacketToAp(m_nextMainPhyLinkId); + + // schedule another packet transmission slightly (10 us) before the end of aux PHY switch + Simulator::Schedule(m_channelSwitchDelay - MicroSeconds(10), + &EmlsrCcaBusyTest::TransmitPacketToAp, + this, + m_currMainPhyLinkId); + + // first checkpoint is after that the preamble of the PPDU has been received + Simulator::Schedule(MicroSeconds(8), &EmlsrCcaBusyTest::CheckPoint1, this); +} + +/** + * ┌───────────────┐ + * [link X] │ other to AP │CP3 + * ──────────────────────────────┴───────────────┴────────────────────────────────────────────── + * |------ main PHY ------| |------------------- aux PHY --------------------- + * .\_ _/ + * . \_ _/ + * . \_ _/ + * . \_ _/ + * [link Y] . CP1 \/ CP2 + * .┌───────────────┐ + * .│ other to AP │ + * ─────────────────────────┴───────────────┴──────────────────────────────────────────────────── + * |------------ aux PHY ----------|---------------------- main PHY ---------------------------- + * + */ + +void +EmlsrCcaBusyTest::CheckPoint1() +{ + // first checkpoint is after that the preamble of the first PPDU has been received + auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId); + + // 1. Main PHY is switching + NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(), true, "Main PHY is not switching"); + + auto auxPhy = m_staMacs[0]->GetWifiPhy(m_nextMainPhyLinkId); + NS_TEST_EXPECT_MSG_NE(mainPhy, auxPhy, "Main PHY is operating on an unexpected link"); + + // 2. Aux PHY is receiving the PHY header + NS_TEST_EXPECT_MSG_EQ(auxPhy->IsReceivingPhyHeader(), + true, + "Aux PHY is not receiving a PHY header"); + + // 3. Main PHY dropped the preamble because it is switching + NS_TEST_EXPECT_MSG_EQ(mainPhy->IsReceivingPhyHeader(), + false, + "Main PHY is receiving a PHY header"); + + // 4. Channel access manager on destination link (Y) has been notified of CCA busy, but not + // until the end of transmission (main PHY dropped the preamble and notified CCA busy until + // end of transmission but the channel access manager on link Y does not yet have a listener + // attached to the main PHY; aux PHY notified CCA busy until the end of the PHY header field + // being received) + const auto caManager = m_staMacs[0]->GetChannelAccessManager(m_nextMainPhyLinkId); + const auto endTxTime = m_staMacs[1]->GetChannelAccessManager(m_nextMainPhyLinkId)->m_lastTxEnd; + NS_TEST_ASSERT_MSG_EQ(caManager->m_lastBusyEnd.contains(WIFI_CHANLIST_PRIMARY), + true, + "No CCA information for primary20 channel"); + NS_TEST_EXPECT_MSG_GT_OR_EQ( + caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY], + Simulator::Now(), + "ChannelAccessManager on destination link not notified of CCA busy"); + NS_TEST_EXPECT_MSG_LT( + caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY], + endTxTime, + "ChannelAccessManager on destination link notified of CCA busy until end of transmission"); + + // second checkpoint is after that the main PHY completed the link switch + Simulator::Schedule(mainPhy->GetDelayUntilIdle() + TimeStep(1), + &EmlsrCcaBusyTest::CheckPoint2, + this); +} + +void +EmlsrCcaBusyTest::CheckPoint2() +{ + // second checkpoint is after that the main PHY completed the link switch. The channel access + // manager on destination link (Y) is expected to be notified by the main PHY that medium is + // busy until the end of the ongoing transmission + const auto caManager = m_staMacs[0]->GetChannelAccessManager(m_nextMainPhyLinkId); + const auto endTxTime = m_staMacs[1]->GetChannelAccessManager(m_nextMainPhyLinkId)->m_lastTxEnd; + NS_TEST_ASSERT_MSG_EQ(caManager->m_lastBusyEnd.contains(WIFI_CHANLIST_PRIMARY), + true, + "No CCA information for primary20 channel"); + NS_TEST_EXPECT_MSG_GT_OR_EQ( + caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY], + Simulator::Now(), + "ChannelAccessManager on destination link not notified of CCA busy"); + NS_TEST_EXPECT_MSG_GT_OR_EQ(caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY], + endTxTime, + "ChannelAccessManager on destination link not notified of CCA busy " + "until end of transmission"); + + // third checkpoint is after that the aux PHY completed the link switch + Simulator::Schedule(m_channelSwitchDelay, &EmlsrCcaBusyTest::CheckPoint3, this); +} + +void +EmlsrCcaBusyTest::CheckPoint3() +{ + // third checkpoint is after that the aux PHY completed the link switch. The channel access + // manager on source link (X) is expected to be notified by the aux PHY that medium is + // busy until the end of the ongoing transmission (even if the aux PHY was not listening to + // link X when transmission started, its interface on link X recorded the transmission) + const auto caManager = m_staMacs[0]->GetChannelAccessManager(m_currMainPhyLinkId); + const auto endTxTime = m_staMacs[1]->GetChannelAccessManager(m_currMainPhyLinkId)->m_lastTxEnd; + NS_TEST_ASSERT_MSG_EQ(caManager->m_lastBusyEnd.contains(WIFI_CHANLIST_PRIMARY), + true, + "No CCA information for primary20 channel"); + NS_TEST_EXPECT_MSG_GT_OR_EQ(caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY], + Simulator::Now(), + "ChannelAccessManager on source link not notified of CCA busy"); + NS_TEST_EXPECT_MSG_GT_OR_EQ(caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY], + endTxTime, + "ChannelAccessManager on source link not notified of CCA busy " + "until end of transmission"); +} + WifiEmlsrTestSuite::WifiEmlsrTestSuite() : TestSuite("wifi-emlsr", Type::UNIT) { @@ -4107,6 +4322,9 @@ WifiEmlsrTestSuite::WifiEmlsrTestSuite() } } } + + AddTestCase(new EmlsrCcaBusyTest(20), TestCase::Duration::QUICK); + AddTestCase(new EmlsrCcaBusyTest(80), TestCase::Duration::QUICK); } static WifiEmlsrTestSuite g_wifiEmlsrTestSuite; ///< the test suite diff --git a/src/wifi/test/wifi-emlsr-test.h b/src/wifi/test/wifi-emlsr-test.h index 12ba6b4f4..d0fe5bf0a 100644 --- a/src/wifi/test/wifi-emlsr-test.h +++ b/src/wifi/test/wifi-emlsr-test.h @@ -781,4 +781,70 @@ class WifiEmlsrTestSuite : public TestSuite WifiEmlsrTestSuite(); }; +/** + * \ingroup wifi-test + * \ingroup tests + * + * \brief Test CCA busy notifications on EMLSR clients. + * + * SwitchAuxPhy is set to true, so that the aux PHY starts switching when the main PHY switch is + * completed. + * + * - Main PHY switches to a link on which an aux PHY is operating. Right after the start of the + * channel switch, the AP transmits a frame to another device on the aux PHY link. Verify that, + * once the main PHY is operating on the new link, the channel access manager on that link is + * notified of CCA busy until the end of the transmission + * - When the main PHY switch is completed, the aux PHY switches to a link on which no PHY is + * operating. Before the aux PHY starts switching, the AP starts transmitting a frame to another + * device on the link on which no PHY is operating. Verify that, once the aux PHY is operating + * on the new link, the channel access manager on that link is notified of CCA busy until the + * end of the transmission + */ +class EmlsrCcaBusyTest : public EmlsrOperationsTestBase +{ + public: + /** + * Constructor + * + * \param auxPhyMaxChWidth max channel width (MHz) supported by aux PHYs + */ + EmlsrCcaBusyTest(uint16_t auxPhyMaxChWidth); + + ~EmlsrCcaBusyTest() override = default; + + protected: + void DoSetup() override; + void DoRun() override; + + private: + void StartTraffic() override; + + /** + * Make the other MLD transmit a packet to the AP on the given link. + * + * \param linkId the ID of the given link + */ + void TransmitPacketToAp(uint8_t linkId); + + /** + * Perform checks after that the preamble of the first PPDU has been received. + */ + void CheckPoint1(); + + /** + * Perform checks after that the main PHY completed the link switch. + */ + void CheckPoint2(); + + /** + * Perform checks after that the aux PHY completed the link switch. + */ + void CheckPoint3(); + + uint16_t m_auxPhyMaxChWidth; //!< max channel width (MHz) supported by aux PHYs + Time m_channelSwitchDelay; //!< the PHY channel switch delay + uint8_t m_currMainPhyLinkId; //!< the ID of the link the main PHY switches from + uint8_t m_nextMainPhyLinkId; //!< the ID of the link the main PHY switches to +}; + #endif /* WIFI_EMLSR_TEST_H */