wifi: Extend EMLSR test to check switch main PHY back timer handling

This commit is contained in:
Stefano Avallone
2025-03-01 17:09:48 +01:00
parent 2fe280db62
commit bedc291ecb
4 changed files with 727 additions and 8 deletions

View File

@@ -15,6 +15,8 @@
#include <memory>
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.

View File

@@ -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.
*

View File

@@ -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<WifiMpdu>(Create<Packet>(1000), hdr);
}
void
EmlsrSwitchMainPhyBackTest::Transmit(Ptr<WifiMac> 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<TestScenario>(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<const EmlsrSwitchMainPhyBackTrace&>(info);
switch (static_cast<TestScenario>(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<uint8_t>(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<TestScenario>(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<AdvancedEmlsrManager>(m_staMacs[0]->GetEmlsrManager());
m_events.emplace_back(
WIFI_MAC_QOSDATA,
[=, this](Ptr<const WifiPsdu> 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<const WifiPsdu> 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<const WifiPsdu> 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<uint8_t>(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);

View File

@@ -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<WifiMac> 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<void(Ptr<const WifiPsdu>, const WifiTxVector&, uint8_t)>&& f = {})
: hdrType(type),
func(f)
{
}
WifiMacType hdrType; ///< MAC header type of frame being transmitted
std::function<void(Ptr<const WifiPsdu>, 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<Events> 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<WifiMpdu> 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 */