diff --git a/src/wifi/test/wifi-emlsr-test.cc b/src/wifi/test/wifi-emlsr-test.cc index a5ed373d4..66dd72a96 100644 --- a/src/wifi/test/wifi-emlsr-test.cc +++ b/src/wifi/test/wifi-emlsr-test.cc @@ -255,6 +255,48 @@ EmlsrOperationsTestBase::CheckAuxPhysSleepMode(Ptr staMac, bool slee } } +void +EmlsrOperationsTestBase::MainPhySwitchInfoCallback(std::size_t index, + const EmlsrMainPhySwitchTrace& info) +{ + m_traceInfo[index] = info.Clone(); +} + +void +EmlsrOperationsTestBase::CheckMainPhyTraceInfo(std::size_t index, + std::string_view reason, + const std::optional& fromLinkId, + uint8_t toLinkId, + bool checkFromLinkId, + bool checkToLinkId) +{ + const auto traceInfoIt = m_traceInfo.find(index); + NS_TEST_ASSERT_MSG_EQ((traceInfoIt != m_traceInfo.cend()), true, "Expected stored trace info"); + const auto& traceInfo = traceInfoIt->second; + + NS_TEST_EXPECT_MSG_EQ(traceInfo->GetName(), reason, "Unexpected reason"); + + if (checkFromLinkId) + { + NS_TEST_ASSERT_MSG_EQ(traceInfo->fromLinkId.has_value(), + fromLinkId.has_value(), + "Unexpected stored from_link ID"); + if (fromLinkId.has_value()) + { + NS_TEST_EXPECT_MSG_EQ(+traceInfo->fromLinkId.value(), + +fromLinkId.value(), + "Unexpected from_link ID"); + } + } + + if (checkToLinkId) + { + NS_TEST_EXPECT_MSG_EQ(+traceInfo->toLinkId, +toLinkId, "Unexpected to_link ID"); + } + + m_traceInfo.erase(traceInfoIt); +} + void EmlsrOperationsTestBase::DoSetup() { @@ -327,12 +369,14 @@ EmlsrOperationsTestBase::DoSetup() { auto device = DynamicCast(staDevices.Get(i)); auto staMac = DynamicCast(device->GetMac()); + auto emlsrManager = staMac->GetEmlsrManager(); NS_ASSERT_MSG(i < m_paddingDelay.size(), "Not enough padding delay values provided"); - staMac->GetEmlsrManager()->SetAttribute("EmlsrPaddingDelay", - TimeValue(m_paddingDelay.at(i))); + emlsrManager->SetAttribute("EmlsrPaddingDelay", TimeValue(m_paddingDelay.at(i))); NS_ASSERT_MSG(i < m_transitionDelay.size(), "Not enough transition delay values provided"); - staMac->GetEmlsrManager()->SetAttribute("EmlsrTransitionDelay", - TimeValue(m_transitionDelay.at(i))); + emlsrManager->SetAttribute("EmlsrTransitionDelay", TimeValue(m_transitionDelay.at(i))); + emlsrManager->TraceConnectWithoutContext( + "MainPhySwitch", + MakeCallback(&EmlsrOperationsTestBase::MainPhySwitchInfoCallback, this).Bind(i)); } if (m_nNonEmlsrStations > 0) @@ -2066,8 +2110,8 @@ EmlsrDlTxopTest::CheckInitialControlFrame(Ptr mpdu, << maxPaddingDelay.As(Time::US)); } - // check that the EMLSR clients have blocked transmissions on other links and have put aux PHYs - // to sleep after receiving this ICF + // check that the EMLSR clients have blocked transmissions on other links, switched their main + // PHY (if needed) and have put aux PHYs to sleep after receiving this ICF for (const auto& userInfo : trigger) { for (std::size_t i = 0; i < m_nEmlsrStations; i++) @@ -2077,6 +2121,8 @@ EmlsrDlTxopTest::CheckInitialControlFrame(Ptr mpdu, continue; } + const auto mainPhyLinkId = m_staMacs[i]->GetLinkForPhy(m_mainPhyId); + Simulator::Schedule(txDuration + NanoSeconds(5), [=, this]() { for (uint8_t id = 0; id < m_staMacs[i]->GetNLinks(); id++) { @@ -2090,6 +2136,11 @@ EmlsrDlTxopTest::CheckInitialControlFrame(Ptr mpdu, " after receiving ICF"); } + if (mainPhyLinkId != linkId) + { + CheckMainPhyTraceInfo(i, "DlTxopIcfReceivedByAuxPhy", mainPhyLinkId, linkId); + } + CheckAuxPhysSleepMode(m_staMacs[i], true); }); @@ -2945,6 +2996,28 @@ EmlsrUlTxopTest::CheckBlockAck(const WifiConstPsduMap& psduMap, this, m_staMacs[0], false); + + // if the TXOP has been carried out on a link other than the preferred link, the main PHY + // switches back to the preferred link when the TXOP ends + if (m_staMacs[0]->GetLinkForPhy(m_mainPhyId) != linkId) + { + Simulator::Schedule(txDuration + TimeStep(1), [=, this]() { + // check the traced remaining time before calling CheckMainPhyTraceInfo + if (const auto traceInfoIt = m_traceInfo.find(0); + traceInfoIt != m_traceInfo.cend() && + traceInfoIt->second->GetName() == "TxopEnded") + { + const auto& traceInfo = + static_cast(*traceInfoIt->second); + NS_TEST_EXPECT_MSG_EQ( + traceInfo.remTime, + Time{0}, + "Expected null remaining time because TXOP ended regularly"); + } + + CheckMainPhyTraceInfo(0, "TxopEnded", linkId, m_mainPhyId); + }); + } } switch (m_countBlockAck) @@ -3136,6 +3209,13 @@ EmlsrUlTxopTest::CheckBlockAck(const WifiConstPsduMap& psduMap, false, "Main PHY should not be operating on a link because it " "should be switching to an auxiliary link"); + // check that the appropriate trace info was received + CheckMainPhyTraceInfo(0, + "UlTxopAuxPhyNotTxCapable", + std::nullopt, + 0, + false, + false); // events to be scheduled when main PHY finishes switching to auxiliary link Simulator::Schedule(mainPhy->GetDelayUntilIdle(), [=, this]() { @@ -3171,7 +3251,6 @@ EmlsrUlTxopTest::CheckBlockAck(const WifiConstPsduMap& psduMap, // generate data packets for another UL data frame NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n"); m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 2, 1000)); - break; } } @@ -3228,43 +3307,62 @@ EmlsrUlTxopTest::CheckCtsFrames(Ptr mpdu, auto txDuration = WifiPhy::CalculateTxDuration(mpdu->GetSize(), txVector, m_apMac->GetWifiPhy(linkId)->GetPhyBand()); + const auto doCorruptCts = m_corruptCts.has_value() && *m_corruptCts; - if (linkId != m_mainPhyId && linkId != m_nonEmlsrLink && + if (linkId != m_staMacs[0]->GetLinkForPhy(m_mainPhyId) && linkId != m_nonEmlsrLink && mpdu->GetHeader().GetAddr1() == m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress()) { // this is a CTS sent to an aux PHY starting an UL TXOP. Given that aux PHYs do not // switch channel, they are put in sleep mode when the main PHY starts operating on their // link, which coincides with the end of CTS plus two propagation delays - auto auxPhy = m_staMacs[0]->GetWifiPhy(linkId); + const auto auxPhy = m_staMacs[0]->GetWifiPhy(linkId); + const auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId); Simulator::Schedule(txDuration, [=, this]() { + // when CTS ends, the main PHY is still switching and the aux PHY is not yet sleeping + NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(), + true, + "Expecting the main PHY to be switching link"); NS_TEST_EXPECT_MSG_EQ(auxPhy->IsStateSleep(), false, "Aux PHY on link " << +linkId << " already in sleep mode"); + // when CTS is sent, the main PHY may have already started switching, thus we may not + // know which link the main PHY is moving from + CheckMainPhyTraceInfo(0, "UlTxopRtsSentByAuxPhy", std::nullopt, linkId, false); }); - // if the CTS is corrupted, the TXOP ends and the aux PHY is not put to sleep - auto isStateSleep = !(m_corruptCts.has_value() && *m_corruptCts); + Simulator::Schedule( + txDuration + MicroSeconds(2 * MAX_PROPAGATION_DELAY_USEC) + TimeStep(1), + [=, this]() { + // aux PHYs are put to sleep if and only if CTS is not corrupted + // (causing the end of the TXOP) + CheckAuxPhysSleepMode(m_staMacs[0], !doCorruptCts); + // if CTS is corrupted, TXOP ends and the main PHY switches back + // to the preferred link + if (doCorruptCts) + { + // check the traced remaining time before calling CheckMainPhyTraceInfo + if (const auto traceInfoIt = m_traceInfo.find(0); + traceInfoIt != m_traceInfo.cend() && + traceInfoIt->second->GetName() == "TxopEnded") + { + const auto& traceInfo = + static_cast(*traceInfoIt->second); + NS_TEST_EXPECT_MSG_GT(traceInfo.remTime, + Time{0}, + "Expected non-zero remaining time because main PHY " + "was switching when TXOP ended"); + } - Simulator::Schedule(txDuration + MicroSeconds(2 * MAX_PROPAGATION_DELAY_USEC) + TimeStep(1), - &EmlsrUlTxopTest::CheckAuxPhysSleepMode, - this, - m_staMacs[0], - isStateSleep); + CheckMainPhyTraceInfo(0, "TxopEnded", linkId, m_mainPhyId); + } + }); } - if (m_corruptCts.has_value() && *m_corruptCts) + if (doCorruptCts) { // corrupt reception at EMLSR client NS_LOG_INFO("CORRUPTED"); m_errorModel->SetList({mpdu->GetPacket()->GetUid()}); m_corruptCts = false; - - // main PHY is about to complete channel switch when CTS ends - Simulator::Schedule(txDuration, [=, this]() { - const auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId); - NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(), - true, - "Expecting the main PHY to be switching link"); - }); } } diff --git a/src/wifi/test/wifi-emlsr-test.h b/src/wifi/test/wifi-emlsr-test.h index 855c28270..84d849fa0 100644 --- a/src/wifi/test/wifi-emlsr-test.h +++ b/src/wifi/test/wifi-emlsr-test.h @@ -23,6 +23,12 @@ using namespace ns3; +// forward declaration +namespace ns3 +{ +struct EmlsrMainPhySwitchTrace; +} + /** * @ingroup wifi-test * @ingroup tests @@ -146,6 +152,31 @@ class EmlsrOperationsTestBase : public TestCase */ void CheckAuxPhysSleepMode(Ptr staMac, bool sleep); + /** + * Callback connected to the EMLSR Manager MainPhySwitch trace source. + * + * @param index the index of the EMLSR client whose main PHY switch event is logged + * @param info the information associated with the main PHY switch event + */ + void MainPhySwitchInfoCallback(std::size_t index, const EmlsrMainPhySwitchTrace& info); + + /** + * Check information provided by the EMLSR Manager MainPhySwitch trace. + * + * @param index the ID of the EMLSR client this check refers to + * @param reason the reason for main PHY to switch + * @param fromLinkId the ID of the link the main PHY is moving from (if any) + * @param toLinkId the ID of the link the main PHY is moving to + * @param checkFromLinkId whether to check the given fromLinkId value + * @param checkToLinkId whether to check the given toLinkId value + */ + void CheckMainPhyTraceInfo(std::size_t index, + std::string_view reason, + const std::optional& fromLinkId, + uint8_t toLinkId, + bool checkFromLinkId = true, + bool checkToLinkId = true); + void DoSetup() override; /// Information about transmitted frames @@ -181,6 +212,8 @@ class EmlsrOperationsTestBase : public TestCase std::vector m_ulSockets; ///< packet socket address for UL traffic uint16_t m_lastAid{0}; ///< AID of last associated station Time m_duration{0}; ///< simulation duration + std::map> + m_traceInfo; ///< EMLSR client ID-indexed map of trace info from last main PHY switch private: /**