diff --git a/src/wifi/model/eht/eht-frame-exchange-manager.cc b/src/wifi/model/eht/eht-frame-exchange-manager.cc index 97ab816b2..00c61cbbc 100644 --- a/src/wifi/model/eht/eht-frame-exchange-manager.cc +++ b/src/wifi/model/eht/eht-frame-exchange-manager.cc @@ -59,6 +59,14 @@ EhtFrameExchangeManager::~EhtFrameExchangeManager() void EhtFrameExchangeManager::SetLinkId(uint8_t linkId) { + if (auto protectionManager = GetProtectionManager()) + { + protectionManager->SetLinkId(linkId); + } + if (auto ackManager = GetAckManager()) + { + ackManager->SetLinkId(linkId); + } m_msduAggregator->SetLinkId(linkId); m_mpduAggregator->SetLinkId(linkId); HeFrameExchangeManager::SetLinkId(linkId); diff --git a/src/wifi/model/wifi-mac.cc b/src/wifi/model/wifi-mac.cc index 82c8f789f..830360723 100644 --- a/src/wifi/model/wifi-mac.cc +++ b/src/wifi/model/wifi-mac.cc @@ -934,6 +934,22 @@ WifiMac::GetNLinks() const return m_links.size(); } +void +WifiMac::UpdateLinkId(uint8_t id) +{ + NS_LOG_FUNCTION(this << id); + + auto& link = GetLink(id); + if (link.feManager) + { + link.feManager->SetLinkId(id); + } + if (link.channelAccessManager) + { + link.channelAccessManager->SetLinkId(id); + } +} + std::optional WifiMac::GetLinkIdByAddress(const Mac48Address& address) const { @@ -947,6 +963,58 @@ WifiMac::GetLinkIdByAddress(const Mac48Address& address) const return std::nullopt; } +void +WifiMac::SwapLinks(std::map links) +{ + NS_LOG_FUNCTION(this); + + while (!links.empty()) + { + auto from = links.cbegin()->first; + auto to = links.cbegin()->second; + + if (from == to) + { + // nothing to do + links.erase(links.cbegin()); + continue; + } + + std::unique_ptr linkToMove; + NS_ASSERT(m_links.find(from) != m_links.cend()); + linkToMove.swap(m_links.at(from)); // from is now out of m_links + auto empty = from; // track empty cell in m_links + + do + { + auto [it, inserted] = + m_links.emplace(to, nullptr); // insert an element with key to if not present + m_links[to].swap(linkToMove); // to is the link to move now + UpdateLinkId(to); + links.erase(from); + if (!linkToMove) + { + if (inserted) + { + m_links.erase(empty); + } + break; + } + + auto nextTo = links.find(to); + if (nextTo == links.cend()) + { + // no new position specified for 'to', use the current empty cell + m_links[empty].swap(linkToMove); + break; + } + + from = to; + to = nextTo->second; + } while (true); + } +} + void WifiMac::SetWifiPhys(const std::vector>& phys) { diff --git a/src/wifi/model/wifi-mac.h b/src/wifi/model/wifi-mac.h index da62abf65..1f167669b 100644 --- a/src/wifi/model/wifi-mac.h +++ b/src/wifi/model/wifi-mac.h @@ -752,6 +752,16 @@ class WifiMac : public Object */ virtual void DeaggregateAmsduAndForward(Ptr mpdu); + /** + * Swap the links based on the information included in the given map. This method + * is normally called by a non-AP MLD upon completing ML setup to have its link IDs + * match AP MLD's link IDs. + * + * \param links a set of pairs (from, to) each mapping a current link ID to the + * link ID it has to become (i.e., link 'from' becomes link 'to') + */ + void SwapLinks(std::map links); + /** * Structure holding information specific to a single link. Here, the meaning of * "link" is that of the 11be amendment which introduced multi-link devices. For @@ -841,6 +851,15 @@ class WifiMac : public Object */ virtual std::unique_ptr CreateLinkEntity() const; + /** + * This method is intended to be called when a link changes ID in order to update the + * link ID stored by the Frame Exchange Manager and the Channel Access Manager operating + * on that link. + * + * \param id the (new) ID of the link that has changed ID + */ + void UpdateLinkId(uint8_t id); + /** * This method is called if this device is an MLD to determine the MAC address of * the affiliated STA used to communicate with the single link device having the diff --git a/src/wifi/test/wifi-mlo-test.cc b/src/wifi/test/wifi-mlo-test.cc index 702c9a17c..c051097f7 100644 --- a/src/wifi/test/wifi-mlo-test.cc +++ b/src/wifi/test/wifi-mlo-test.cc @@ -189,6 +189,121 @@ GetRnrLinkInfoTest::DoRun() "Unexpected tbtt ID of the second reported AP"); } +/** + * \ingroup wifi-test + * \ingroup tests + * + * Test the WifiMac::SwapLinks() method. + */ +class MldSwapLinksTest : public TestCase +{ + /** + * Test WifiMac subclass used to access the SwapLinks method. + */ + class TestWifiMac : public WifiMac + { + public: + ~TestWifiMac() override = default; + + using WifiMac::GetLinks; + using WifiMac::SwapLinks; + + bool CanForwardPacketsTo(Mac48Address to) const override + { + return true; + } + + void Enqueue(Ptr packet, Mac48Address to) override + { + } + }; + + public: + MldSwapLinksTest(); + ~MldSwapLinksTest() override = default; + + protected: + void DoRun() override; + + private: + /** + * Run a single test case. + * + * \param text string identifying the test case + * \param nLinks the number of links of the MLD + * \param links a set of pairs (from, to) each mapping a current link ID to the + * link ID it has to become (i.e., link 'from' becomes link 'to') + * \param expected maps each link ID to the id of the PHY that is expected + * to operate on that link after the swap + */ + void RunOne(std::string text, + std::size_t nLinks, + const std::map& links, + const std::map& expected); +}; + +MldSwapLinksTest::MldSwapLinksTest() + : TestCase("Test the WifiMac::SwapLinks() method") +{ +} + +void +MldSwapLinksTest::RunOne(std::string text, + std::size_t nLinks, + const std::map& links, + const std::map& expected) +{ + TestWifiMac mac; + + std::vector> phys; + for (std::size_t i = 0; i < nLinks; i++) + { + phys.emplace_back(CreateObject()); + } + mac.SetWifiPhys(phys); // create links containing the given PHYs + + mac.SwapLinks(links); + + NS_TEST_EXPECT_MSG_EQ(mac.GetNLinks(), nLinks, "Number of links changed after swapping"); + + for (const auto& [linkId, phyId] : expected) + { + NS_TEST_ASSERT_MSG_EQ(mac.GetLinks().count(linkId), + 1, + "Link ID " << +linkId << " does not exist"); + + NS_TEST_ASSERT_MSG_LT(+phyId, nLinks, "Invalid PHY ID"); + + // the id of the PHY operating on a link is the original ID of the link + NS_TEST_EXPECT_MSG_EQ(mac.GetWifiPhy(linkId), + phys.at(phyId), + text << ": Link " << +phyId << " has not been moved to link " + << +linkId); + } +} + +void +MldSwapLinksTest::DoRun() +{ + RunOne("No change needed", 3, {{0, 0}, {1, 1}, {2, 2}}, {{0, 0}, {1, 1}, {2, 2}}); + RunOne("Circular swapping", 3, {{0, 2}, {1, 0}, {2, 1}}, {{0, 1}, {1, 2}, {2, 0}}); + RunOne("Swapping two links, one unchanged", 3, {{0, 2}, {2, 0}}, {{0, 2}, {1, 1}, {2, 0}}); + RunOne("Non-circular swapping, autodetect how to close the loop", + 3, + {{0, 2}, {2, 1}}, + {{0, 1}, {1, 2}, {2, 0}}); + RunOne("One move only, autodetect how to complete the swapping", + 3, + {{2, 0}}, + {{0, 2}, {1, 1}, {2, 0}}); + RunOne("Create a new link ID (2), remove the unused one (0)", + 2, + {{0, 1}, {1, 2}}, + {{1, 0}, {2, 1}}); + RunOne("One move only that creates a new link ID (2)", 2, {{0, 2}}, {{1, 1}, {2, 0}}); + RunOne("Move all links to a new set of IDs", 2, {{0, 2}, {1, 3}}, {{2, 0}, {3, 1}}); +} + /** * \ingroup wifi-test * \ingroup tests @@ -2632,6 +2747,7 @@ WifiMultiLinkOperationsTestSuite::WifiMultiLinkOperationsTestSuite() std::vector>; // IDs of link that cannot change PHY band AddTestCase(new GetRnrLinkInfoTest(), TestCase::QUICK); + AddTestCase(new MldSwapLinksTest(), TestCase::QUICK); for (const auto& [staChannels, apChannels, setupLinks, fixedPhyBands] : {// matching channels: setup all links