diff --git a/src/wifi/model/eht/advanced-emlsr-manager.h b/src/wifi/model/eht/advanced-emlsr-manager.h index 403250097..c26701c76 100644 --- a/src/wifi/model/eht/advanced-emlsr-manager.h +++ b/src/wifi/model/eht/advanced-emlsr-manager.h @@ -15,6 +15,8 @@ #include +class EmlsrSwitchMainPhyBackTest; + namespace ns3 { @@ -27,6 +29,9 @@ class WifiPhyListener; */ class AdvancedEmlsrManager : public DefaultEmlsrManager { + /// Allow test cases to access private members + friend class ::EmlsrSwitchMainPhyBackTest; + public: /** * @brief Get the type ID. diff --git a/src/wifi/model/txop.h b/src/wifi/model/txop.h index 186f634a3..fde9d6f93 100644 --- a/src/wifi/model/txop.h +++ b/src/wifi/model/txop.h @@ -422,6 +422,14 @@ class Txop : public Object */ void StartBackoffNow(uint32_t nSlots, uint8_t linkId); + /** + * Return the current number of backoff slots on the given link. + * + * @param linkId the ID of the given link + * @return the current number of backoff slots + */ + uint32_t GetBackoffSlots(uint8_t linkId) const; + /** * Check if the Txop has frames to transmit over the given link * @param linkId the ID of the given link. @@ -494,13 +502,6 @@ class Txop : public Object */ void RequestAccess(uint8_t linkId); - /** - * Return the current number of backoff slots on the given link. - * - * @param linkId the ID of the given link - * @return the current number of backoff slots - */ - uint32_t GetBackoffSlots(uint8_t linkId) const; /** * Return the time when the backoff procedure started on the given link. * diff --git a/src/wifi/test/wifi-emlsr-test.cc b/src/wifi/test/wifi-emlsr-test.cc index 902071b04..f32be0525 100644 --- a/src/wifi/test/wifi-emlsr-test.cc +++ b/src/wifi/test/wifi-emlsr-test.cc @@ -5712,6 +5712,585 @@ EmlsrIcfSentDuringMainPhySwitchTest::RunOne() }); } +EmlsrSwitchMainPhyBackTest::EmlsrSwitchMainPhyBackTest() + : EmlsrOperationsTestBase("Check handling of the switch main PHY back timer") +{ + m_mainPhyId = 2; + m_linksToEnableEmlsrOn = {0, 1, 2}; + m_nEmlsrStations = 1; + m_nNonEmlsrStations = 0; + + // channel switch delay will be also set to 64 us + m_paddingDelay = {MicroSeconds(64)}; + m_transitionDelay = {MicroSeconds(64)}; + m_establishBaDl = {0}; + m_establishBaUl = {0}; + m_duration = Seconds(0.5); +} + +void +EmlsrSwitchMainPhyBackTest::DoSetup() +{ + Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(MicroSeconds(64))); + Config::SetDefault("ns3::WifiPhy::NotifyMacHdrRxEnd", BooleanValue(true)); + Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(false)); + Config::SetDefault("ns3::EmlsrManager::AuxPhyTxCapable", BooleanValue(false)); + // Use only link 1 for DL and UL traffic + std::string mapping = "0 " + std::to_string(m_linkIdForTid0); + Config::SetDefault("ns3::EhtConfiguration::TidToLinkMappingDl", StringValue(mapping)); + Config::SetDefault("ns3::EhtConfiguration::TidToLinkMappingUl", StringValue(mapping)); + Config::SetDefault("ns3::EmlsrManager::AuxPhyMaxModClass", StringValue("HT")); + Config::SetDefault("ns3::AdvancedEmlsrManager::UseAuxPhyCca", BooleanValue(true)); + + EmlsrOperationsTestBase::DoSetup(); + + WifiMacHeader hdr(WIFI_MAC_QOSDATA); + hdr.SetAddr1(Mac48Address::GetBroadcast()); + hdr.SetAddr2(m_apMac->GetFrameExchangeManager(m_linkIdForTid0)->GetAddress()); + hdr.SetAddr3(m_apMac->GetAddress()); + hdr.SetDsFrom(); + hdr.SetDsNotTo(); + hdr.SetQosTid(0); + + m_bcastFrame = Create(Create(1000), hdr); +} + +void +EmlsrSwitchMainPhyBackTest::Transmit(Ptr mac, + uint8_t phyId, + WifiConstPsduMap psduMap, + WifiTxVector txVector, + double txPowerW) +{ + EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW); + + const auto psdu = psduMap.cbegin()->second; + const auto& hdr = psdu->GetHeader(0); + + // nothing to do before setup is completed + if (!m_setupDone) + { + return; + } + + auto linkId = mac->GetLinkForPhy(phyId); + NS_TEST_ASSERT_MSG_EQ(linkId.has_value(), + true, + "PHY " << +phyId << " is not operating on any link"); + + if (!m_events.empty()) + { + // check that the expected frame is being transmitted + NS_TEST_EXPECT_MSG_EQ(m_events.front().hdrType, + hdr.GetType(), + "Unexpected MAC header type for frame #" << ++m_processedEvents); + // perform actions/checks, if any + if (m_events.front().func) + { + m_events.front().func(psdu, txVector, linkId.value()); + } + + m_events.pop_front(); + } +} + +void +EmlsrSwitchMainPhyBackTest::StartTraffic() +{ + m_setupDone = true; + RunOne(); +} + +void +EmlsrSwitchMainPhyBackTest::DoRun() +{ + Simulator::Stop(m_duration); + Simulator::Run(); + + NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place"); + + Simulator::Destroy(); +} + +void +EmlsrSwitchMainPhyBackTest::MainPhySwitchInfoCallback(std::size_t index, + const EmlsrMainPhySwitchTrace& info) +{ + EmlsrOperationsTestBase::MainPhySwitchInfoCallback(index, info); + + if (!m_setupDone) + { + return; + } + + if (!m_dlPktDone && info.GetName() == "UlTxopAuxPhyNotTxCapable") + { + NS_LOG_INFO("Main PHY starts switching\n"); + const auto delay = + static_cast(m_testIndex) <= RXSTART_WHILE_SWITCH_INTERRUPT + ? Time{0} + : MicroSeconds(30); // greater than duration of PHY header of non-HT PPDU + Simulator::Schedule(delay, + [=, this]() { m_apMac->GetQosTxop(AC_BE)->Queue(m_bcastFrame); }); + return; + } + + if (m_expectedMainPhySwitchBackTime == Simulator::Now() && + info.GetName() == "TxopNotGainedOnAuxPhyLink") + { + NS_LOG_INFO("Main PHY switches back\n"); + + const auto& traceInfo = static_cast(info); + + switch (static_cast(m_testIndex)) + { + case RXSTART_WHILE_SWITCH_NO_INTERRUPT: + case RXSTART_WHILE_SWITCH_INTERRUPT: + NS_TEST_EXPECT_MSG_EQ((traceInfo.elapsed.IsStrictlyPositive() && + traceInfo.elapsed < m_switchMainPhyBackDelay), + true, + "Unexpected value for the elapsed field"); + NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.has_value(), + true, + "earlySwitchReason should hold a value"); + NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.value(), + WifiExpectedAccessReason::RX_END, + "Unexpected earlySwitchReason value"); + NS_TEST_EXPECT_MSG_EQ(traceInfo.isSwitching, true, "Unexpected value for isSwitching"); + break; + + case RXSTART_AFTER_SWITCH_HT_PPDU: + NS_TEST_EXPECT_MSG_EQ((traceInfo.elapsed.IsStrictlyPositive() && + traceInfo.elapsed < m_switchMainPhyBackDelay), + true, + "Unexpected value for the elapsed field"); + NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.has_value(), + true, + "earlySwitchReason should hold a value"); + NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.value(), + WifiExpectedAccessReason::BUSY_END, + "Unexpected earlySwitchReason value"); + NS_TEST_EXPECT_MSG_EQ(traceInfo.isSwitching, false, "Unexpected value for isSwitching"); + break; + + case NON_HT_PPDU_USE_MAC_HDR: + NS_TEST_EXPECT_MSG_EQ((traceInfo.elapsed.IsStrictlyPositive() && + traceInfo.elapsed < m_switchMainPhyBackDelay), + true, + "Unexpected value for the elapsed field"); + NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.has_value(), + true, + "earlySwitchReason should hold a value"); + NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.value(), + WifiExpectedAccessReason::RX_END, + "Unexpected earlySwitchReason value"); + NS_TEST_EXPECT_MSG_EQ(traceInfo.isSwitching, false, "Unexpected value for isSwitching"); + break; + + case LONG_SWITCH_BACK_DELAY_DONT_USE_MAC_HDR: + NS_TEST_EXPECT_MSG_EQ((traceInfo.elapsed.IsStrictlyPositive() && + traceInfo.elapsed >= m_switchMainPhyBackDelay), + true, + "Unexpected value for the elapsed field"); + NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.has_value(), + true, + "earlySwitchReason should hold a value"); + NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.value(), + WifiExpectedAccessReason::BACKOFF_END, + "Unexpected earlySwitchReason value"); + NS_TEST_EXPECT_MSG_EQ(traceInfo.isSwitching, false, "Unexpected value for isSwitching"); + break; + + case LONG_SWITCH_BACK_DELAY_USE_MAC_HDR: + NS_TEST_EXPECT_MSG_EQ((traceInfo.elapsed.IsStrictlyPositive() && + traceInfo.elapsed < m_switchMainPhyBackDelay), + true, + "Unexpected value for the elapsed field"); + NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.has_value(), + true, + "earlySwitchReason should hold a value"); + NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.value(), + WifiExpectedAccessReason::BACKOFF_END, + "Unexpected earlySwitchReason value"); + NS_TEST_EXPECT_MSG_EQ(traceInfo.isSwitching, false, "Unexpected value for isSwitching"); + break; + + default: + NS_TEST_ASSERT_MSG_EQ(true, false, "Unexpected scenario: " << +m_testIndex); + } + m_dlPktDone = true; + } + + if (m_expectedMainPhySwitchBackTime == Simulator::Now() && info.GetName() == "TxopEnded") + { + NS_LOG_INFO("Main PHY switches back\n"); + + NS_TEST_EXPECT_MSG_EQ(+m_testIndex, + +static_cast(NON_HT_PPDU_DONT_USE_MAC_HDR), + "Unexpected TxopEnded reason for switching main PHY back"); + + m_dlPktDone = true; + } +} + +void +EmlsrSwitchMainPhyBackTest::RunOne() +{ + const auto testIndex = static_cast(m_testIndex); + + const auto bcastTxVector = + m_apMac->GetWifiRemoteStationManager(m_linkIdForTid0) + ->GetGroupcastTxVector(m_bcastFrame->GetHeader(), + m_apMac->GetWifiPhy(m_linkIdForTid0)->GetChannelWidth()); + const auto bcastTxDuration = + WifiPhy::CalculateTxDuration(m_bcastFrame->GetSize(), + bcastTxVector, + m_apMac->GetWifiPhy(m_linkIdForTid0)->GetPhyBand()); + + const auto mode = (testIndex >= NON_HT_PPDU_DONT_USE_MAC_HDR ? OfdmPhy::GetOfdmRate6Mbps() + : HtPhy::GetHtMcs0()); + + m_switchMainPhyBackDelay = bcastTxDuration; + if (testIndex != LONG_SWITCH_BACK_DELAY_DONT_USE_MAC_HDR && + testIndex != LONG_SWITCH_BACK_DELAY_USE_MAC_HDR) + { + // make switch main PHY back delay at least two channel switch delays shorter than the + // PPDU TX duration + m_switchMainPhyBackDelay -= MicroSeconds(250); + } + + const auto interruptSwitch = (testIndex == RXSTART_WHILE_SWITCH_INTERRUPT); + const auto useMacHeader = + (testIndex == NON_HT_PPDU_USE_MAC_HDR || testIndex == LONG_SWITCH_BACK_DELAY_USE_MAC_HDR); + + m_apMac->GetWifiRemoteStationManager(m_linkIdForTid0) + ->SetAttribute("NonUnicastMode", WifiModeValue(mode)); + m_staMacs[0]->GetEmlsrManager()->SetAttribute("SwitchMainPhyBackDelay", + TimeValue(m_switchMainPhyBackDelay)); + m_staMacs[0]->GetEmlsrManager()->SetAttribute("InterruptSwitch", BooleanValue(interruptSwitch)); + m_staMacs[0]->GetEmlsrManager()->SetAttribute("UseNotifiedMacHdr", BooleanValue(useMacHeader)); + + NS_LOG_INFO("Starting test #" << +m_testIndex << "\n"); + m_dlPktDone = false; + + // wait some more time to ensure that backoffs count down to zero and then generate a packet + // at the EMLSR client. When notified of the main PHY switch, we decide when the AP MLD has to + // transmit a broadcast frame + Simulator::Schedule(MilliSeconds(5), [=, this]() { + m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 1, 500)); + }); + + auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId); + auto advEmlsrMgr = DynamicCast(m_staMacs[0]->GetEmlsrManager()); + + m_events.emplace_back( + WIFI_MAC_QOSDATA, + [=, this](Ptr psdu, const WifiTxVector& txVector, uint8_t linkId) { + const auto phyHdrDuration = WifiPhy::CalculatePhyPreambleAndHeaderDuration(txVector); + const auto txDuration = + WifiPhy::CalculateTxDuration(psdu, + txVector, + m_apMac->GetWifiPhy(linkId)->GetPhyBand()); + NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr1(), + Mac48Address::GetBroadcast(), + "Expected a broadcast frame"); + NS_TEST_EXPECT_MSG_EQ(+linkId, + +m_linkIdForTid0, + "Broadcast frame transmitted on wrong link"); + NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(), + m_apMac->GetFrameExchangeManager(linkId)->GetAddress(), + "Unexpected TA for the broadcast frame"); + NS_TEST_EXPECT_MSG_EQ(txVector.GetMode(), mode, "Unexpected WifiMode"); + + switch (testIndex) + { + case RXSTART_WHILE_SWITCH_NO_INTERRUPT: + // main PHY is switching before the end of PHY header reception and + // the switch main PHY back timer is running + Simulator::Schedule(phyHdrDuration - TimeStep(1), [=, this]() { + NS_TEST_EXPECT_MSG_EQ( + mainPhy->GetState()->GetLastTime({WifiPhyState::SWITCHING}), + Simulator::Now(), + "Main PHY is not switching at the end of PHY header reception"); + NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to, + +m_linkIdForTid0, + "Main PHY is switching to a wrong link"); + NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(), + true, + "Main PHY switch back timer should be running"); + }); + // main PHY is still switching right after the end of PHY header reception, but + // the switch main PHY back timer has been stopped + Simulator::Schedule(phyHdrDuration + TimeStep(2), [=, this]() { + NS_TEST_EXPECT_MSG_EQ( + mainPhy->GetState()->GetLastTime({WifiPhyState::SWITCHING}), + Simulator::Now(), + "Main PHY is not switching at the end of PHY header reception"); + NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to, + +m_linkIdForTid0, + "Main PHY is switching to a wrong link"); + NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(), + false, + "Main PHY switch back timer should have been stopped"); + }); + // main PHY is expected to switch back when the ongoing switch terminates + m_expectedMainPhySwitchBackTime = Simulator::Now() + mainPhy->GetDelayUntilIdle(); + break; + + case RXSTART_WHILE_SWITCH_INTERRUPT: + // main PHY is switching before the end of PHY header reception and + // the switch main PHY back timer is running + Simulator::Schedule(phyHdrDuration - TimeStep(1), [=, this]() { + NS_TEST_EXPECT_MSG_EQ( + mainPhy->GetState()->GetLastTime({WifiPhyState::SWITCHING}), + Simulator::Now(), + "Main PHY is not switching at the end of PHY header reception"); + NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to, + +m_linkIdForTid0, + "Main PHY is switching to a wrong link"); + NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(), + true, + "Main PHY switch back timer should be running"); + }); + // main PHY is switching back right after the end of PHY header reception, but + // the switch main PHY back timer has been stopped + Simulator::Schedule(phyHdrDuration + TimeStep(2), [=, this]() { + NS_TEST_EXPECT_MSG_EQ( + mainPhy->GetState()->GetLastTime({WifiPhyState::SWITCHING}), + Simulator::Now(), + "Main PHY is not switching at the end of PHY header reception"); + NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to, + +m_mainPhyId, + "Main PHY is switching to a wrong link"); + NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(), + false, + "Main PHY switch back timer should have been stopped"); + }); + // main PHY is expected to switch back when the reception of PHY header ends + m_expectedMainPhySwitchBackTime = Simulator::Now() + phyHdrDuration + TimeStep(1); + break; + + case RXSTART_AFTER_SWITCH_HT_PPDU: + // main PHY is switching back at the end of PHY header reception and + // the switch main PHY back timer has been stopped + Simulator::Schedule(phyHdrDuration, [=, this]() { + NS_TEST_EXPECT_MSG_EQ( + mainPhy->GetState()->GetLastTime({WifiPhyState::SWITCHING}), + Simulator::Now(), + "Main PHY is not switching at the end of PHY header reception"); + NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to, + +m_mainPhyId, + "Main PHY is switching to a wrong link"); + NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(), + false, + "Main PHY switch back timer should have been stopped"); + }); + // main PHY is expected to switch back when the reception of PHY header ends + m_expectedMainPhySwitchBackTime = + Simulator::Now() + mainPhy->GetDelayUntilIdle() + TimeStep(1); + break; + + case NON_HT_PPDU_DONT_USE_MAC_HDR: + // when the main PHY completes the channel switch, it is not connected to the aux + // PHY link and the switch main PHY back timer is running + Simulator::Schedule(mainPhy->GetDelayUntilIdle() + TimeStep(1), [=, this]() { + NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_mainPhySwitchInfo.disconnected, + true, + "Main PHY should be waiting to be connected to a link"); + NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to, + +m_linkIdForTid0, + "Main PHY is waiting to be connected to a wrong link"); + NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(), + true, + "Main PHY switch back timer should be running"); + // when PIFS check is performed at the end of the main PHY switch, the medium + // is found busy and a backoff value is generated; make sure that this value is + // at most 2 to ensure the conditions expected by this scenario + if (auto beTxop = m_staMacs[0]->GetQosTxop(AC_BE); + beTxop->GetBackoffSlots(m_linkIdForTid0) > 2) + { + beTxop->StartBackoffNow(2, m_linkIdForTid0); + m_staMacs[0] + ->GetChannelAccessManager(m_linkIdForTid0) + ->NotifyAckTimeoutResetNow(); // force restart access timeout + } + }); + // once the PPDU is received, the main PHY is connected to the aux PHY and the + // switch main PHY back timer is still running + Simulator::Schedule(txDuration + TimeStep(1), [=, this]() { + NS_TEST_EXPECT_MSG_EQ( + mainPhy->GetState()->IsStateSwitching(), + false, + "Main PHY should not be switching at the end of PPDU reception"); + NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_mainPhySwitchInfo.disconnected, + false, + "Main PHY should have been connected to a link"); + NS_TEST_ASSERT_MSG_EQ(m_staMacs[0]->GetLinkForPhy(m_mainPhyId).has_value(), + true, + "Main PHY should have been connected to a link"); + NS_TEST_EXPECT_MSG_EQ(+m_staMacs[0]->GetLinkForPhy(m_mainPhyId).value(), + +m_linkIdForTid0, + "Main PHY is connected to a wrong link"); + NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(), + true, + "Main PHY switch back timer should be running"); + }); + break; + + case NON_HT_PPDU_USE_MAC_HDR: + case LONG_SWITCH_BACK_DELAY_USE_MAC_HDR: + // when the main PHY completes the channel switch, it is not connected to the aux + // PHY link and the switch main PHY back timer is running. The aux PHY is in RX + // state and has MAC header info available + Simulator::Schedule(mainPhy->GetDelayUntilIdle() + TimeStep(1), [=, this]() { + NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_mainPhySwitchInfo.disconnected, + true, + "Main PHY should be waiting to be connected to a link"); + NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to, + +m_linkIdForTid0, + "Main PHY is waiting to be connected to a wrong link"); + NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(), + true, + "Main PHY switch back timer should be running"); + const auto auxPhy = m_staMacs[0]->GetDevice()->GetPhy(m_linkIdForTid0); + NS_TEST_EXPECT_MSG_EQ(auxPhy->IsStateRx(), + true, + "Aux PHY should be in RX state"); + auto remTime = auxPhy->GetTimeToMacHdrEnd(SU_STA_ID); + NS_TEST_ASSERT_MSG_EQ(remTime.has_value(), + true, + "No MAC header info available"); + if (testIndex == LONG_SWITCH_BACK_DELAY_USE_MAC_HDR) + { + // when PIFS check is performed at the end of the main PHY switch, the + // medium is found busy and a backoff value is generated; make sure that + // this value is at least 7 to ensure that the backoff timer is still + // running when the switch main PHY back timer is expected to expire + if (auto beTxop = m_staMacs[0]->GetQosTxop(AC_BE); + beTxop->GetBackoffSlots(m_linkIdForTid0) <= 6) + { + beTxop->StartBackoffNow(7, m_linkIdForTid0); + } + } + // main PHY is expected to switch back when the MAC header is received + m_expectedMainPhySwitchBackTime = Simulator::Now() + remTime.value(); + // once the MAC header is received, the main PHY switches back and the + // switch main PHY back timer is stopped + Simulator::Schedule(remTime.value() + TimeStep(1), [=, this]() { + NS_TEST_EXPECT_MSG_EQ( + mainPhy->GetState()->IsStateSwitching(), + true, + "Main PHY should be switching after receiving the MAC header"); + NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to, + +m_mainPhyId, + "Main PHY should be switching to the preferred link"); + NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(), + false, + "Main PHY switch back timer should not be running"); + }); + }); + break; + + case LONG_SWITCH_BACK_DELAY_DONT_USE_MAC_HDR: + // when the main PHY completes the channel switch, it is not connected to the aux + // PHY link and the switch main PHY back timer is running + Simulator::Schedule(mainPhy->GetDelayUntilIdle() + TimeStep(1), [=, this]() { + NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_mainPhySwitchInfo.disconnected, + true, + "Main PHY should be waiting to be connected to a link"); + NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to, + +m_linkIdForTid0, + "Main PHY is waiting to be connected to a wrong link"); + NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(), + true, + "Main PHY switch back timer should be running"); + // when PIFS check is performed at the end of the main PHY switch, the medium + // is found busy and a backoff value is generated; make sure that this value is + // at least 7 to ensure that the backoff timer is still running when the switch + // main PHY back timer is expected to expire + if (auto beTxop = m_staMacs[0]->GetQosTxop(AC_BE); + beTxop->GetBackoffSlots(m_linkIdForTid0) <= 6) + { + beTxop->StartBackoffNow(7, m_linkIdForTid0); + } + }); + // once the PPDU is received, the switch main PHY back timer is stopped and the + // main PHY switches back to the preferred link + Simulator::Schedule(txDuration + TimeStep(2), [=, this]() { + NS_TEST_EXPECT_MSG_EQ( + mainPhy->GetState()->IsStateSwitching(), + true, + "Main PHY should be switching at the end of PPDU reception"); + NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to, + +m_mainPhyId, + "Main PHY should be switching back to preferred link"); + NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(), + false, + "Main PHY switch back timer should be not running"); + }); + // main PHY is expected to switch back when the reception of PPDU ends + m_expectedMainPhySwitchBackTime = Simulator::Now() + txDuration + TimeStep(1); + break; + + default: + NS_TEST_ASSERT_MSG_EQ(true, false, "Unexpected scenario: " << +m_testIndex); + } + }); + + m_events.emplace_back( + WIFI_MAC_QOSDATA, + [=, this](Ptr psdu, const WifiTxVector& txVector, uint8_t linkId) { + NS_TEST_EXPECT_MSG_EQ(+linkId, + +m_linkIdForTid0, + "Unicast frame transmitted on wrong link"); + NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr1(), + m_apMac->GetFrameExchangeManager(linkId)->GetAddress(), + "Unexpected RA for the unicast frame"); + NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(), + m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress(), + "Unexpected TA for the unicast frame"); + + if (testIndex == NON_HT_PPDU_DONT_USE_MAC_HDR) + { + Simulator::Schedule(TimeStep(1), [=, this]() { + // UL TXOP started, main PHY switch back time was cancelled + NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(), + false, + "Main PHY switch back timer should not be running"); + }); + } + }); + + m_events.emplace_back( + WIFI_MAC_CTL_ACK, + [=, this](Ptr psdu, const WifiTxVector& txVector, uint8_t linkId) { + const auto phyHdrDuration = WifiPhy::CalculatePhyPreambleAndHeaderDuration(txVector); + const auto txDuration = + WifiPhy::CalculateTxDuration(psdu, + txVector, + m_apMac->GetWifiPhy(linkId)->GetPhyBand()); + + if (testIndex == NON_HT_PPDU_DONT_USE_MAC_HDR) + { + // main PHY is expected to switch back when the UL TXOP ends + m_expectedMainPhySwitchBackTime = Simulator::Now() + txDuration; + } + + Simulator::Schedule(MilliSeconds(2), [=, this]() { + // check that trace infos have been received + NS_TEST_EXPECT_MSG_EQ(m_dlPktDone, + true, + "Did not receive the expected trace infos"); + NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place"); + + if (++m_testIndex < static_cast(COUNT)) + { + RunOne(); + } + }); + }); +} + WifiEmlsrTestSuite::WifiEmlsrTestSuite() : TestSuite("wifi-emlsr", Type::UNIT) { @@ -5819,6 +6398,7 @@ WifiEmlsrTestSuite::WifiEmlsrTestSuite() } AddTestCase(new EmlsrIcfSentDuringMainPhySwitchTest(), TestCase::Duration::QUICK); + AddTestCase(new EmlsrSwitchMainPhyBackTest(), TestCase::Duration::QUICK); AddTestCase(new EmlsrUlOfdmaTest(false), TestCase::Duration::QUICK); AddTestCase(new EmlsrUlOfdmaTest(true), TestCase::Duration::QUICK); diff --git a/src/wifi/test/wifi-emlsr-test.h b/src/wifi/test/wifi-emlsr-test.h index 2109cc301..d2b8a6081 100644 --- a/src/wifi/test/wifi-emlsr-test.h +++ b/src/wifi/test/wifi-emlsr-test.h @@ -169,7 +169,7 @@ class EmlsrOperationsTestBase : public TestCase * @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); + virtual void MainPhySwitchInfoCallback(std::size_t index, const EmlsrMainPhySwitchTrace& info); /** * Check information provided by the EMLSR Manager MainPhySwitch trace. @@ -1240,4 +1240,137 @@ class EmlsrIcfSentDuringMainPhySwitchTest : public EmlsrOperationsTestBase const uint8_t m_linkIdForTid3{2}; //!< ID of the link on which TID 3 is mapped }; +/** + * @ingroup wifi-test + * @ingroup tests + * + * @brief Switch main PHY back timer test + * + * An AP MLD and an EMLSR client, both having 3 links, are considered in this test. Aux PHYs are + * not TX capable, do not switch links and support up to the HT modulation class; the preferred link + * is link 2. In order to control link switches, a TID-to-Link mapping is configured so that TID 0 + * is mapped onto link 1 for both DL and UL. In this test, the main PHY switches to link 1 to start + * an UL TXOP but, while the main PHY is switching or shortly after the channel switch ends, the AP + * MLD transmits a QoS Data broadcast frame on link 1 using a modulation supported by the aux PHYs. + * Different situations are tested and it is verified that the main PHY switches back to the + * preferred link as expected. Scenarios: + * + * - RXSTART_WHILE_SWITCH_NO_INTERRUPT: the AP MLD transmits an HT PPDU while the main PHY is + * switching; at the end of the PHY header reception (while the main PHY is still switching), the + * MAC of the EMLSR client receives the RX start notification, which indicates that the modulation + * is HT (hence the PPDU does not carry an ICF) and the PPDU duration exceeds the switch main PHY + * back delay. The EMLSR client decides to switch the main PHY back to the preferred link (with + * reason RX_END), but the actual main PHY switch is postponed until the ongoing channel switch + * terminates. + * - RXSTART_WHILE_SWITCH_INTERRUPT: same as previous scenario, except that the main PHY switch can + * be interrupted, hence the main PHY switches back to the preferred link as soon as the reception + * of the PHY header ends. + * - RXSTART_AFTER_SWITCH_HT_PPDU: the AP MLD transmits an HT PPDU some time after the main PHY + * starts switching to link 1; the delay is computed so that the RX START notification is sent + * after that the main PHY has completed the channel switch. When the main PHY completes the + * switch to link 1, it is determined that the PPDU being received (using HT modulation) cannot + * be an ICF, hence the main PHY is connected to link 1. Connecting the main PHY to link 1 + * triggers a CCA busy notification until the end of the PPDU (we assume this information is + * available from the PHY header decoded by the aux PHY), thus the main PHY switches back to the + * preferred link (with reason BUSY_END). + * - NON_HT_PPDU_DONT_USE_MAC_HDR: the AP MLD transmits a non-HT PPDU on link 1 (it does not really + * matter if the RX START notification is sent before or after the end of main PHY switch). When + * the main PHY completes the switch to link 1, it is detected that the aux PHY on link 1 is + * receiving a PPDU which may be an ICF (the modulation is non-HT), hence the main PHY is not + * connected to link 1 until the end of the PPDU reception (MAC header info is not used). At that + * time, it is detected that the PPDU does not contain an ICF, but it is determined that channel + * access can be gained before the end of the switch main PHY back timer, hence the main PHY stays + * on link 1 and transmits its unicast data frame. The start of the UL TXOP cancels the main PHY + * switch back timer and the main PHY switches back to the preferred link at the end of the TXOP. + * - NON_HT_PPDU_USE_MAC_HDR: same as previous scenario, except that the MAC header info can be + * used. After completing the channel switch, the main PHY is not connected to link 1 because the + * non-HT PPDU being received may be an ICF. When the MAC header info is notified, it is detected + * that the PPDU does not contain an ICF, channel access would not be gained before the end of the + * switch main PHY back timer and therefore the main PHY switches back to the preferred link (with + * reason RX_END). + * - LONG_SWITCH_BACK_DELAY_DONT_USE_MAC_HDR: same as the NON_HT_PPDU_DONT_USE_MAC_HDR scenario, + * except that the switch main PHY back delay is longer and exceeds the PPDU duration, but it is + * does not exceed the PPDU duration plus AIFS and the backoff slots. Therefore, at the end of the + * PPDU reception, it is determined that the backoff counter will not reach zero before the end of + * the switch main PHY back timer plus a channel switch delay and the main PHY switches back to + * the preferred link (with reason BACKOFF_END). + * - LONG_SWITCH_BACK_DELAY_USE_MAC_HDR: same as the NON_HT_PPDU_USE_MAC_HDR scenario, + * except that the switch main PHY back delay is longer and exceeds the PPDU duration, but it + * does not exceed the PPDU duration plus AIFS and the backoff slots. Therefore, at the end of the + * MAC header reception, it is determined that the backoff counter will not reach zero before the + * end of the switch main PHY back timer plus a channel switch delay and the main PHY switches + * back to the preferred link (with reason BACKOFF_END). + * + * In all the cases, it is verified that, after the reception of the broadcast data frame, the EMLSR + * client transmits the data frame and receives the acknowledgment. + */ +class EmlsrSwitchMainPhyBackTest : public EmlsrOperationsTestBase +{ + public: + /// Constructor. + EmlsrSwitchMainPhyBackTest(); + + /** + * Enumeration indicating the tested scenario + */ + enum TestScenario : uint8_t + { + RXSTART_WHILE_SWITCH_NO_INTERRUPT = 0, + RXSTART_WHILE_SWITCH_INTERRUPT, + RXSTART_AFTER_SWITCH_HT_PPDU, + NON_HT_PPDU_DONT_USE_MAC_HDR, + NON_HT_PPDU_USE_MAC_HDR, + LONG_SWITCH_BACK_DELAY_DONT_USE_MAC_HDR, + LONG_SWITCH_BACK_DELAY_USE_MAC_HDR, + COUNT + }; + + protected: + void DoSetup() override; + void DoRun() override; + void Transmit(Ptr mac, + uint8_t phyId, + WifiConstPsduMap psduMap, + WifiTxVector txVector, + double txPowerW) override; + void MainPhySwitchInfoCallback(std::size_t index, const EmlsrMainPhySwitchTrace& info) override; + + /// Runs a test case and invokes itself for the next test case + void RunOne(); + + /// Actions and checks to perform upon the transmission of each frame + struct Events + { + /** + * Constructor. + * + * @param type the frame MAC header type + * @param f function to perform actions and checks + */ + Events(WifiMacType type, + std::function, const WifiTxVector&, uint8_t)>&& f = {}) + : hdrType(type), + func(f) + { + } + + WifiMacType hdrType; ///< MAC header type of frame being transmitted + std::function, const WifiTxVector&, uint8_t)> + func; ///< function to perform actions and checks + }; + + private: + void StartTraffic() override; + + uint8_t m_testIndex{0}; //!< index to iterate over test scenarios + bool m_setupDone{false}; //!< whether association, BA, ... have been done + bool m_dlPktDone{false}; //!< whether the DL packet has been generated + std::list m_events; //!< list of events for a test run + std::size_t m_processedEvents{0}; //!< number of processed events + const uint8_t m_linkIdForTid0{1}; //!< ID of the link on which TID 0 is mapped + Ptr m_bcastFrame; //!< the broadcast frame sent by the AP MLD + Time m_switchMainPhyBackDelay; //!< the switch main PHY back delay + Time m_expectedMainPhySwitchBackTime; //!< expected main PHY switch back time +}; + #endif /* WIFI_EMLSR_TEST_H */