diff --git a/src/wifi/test/wifi-emlsr-test.cc b/src/wifi/test/wifi-emlsr-test.cc index fe85bdb97..ba40489e6 100644 --- a/src/wifi/test/wifi-emlsr-test.cc +++ b/src/wifi/test/wifi-emlsr-test.cc @@ -2407,6 +2407,10 @@ EmlsrUlTxopTest::Transmit(Ptr mac, CheckRtsFrames(*psdu->begin(), txVector, linkId); break; + case WIFI_MAC_CTL_CTS: + CheckCtsFrames(*psdu->begin(), txVector, linkId); + break; + case WIFI_MAC_QOSDATA: CheckQosFrames(psduMap, txVector, linkId); break; @@ -2690,6 +2694,29 @@ EmlsrUlTxopTest::CheckBlockAck(const WifiConstPsduMap& psduMap, // the main PHY is operating NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n"); m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 2, 1000)); + break; + case 6: + // block transmission on the main PHY link and on the non-EMLSR link (if any), so that + // the next QoS frames are sent on a link where an aux PHY is operating + std::set linkIds{m_mainPhyId}; + if (m_nonEmlsrLink) + { + linkIds.insert(*m_nonEmlsrLink); + } + m_staMacs[0]->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED, + AC_BE, + {WIFI_QOSDATA_QUEUE}, + m_apMac->GetAddress(), + m_staMacs[0]->GetAddress(), + {0}, + linkIds); + // make sure aux PHYs are capable of transmitting frames + m_staMacs[0]->GetEmlsrManager()->SetAttribute("AuxPhyTxCapable", BooleanValue(true)); + + // 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; } } @@ -2699,13 +2726,28 @@ EmlsrUlTxopTest::CheckRtsFrames(Ptr mpdu, const WifiTxVector& txVector, uint8_t linkId) { - if (linkId != m_mainPhyId || m_firstUlPktsGenTime.IsZero()) + if (m_firstUlPktsGenTime.IsZero()) { - // this function only considers RTS frames sent by the main PHY while the MediumSyncDelay - // timer is running + // this function only considers RTS frames sent after the first QoS data frame return; } + if (linkId != m_mainPhyId) + { + if (m_countRtsframes > 0 && !m_corruptCts.has_value()) + { + // we get here for the frame exchange in which the CTS response must be corrupted. + // Install post reception error model on the STA affiliated with the EMLSR client that + // is transmitting this RTS frame + m_errorModel = CreateObject(); + m_staMacs[0]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel); + m_corruptCts = true; + } + + return; + } + + // we get here for RTS frames sent by the main PHY while the MediumSyncDelay timer is running m_countRtsframes++; NS_TEST_EXPECT_MSG_EQ(txVector.GetChannelWidth(), @@ -2717,6 +2759,31 @@ EmlsrUlTxopTest::CheckRtsFrames(Ptr mpdu, m_errorModel->SetList({mpdu->GetPacket()->GetUid()}); } +void +EmlsrUlTxopTest::CheckCtsFrames(Ptr mpdu, + const WifiTxVector& txVector, + uint8_t linkId) +{ + if (m_corruptCts.has_value() && *m_corruptCts) + { + // corrupt reception at EMLSR client + NS_LOG_INFO("CORRUPTED"); + m_errorModel->SetList({mpdu->GetPacket()->GetUid()}); + m_corruptCts = false; + + auto txDuration = WifiPhy::CalculateTxDuration(mpdu->GetSize(), + txVector, + m_apMac->GetWifiPhy(linkId)->GetPhyBand()); + // 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"); + }); + } +} + void EmlsrUlTxopTest::DoRun() { @@ -2804,6 +2871,7 @@ EmlsrUlTxopTest::CheckResults() * ────────────────────────┬───┬┴───┴┬───┬───┬┴──┴───────────────────────────────────────── * │RTS│ │QoS│QoS│ * └───┘ │ 8 │ 9 │ + * └───┴───┘ * gen backoff gen backoff if MediumSyncDelay * ┌──┐ (also many times) not running timer expired ┌──┐ * [link 1] │BA│ │ if allowed │ │ │BA│ @@ -2818,6 +2886,16 @@ EmlsrUlTxopTest::CheckResults() * │ 6 │ 7 │ * └───┴───┘ * + * For both scenarios, after the last frame exchange on the main PHY link, we have the + * following frame exchange on an EMLSR link where an aux PHY is operating on: + * + * ┌───┐ ┌───┐ ┌──┐ + * │CTS│ │CTS│ │BA│ + * ──────┬───┬┴───X─────────┬───┬┴───┴┬───┬───┬┴──┴───────────────────────────────────── + * │RTS│ │RTS│ │QoS│QoS│ + * └───┘ └───┘ │ X │ Y │ + * └───┴───┘ + * For all EMLSR links scenario, X=10, Y=11; otherwise, X=12, Y=13. */ // jump to the first (non-Beacon) frame transmitted after establishing BA agreements and @@ -2912,7 +2990,7 @@ EmlsrUlTxopTest::CheckResults() m_auxPhyChannelWidth, "Second data frame not transmitted on the same width as RTS"); - bool lastQosDataFound = false; + bool moreQosDataFound = false; while (++psduIt != m_txPsdus.cend()) { @@ -2920,7 +2998,7 @@ EmlsrUlTxopTest::CheckResults() if (psduIt != m_txPsdus.cend() && psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData()) { - lastQosDataFound = true; + moreQosDataFound = true; NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId, +m_mainPhyId, @@ -2938,7 +3016,78 @@ EmlsrUlTxopTest::CheckResults() } } - NS_TEST_EXPECT_MSG_EQ(lastQosDataFound, true, "Last QoS data frame not found"); + NS_TEST_EXPECT_MSG_EQ(moreQosDataFound, + true, + "Last QoS data frame transmitted by the main PHY not found"); + + NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()), true, "Expected more frames"); + ++psduIt; + jumpToQosDataOrMuRts(); + + // the first attempt at transmitting the last QoS data frame fails because CTS is corrupted + // RTS + NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()), + true, + "RTS before last QoS data frame has not been transmitted"); + NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts(), + true, + "Last QoS data frame should be transmitted with protection"); + NS_TEST_EXPECT_MSG_NE( + +psduIt->phyId, + +m_mainPhyId, + "RTS before last QoS data frame should not be transmitted by the main PHY"); + NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(), + m_auxPhyChannelWidth, + "RTS before last data frame transmitted on an unexpected width"); + psduIt++; + // CTS + NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()), + true, + "CTS before last QoS data frame has not been transmitted"); + NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsCts(), + true, + "CTS before last QoS data frame has not been transmitted"); + psduIt++; + jumpToQosDataOrMuRts(); + + // the last QoS data frame is transmitted by an aux PHY after that the aux PHY has + // obtained a TXOP and sent an RTS + // RTS + NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()), + true, + "RTS before last QoS data frame has not been transmitted"); + NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts(), + true, + "Last QoS data frame should be transmitted with protection"); + NS_TEST_EXPECT_MSG_NE( + +psduIt->phyId, + +m_mainPhyId, + "RTS before last QoS data frame should not be transmitted by the main PHY"); + NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(), + m_auxPhyChannelWidth, + "RTS before last data frame transmitted on an unexpected width"); + psduIt++; + // CTS + NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()), + true, + "CTS before last QoS data frame has not been transmitted"); + NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsCts(), + true, + "CTS before last QoS data frame has not been transmitted"); + psduIt++; + // QoS Data + NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()), + true, + "Last QoS data frame has not been transmitted"); + NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData(), + true, + "Last QoS data frame has not been transmitted"); + NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId, + +m_mainPhyId, + "Last QoS data frame should be transmitted by the main PHY"); + NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(), + m_auxPhyChannelWidth, + "Last data frame not transmitted on the same width as RTS"); } EmlsrLinkSwitchTest::EmlsrLinkSwitchTest(const Params& params) diff --git a/src/wifi/test/wifi-emlsr-test.h b/src/wifi/test/wifi-emlsr-test.h index c275d2506..87bb1aadf 100644 --- a/src/wifi/test/wifi-emlsr-test.h +++ b/src/wifi/test/wifi-emlsr-test.h @@ -498,9 +498,13 @@ class EmlsrDlTxopTest : public EmlsrOperationsTestBase * is now running on the link where the main PHY is operating, hence transmissions are protected * by an RTS frame. We install a post reception error model on the AP MLD so that all RTS frames * sent by the EMLSR client are not received by the AP MLD. We check that the EMLSR client makes - * at most the configured max number of transmission attempts and that the last UL data frame is + * at most the configured max number of transmission attempts and that another UL data frame is * sent once the MediumSyncDelay timer is expired. We also check that the TX width of the RTS - * frames and the last UL data frame equal the channel width used by the main PHY. + * frames and the UL data frame equal the channel width used by the main PHY. + * - We check that no issue arises in case an aux PHY sends an RTS frame but the CTS response is + * not transmitted successfully. Specifically, we check that the main PHY is completing the + * channel switch when the (unsuccessful) reception of the CTS ends and that a new RTS/CTS + * exchange is carried out to protect the transmission of the last data frame. */ class EmlsrUlTxopTest : public EmlsrOperationsTestBase { @@ -555,6 +559,16 @@ class EmlsrUlTxopTest : public EmlsrOperationsTestBase */ void CheckRtsFrames(Ptr mpdu, const WifiTxVector& txVector, uint8_t linkId); + /** + * Check that appropriate actions are taken by the EMLSR client when receiving a CTS + * frame on the given link. + * + * \param mpdu the MPDU carrying the CTS frame + * \param txVector the TXVECTOR used to send the PPDU + * \param linkId the ID of the given link + */ + void CheckCtsFrames(Ptr mpdu, const WifiTxVector& txVector, uint8_t linkId); + /** * Check that appropriate actions are taken when an MLD transmits a PPDU containing * QoS data frames on the given link. @@ -613,6 +627,7 @@ class EmlsrUlTxopTest : public EmlsrOperationsTestBase bool m_genBackoffIfTxopWithoutTx; //!< whether the backoff should be invoked when the AC //!< gains the right to start a TXOP but it does not //!< transmit any frame + std::optional m_corruptCts; //!< whether the transmitted CTS must be corrupted }; /**