A Discrete-Event Network Simulator
API
wifi-mlo-test.cc
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2022 Universita' degli Studi di Napoli Federico II
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free Software Foundation;
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  *
17  * Author: Stefano Avallone <stavallo@unina.it>
18  */
19 
20 #include "ns3/ap-wifi-mac.h"
21 #include "ns3/config.h"
22 #include "ns3/eht-configuration.h"
23 #include "ns3/frame-exchange-manager.h"
24 #include "ns3/log.h"
25 #include "ns3/mgt-headers.h"
26 #include "ns3/mobility-helper.h"
27 #include "ns3/multi-link-element.h"
28 #include "ns3/multi-model-spectrum-channel.h"
29 #include "ns3/node-list.h"
30 #include "ns3/packet-socket-client.h"
31 #include "ns3/packet-socket-helper.h"
32 #include "ns3/packet-socket-server.h"
33 #include "ns3/packet.h"
34 #include "ns3/pointer.h"
35 #include "ns3/qos-utils.h"
36 #include "ns3/rng-seed-manager.h"
37 #include "ns3/rr-multi-user-scheduler.h"
38 #include "ns3/spectrum-wifi-helper.h"
39 #include "ns3/spectrum-wifi-phy.h"
40 #include "ns3/sta-wifi-mac.h"
41 #include "ns3/string.h"
42 #include "ns3/test.h"
43 #include "ns3/wifi-acknowledgment.h"
44 #include "ns3/wifi-assoc-manager.h"
45 #include "ns3/wifi-mac-header.h"
46 #include "ns3/wifi-mac-queue.h"
47 #include "ns3/wifi-net-device.h"
48 #include "ns3/wifi-protection.h"
49 #include "ns3/wifi-psdu.h"
50 
51 #include <algorithm>
52 #include <array>
53 #include <iomanip>
54 #include <optional>
55 #include <sstream>
56 #include <tuple>
57 #include <vector>
58 
59 using namespace ns3;
60 
61 NS_LOG_COMPONENT_DEFINE("WifiMloTest");
62 
72 {
73  public:
78  ~GetRnrLinkInfoTest() override = default;
79 
80  private:
81  void DoRun() override;
82 };
83 
85  : TestCase("Check the implementation of WifiAssocManager::GetNextAffiliatedAp()")
86 {
87 }
88 
89 void
91 {
93  std::size_t nbrId;
94  std::size_t tbttId;
95 
96  // Add a first Neighbor AP Information field without MLD Parameters
97  rnr.AddNbrApInfoField();
98  nbrId = rnr.GetNNbrApInfoFields() - 1;
99 
100  rnr.AddTbttInformationField(nbrId);
101  rnr.AddTbttInformationField(nbrId);
102 
103  // Add a second Neighbor AP Information field with MLD Parameters; the first
104  // TBTT Information field is related to an AP affiliated to the same AP MLD
105  // as the reported AP; the second TBTT Information field is not (it does not
106  // make sense that two APs affiliated to the same AP MLD are using the same
107  // channel).
108  rnr.AddNbrApInfoField();
109  nbrId = rnr.GetNNbrApInfoFields() - 1;
110 
111  rnr.AddTbttInformationField(nbrId);
112  tbttId = rnr.GetNTbttInformationFields(nbrId) - 1;
113  rnr.SetMldParameters(nbrId, tbttId, 0, 0, 0);
114 
115  rnr.AddTbttInformationField(nbrId);
116  tbttId = rnr.GetNTbttInformationFields(nbrId) - 1;
117  rnr.SetMldParameters(nbrId, tbttId, 5, 0, 0);
118 
119  // Add a third Neighbor AP Information field with MLD Parameters; none of the
120  // TBTT Information fields is related to an AP affiliated to the same AP MLD
121  // as the reported AP.
122  rnr.AddNbrApInfoField();
123  nbrId = rnr.GetNNbrApInfoFields() - 1;
124 
125  rnr.AddTbttInformationField(nbrId);
126  tbttId = rnr.GetNTbttInformationFields(nbrId) - 1;
127  rnr.SetMldParameters(nbrId, tbttId, 3, 0, 0);
128 
129  rnr.AddTbttInformationField(nbrId);
130  tbttId = rnr.GetNTbttInformationFields(nbrId) - 1;
131  rnr.SetMldParameters(nbrId, tbttId, 4, 0, 0);
132 
133  // Add a fourth Neighbor AP Information field with MLD Parameters; the first
134  // TBTT Information field is not related to an AP affiliated to the same AP MLD
135  // as the reported AP; the second TBTT Information field is.
136  rnr.AddNbrApInfoField();
137  nbrId = rnr.GetNNbrApInfoFields() - 1;
138 
139  rnr.AddTbttInformationField(nbrId);
140  tbttId = rnr.GetNTbttInformationFields(nbrId) - 1;
141  rnr.SetMldParameters(nbrId, tbttId, 6, 0, 0);
142 
143  rnr.AddTbttInformationField(nbrId);
144  tbttId = rnr.GetNTbttInformationFields(nbrId) - 1;
145  rnr.SetMldParameters(nbrId, tbttId, 0, 0, 0);
146 
147  // check implementation of WifiAssocManager::GetNextAffiliatedAp()
148  auto ret = WifiAssocManager::GetNextAffiliatedAp(rnr, 0);
149 
150  NS_TEST_EXPECT_MSG_EQ(ret.has_value(), true, "Expected to find a suitable reported AP");
151  NS_TEST_EXPECT_MSG_EQ(ret->m_nbrApInfoId, 1, "Unexpected neighbor ID of the first reported AP");
152  NS_TEST_EXPECT_MSG_EQ(ret->m_tbttInfoFieldId, 0, "Unexpected tbtt ID of the first reported AP");
153 
154  ret = WifiAssocManager::GetNextAffiliatedAp(rnr, ret->m_nbrApInfoId + 1);
155 
156  NS_TEST_EXPECT_MSG_EQ(ret.has_value(), true, "Expected to find a second suitable reported AP");
157  NS_TEST_EXPECT_MSG_EQ(ret->m_nbrApInfoId,
158  3,
159  "Unexpected neighbor ID of the second reported AP");
160  NS_TEST_EXPECT_MSG_EQ(ret->m_tbttInfoFieldId,
161  1,
162  "Unexpected tbtt ID of the second reported AP");
163 
164  ret = WifiAssocManager::GetNextAffiliatedAp(rnr, ret->m_nbrApInfoId + 1);
165 
166  NS_TEST_EXPECT_MSG_EQ(ret.has_value(),
167  false,
168  "Did not expect to find a third suitable reported AP");
169 
170  // check implementation of WifiAssocManager::GetAllAffiliatedAps()
171  auto allAps = WifiAssocManager::GetAllAffiliatedAps(rnr);
172 
173  NS_TEST_EXPECT_MSG_EQ(allAps.size(), 2, "Expected to find two suitable reported APs");
174 
175  auto apIt = allAps.begin();
176  NS_TEST_EXPECT_MSG_EQ(apIt->m_nbrApInfoId,
177  1,
178  "Unexpected neighbor ID of the first reported AP");
179  NS_TEST_EXPECT_MSG_EQ(apIt->m_tbttInfoFieldId,
180  0,
181  "Unexpected tbtt ID of the first reported AP");
182 
183  apIt++;
184  NS_TEST_EXPECT_MSG_EQ(apIt->m_nbrApInfoId,
185  3,
186  "Unexpected neighbor ID of the second reported AP");
187  NS_TEST_EXPECT_MSG_EQ(apIt->m_tbttInfoFieldId,
188  1,
189  "Unexpected tbtt ID of the second reported AP");
190 }
191 
199 {
203  class TestWifiMac : public WifiMac
204  {
205  public:
206  ~TestWifiMac() override = default;
207 
208  using WifiMac::GetLinks;
209  using WifiMac::SwapLinks;
210 
211  bool CanForwardPacketsTo(Mac48Address to) const override
212  {
213  return true;
214  }
215 
216  void Enqueue(Ptr<Packet> packet, Mac48Address to) override
217  {
218  }
219  };
220 
221  public:
223  ~MldSwapLinksTest() override = default;
224 
225  protected:
226  void DoRun() override;
227 
228  private:
239  void RunOne(std::string text,
240  std::size_t nLinks,
241  const std::map<uint8_t, uint8_t>& links,
242  const std::map<uint8_t, uint8_t>& expected);
243 };
244 
246  : TestCase("Test the WifiMac::SwapLinks() method")
247 {
248 }
249 
250 void
251 MldSwapLinksTest::RunOne(std::string text,
252  std::size_t nLinks,
253  const std::map<uint8_t, uint8_t>& links,
254  const std::map<uint8_t, uint8_t>& expected)
255 {
257 
258  std::vector<Ptr<WifiPhy>> phys;
259  for (std::size_t i = 0; i < nLinks; i++)
260  {
261  phys.emplace_back(CreateObject<SpectrumWifiPhy>());
262  }
263  mac.SetWifiPhys(phys); // create links containing the given PHYs
264 
265  mac.SwapLinks(links);
266 
267  NS_TEST_EXPECT_MSG_EQ(mac.GetNLinks(), nLinks, "Number of links changed after swapping");
268 
269  for (const auto& [linkId, phyId] : expected)
270  {
271  NS_TEST_ASSERT_MSG_EQ(mac.GetLinks().count(linkId),
272  1,
273  "Link ID " << +linkId << " does not exist");
274 
275  NS_TEST_ASSERT_MSG_LT(+phyId, nLinks, "Invalid PHY ID");
276 
277  // the id of the PHY operating on a link is the original ID of the link
278  NS_TEST_EXPECT_MSG_EQ(mac.GetWifiPhy(linkId),
279  phys.at(phyId),
280  text << ": Link " << +phyId << " has not been moved to link "
281  << +linkId);
282  }
283 }
284 
285 void
287 {
288  RunOne("No change needed", 3, {{0, 0}, {1, 1}, {2, 2}}, {{0, 0}, {1, 1}, {2, 2}});
289  RunOne("Circular swapping", 3, {{0, 2}, {1, 0}, {2, 1}}, {{0, 1}, {1, 2}, {2, 0}});
290  RunOne("Swapping two links, one unchanged", 3, {{0, 2}, {2, 0}}, {{0, 2}, {1, 1}, {2, 0}});
291  RunOne("Non-circular swapping, autodetect how to close the loop",
292  3,
293  {{0, 2}, {2, 1}},
294  {{0, 1}, {1, 2}, {2, 0}});
295  RunOne("One move only, autodetect how to complete the swapping",
296  3,
297  {{2, 0}},
298  {{0, 2}, {1, 1}, {2, 0}});
299  RunOne("Create a new link ID (2), remove the unused one (0)",
300  2,
301  {{0, 1}, {1, 2}},
302  {{1, 0}, {2, 1}});
303  RunOne("One move only that creates a new link ID (2)", 2, {{0, 2}}, {{1, 1}, {2, 0}});
304  RunOne("Move all links to a new set of IDs", 2, {{0, 2}, {1, 3}}, {{2, 0}, {3, 1}});
305 }
306 
318 {
319  public:
323  struct BaseParams
324  {
325  std::vector<std::string>
327  std::vector<std::string>
329  std::vector<uint8_t>
331  };
332 
340  MultiLinkOperationsTestBase(const std::string& name,
341  uint8_t nStations,
342  const BaseParams& baseParams);
343  ~MultiLinkOperationsTestBase() override = default;
344 
345  protected:
355  virtual void Transmit(Ptr<WifiMac> mac,
356  uint8_t phyId,
357  WifiConstPsduMap psduMap,
358  WifiTxVector txVector,
359  double txPowerW);
360 
367  virtual void L7Receive(uint8_t nodeId, Ptr<const Packet> p, const Address& addr);
368 
380  std::size_t count,
381  std::size_t pktSize,
382  Time delay = Seconds(0),
383  uint8_t priority = 0) const;
384 
385  void DoSetup() override;
386 
388  using ChannelMap = std::map<FrequencyRange, Ptr<MultiModelSpectrumChannel>>;
389 
394  {
395  DL = 0,
396  UL
397  };
398 
406  std::optional<Direction> direction = std::nullopt);
407 
409  struct FrameInfo
410  {
414  uint8_t linkId;
415  uint8_t phyId;
416  };
417 
418  std::vector<FrameInfo> m_txPsdus;
419  const std::vector<std::string> m_staChannels;
420  const std::vector<std::string> m_apChannels;
421  const std::vector<uint8_t> m_fixedPhyBands;
423  std::vector<Ptr<StaWifiMac>> m_staMacs;
424  uint8_t m_nStations;
425  uint16_t m_lastAid;
427  std::vector<std::size_t> m_rxPkts;
429 
430  private:
440  void SetChannels(SpectrumWifiPhyHelper& helper,
441  const std::vector<std::string>& channels,
442  const ChannelMap& channelMap);
443 
451  void SetSsid(uint16_t aid, Mac48Address /* addr */);
452 
456  virtual void StartTraffic()
457  {
458  }
459 };
460 
462  uint8_t nStations,
463  const BaseParams& baseParams)
464  : TestCase(name),
465  m_staChannels(baseParams.staChannels),
466  m_apChannels(baseParams.apChannels),
467  m_fixedPhyBands(baseParams.fixedPhyBands),
468  m_staMacs(nStations),
469  m_nStations(nStations),
470  m_lastAid(0),
471  m_rxPkts(nStations + 1)
472 {
473 }
474 
475 void
477  std::optional<Direction> direction)
478 {
479  std::optional<Mac48Address> apAddr;
480  std::optional<Mac48Address> staAddr;
481 
482  // direction for Data frames is derived from ToDS/FromDS flags
483  if (psdu->GetHeader(0).IsQosData())
484  {
485  direction = (!psdu->GetHeader(0).IsToDs() && psdu->GetHeader(0).IsFromDs()) ? DL : UL;
486  }
487  NS_ASSERT(direction);
488 
489  if (direction == DL)
490  {
491  if (!psdu->GetAddr1().IsGroup())
492  {
493  staAddr = psdu->GetAddr1();
494  }
495  apAddr = psdu->GetAddr2();
496  }
497  else
498  {
499  if (!psdu->GetAddr1().IsGroup())
500  {
501  apAddr = psdu->GetAddr1();
502  }
503  staAddr = psdu->GetAddr2();
504  }
505 
506  if (apAddr)
507  {
508  bool found = false;
509  for (uint8_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
510  {
511  if (m_apMac->GetFrameExchangeManager(linkId)->GetAddress() == *apAddr)
512  {
513  found = true;
514  break;
515  }
516  }
517  NS_TEST_EXPECT_MSG_EQ(found,
518  true,
519  "Address " << *apAddr << " is not an AP device address. "
520  << "PSDU: " << *psdu);
521  }
522 
523  if (staAddr)
524  {
525  bool found = false;
526  for (uint8_t i = 0; i < m_nStations; i++)
527  {
528  for (const auto& linkId : m_staMacs[i]->GetLinkIds())
529  {
530  if (m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress() == *staAddr)
531  {
532  found = true;
533  break;
534  }
535  }
536  if (found)
537  {
538  break;
539  }
540  }
541  NS_TEST_EXPECT_MSG_EQ(found,
542  true,
543  "Address " << *staAddr << " is not a STA device address. "
544  << "PSDU: " << *psdu);
545  }
546 }
547 
548 void
550  uint8_t phyId,
551  WifiConstPsduMap psduMap,
552  WifiTxVector txVector,
553  double txPowerW)
554 {
555  auto linkId = mac->GetLinkForPhy(phyId);
556  NS_TEST_ASSERT_MSG_EQ(linkId.has_value(), true, "No link found for PHY ID " << +phyId);
557  m_txPsdus.push_back({Simulator::Now(), psduMap, txVector, *linkId, phyId});
558 
559  for (const auto& [aid, psdu] : psduMap)
560  {
561  std::stringstream ss;
562  ss << std::setprecision(10) << "PSDU #" << m_txPsdus.size() << " Link ID "
563  << +linkId.value() << " Phy ID " << +phyId << " " << psdu->GetHeader(0).GetTypeString()
564  << " #MPDUs " << psdu->GetNMpdus() << " duration/ID " << psdu->GetHeader(0).GetDuration()
565  << " RA = " << psdu->GetAddr1() << " TA = " << psdu->GetAddr2()
566  << " ADDR3 = " << psdu->GetHeader(0).GetAddr3()
567  << " ToDS = " << psdu->GetHeader(0).IsToDs()
568  << " FromDS = " << psdu->GetHeader(0).IsFromDs();
569  if (psdu->GetHeader(0).IsQosData())
570  {
571  ss << " seqNo = {";
572  for (auto& mpdu : *PeekPointer(psdu))
573  {
574  ss << mpdu->GetHeader().GetSequenceNumber() << ",";
575  }
576  ss << "} TID = " << +psdu->GetHeader(0).GetQosTid();
577  }
578  NS_LOG_INFO(ss.str());
579  }
580  NS_LOG_INFO("TXVECTOR = " << txVector << "\n");
581 }
582 
583 void
585 {
586  NS_LOG_INFO("Packet received by NODE " << +nodeId << "\n");
587  m_rxPkts[nodeId]++;
588 }
589 
590 void
592  const std::vector<std::string>& channels,
593  const ChannelMap& channelMap)
594 {
595  helper = SpectrumWifiPhyHelper(channels.size());
596  helper.SetPcapDataLinkType(WifiPhyHelper::DLT_IEEE802_11_RADIO);
597 
598  uint8_t linkId = 0;
599  for (const auto& str : channels)
600  {
601  helper.Set(linkId++, "ChannelSettings", StringValue(str));
602  }
603 
604  // NOTE replace this for loop with the line below to use a single spectrum channel
605  // helper.SetChannel(channelMap.begin()->second);
606  for (const auto& [band, channel] : channelMap)
607  {
608  helper.AddChannel(channel, band);
609  }
610 }
611 
612 void
614 {
615  RngSeedManager::SetSeed(1);
616  RngSeedManager::SetRun(3);
617  int64_t streamNumber = 30;
618 
620  wifiApNode.Create(1);
621 
623  wifiStaNodes.Create(m_nStations);
624 
626  // wifi.EnableLogComponents ();
627  wifi.SetStandard(WIFI_STANDARD_80211be);
628  wifi.SetRemoteStationManager("ns3::ConstantRateWifiManager",
629  "DataMode",
630  StringValue("EhtMcs0"),
631  "ControlMode",
632  StringValue("HtMcs0"));
633 
634  ChannelMap channelMap{{WIFI_SPECTRUM_2_4_GHZ, CreateObject<MultiModelSpectrumChannel>()},
635  {WIFI_SPECTRUM_5_GHZ, CreateObject<MultiModelSpectrumChannel>()},
636  {WIFI_SPECTRUM_6_GHZ, CreateObject<MultiModelSpectrumChannel>()}};
637 
638  SpectrumWifiPhyHelper staPhyHelper;
639  SpectrumWifiPhyHelper apPhyHelper;
640  SetChannels(staPhyHelper, m_staChannels, channelMap);
641  SetChannels(apPhyHelper, m_apChannels, channelMap);
642 
643  for (const auto& linkId : m_fixedPhyBands)
644  {
645  staPhyHelper.Set(linkId, "FixedPhyBand", BooleanValue(true));
646  }
647 
649  mac.SetType("ns3::StaWifiMac", // default SSID
650  "ActiveProbing",
651  BooleanValue(false));
652 
653  NetDeviceContainer staDevices = wifi.Install(staPhyHelper, mac, wifiStaNodes);
654 
655  mac.SetType("ns3::ApWifiMac",
656  "Ssid",
657  SsidValue(Ssid("ns-3-ssid")),
658  "BeaconGeneration",
659  BooleanValue(true));
660 
661  NetDeviceContainer apDevices = wifi.Install(apPhyHelper, mac, wifiApNode);
662 
663  // Uncomment the lines below to write PCAP files
664  // apPhyHelper.EnablePcap("wifi-mlo_AP", apDevices);
665  // staPhyHelper.EnablePcap("wifi-mlo_STA", staDevices);
666 
667  // Assign fixed streams to random variables in use
668  streamNumber += wifi.AssignStreams(apDevices, streamNumber);
669  streamNumber += wifi.AssignStreams(staDevices, streamNumber);
670 
672  Ptr<ListPositionAllocator> positionAlloc = CreateObject<ListPositionAllocator>();
673 
674  positionAlloc->Add(Vector(0.0, 0.0, 0.0));
675  positionAlloc->Add(Vector(1.0, 0.0, 0.0));
676  mobility.SetPositionAllocator(positionAlloc);
677 
678  mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
679  mobility.Install(wifiApNode);
680  mobility.Install(wifiStaNodes);
681 
682  m_apMac = DynamicCast<ApWifiMac>(DynamicCast<WifiNetDevice>(apDevices.Get(0))->GetMac());
683  for (uint8_t i = 0; i < m_nStations; i++)
684  {
685  m_staMacs[i] =
686  DynamicCast<StaWifiMac>(DynamicCast<WifiNetDevice>(staDevices.Get(i))->GetMac());
687  }
688 
689  // Trace PSDUs passed to the PHY on all devices
690  for (uint8_t phyId = 0; phyId < m_apMac->GetDevice()->GetNPhys(); phyId++)
691  {
693  "/NodeList/0/DeviceList/*/$ns3::WifiNetDevice/Phys/" + std::to_string(phyId) +
694  "/PhyTxPsduBegin",
696  }
697  for (uint8_t i = 0; i < m_nStations; i++)
698  {
699  for (uint8_t phyId = 0; phyId < m_staMacs[i]->GetDevice()->GetNPhys(); phyId++)
700  {
701  Config::ConnectWithoutContext("/NodeList/" + std::to_string(i + 1) +
702  "/DeviceList/*/$ns3::WifiNetDevice/Phys/" +
703  std::to_string(phyId) + "/PhyTxPsduBegin",
705  .Bind(m_staMacs[i], phyId));
706  }
707  }
708 
709  // install packet socket on all nodes
710  PacketSocketHelper packetSocket;
711  packetSocket.Install(wifiApNode);
712  packetSocket.Install(wifiStaNodes);
713 
714  // install a packet socket server on all nodes
715  for (auto nodeIt = NodeList::Begin(); nodeIt != NodeList::End(); ++nodeIt)
716  {
717  PacketSocketAddress srvAddr;
718  auto device = DynamicCast<WifiNetDevice>((*nodeIt)->GetDevice(0));
719  NS_TEST_ASSERT_MSG_NE(device, nullptr, "Expected a WifiNetDevice");
720  srvAddr.SetSingleDevice(device->GetIfIndex());
721  srvAddr.SetProtocol(1);
722 
723  auto server = CreateObject<PacketSocketServer>();
724  server->SetLocal(srvAddr);
725  (*nodeIt)->AddApplication(server);
726  server->SetStartTime(Seconds(0)); // now
727  server->SetStopTime(m_duration);
728  }
729 
730  for (std::size_t nodeId = 0; nodeId < NodeList::GetNNodes(); nodeId++)
731  {
733  "/NodeList/" + std::to_string(nodeId) +
734  "/ApplicationList/*/$ns3::PacketSocketServer/Rx",
736  }
737 
738  // schedule ML setup for one station at a time
739  m_apMac->TraceConnectWithoutContext("AssociatedSta",
741  m_staMacs[0]->SetSsid(Ssid("ns-3-ssid"));
742 }
743 
746  std::size_t count,
747  std::size_t pktSize,
748  Time delay,
749  uint8_t priority) const
750 {
751  auto client = CreateObject<PacketSocketClient>();
752  client->SetAttribute("PacketSize", UintegerValue(pktSize));
753  client->SetAttribute("MaxPackets", UintegerValue(count));
754  client->SetAttribute("Interval", TimeValue(MicroSeconds(0)));
755  client->SetAttribute("Priority", UintegerValue(priority));
756  client->SetRemote(sockAddr);
757  client->SetStartTime(delay);
758  client->SetStopTime(m_duration - Simulator::Now());
759 
760  return client;
761 }
762 
763 void
765 {
766  if (m_lastAid == aid)
767  {
768  // another STA of this non-AP MLD has already fired this callback
769  return;
770  }
771  m_lastAid = aid;
772 
773  // make the next STA to start ML discovery & setup
774  if (aid < m_nStations)
775  {
776  m_staMacs[aid]->SetSsid(Ssid("ns-3-ssid"));
777  return;
778  }
779  // wait some time (5ms) to allow the completion of association before generating traffic
780  Simulator::Schedule(MilliSeconds(5), &MultiLinkOperationsTestBase::StartTraffic, this);
781 }
782 
814 {
815  public:
827  MultiLinkSetupTest(const BaseParams& baseParams,
828  WifiScanType scanType,
829  const std::vector<uint8_t>& setupLinks,
830  uint8_t apNegSupport,
831  const std::string& dlTidToLinkMapping,
832  const std::string& ulTidToLinkMapping);
833  ~MultiLinkSetupTest() override = default;
834 
835  protected:
836  void DoSetup() override;
837  void DoRun() override;
838 
839  private:
840  void StartTraffic() override;
841 
845  void CheckMlSetup();
846 
850  void CheckDisabledLinks();
851 
858  void CheckBeacon(Ptr<WifiMpdu> mpdu, uint8_t linkId);
859 
866  void CheckProbeResponse(Ptr<WifiMpdu> mpdu, uint8_t linkId);
867 
874  void CheckAssocRequest(Ptr<WifiMpdu> mpdu, uint8_t linkId);
875 
882  void CheckAssocResponse(Ptr<WifiMpdu> mpdu, uint8_t linkId);
883 
891  void CheckQosData(Ptr<WifiMpdu> mpdu, uint8_t linkId, std::size_t index);
892 
893  const std::vector<uint8_t> m_setupLinks;
895  std::size_t m_nProbeResp;
896  uint8_t m_apNegSupport;
897  std::string m_dlTidLinkMappingStr;
898  std::string m_ulTidLinkMappingStr;
903  uint8_t m_dlTid1;
904  uint8_t m_ulTid1;
905  std::optional<uint8_t> m_dlTid2;
906  std::optional<uint8_t> m_ulTid2;
907  std::vector<std::size_t>
909  std::vector<std::size_t>
911 };
912 
914  WifiScanType scanType,
915  const std::vector<uint8_t>& setupLinks,
916  uint8_t apNegSupport,
917  const std::string& dlTidToLinkMapping,
918  const std::string& ulTidToLinkMapping)
919  : MultiLinkOperationsTestBase("Check correctness of Multi-Link Setup", 1, baseParams),
920  m_setupLinks(setupLinks),
921  m_scanType(scanType),
922  m_nProbeResp(0),
923  m_apNegSupport(apNegSupport),
924  m_dlTidLinkMappingStr(dlTidToLinkMapping),
925  m_ulTidLinkMappingStr(ulTidToLinkMapping)
926 {
927 }
928 
929 void
931 {
933 
934  m_staMacs[0]->SetAttribute("ActiveProbing", BooleanValue(m_scanType == WifiScanType::ACTIVE));
935  m_apMac->GetEhtConfiguration()->SetAttribute(
936  "TidToLinkMappingNegSupport",
938  // For non-AP MLD, it does not make sense to set the negotiation type to 0 (unless the AP MLD
939  // also advertises 0) or 1 (the AP MLD is discarded if it advertises a support of 3)
940  auto staEhtConfig = m_staMacs[0]->GetEhtConfiguration();
941  staEhtConfig->SetAttribute("TidToLinkMappingNegSupport",
943  staEhtConfig->SetAttribute("TidToLinkMappingDl", StringValue(m_dlTidLinkMappingStr));
944  staEhtConfig->SetAttribute("TidToLinkMappingUl", StringValue(m_ulTidLinkMappingStr));
945 
946  // the negotiated link mapping matches the one configured in EHT configuration, unless
947  // the AP MLD does not support TID-to-link mapping negotiation or the AP MLD supports
948  // the negotiation type 1 and the non-AP MLD is configured with a link mapping that
949  // maps distinct link sets to the TIDs, in which case the default link mapping is used
950  m_dlTidLinkMapping = staEhtConfig->GetTidLinkMapping(WifiDirection::DOWNLINK);
951  m_ulTidLinkMapping = staEhtConfig->GetTidLinkMapping(WifiDirection::UPLINK);
952 
953  if (m_apNegSupport == 0 ||
954  (m_apNegSupport == 1 &&
956  {
957  m_dlTidLinkMapping.clear(); // default link mapping
958  m_ulTidLinkMapping.clear(); // default link mapping
959  }
960 
961  // find (if any) a TID that is not mapped to all setup links
962  using TupleRefs = std::tuple<std::reference_wrapper<const WifiTidLinkMapping>,
963  std::reference_wrapper<uint8_t>,
964  std::reference_wrapper<std::optional<uint8_t>>,
965  Ptr<WifiMac>>;
966  for (auto& [mappingRef, tid1Ref, tid2Ref, mac] :
968  TupleRefs{m_ulTidLinkMapping, m_ulTid1, m_ulTid2, m_staMacs[0]}})
969  {
970  tid1Ref.get() = 0;
971  for (uint8_t tid1 = 0; tid1 < 8; tid1++)
972  {
973  if (auto it1 = mappingRef.get().find(tid1);
974  it1 != mappingRef.get().cend() && it1->second.size() != m_setupLinks.size())
975  {
976  // found. Now search for another TID with a disjoint mapped link set
977  for (uint8_t tid2 = tid1 + 1; tid2 < 8; tid2++)
978  {
979  if (auto it2 = mappingRef.get().find(tid2);
980  it2 != mappingRef.get().cend() && it2->second.size() != m_setupLinks.size())
981  {
982  std::list<uint8_t> intersection;
983  std::set_intersection(it1->second.cbegin(),
984  it1->second.cend(),
985  it2->second.cbegin(),
986  it2->second.cend(),
987  std::back_inserter(intersection));
988  if (intersection.empty())
989  {
990  // found a second TID
991  tid2Ref.get() = tid2;
992  break;
993  }
994  }
995  }
996  tid1Ref.get() = tid1;
997  break;
998  }
999  }
1000 
1001  std::list<uint8_t> tids = {tid1Ref.get()};
1002  if (tid2Ref.get())
1003  {
1004  tids.emplace_back(*tid2Ref.get());
1005  }
1006 
1007  // prevent aggregation of MPDUs
1008  for (auto tid : tids)
1009  {
1010  std::string attrName;
1011  switch (QosUtilsMapTidToAc(tid))
1012  {
1013  case AC_VI:
1014  attrName = "VI_MaxAmpduSize";
1015  break;
1016  case AC_VO:
1017  attrName = "VO_MaxAmpduSize";
1018  break;
1019  case AC_BE:
1020  attrName = "BE_MaxAmpduSize";
1021  break;
1022  case AC_BK:
1023  attrName = "BK_MaxAmpduSize";
1024  break;
1025  default:
1026  NS_FATAL_ERROR("Invalid TID " << +tid);
1027  }
1028 
1029  mac->SetAttribute(attrName, UintegerValue(100));
1030  }
1031  }
1032 }
1033 
1034 void
1036 {
1037  // DL traffic
1038  {
1039  PacketSocketAddress sockAddr;
1040  sockAddr.SetSingleDevice(m_apMac->GetDevice()->GetIfIndex());
1041  sockAddr.SetPhysicalAddress(m_staMacs[0]->GetDevice()->GetAddress());
1042  sockAddr.SetProtocol(1);
1043 
1045  GetApplication(sockAddr, m_setupLinks.size(), 500, Seconds(0), m_dlTid1));
1046  if (m_dlTid2)
1047  {
1049  GetApplication(sockAddr, m_setupLinks.size(), 500, Seconds(0), *m_dlTid2));
1050  }
1051  }
1052 
1053  // UL Traffic
1054  {
1055  PacketSocketAddress sockAddr;
1056  sockAddr.SetSingleDevice(m_staMacs[0]->GetDevice()->GetIfIndex());
1058  sockAddr.SetProtocol(1);
1059 
1060  m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
1061  GetApplication(sockAddr, m_setupLinks.size(), 500, MilliSeconds(500), m_ulTid1));
1062  if (m_ulTid2)
1063  {
1064  m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
1065  GetApplication(sockAddr, m_setupLinks.size(), 500, MilliSeconds(500), *m_ulTid2));
1066  }
1067  }
1068 }
1069 
1070 void
1072 {
1073  Simulator::Schedule(MilliSeconds(500), &MultiLinkSetupTest::CheckMlSetup, this);
1074 
1075  Simulator::Stop(m_duration);
1076  Simulator::Run();
1077 
1081  std::size_t index = 0;
1082 
1083  for (const auto& frameInfo : m_txPsdus)
1084  {
1085  const auto& mpdu = *frameInfo.psduMap.begin()->second->begin();
1086  const auto& linkId = frameInfo.linkId;
1087 
1088  switch (mpdu->GetHeader().GetType())
1089  {
1090  case WIFI_MAC_MGT_BEACON:
1091  CheckBeacon(mpdu, linkId);
1092  break;
1093 
1095  CheckProbeResponse(mpdu, linkId);
1096  m_nProbeResp++;
1097  break;
1098 
1100  CheckAssocRequest(mpdu, linkId);
1101  break;
1102 
1104  CheckAssocResponse(mpdu, linkId);
1105  break;
1106 
1107  case WIFI_MAC_QOSDATA:
1108  CheckQosData(mpdu, linkId, index);
1109  break;
1110 
1111  default:
1112  break;
1113  }
1114 
1115  index++;
1116  }
1117 
1119 
1120  std::size_t expectedProbeResp = 0;
1121  if (m_scanType == WifiScanType::ACTIVE)
1122  {
1123  // the number of Probe Response frames that we expect to receive in active mode equals
1124  // the number of channels in common between AP MLD and non-AP MLD at initialization
1125  for (const auto& staChannel : m_staChannels)
1126  {
1127  for (const auto& apChannel : m_apChannels)
1128  {
1129  if (staChannel == apChannel)
1130  {
1131  expectedProbeResp++;
1132  break;
1133  }
1134  }
1135  }
1136  }
1137 
1138  NS_TEST_EXPECT_MSG_EQ(m_nProbeResp, expectedProbeResp, "Unexpected number of Probe Responses");
1139 
1140  std::size_t expectedRxDlPkts = m_setupLinks.size();
1141  if (m_dlTid2)
1142  {
1143  expectedRxDlPkts *= 2;
1144  }
1145  NS_TEST_EXPECT_MSG_EQ(m_rxPkts[m_staMacs[0]->GetDevice()->GetNode()->GetId()],
1146  expectedRxDlPkts,
1147  "Unexpected number of DL packets received");
1148 
1149  std::size_t expectedRxUlPkts = m_setupLinks.size();
1150  if (m_ulTid2)
1151  {
1152  expectedRxUlPkts *= 2;
1153  }
1155  expectedRxUlPkts,
1156  "Unexpected number of UL packets received");
1157 
1158  Simulator::Destroy();
1159 }
1160 
1161 void
1163 {
1164  NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_BEACON);
1165 
1166  CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::DL);
1167 
1168  NS_TEST_EXPECT_MSG_EQ(m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1169  mpdu->GetHeader().GetAddr2(),
1170  "TA of Beacon frame is not the address of the link it is transmitted on");
1171  MgtBeaconHeader beacon;
1172  mpdu->GetPacket()->PeekHeader(beacon);
1173  const auto& rnr = beacon.Get<ReducedNeighborReport>();
1174  const auto& mle = beacon.Get<MultiLinkElement>();
1175 
1176  if (m_apMac->GetNLinks() == 1)
1177  {
1178  NS_TEST_EXPECT_MSG_EQ(rnr.has_value(),
1179  false,
1180  "RNR Element in Beacon frame from single link AP");
1181  NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1182  false,
1183  "Multi-Link Element in Beacon frame from single link AP");
1184  return;
1185  }
1186 
1187  NS_TEST_EXPECT_MSG_EQ(rnr.has_value(), true, "No RNR Element in Beacon frame");
1188  // All the other APs affiliated with the same AP MLD as the AP sending
1189  // the Beacon frame must be reported in a separate Neighbor AP Info field
1190  NS_TEST_EXPECT_MSG_EQ(rnr->GetNNbrApInfoFields(),
1191  static_cast<std::size_t>(m_apMac->GetNLinks() - 1),
1192  "Unexpected number of Neighbor AP Info fields in RNR");
1193  for (std::size_t nbrApInfoId = 0; nbrApInfoId < rnr->GetNNbrApInfoFields(); nbrApInfoId++)
1194  {
1195  NS_TEST_EXPECT_MSG_EQ(rnr->HasMldParameters(nbrApInfoId),
1196  true,
1197  "MLD Parameters not present");
1198  NS_TEST_EXPECT_MSG_EQ(rnr->GetNTbttInformationFields(nbrApInfoId),
1199  1,
1200  "Expected only one TBTT Info subfield per Neighbor AP Info");
1201  uint8_t nbrLinkId = rnr->GetLinkId(nbrApInfoId, 0);
1202  NS_TEST_EXPECT_MSG_EQ(rnr->GetBssid(nbrApInfoId, 0),
1203  m_apMac->GetFrameExchangeManager(nbrLinkId)->GetAddress(),
1204  "BSSID advertised in Neighbor AP Info field "
1205  << nbrApInfoId
1206  << " does not match the address configured on the link "
1207  "advertised in the same field");
1208  }
1209 
1210  NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Beacon frame");
1211  NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1212  m_apMac->GetAddress(),
1213  "Incorrect MLD address advertised in Multi-Link Element");
1214  NS_TEST_EXPECT_MSG_EQ(mle->GetLinkIdInfo(),
1215  +linkId,
1216  "Incorrect Link ID advertised in Multi-Link Element");
1217 }
1218 
1219 void
1221 {
1222  NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_PROBE_RESPONSE);
1223 
1224  CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::DL);
1225 
1227  m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1228  mpdu->GetHeader().GetAddr2(),
1229  "TA of Probe Response is not the address of the link it is transmitted on");
1230  MgtProbeResponseHeader probeResp;
1231  mpdu->GetPacket()->PeekHeader(probeResp);
1232  const auto& rnr = probeResp.Get<ReducedNeighborReport>();
1233  const auto& mle = probeResp.Get<MultiLinkElement>();
1234 
1235  if (m_apMac->GetNLinks() == 1)
1236  {
1237  NS_TEST_EXPECT_MSG_EQ(rnr.has_value(),
1238  false,
1239  "RNR Element in Probe Response frame from single link AP");
1240  NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1241  false,
1242  "Multi-Link Element in Probe Response frame from single link AP");
1243  return;
1244  }
1245 
1246  NS_TEST_EXPECT_MSG_EQ(rnr.has_value(), true, "No RNR Element in Probe Response frame");
1247  // All the other APs affiliated with the same AP MLD as the AP sending
1248  // the Probe Response frame must be reported in a separate Neighbor AP Info field
1249  NS_TEST_EXPECT_MSG_EQ(rnr->GetNNbrApInfoFields(),
1250  static_cast<std::size_t>(m_apMac->GetNLinks() - 1),
1251  "Unexpected number of Neighbor AP Info fields in RNR");
1252  for (std::size_t nbrApInfoId = 0; nbrApInfoId < rnr->GetNNbrApInfoFields(); nbrApInfoId++)
1253  {
1254  NS_TEST_EXPECT_MSG_EQ(rnr->HasMldParameters(nbrApInfoId),
1255  true,
1256  "MLD Parameters not present");
1257  NS_TEST_EXPECT_MSG_EQ(rnr->GetNTbttInformationFields(nbrApInfoId),
1258  1,
1259  "Expected only one TBTT Info subfield per Neighbor AP Info");
1260  uint8_t nbrLinkId = rnr->GetLinkId(nbrApInfoId, 0);
1261  NS_TEST_EXPECT_MSG_EQ(rnr->GetBssid(nbrApInfoId, 0),
1262  m_apMac->GetFrameExchangeManager(nbrLinkId)->GetAddress(),
1263  "BSSID advertised in Neighbor AP Info field "
1264  << nbrApInfoId
1265  << " does not match the address configured on the link "
1266  "advertised in the same field");
1267  }
1268 
1269  NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Probe Response frame");
1270  NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1271  m_apMac->GetAddress(),
1272  "Incorrect MLD address advertised in Multi-Link Element");
1273  NS_TEST_EXPECT_MSG_EQ(mle->GetLinkIdInfo(),
1274  +linkId,
1275  "Incorrect Link ID advertised in Multi-Link Element");
1276 }
1277 
1278 void
1280 {
1281  NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_ASSOCIATION_REQUEST);
1282 
1283  CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::UL);
1284 
1286  m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress(),
1287  mpdu->GetHeader().GetAddr2(),
1288  "TA of Assoc Request frame is not the address of the link it is transmitted on");
1289  MgtAssocRequestHeader assoc;
1290  mpdu->GetPacket()->PeekHeader(assoc);
1291  const auto& mle = assoc.Get<MultiLinkElement>();
1292 
1293  if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetNLinks() == 1)
1294  {
1295  NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1296  false,
1297  "Multi-Link Element in Assoc Request frame from single link STA");
1298  }
1299  else
1300  {
1301  NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1302  true,
1303  "No Multi-Link Element in Assoc Request frame");
1304  NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1305  m_staMacs[0]->GetAddress(),
1306  "Incorrect MLD Address advertised in Multi-Link Element");
1308  mle->GetNPerStaProfileSubelements(),
1309  m_setupLinks.size() - 1,
1310  "Incorrect number of Per-STA Profile subelements in Multi-Link Element");
1311  for (std::size_t i = 0; i < mle->GetNPerStaProfileSubelements(); i++)
1312  {
1313  auto& perStaProfile = mle->GetPerStaProfile(i);
1314  NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasStaMacAddress(),
1315  true,
1316  "Per-STA Profile must contain STA MAC address");
1317  // find ID of the local link corresponding to this subelement
1318  auto staLinkId = m_staMacs[0]->GetLinkIdByAddress(perStaProfile.GetStaMacAddress());
1320  staLinkId.has_value(),
1321  true,
1322  "No link found with the STA MAC address advertised in Per-STA Profile");
1324  +staLinkId.value(),
1325  +linkId,
1326  "The STA that sent the Assoc Request should not be included in a Per-STA Profile");
1327  auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), staLinkId.value());
1328  NS_TEST_EXPECT_MSG_EQ((it != m_setupLinks.end()),
1329  true,
1330  "Not expecting to setup STA link ID " << +staLinkId.value());
1332  +staLinkId.value(),
1333  +perStaProfile.GetLinkId(),
1334  "Not expecting to request association to AP Link ID in Per-STA Profile");
1335  NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasAssocRequest(),
1336  true,
1337  "Missing Association Request in Per-STA Profile");
1338  }
1339  }
1340 
1341  const auto& tlm = assoc.Get<TidToLinkMapping>();
1342 
1343  // A TID-to-Link Mapping IE is included in the Association Request if and only if the AP MLD
1344  // and the non-AP MLD are performing ML setup (i.e., they both have multiple links) and the
1345  // AP MLD advertises a non-null negotiation support type
1346  if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetNLinks() == 1 || m_apNegSupport == 0)
1347  {
1348  NS_TEST_EXPECT_MSG_EQ(tlm.empty(),
1349  true,
1350  "Didn't expect a TID-to-Link Mapping IE in Assoc Request frame");
1351  }
1352  else
1353  {
1354  std::size_t expectedNTlm = (m_dlTidLinkMapping == m_ulTidLinkMapping ? 1 : 2);
1355 
1356  NS_TEST_ASSERT_MSG_EQ(tlm.size(),
1357  expectedNTlm,
1358  "Unexpected number of TID-to-Link Mapping IE in Assoc Request");
1359 
1360  // lambda to check content of TID-to-Link Mapping IE(s)
1361  auto checkTlm = [&](std::size_t tlmId, WifiDirection dir) {
1362  NS_TEST_EXPECT_MSG_EQ(+static_cast<uint8_t>(tlm[tlmId].m_control.direction),
1363  +static_cast<uint8_t>(dir),
1364  "Unexpected direction in TID-to-Link Mapping IE " << tlmId);
1365  auto& expectedMapping =
1366  (dir == WifiDirection::UPLINK ? m_ulTidLinkMapping : m_dlTidLinkMapping);
1367 
1368  NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].m_control.defaultMapping,
1369  expectedMapping.empty(),
1370  "Default Link Mapping bit not set correctly");
1371  NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].m_linkMapping.size(),
1372  expectedMapping.size(),
1373  "Unexpected number of Link Mapping Of TID n fields");
1374  for (uint8_t tid = 0; tid < 8; tid++)
1375  {
1376  if (auto it = expectedMapping.find(tid); it != expectedMapping.cend())
1377  {
1378  NS_TEST_EXPECT_MSG_EQ((tlm[tlmId].GetLinkMappingOfTid(tid) == it->second),
1379  true,
1380  "Unexpected link mapping for TID "
1381  << +tid << " direction " << dir);
1382  }
1383  else
1384  {
1385  NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].GetLinkMappingOfTid(tid).empty(),
1386  true,
1387  "Expecting no Link Mapping Of TID n field for TID "
1388  << +tid << " direction " << dir);
1389  }
1390  }
1391  };
1392 
1393  if (tlm.size() == 1)
1394  {
1395  checkTlm(0, WifiDirection::BOTH_DIRECTIONS);
1396  }
1397  else
1398  {
1399  std::size_t dlId = (tlm[0].m_control.direction == WifiDirection::DOWNLINK ? 0 : 1);
1400  std::size_t ulId = (dlId == 0 ? 1 : 0);
1401 
1402  checkTlm(dlId, WifiDirection::DOWNLINK);
1403  checkTlm(ulId, WifiDirection::UPLINK);
1404  }
1405  }
1406 }
1407 
1408 void
1410 {
1411  NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_ASSOCIATION_RESPONSE);
1412 
1413  CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::DL);
1414 
1416  m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1417  mpdu->GetHeader().GetAddr2(),
1418  "TA of Assoc Response frame is not the address of the link it is transmitted on");
1419  MgtAssocResponseHeader assoc;
1420  mpdu->GetPacket()->PeekHeader(assoc);
1421  const auto& mle = assoc.Get<MultiLinkElement>();
1422 
1423  if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetNLinks() == 1)
1424  {
1426  mle.has_value(),
1427  false,
1428  "Multi-Link Element in Assoc Response frame with single link AP or single link STA");
1429  return;
1430  }
1431 
1432  NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Assoc Request frame");
1433  NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1434  m_apMac->GetAddress(),
1435  "Incorrect MLD Address advertised in Multi-Link Element");
1436  NS_TEST_EXPECT_MSG_EQ(mle->GetNPerStaProfileSubelements(),
1437  m_setupLinks.size() - 1,
1438  "Incorrect number of Per-STA Profile subelements in Multi-Link Element");
1439  for (std::size_t i = 0; i < mle->GetNPerStaProfileSubelements(); i++)
1440  {
1441  auto& perStaProfile = mle->GetPerStaProfile(i);
1442  NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasStaMacAddress(),
1443  true,
1444  "Per-STA Profile must contain STA MAC address");
1445  // find ID of the local link corresponding to this subelement
1446  auto apLinkId = m_apMac->GetLinkIdByAddress(perStaProfile.GetStaMacAddress());
1448  apLinkId.has_value(),
1449  true,
1450  "No link found with the STA MAC address advertised in Per-STA Profile");
1451  NS_TEST_EXPECT_MSG_EQ(+apLinkId.value(),
1452  +perStaProfile.GetLinkId(),
1453  "Link ID and MAC address advertised in Per-STA Profile do not match");
1455  +apLinkId.value(),
1456  +linkId,
1457  "The AP that sent the Assoc Response should not be included in a Per-STA Profile");
1458  auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), apLinkId.value());
1459  NS_TEST_EXPECT_MSG_EQ((it != m_setupLinks.end()),
1460  true,
1461  "Not expecting to setup AP link ID " << +apLinkId.value());
1462  NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasAssocResponse(),
1463  true,
1464  "Missing Association Response in Per-STA Profile");
1465  }
1466 
1467  // For the moment, the AP MLD always accepts a valid TID-to-Link Mapping request, hence
1468  // in every case there is no TID-to-Link Mapping IE in the Association Response
1469  NS_TEST_EXPECT_MSG_EQ(assoc.Get<TidToLinkMapping>().empty(),
1470  true,
1471  "Didn't expect to find a TID-to-Link Mapping IE in Association Response");
1472 }
1473 
1474 void
1476 {
1480  NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->IsAssociated(), true, "Expected the STA to be associated");
1481 
1482  for (const auto linkId : m_setupLinks)
1483  {
1484  auto staLinkId = (m_staMacs[0]->GetNLinks() > 1 ? linkId : SINGLE_LINK_OP_ID);
1485  auto apLinkId = (m_apMac->GetNLinks() > 1 ? linkId : SINGLE_LINK_OP_ID);
1486 
1487  auto staAddr = m_staMacs[0]->GetFrameExchangeManager(staLinkId)->GetAddress();
1488  auto apAddr = m_apMac->GetFrameExchangeManager(apLinkId)->GetAddress();
1489 
1490  auto staRemoteMgr = m_staMacs[0]->GetWifiRemoteStationManager(staLinkId);
1491  auto apRemoteMgr = m_apMac->GetWifiRemoteStationManager(apLinkId);
1492 
1493  // STA side
1494  NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetFrameExchangeManager(staLinkId)->GetBssid(),
1495  apAddr,
1496  "Unexpected BSSID for STA link ID " << +staLinkId);
1497  if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1498  {
1499  NS_TEST_EXPECT_MSG_EQ((staRemoteMgr->GetMldAddress(apAddr) == m_apMac->GetAddress()),
1500  true,
1501  "Incorrect MLD address stored by STA on link ID " << +staLinkId);
1503  (staRemoteMgr->GetAffiliatedStaAddress(m_apMac->GetAddress()) == apAddr),
1504  true,
1505  "Incorrect affiliated address stored by STA on link ID " << +staLinkId);
1506  }
1507 
1508  // AP side
1509  NS_TEST_EXPECT_MSG_EQ(apRemoteMgr->IsAssociated(staAddr),
1510  true,
1511  "Expecting STA " << staAddr << " to be associated on link "
1512  << +apLinkId);
1513  if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1514  {
1516  (apRemoteMgr->GetMldAddress(staAddr) == m_staMacs[0]->GetAddress()),
1517  true,
1518  "Incorrect MLD address stored by AP on link ID " << +apLinkId);
1520  (apRemoteMgr->GetAffiliatedStaAddress(m_staMacs[0]->GetAddress()) == staAddr),
1521  true,
1522  "Incorrect affiliated address stored by AP on link ID " << +apLinkId);
1523  }
1524  auto aid = m_apMac->GetAssociationId(staAddr, apLinkId);
1525  const auto& staList = m_apMac->GetStaList(apLinkId);
1526  NS_TEST_EXPECT_MSG_EQ((staList.find(aid) != staList.end()),
1527  true,
1528  "STA " << staAddr << " not found in list of associated STAs");
1529 
1530  // STA of non-AP MLD operate on the same channel as the AP
1532  +m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetNumber(),
1534  "Incorrect operating channel number for STA on link " << +staLinkId);
1536  m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetFrequency(),
1538  "Incorrect operating channel frequency for STA on link " << +staLinkId);
1539  NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetWidth(),
1541  "Incorrect operating channel width for STA on link " << +staLinkId);
1543  +m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetPhyBand(),
1545  "Incorrect operating PHY band for STA on link " << +staLinkId);
1547  +m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetPrimaryChannelIndex(20),
1549  "Incorrect operating primary channel index for STA on link " << +staLinkId);
1550  }
1551 
1552  // lambda to check the link mapping stored at wifi MAC
1553  auto checkStoredMapping =
1554  [this](Ptr<WifiMac> mac, Ptr<WifiMac> dest, WifiDirection dir, bool present) {
1555  NS_TEST_ASSERT_MSG_EQ(mac->GetTidToLinkMapping(dest->GetAddress(), dir).has_value(),
1556  present,
1557  "Link mapping stored by "
1558  << (mac->GetTypeOfStation() == AP ? "AP" : "non-AP")
1559  << " MLD for " << dir << " direction "
1560  << (present ? "expected" : "not expected"));
1561  if (present)
1562  {
1563  const auto& mapping =
1564  (dir == WifiDirection::DOWNLINK ? m_dlTidLinkMapping : m_ulTidLinkMapping);
1566  (mac->GetTidToLinkMapping(dest->GetAddress(), dir)->get() == mapping),
1567  true,
1568  "Incorrect link mapping stored by "
1569  << (mac->GetTypeOfStation() == AP ? "AP" : "non-AP") << " MLD for " << dir
1570  << " direction");
1571  }
1572  };
1573 
1574  auto storedMapping =
1575  m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1 && m_apNegSupport > 0;
1576  checkStoredMapping(m_apMac, m_staMacs[0], WifiDirection::DOWNLINK, storedMapping);
1577  checkStoredMapping(m_apMac, m_staMacs[0], WifiDirection::UPLINK, storedMapping);
1578  checkStoredMapping(m_staMacs[0], m_apMac, WifiDirection::DOWNLINK, storedMapping);
1579  checkStoredMapping(m_staMacs[0], m_apMac, WifiDirection::UPLINK, storedMapping);
1580 }
1581 
1582 void
1584 {
1585  if (m_staMacs[0]->GetNLinks() == 1)
1586  {
1587  // no link is disabled on a single link device
1588  return;
1589  }
1590 
1591  for (const auto& linkId : m_staMacs[0]->GetLinkIds())
1592  {
1593  auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), linkId);
1594  if (it == m_setupLinks.end())
1595  {
1596  // the link has not been setup
1597  NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId)->GetState()->IsStateOff(),
1598  true,
1599  "Link " << +linkId << " has not been setup but is not disabled");
1600  continue;
1601  }
1602 
1603  // the link has been setup and must be active
1604  NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId)->GetState()->IsStateOff(),
1605  false,
1606  "Expecting link " << +linkId << " to be active");
1607  }
1608 }
1609 
1610 void
1611 MultiLinkSetupTest::CheckQosData(Ptr<WifiMpdu> mpdu, uint8_t linkId, std::size_t index)
1612 {
1614  const auto& hdr = mpdu->GetHeader();
1615 
1616  NS_TEST_ASSERT_MSG_EQ(hdr.IsQosData(), true, "Expected a QoS data frame");
1617 
1618  if (!hdr.IsToDs() && hdr.IsFromDs())
1619  {
1620  dir = WifiDirection::DOWNLINK;
1621  }
1622  else if (hdr.IsToDs() && !hdr.IsFromDs())
1623  {
1624  dir = WifiDirection::UPLINK;
1625  }
1626  else
1627  {
1628  NS_ABORT_MSG("Invalid combination for QoS data frame: ToDS(" << hdr.IsToDs() << ") FromDS("
1629  << hdr.IsFromDs() << ")");
1630  }
1631 
1632  const auto& tid1 = (dir == WifiDirection::DOWNLINK) ? m_dlTid1 : m_ulTid1;
1633  const auto& tid2 = (dir == WifiDirection::DOWNLINK) ? m_dlTid2 : m_ulTid2;
1634  uint8_t tid = hdr.GetQosTid();
1635 
1636  NS_TEST_ASSERT_MSG_NE((tid == tid1),
1637  (tid2.has_value() && tid == *tid2),
1638  "QoS frame with unexpected TID " << +tid);
1639 
1640  // lambda to find the link set the given TID is mapped to
1641  auto findLinkSet = [this, dir](uint8_t tid) -> std::set<uint8_t> {
1642  std::set<uint8_t> linkSet(m_setupLinks.cbegin(), m_setupLinks.cend());
1643  if (auto mappingOptRef = m_apMac->GetTidToLinkMapping(m_staMacs[0]->GetAddress(), dir))
1644  {
1645  // if the TID is not present in the mapping, it is mapped to all setup links
1646  if (auto it = mappingOptRef->get().find(tid); it != mappingOptRef->get().cend())
1647  {
1648  linkSet = it->second;
1649  NS_ASSERT_MSG(!linkSet.empty(), "TID " << +tid << " mapped to no link");
1650  }
1651  }
1652  return linkSet;
1653  };
1654 
1655  auto linkSet = findLinkSet(tid);
1656  auto& qosFrames = (tid == tid1) ? m_qosFrames1 : m_qosFrames2;
1657 
1658  // Let N the size of the link set, the first N QoS data frames are sent simultaneously
1659  // on the links of the set, the others (if any) will be sent afterwards on such links
1660 
1661  // number of concurrent frames of the same TID transmitted so far (excluding current frame)
1662  std::size_t nConcurFrames = std::min(qosFrames.size(), linkSet.size());
1663 
1664  // iterate over the concurrent frames of the same TID transmitted so far
1665  for (std::size_t i = 0; i < nConcurFrames; i++)
1666  {
1667  auto prev = qosFrames[i];
1668 
1669  // TX duration of i-th frame
1670  auto band = m_apMac->GetWifiPhy(m_txPsdus[prev].linkId)->GetPhyBand();
1671  Time txDuration =
1672  WifiPhy::CalculateTxDuration(m_txPsdus[prev].psduMap, m_txPsdus[prev].txVector, band);
1673 
1674  // the current frame is transmitted concurrently with this previous frame if it is
1675  // within the first N (size of the link set) frames, otherwise it is transmitted after
1676  // this previous frame if they have been transmitted on the same link
1677  if (qosFrames.size() < linkSet.size())
1678  {
1679  // the current frame can be sent concurrently with this previous frame
1680  NS_TEST_EXPECT_MSG_LT(m_txPsdus[index].startTx,
1681  m_txPsdus[prev].startTx + txDuration,
1682  "The " << dir << " QoS frame number " << qosFrames.size()
1683  << " was not sent concurrently with others on link "
1684  << +linkId << " which TID " << +tid << " is mapped to");
1685  }
1686  else if (m_txPsdus[prev].linkId == linkId)
1687  {
1688  // the current frame is sent afterwards
1689  NS_TEST_EXPECT_MSG_GT(m_txPsdus[index].startTx,
1690  m_txPsdus[prev].startTx + txDuration,
1691  "The " << dir << " QoS frame number " << qosFrames.size()
1692  << " was sent concurrently with others on a link "
1693  << +linkId << " which TID " << +tid << " is mapped to");
1694  }
1695  }
1696 
1697  if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1698  {
1699  NS_TEST_EXPECT_MSG_EQ(std::count(linkSet.cbegin(), linkSet.cend(), linkId),
1700  1,
1701  "QoS frame sent on Link ID "
1702  << +linkId << " that does not belong to the link set of TID "
1703  << +tid);
1704  }
1705 
1706  if (tid2)
1707  {
1708  // QoS frames of two distinct TIDs are sent.
1709  auto otherTid = (tid == tid1) ? *tid2 : tid1;
1710  const auto& otherQosFrames = (tid == tid1) ? m_qosFrames2 : m_qosFrames1;
1711  auto otherLinkSet = findLinkSet(otherTid);
1712 
1713  // number of concurrent frames of the other TID transmitted so far
1714  std::size_t nOtherConcurFrames = std::min(otherQosFrames.size(), otherLinkSet.size());
1715 
1716  // iterate over the concurrent frames of the other TID
1717  for (std::size_t i = 0; i < nOtherConcurFrames; i++)
1718  {
1719  auto prev = otherQosFrames[i];
1720 
1721  // TX duration of i-th frame
1722  auto band = m_apMac->GetWifiPhy(m_txPsdus[prev].linkId)->GetPhyBand();
1723  Time txDuration = WifiPhy::CalculateTxDuration(m_txPsdus[prev].psduMap,
1724  m_txPsdus[prev].txVector,
1725  band);
1726 
1727  // the current frame is transmitted concurrently with this previous frame of the
1728  // other TID if it is within the first N (size of the link set) frames of its TID
1729  if (qosFrames.size() < linkSet.size())
1730  {
1731  // the current frame can be sent concurrently with this previous frame
1732  NS_TEST_EXPECT_MSG_LT(m_txPsdus[index].startTx,
1733  m_txPsdus[prev].startTx + txDuration,
1734  "The " << dir << " QoS frame number " << qosFrames.size()
1735  << " was not sent concurrently with others with TID "
1736  << +otherTid);
1737  }
1738  }
1739  }
1740 
1741  // insert the frame
1742  qosFrames.emplace_back(index);
1743 
1744  if (qosFrames.size() == m_setupLinks.size())
1745  {
1746  qosFrames.clear();
1747  }
1748 }
1749 
1753 enum class WifiTrafficPattern : uint8_t
1754 {
1755  STA_TO_STA = 0,
1756  STA_TO_AP,
1757  AP_TO_STA,
1758  AP_TO_BCAST,
1759  STA_TO_BCAST
1760 };
1761 
1765 enum class WifiBaEnabled : uint8_t
1766 {
1767  NO = 0,
1768  YES
1769 };
1770 
1774 enum class WifiUseBarAfterMissedBa : uint8_t
1775 {
1776  NO = 0,
1777  YES
1778 };
1779 
1809 {
1810  public:
1821  MultiLinkTxTest(const BaseParams& baseParams,
1822  WifiTrafficPattern trafficPattern,
1823  WifiBaEnabled baEnabled,
1824  WifiUseBarAfterMissedBa useBarAfterMissedBa,
1825  uint8_t nMaxInflight);
1826  ~MultiLinkTxTest() override = default;
1827 
1828  protected:
1837  void CheckBlockAck(Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId);
1838 
1839  void Transmit(Ptr<WifiMac> mac,
1840  uint8_t phyId,
1841  WifiConstPsduMap psduMap,
1842  WifiTxVector txVector,
1843  double txPowerW) override;
1844  void DoSetup() override;
1845  void DoRun() override;
1846 
1847  private:
1848  void StartTraffic() override;
1849 
1851  using RxErrorModelMap = std::unordered_map<Mac48Address, Ptr<ListErrorModel>, WifiAddressHash>;
1852 
1854  std::list<uint64_t> m_uidList;
1855  bool m_dataCorrupted{false};
1859  std::size_t m_nMaxInflight;
1860  std::size_t m_nPackets;
1861  std::size_t m_blockAckCount{0};
1862  std::size_t m_blockAckReqCount{0};
1863  std::map<uint16_t, std::size_t> m_inflightCount;
1866 };
1867 
1869  WifiTrafficPattern trafficPattern,
1870  WifiBaEnabled baEnabled,
1871  WifiUseBarAfterMissedBa useBarAfterMissedBa,
1872  uint8_t nMaxInflight)
1874  std::string("Check data transmission between MLDs ") +
1875  (baEnabled == WifiBaEnabled::YES
1876  ? (useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES
1877  ? "with BA agreement, send BAR after BlockAck timeout"
1878  : "with BA agreement, send Data frames after BlockAck timeout")
1879  : "without BA agreement") +
1880  " (Traffic pattern: " + std::to_string(static_cast<uint8_t>(trafficPattern)) +
1881  (baEnabled == WifiBaEnabled::YES ? ", nMaxInflight=" + std::to_string(nMaxInflight)
1882  : "") +
1883  ")",
1884  2,
1885  baseParams),
1886  m_trafficPattern(trafficPattern),
1887  m_baEnabled(baEnabled == WifiBaEnabled::YES),
1888  m_useBarAfterMissedBa(useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES),
1889  m_nMaxInflight(nMaxInflight),
1890  m_nPackets(trafficPattern == WifiTrafficPattern::STA_TO_BCAST ||
1891  trafficPattern == WifiTrafficPattern::STA_TO_STA
1892  ? 4
1893  : 8)
1894 {
1895 }
1896 
1897 void
1899  uint8_t phyId,
1900  WifiConstPsduMap psduMap,
1901  WifiTxVector txVector,
1902  double txPowerW)
1903 {
1904  MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
1905  auto linkId = m_txPsdus.back().linkId;
1906 
1907  auto psdu = psduMap.begin()->second;
1908 
1909  switch (psdu->GetHeader(0).GetType())
1910  {
1911  case WIFI_MAC_MGT_ACTION:
1912  // a management frame is a DL frame if TA equals BSSID
1913  CheckAddresses(psdu,
1914  psdu->GetHeader(0).GetAddr2() == psdu->GetHeader(0).GetAddr3() ? DL : UL);
1915  if (!m_baEnabled)
1916  {
1917  // corrupt all management action frames (ADDBA Request frames) to prevent
1918  // the establishment of a BA agreement
1919  m_uidList.push_front(psdu->GetPacket()->GetUid());
1920  m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1921  NS_LOG_INFO("CORRUPTED");
1922  }
1923  break;
1924  case WIFI_MAC_QOSDATA:
1925  CheckAddresses(psdu);
1926 
1927  for (const auto& mpdu : *psdu)
1928  {
1929  // determine the max number of simultaneous transmissions for this MPDU
1930  // (only if sent by the traffic source and this is not a broadcast frame)
1931  if (m_baEnabled && m_sourceMac->GetLinkIds().count(linkId) == 1 &&
1932  m_sourceMac->GetFrameExchangeManager(linkId)->GetAddress() ==
1933  mpdu->GetHeader().GetAddr2() &&
1934  !mpdu->GetHeader().GetAddr1().IsGroup())
1935  {
1936  auto seqNo = mpdu->GetHeader().GetSequenceNumber();
1937  auto [it, success] =
1938  m_inflightCount.insert({seqNo, mpdu->GetInFlightLinkIds().size()});
1939  if (!success)
1940  {
1941  it->second = std::max(it->second, mpdu->GetInFlightLinkIds().size());
1942  }
1943  }
1944  }
1945  for (std::size_t i = 0; i < psdu->GetNMpdus(); i++)
1946  {
1947  // corrupt QoS data frame with sequence number equal to 1 (only once) if we are
1948  // not in the AP to broadcast traffic pattern (broadcast frames are not retransmitted)
1949  // nor in the STA to broadcast or STA to STA traffic patterns (retransmissions from
1950  // STA 1 could collide with frames forwarded by the AP)
1951  if (psdu->GetHeader(i).GetSequenceNumber() != 1 ||
1955  {
1956  continue;
1957  }
1958  auto uid = psdu->GetPayload(i)->GetUid();
1959  if (!m_dataCorrupted)
1960  {
1961  m_uidList.push_front(uid);
1962  m_dataCorrupted = true;
1963  NS_LOG_INFO("CORRUPTED");
1964  m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1965  }
1966  else
1967  {
1968  // do not corrupt the QoS data frame anymore
1969  if (auto it = std::find(m_uidList.cbegin(), m_uidList.cend(), uid);
1970  it != m_uidList.cend())
1971  {
1972  m_uidList.erase(it);
1973  }
1974  m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1975  }
1976  break;
1977  }
1978  break;
1979  case WIFI_MAC_CTL_BACKRESP: {
1980  // ignore BlockAck frames not addressed to the source of the application packets
1981  if (!m_sourceMac->GetLinkIdByAddress(psdu->GetHeader(0).GetAddr1()))
1982  {
1983  break;
1984  }
1985  if (m_nMaxInflight > 1)
1986  {
1987  // we do not check the content of BlockAck when m_nMaxInflight is greater than 1
1988  break;
1989  }
1990  CheckBlockAck(psdu, txVector, linkId);
1991  m_blockAckCount++;
1992  if (m_blockAckCount == 2)
1993  {
1994  // corrupt the second BlockAck frame to simulate a missed BlockAck
1995  m_uidList.push_front(psdu->GetPacket()->GetUid());
1996  NS_LOG_INFO("CORRUPTED");
1997  m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1998  }
1999  break;
2000  case WIFI_MAC_CTL_BACKREQ:
2001  // ignore BlockAckReq frames not transmitted by the source of the application packets
2002  if (m_sourceMac->GetLinkIdByAddress(psdu->GetHeader(0).GetAddr2()))
2003  {
2005  }
2006  break;
2007  }
2008  default:;
2009  }
2010 }
2011 
2012 void
2014  const WifiTxVector& txVector,
2015  uint8_t linkId)
2016 {
2017  NS_TEST_ASSERT_MSG_EQ(m_baEnabled, true, "No BlockAck expected without BA agreement");
2019  true,
2020  "No BlockAck expected in AP to broadcast traffic pattern");
2021 
2022  /*
2023  * ┌───────┬───────X ┌───────┐
2024  * link 0 │ 0 │ 1 │ │ 1 │
2025  * ───────┴───────┴───────┴┬──┬────┴───────┴┬───┬────────────────────────
2026  * │BA│ │ACK│
2027  * └──┘ └───┘
2028  * ┌───────┬───────┐ ┌───────┬───────┐
2029  * link 1 │ 2 │ 3 │ │ 2 │ 3 │
2030  * ────────────────────┴───────┴───────┴┬──X───┴───────┴───────┴┬──┬─────
2031  * │BA│ │BA│
2032  * └──┘ └──┘
2033  */
2034  auto mpdu = *psdu->begin();
2035  CtrlBAckResponseHeader blockAck;
2036  mpdu->GetPacket()->PeekHeader(blockAck);
2037  bool isMpdu1corrupted = (m_trafficPattern == WifiTrafficPattern::STA_TO_AP ||
2039 
2040  switch (m_blockAckCount)
2041  {
2042  case 0: // first BlockAck frame (all traffic patterns)
2044  true,
2045  "MPDU 0 expected to be successfully received");
2047  blockAck.IsPacketReceived(1),
2048  !isMpdu1corrupted,
2049  "MPDU 1 expected to be received only in STA_TO_STA/STA_TO_BCAST scenarios");
2050  // if there are at least two links setup, we expect all MPDUs to be inflight
2051  // (on distinct links)
2052  if (m_staMacs[0]->GetSetupLinkIds().size() > 1)
2053  {
2054  auto queue = m_sourceMac->GetTxopQueue(AC_BE);
2055  auto rcvMac = m_sourceMac == m_staMacs[0] ? StaticCast<WifiMac>(m_apMac)
2056  : StaticCast<WifiMac>(m_staMacs[1]);
2057  auto item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress());
2058  std::size_t nQueuedPkt = 0;
2059  auto delay = WifiPhy::CalculateTxDuration(psdu,
2060  txVector,
2061  rcvMac->GetWifiPhy(linkId)->GetPhyBand()) +
2062  MicroSeconds(1); // to account for propagation delay
2063 
2064  while (item)
2065  {
2066  auto seqNo = item->GetHeader().GetSequenceNumber();
2067  NS_TEST_EXPECT_MSG_EQ(item->IsInFlight(),
2068  true,
2069  "MPDU with seqNo=" << seqNo << " is not in flight");
2070  auto linkIds = item->GetInFlightLinkIds();
2071  NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
2072  1,
2073  "MPDU with seqNo=" << seqNo
2074  << " is in flight on multiple links");
2075  // The first two MPDUs are in flight on the same link on which the BlockAck
2076  // is sent. The other two MPDUs (only for AP to STA/STA to AP scenarios) are
2077  // in flight on a different link.
2078  auto srcLinkId = m_sourceMac->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1());
2079  NS_TEST_ASSERT_MSG_EQ(srcLinkId.has_value(),
2080  true,
2081  "Addr1 of BlockAck is not an originator's link address");
2082  NS_TEST_EXPECT_MSG_EQ((*linkIds.begin() == *srcLinkId),
2083  (seqNo <= 1),
2084  "MPDU with seqNo=" << seqNo
2085  << " in flight on unexpected link");
2086  // check the Retry subfield and whether this MPDU is still queued
2087  // after the originator has processed this BlockAck
2088 
2089  // MPDUs acknowledged via this BlockAck are no longer queued
2090  bool isQueued = (seqNo > (isMpdu1corrupted ? 0 : 1));
2091  // The Retry subfield is set if the MPDU has not been acknowledged (i.e., it
2092  // is still queued) and has been transmitted on the same link as the BlockAck
2093  // (i.e., its sequence number is less than or equal to 1)
2094  bool isRetry = isQueued && seqNo <= 1;
2095 
2096  Simulator::Schedule(delay, [this, item, isQueued, isRetry]() {
2097  NS_TEST_EXPECT_MSG_EQ(item->IsQueued(),
2098  isQueued,
2099  "MPDU with seqNo="
2100  << item->GetHeader().GetSequenceNumber() << " should "
2101  << (isQueued ? "" : "not") << " be queued");
2103  item->GetHeader().IsRetry(),
2104  isRetry,
2105  "Unexpected value for the Retry subfield of the MPDU with seqNo="
2106  << item->GetHeader().GetSequenceNumber());
2107  });
2108 
2109  nQueuedPkt++;
2110  item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress(), item);
2111  }
2112  // Each MPDU contains an A-MSDU consisting of two MSDUs
2113  NS_TEST_EXPECT_MSG_EQ(nQueuedPkt, m_nPackets / 2, "Unexpected number of queued MPDUs");
2114  }
2115  break;
2116  case 1: // second BlockAck frame (STA to AP and AP to STA traffic patterns only)
2117  case 2: // third BlockAck frame (STA to AP and AP to STA traffic patterns only)
2120  true,
2121  "Did not expect to receive a second BlockAck");
2122  // the second BlockAck is corrupted, but the data frames have been received successfully
2123  std::pair<uint16_t, uint16_t> seqNos;
2124  // if multiple links were setup, the transmission of the second A-MPDU started before
2125  // the end of the first one, so the second A-MPDU includes MPDUs with sequence numbers
2126  // 2 and 3. Otherwise, MPDU with sequence number 1 is retransmitted along with the MPDU
2127  // with sequence number 2.
2128  if (m_staMacs[0]->GetSetupLinkIds().size() > 1)
2129  {
2130  seqNos = {2, 3};
2131  }
2132  else
2133  {
2134  seqNos = {1, 2};
2135  }
2136  NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(seqNos.first),
2137  true,
2138  "MPDU " << seqNos.first << " expected to be successfully received");
2139  NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(seqNos.second),
2140  true,
2141  "MPDU " << seqNos.second << " expected to be successfully received");
2142  break;
2143  }
2144 }
2145 
2146 void
2148 {
2150 
2151  if (m_baEnabled)
2152  {
2153  // Enable A-MSDU aggregation. Max A-MSDU size is set such that two MSDUs can be aggregated
2154  for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
2155  {
2156  mac->SetAttribute("BE_MaxAmsduSize", UintegerValue(2100));
2157  mac->GetQosTxop(AC_BE)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2159  mac->GetQosTxop(AC_BE)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2160  }
2161  }
2162 
2163  // install post reception error model on all devices
2164  for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
2165  {
2166  auto errorModel = CreateObject<ListErrorModel>();
2167  m_errorModels[m_apMac->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2168  m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2169  }
2170  for (std::size_t i : {0, 1})
2171  {
2172  for (const auto linkId : m_staMacs[i]->GetLinkIds())
2173  {
2174  auto errorModel = CreateObject<ListErrorModel>();
2175  m_errorModels[m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2176  m_staMacs[i]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2177  }
2178  }
2179 }
2180 
2181 void
2183 {
2184  Address destAddr;
2185 
2186  switch (m_trafficPattern)
2187  {
2189  m_sourceMac = m_staMacs[0];
2190  destAddr = m_staMacs[1]->GetDevice()->GetAddress();
2191  break;
2193  m_sourceMac = m_staMacs[0];
2194  destAddr = m_apMac->GetDevice()->GetAddress();
2195  break;
2197  m_sourceMac = m_apMac;
2198  destAddr = m_staMacs[1]->GetDevice()->GetAddress();
2199  break;
2201  m_sourceMac = m_apMac;
2202  destAddr = Mac48Address::GetBroadcast();
2203  break;
2205  m_sourceMac = m_staMacs[0];
2206  destAddr = Mac48Address::GetBroadcast();
2207  break;
2208  }
2209 
2210  PacketSocketAddress sockAddr;
2212  sockAddr.SetPhysicalAddress(destAddr);
2213  sockAddr.SetProtocol(1);
2214 
2215  // install first client application generating at most 4 packets
2217  GetApplication(sockAddr, std::min<std::size_t>(m_nPackets, 4), 1000));
2218 
2219  if (m_nPackets > 4)
2220  {
2221  // install a second client application generating the remaining packets and
2222  // starting during transmission of first A-MPDU, if multiple links are setup
2224  GetApplication(sockAddr, m_nPackets - 4, 1000, MilliSeconds(4)));
2225  }
2226 
2227  Simulator::Stop(m_duration);
2228 }
2229 
2230 void
2232 {
2233  Simulator::Run();
2234 
2235  // Expected number of packets received by each node (AP, STA 0, STA 1) at application layer
2236  std::array<std::size_t, 3> expectedRxPkts{};
2237 
2238  switch (m_trafficPattern)
2239  {
2242  // only STA 1 receives the m_nPackets packets that have been transmitted
2243  expectedRxPkts[2] = m_nPackets;
2244  break;
2246  // only the AP receives the m_nPackets packets that have been transmitted
2247  expectedRxPkts[0] = m_nPackets;
2248  break;
2250  // the AP replicates the broadcast frames on all the links, hence each station
2251  // receives the m_nPackets packets N times, where N is the number of setup link
2252  expectedRxPkts[1] = m_nPackets * m_staMacs[0]->GetSetupLinkIds().size();
2253  expectedRxPkts[2] = m_nPackets * m_staMacs[1]->GetSetupLinkIds().size();
2254  break;
2256  // the AP receives the m_nPackets packets and then replicates them on all the links,
2257  // hence STA 1 receives m_nPackets packets N times, where N is the number of setup link
2258  expectedRxPkts[0] = m_nPackets;
2259  expectedRxPkts[2] = m_nPackets * m_staMacs[1]->GetSetupLinkIds().size();
2260  break;
2261  }
2262 
2264  +expectedRxPkts[0],
2265  "Unexpected number of packets received by the AP");
2267  +expectedRxPkts[1],
2268  "Unexpected number of packets received by STA 0");
2270  +expectedRxPkts[2],
2271  "Unexpected number of packets received by STA 1");
2272 
2273  // check that the expected number of BlockAck frames are transmitted
2274  if (m_baEnabled && m_nMaxInflight == 1)
2275  {
2276  std::size_t expectedBaCount = 0;
2277  std::size_t expectedBarCount = 0;
2278 
2279  switch (m_trafficPattern)
2280  {
2283  // two A-MPDUs are transmitted and one BlockAck is corrupted
2284  expectedBaCount = 3;
2285  // one BlockAckReq is sent if m_useBarAfterMissedBa is true
2286  expectedBarCount = m_useBarAfterMissedBa ? 1 : 0;
2287  break;
2290  // only one A-MPDU is transmitted and the BlockAck is not corrupted
2291  expectedBaCount = 1;
2292  break;
2293  default:;
2294  }
2296  expectedBaCount,
2297  "Unexpected number of BlockAck frames");
2299  expectedBarCount,
2300  "Unexpected number of BlockAckReq frames");
2301  }
2302 
2303  // check that setting the QosTxop::NMaxInflights attribute has the expected effect.
2304  // We do not support sending an MPDU multiple times concurrently without Block Ack
2305  // agreement. Also, broadcast frames are already duplicated and sent on all links.
2307  {
2309  m_inflightCount.size(),
2310  m_nPackets / 2,
2311  "Did not collect number of simultaneous transmissions for all data frames");
2312 
2313  auto nMaxInflight = std::min(m_nMaxInflight, m_staMacs[0]->GetSetupLinkIds().size());
2314  std::size_t maxCount = 0;
2315  for (const auto& [seqNo, count] : m_inflightCount)
2316  {
2318  count,
2319  nMaxInflight,
2320  "MPDU with seqNo=" << seqNo
2321  << " transmitted simultaneously more times than allowed");
2322  maxCount = std::max(maxCount, count);
2323  }
2324 
2326  maxCount,
2327  nMaxInflight,
2328  "Expected that at least one data frame was transmitted simultaneously a number of "
2329  "times equal to the NMaxInflights attribute");
2330  }
2331 
2332  Simulator::Destroy();
2333 }
2334 
2338 enum class WifiMuTrafficPattern : uint8_t
2339 {
2341  DL_MU_MU_BAR,
2343  UL_MU
2344 };
2345 
2370 {
2371  public:
2381  MultiLinkMuTxTest(const BaseParams& baseParams,
2382  WifiMuTrafficPattern muTrafficPattern,
2383  WifiUseBarAfterMissedBa useBarAfterMissedBa,
2384  uint8_t nMaxInflight);
2385  ~MultiLinkMuTxTest() override = default;
2386 
2387  protected:
2396  void CheckBlockAck(Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId);
2397 
2398  void Transmit(Ptr<WifiMac> mac,
2399  uint8_t phyId,
2400  WifiConstPsduMap psduMap,
2401  WifiTxVector txVector,
2402  double txPowerW) override;
2403  void DoSetup() override;
2404  void DoRun() override;
2405 
2406  private:
2407  void StartTraffic() override;
2408 
2410  using RxErrorModelMap = std::unordered_map<Mac48Address, Ptr<ListErrorModel>, WifiAddressHash>;
2411 
2414  using AddrSeqNoPair = std::pair<Mac48Address, uint16_t>;
2415 
2417  std::list<uint64_t> m_uidList;
2418  std::optional<Mac48Address> m_dataCorruptedSta;
2420  bool m_waitFirstTf{true};
2423  std::size_t m_nMaxInflight;
2424  std::vector<PacketSocketAddress> m_sockets;
2425  std::size_t m_nPackets;
2426  std::size_t m_blockAckCount{0};
2427  std::size_t m_tfCount{0};
2428  // std::size_t m_blockAckReqCount{0}; ///< transmitted BlockAckReq counter
2429  std::map<AddrSeqNoPair, std::size_t> m_inflightCount;
2432 };
2433 
2435  WifiMuTrafficPattern muTrafficPattern,
2436  WifiUseBarAfterMissedBa useBarAfterMissedBa,
2437  uint8_t nMaxInflight)
2439  std::string("Check MU data transmission between MLDs ") +
2440  (useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES
2441  ? "(send BAR after BlockAck timeout,"
2442  : "(send Data frames after BlockAck timeout,") +
2443  " MU Traffic pattern: " + std::to_string(static_cast<uint8_t>(muTrafficPattern)) +
2444  ", nMaxInflight=" + std::to_string(nMaxInflight) + ")",
2445  2,
2446  baseParams),
2447  m_muTrafficPattern(muTrafficPattern),
2448  m_useBarAfterMissedBa(useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES),
2449  m_nMaxInflight(nMaxInflight),
2450  m_sockets(m_nStations),
2451  m_nPackets(muTrafficPattern == WifiMuTrafficPattern::UL_MU ? 4 : 8)
2452 {
2453 }
2454 
2455 void
2457  uint8_t phyId,
2458  WifiConstPsduMap psduMap,
2459  WifiTxVector txVector,
2460  double txPowerW)
2461 {
2462  MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2463  auto linkId = m_txPsdus.back().linkId;
2464 
2465  CtrlTriggerHeader trigger;
2466 
2467  for (const auto& [staId, psdu] : psduMap)
2468  {
2469  switch (psdu->GetHeader(0).GetType())
2470  {
2471  case WIFI_MAC_QOSDATA:
2472  CheckAddresses(psdu);
2473  if (psdu->GetHeader(0).HasData())
2474  {
2475  bool isDl = psdu->GetHeader(0).IsFromDs();
2476  auto linkAddress =
2477  isDl ? psdu->GetHeader(0).GetAddr1() : psdu->GetHeader(0).GetAddr2();
2478  auto address = m_apMac->GetMldAddress(linkAddress).value_or(linkAddress);
2479 
2480  for (const auto& mpdu : *psdu)
2481  {
2482  // determine the max number of simultaneous transmissions for this MPDU
2483  auto seqNo = mpdu->GetHeader().GetSequenceNumber();
2484  auto [it, success] = m_inflightCount.insert(
2485  {{address, seqNo}, mpdu->GetInFlightLinkIds().size()});
2486  if (!success)
2487  {
2488  it->second = std::max(it->second, mpdu->GetInFlightLinkIds().size());
2489  }
2490  }
2491  for (std::size_t i = 0; i < psdu->GetNMpdus(); i++)
2492  {
2493  // MPDUs with seqNo=2 are always transmitted in an MU PPDU
2494  if (psdu->GetHeader(i).GetSequenceNumber() == 2)
2495  {
2497  {
2498  NS_TEST_EXPECT_MSG_EQ(txVector.IsUlMu(),
2499  true,
2500  "MPDU " << **std::next(psdu->begin(), i)
2501  << " not transmitted in a TB PPDU");
2502  }
2503  else
2504  {
2505  NS_TEST_EXPECT_MSG_EQ(txVector.GetHeMuUserInfoMap().size(),
2506  2,
2507  "MPDU " << **std::next(psdu->begin(), i)
2508  << " not transmitted in a DL MU PPDU");
2509  }
2510  }
2511  // corrupt QoS data frame with sequence number equal to 3 (only once)
2512  if (psdu->GetHeader(i).GetSequenceNumber() != 3)
2513  {
2514  continue;
2515  }
2516  auto uid = psdu->GetPayload(i)->GetUid();
2517  if (!m_dataCorruptedSta)
2518  {
2519  m_uidList.push_front(uid);
2520  m_dataCorruptedSta = isDl ? psdu->GetAddr1() : psdu->GetAddr2();
2521  NS_LOG_INFO("CORRUPTED");
2522  m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2523  }
2524  else if ((isDl && m_dataCorruptedSta == psdu->GetAddr1()) ||
2525  (!isDl && m_dataCorruptedSta == psdu->GetAddr2()))
2526  {
2527  // do not corrupt the QoS data frame anymore
2528  if (auto it = std::find(m_uidList.cbegin(), m_uidList.cend(), uid);
2529  it != m_uidList.cend())
2530  {
2531  m_uidList.erase(it);
2532  }
2533  m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2534  }
2535  break;
2536  }
2537  }
2538  break;
2539  case WIFI_MAC_CTL_BACKRESP:
2540  if (m_nMaxInflight > 1)
2541  {
2542  // we do not check the content of BlockAck when m_nMaxInflight is greater than 1
2543  break;
2544  }
2545  CheckBlockAck(psdu, txVector, linkId);
2546  m_blockAckCount++;
2547  // to simulate a missed BlockAck, corrupt the fifth BlockAck frame (the first
2548  // two BlockAck frames are sent to acknowledge the QoS data frames that triggered
2549  // the establishment of Block Ack agreements)
2550  if (m_blockAckCount == 5)
2551  {
2552  // corrupt the third BlockAck frame to simulate a missed BlockAck
2553  m_uidList.push_front(psdu->GetPacket()->GetUid());
2554  NS_LOG_INFO("CORRUPTED");
2555  m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2556  }
2557  break;
2558  case WIFI_MAC_CTL_TRIGGER:
2559  psdu->GetPayload(0)->PeekHeader(trigger);
2560  // the MU scheduler requests channel access on all links but we have to perform the
2561  // following actions only once (hence why we only consider TF transmitted on link 0)
2562  if (trigger.IsBasic() && m_waitFirstTf)
2563  {
2564  m_waitFirstTf = false;
2565  // the AP is starting the transmission of the Basic Trigger frame, so generate
2566  // the configured number of packets at STAs, which are sent in TB PPDUs, when
2567  // transmission of the Trigger Frame ends
2568  auto band = mac->GetWifiPhy(linkId)->GetPhyBand();
2569  Time txDuration = WifiPhy::CalculateTxDuration(psduMap, txVector, band);
2570  for (uint8_t i = 0; i < m_nStations; i++)
2571  {
2572  m_staMacs[i]->GetDevice()->GetNode()->AddApplication(
2573  GetApplication(m_sockets[i], m_nPackets, 450, txDuration, i * 4));
2574  }
2575  }
2576  if (++m_tfCount == m_staMacs[0]->GetSetupLinkIds().size())
2577  {
2578  // a TF has been sent on all the setup links, we can now disable UL OFDMA
2579  auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
2580  NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "Expected an aggregated MU scheduler");
2581  muScheduler->SetAttribute("EnableUlOfdma", BooleanValue(false));
2582  }
2583  break;
2584  default:;
2585  }
2586  }
2587 }
2588 
2589 void
2591  const WifiTxVector& txVector,
2592  uint8_t linkId)
2593 {
2594  /*
2595  * Example sequence with DL_MU_BAR_BA_SEQUENCE
2596  * ┌───────┬───────X
2597  * (To:1) │ 2 │ 3 │
2598  * ├───────┼───────┤ ┌───┐ ┌───────┐
2599  * [link 0] (To:0) │ 2 │ 3 │ │BAR│ (To:1) │ 3 │
2600  * ────────────────┴───────┴───────┴┬──┼───┼──┬──────────┴───────┴┬───┬────────
2601  * │BA│ │BA│ │ACK│
2602  * └──┘ └──┘ └───┘
2603  * ┌───────┬───────┐
2604  * (To:1) │ 4 │ 5 │
2605  * ├───────┼───────┤ ┌───┐ ┌───┐
2606  * [link 1] (To:0) │ 4 │ 5 │ │BAR│ │BAR│
2607  * ────────────────────────────┴───────┴───────┴┬──X────┴───┴┬──┼───┼──┬───────
2608  * │BA│ │BA│ │BA│
2609  * └──┘ └──┘ └──┘
2610  *
2611  * Example sequence with UL_MU
2612  *
2613  * ┌──┐ ┌────┐ ┌───┐
2614  * [link 0] │TF│ │M-BA│ │ACK│
2615  * ─────────┴──┴──┬───────┬───────┬──┴────┴────────────┬───────┬─┴───┴─────────
2616  * (From:0) │ 2 │ 3 │ (From:1) │ 3 │
2617  * ├───────┼───────┤ └───────┘
2618  * (From:1) │ 2 │ 3 │
2619  * └───────┴───────X
2620  * ┌──┐
2621  * [link 1] │TF│
2622  * ─────────┴──┴──┬───────────────┬────────────────────────────────────────────
2623  * (From:0) │ QoS Null │
2624  * ├───────────────┤
2625  * (From:1) │ QoS Null │
2626  * └───────────────┘
2627  */
2628  auto mpdu = *psdu->begin();
2629  CtrlBAckResponseHeader blockAck;
2630  mpdu->GetPacket()->PeekHeader(blockAck);
2631  bool isMpdu3corrupted;
2632 
2633  switch (m_blockAckCount)
2634  {
2635  case 0:
2636  case 1: // Ignore the first two BlockAck frames that acknowledged frames sent to establish BA
2637  break;
2638  case 2:
2640  {
2641  NS_TEST_EXPECT_MSG_EQ(blockAck.IsMultiSta(), true, "Expected a Multi-STA BlockAck");
2642  for (uint8_t i = 0; i < m_nStations; i++)
2643  {
2644  auto indices = blockAck.FindPerAidTidInfoWithAid(m_staMacs[i]->GetAssociationId());
2645  NS_TEST_ASSERT_MSG_EQ(indices.size(), 1, "Expected one Per AID TID Info per STA");
2646  auto index = indices.front();
2648  true,
2649  "Expected that a QoS data frame was corrupted");
2650  isMpdu3corrupted =
2651  m_staMacs[i]->GetLinkIdByAddress(*m_dataCorruptedSta).has_value();
2652  NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(2, index),
2653  true,
2654  "MPDU 2 expected to be successfully received");
2655  NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(3, index),
2656  !isMpdu3corrupted,
2657  "Unexpected reception status for MPDU 3");
2658  }
2659 
2660  break;
2661  }
2662  case 3:
2663  // BlockAck frames in response to the first DL MU PPDU
2664  isMpdu3corrupted = (mpdu->GetHeader().GetAddr2() == m_dataCorruptedSta);
2666  true,
2667  "MPDU 2 expected to be successfully received");
2669  !isMpdu3corrupted,
2670  "Unexpected reception status for MPDU 3");
2671  // in case of DL MU, if there are at least two links setup, we expect all MPDUs to
2672  // be inflight (on distinct links)
2674  m_staMacs[0]->GetSetupLinkIds().size() > 1)
2675  {
2676  auto queue = m_apMac->GetTxopQueue(AC_BE);
2677  Ptr<StaWifiMac> rcvMac;
2678  if (m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress() ==
2679  mpdu->GetHeader().GetAddr2())
2680  {
2681  rcvMac = m_staMacs[0];
2682  }
2683  else if (m_staMacs[1]->GetFrameExchangeManager(linkId)->GetAddress() ==
2684  mpdu->GetHeader().GetAddr2())
2685  {
2686  rcvMac = m_staMacs[1];
2687  }
2688  else
2689  {
2690  NS_ABORT_MSG("BlockAck frame not sent by a station in DL scenario");
2691  }
2692  auto item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress());
2693  std::size_t nQueuedPkt = 0;
2694  auto delay = WifiPhy::CalculateTxDuration(psdu,
2695  txVector,
2696  rcvMac->GetWifiPhy(linkId)->GetPhyBand()) +
2697  MicroSeconds(1); // to account for propagation delay
2698 
2699  while (item)
2700  {
2701  auto seqNo = item->GetHeader().GetSequenceNumber();
2702  NS_TEST_EXPECT_MSG_EQ(item->IsInFlight(),
2703  true,
2704  "MPDU with seqNo=" << seqNo << " is not in flight");
2705  auto linkIds = item->GetInFlightLinkIds();
2706  NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
2707  1,
2708  "MPDU with seqNo=" << seqNo
2709  << " is in flight on multiple links");
2710  // The first two MPDUs are in flight on the same link on which the BlockAck
2711  // is sent. The other two MPDUs (only for AP to STA/STA to AP scenarios) are
2712  // in flight on a different link.
2713  auto srcLinkId = m_apMac->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1());
2714  NS_TEST_ASSERT_MSG_EQ(srcLinkId.has_value(),
2715  true,
2716  "Addr1 of BlockAck is not an originator's link address");
2717  NS_TEST_EXPECT_MSG_EQ((*linkIds.begin() == *srcLinkId),
2718  (seqNo <= 3),
2719  "MPDU with seqNo=" << seqNo
2720  << " in flight on unexpected link");
2721  // check the Retry subfield and whether this MPDU is still queued
2722  // after the originator has processed this BlockAck
2723 
2724  // MPDUs acknowledged via this BlockAck are no longer queued
2725  bool isQueued = (seqNo > (isMpdu3corrupted ? 2 : 3));
2726  // The Retry subfield is set if the MPDU has not been acknowledged (i.e., it
2727  // is still queued) and has been transmitted on the same link as the BlockAck
2728  // (i.e., its sequence number is less than or equal to 2)
2729  bool isRetry = isQueued && seqNo <= 3;
2730 
2731  Simulator::Schedule(delay, [this, item, isQueued, isRetry]() {
2732  NS_TEST_EXPECT_MSG_EQ(item->IsQueued(),
2733  isQueued,
2734  "MPDU with seqNo="
2735  << item->GetHeader().GetSequenceNumber() << " should "
2736  << (isQueued ? "" : "not") << " be queued");
2738  item->GetHeader().IsRetry(),
2739  isRetry,
2740  "Unexpected value for the Retry subfield of the MPDU with seqNo="
2741  << item->GetHeader().GetSequenceNumber());
2742  });
2743 
2744  nQueuedPkt++;
2745  item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress(), item);
2746  }
2747  // Each MPDU contains an A-MSDU consisting of two MSDUs
2748  NS_TEST_EXPECT_MSG_EQ(nQueuedPkt, m_nPackets / 2, "Unexpected number of queued MPDUs");
2749  }
2750  break;
2751  }
2752 }
2753 
2754 void
2756 {
2757  switch (m_muTrafficPattern)
2758  {
2760  Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2761  EnumValue(WifiAcknowledgment::DL_MU_BAR_BA_SEQUENCE));
2762  break;
2764  Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2765  EnumValue(WifiAcknowledgment::DL_MU_TF_MU_BAR));
2766  break;
2768  Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2769  EnumValue(WifiAcknowledgment::DL_MU_AGGREGATE_TF));
2770  break;
2771  default:;
2772  }
2773 
2775 
2776  // Enable A-MSDU aggregation. Max A-MSDU size is set such that two MSDUs can be aggregated
2777  for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
2778  {
2779  mac->SetAttribute("BE_MaxAmsduSize", UintegerValue(1050));
2780  mac->GetQosTxop(AC_BE)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2782  mac->GetQosTxop(AC_BE)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2783 
2784  mac->SetAttribute("VI_MaxAmsduSize", UintegerValue(1050));
2785  mac->GetQosTxop(AC_VI)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2787  mac->GetQosTxop(AC_VI)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2788  }
2789 
2790  // aggregate MU scheduler
2791  auto muScheduler = CreateObjectWithAttributes<RrMultiUserScheduler>("EnableUlOfdma",
2792  BooleanValue(false),
2793  "EnableBsrp",
2794  BooleanValue(false),
2795  "UlPsduSize",
2796  UintegerValue(2000));
2797  m_apMac->AggregateObject(muScheduler);
2798 
2799  // install post reception error model on all devices
2800  for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
2801  {
2802  auto errorModel = CreateObject<ListErrorModel>();
2803  m_errorModels[m_apMac->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2804  m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2805  }
2806  for (std::size_t i : {0, 1})
2807  {
2808  for (const auto linkId : m_staMacs[i]->GetLinkIds())
2809  {
2810  auto errorModel = CreateObject<ListErrorModel>();
2811  m_errorModels[m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2812  m_staMacs[i]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2813  }
2814  }
2815 }
2816 
2817 void
2819 {
2821  {
2822  // DL Traffic
2823  for (uint8_t i = 0; i < m_nStations; i++)
2824  {
2825  PacketSocketAddress sockAddr;
2826  sockAddr.SetSingleDevice(m_apMac->GetDevice()->GetIfIndex());
2827  sockAddr.SetPhysicalAddress(m_staMacs[i]->GetDevice()->GetAddress());
2828  sockAddr.SetProtocol(1);
2829 
2830  // the first client application generates three packets in order
2831  // to trigger the establishment of a Block Ack agreement
2833  GetApplication(sockAddr, 3, 450, i * MilliSeconds(50)));
2834 
2835  // the second client application generates the first half of the selected number
2836  // of packets, which are sent in DL MU PPDUs, and starts after all BA agreements
2837  // are established
2839  GetApplication(sockAddr, m_nPackets / 2, 450, m_nStations * MilliSeconds(50)));
2840 
2841  // the third client application generates the second half of the selected number
2842  // of packets, which are sent in DL MU PPDUs, and starts during transmission of
2843  // first A-MPDU, if multiple links are setup
2845  GetApplication(sockAddr,
2846  m_nPackets / 2,
2847  450,
2848  m_nStations * MilliSeconds(50) + MilliSeconds(3)));
2849  }
2850  }
2851  else
2852  {
2853  // UL Traffic
2854  for (uint8_t i = 0; i < m_nStations; i++)
2855  {
2856  m_sockets[i].SetSingleDevice(m_staMacs[i]->GetDevice()->GetIfIndex());
2857  m_sockets[i].SetPhysicalAddress(m_apMac->GetDevice()->GetAddress());
2858  m_sockets[i].SetProtocol(1);
2859 
2860  // the first client application generates three packets in order
2861  // to trigger the establishment of a Block Ack agreement
2862  m_staMacs[i]->GetDevice()->GetNode()->AddApplication(
2863  GetApplication(m_sockets[i], 3, 450, i * MilliSeconds(50), i * 4));
2864 
2865  // packets to be included in TB PPDUs are generated (by Transmit()) when
2866  // the first Basic Trigger Frame is sent by the AP
2867  }
2868 
2869  // MU scheduler starts requesting channel access when we are done with BA agreements
2870  Simulator::Schedule(m_nStations * MilliSeconds(50), [this]() {
2871  auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
2872  NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "Expected an aggregated MU scheduler");
2873  muScheduler->SetAttribute("EnableUlOfdma", BooleanValue(true));
2874  muScheduler->SetAccessReqInterval(MilliSeconds(3));
2875  // channel access is requested only once
2876  muScheduler->SetAccessReqInterval(Seconds(0));
2877  });
2878  }
2879 
2880  Simulator::Stop(m_duration);
2881 }
2882 
2883 void
2885 {
2886  Simulator::Run();
2887 
2888  // Expected number of packets received by each node (AP, STA 0, STA 1) at application layer
2889  std::array<std::size_t, 3> expectedRxPkts{};
2890 
2891  switch (m_muTrafficPattern)
2892  {
2896  // both STA 0 and STA 1 receive m_nPackets + 3 (sent to trigger BA establishment) packets
2897  expectedRxPkts[1] = m_nPackets + 3;
2898  expectedRxPkts[2] = m_nPackets + 3;
2899  break;
2901  // AP receives m_nPackets + 3 (sent to trigger BA establishment) packets from each station
2902  expectedRxPkts[0] = 2 * (m_nPackets + 3);
2903  break;
2904  }
2905 
2907  +expectedRxPkts[0],
2908  "Unexpected number of packets received by the AP");
2910  +expectedRxPkts[1],
2911  "Unexpected number of packets received by STA 0");
2913  +expectedRxPkts[2],
2914  "Unexpected number of packets received by STA 1");
2915 
2916  // check that setting the QosTxop::NMaxInflights attribute has the expected effect.
2917  // For DL, for each station we send 2 MPDUs to trigger BA agreement and m_nPackets / 2 MPDUs
2918  // For UL, each station sends 2 MPDUs to trigger BA agreement and m_nPackets / 2 MPDUs
2920  m_inflightCount.size(),
2921  2 * (2 + m_nPackets / 2),
2922  "Did not collect number of simultaneous transmissions for all data frames");
2923 
2924  auto nMaxInflight = std::min(m_nMaxInflight, m_staMacs[0]->GetSetupLinkIds().size());
2925  std::size_t maxCount = 0;
2926  for (const auto& [txSeqNoPair, count] : m_inflightCount)
2927  {
2929  nMaxInflight,
2930  "MPDU with seqNo="
2931  << txSeqNoPair.second
2932  << " transmitted simultaneously more times than allowed");
2933  maxCount = std::max(maxCount, count);
2934  }
2935 
2937  maxCount,
2938  nMaxInflight,
2939  "Expected that at least one data frame was transmitted simultaneously a number of "
2940  "times equal to the NMaxInflights attribute");
2941 
2942  Simulator::Destroy();
2943 }
2944 
2964 {
2965  public:
2967  ~ReleaseSeqNoAfterCtsTimeoutTest() override = default;
2968 
2969  protected:
2970  void DoSetup() override;
2971  void DoRun() override;
2972  void Transmit(Ptr<WifiMac> mac,
2973  uint8_t phyId,
2974  WifiConstPsduMap psduMap,
2975  WifiTxVector txVector,
2976  double txPowerW) override;
2977 
2978  private:
2979  void StartTraffic() override;
2980 
2982  std::size_t m_nQosDataFrames;
2985 };
2986 
2989  "Check sequence numbers after CTS timeout",
2990  1,
2991  BaseParams{{"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2992  {"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2993  {}}),
2994  m_nQosDataFrames(0),
2995  m_errorModel(CreateObject<ListErrorModel>()),
2996  m_rtsCorrupted(false)
2997 {
2998 }
2999 
3000 void
3002 {
3003  // Enable RTS/CTS
3004  Config::SetDefault("ns3::WifiRemoteStationManager::RtsCtsThreshold", StringValue("1000"));
3005 
3007 
3008  // install post reception error model on all STAs affiliated with non-AP MLD
3009  for (const auto linkId : m_staMacs[0]->GetLinkIds())
3010  {
3011  m_staMacs[0]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel);
3012  }
3013 }
3014 
3015 void
3017 {
3019  m_sockAddr.SetPhysicalAddress(m_staMacs[0]->GetAddress());
3021 
3022  // install client application generating 4 packets
3024 }
3025 
3026 void
3028  uint8_t phyId,
3029  WifiConstPsduMap psduMap,
3030  WifiTxVector txVector,
3031  double txPowerW)
3032 {
3033  MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
3034 
3035  auto psdu = psduMap.begin()->second;
3036 
3037  if (psdu->GetHeader(0).IsRts() && !m_rtsCorrupted)
3038  {
3039  m_errorModel->SetList({psdu->GetPacket()->GetUid()});
3040  m_rtsCorrupted = true;
3041  // generate other packets when the first RTS is transmitted
3043  }
3044  else if (psdu->GetHeader(0).IsQosData())
3045  {
3046  m_nQosDataFrames++;
3047 
3048  if (m_nQosDataFrames == 2)
3049  {
3050  // generate other packets when the second QoS data frame is transmitted
3052  }
3053  }
3054 }
3055 
3056 void
3058 {
3059  Simulator::Stop(m_duration);
3060  Simulator::Run();
3061 
3062  NS_TEST_EXPECT_MSG_EQ(m_nQosDataFrames, 3, "Unexpected number of transmitted QoS data frames");
3063 
3064  std::size_t count{};
3065 
3066  for (const auto& txPsdu : m_txPsdus)
3067  {
3068  auto psdu = txPsdu.psduMap.begin()->second;
3069 
3070  if (!psdu->GetHeader(0).IsQosData())
3071  {
3072  continue;
3073  }
3074 
3075  NS_TEST_EXPECT_MSG_EQ(psdu->GetNMpdus(), 4, "Unexpected number of MPDUs in A-MPDU");
3076 
3077  count++;
3078  uint16_t expectedSeqNo{};
3079 
3080  switch (count)
3081  {
3082  case 1:
3083  expectedSeqNo = 4;
3084  break;
3085  case 2:
3086  expectedSeqNo = 0;
3087  break;
3088  case 3:
3089  expectedSeqNo = 8;
3090  break;
3091  }
3092 
3093  for (const auto& mpdu : *PeekPointer(psdu))
3094  {
3095  NS_TEST_EXPECT_MSG_EQ(mpdu->GetHeader().GetSequenceNumber(),
3096  expectedSeqNo++,
3097  "Unexpected sequence number");
3098  }
3099  }
3100 
3101  Simulator::Destroy();
3102 }
3103 
3111 {
3112  public:
3114 };
3115 
3117  : TestSuite("wifi-mlo", UNIT)
3118 {
3119  using ParamsTuple = std::tuple<MultiLinkOperationsTestBase::BaseParams, // base config params
3120  std::vector<uint8_t>, // link ID of setup links
3121  uint8_t, // AP negotiation support
3122  std::string, // DL TID-to-Link Mapping
3123  std::string>; // UL TID-to-Link Mapping
3124 
3125  AddTestCase(new GetRnrLinkInfoTest(), TestCase::QUICK);
3126  AddTestCase(new MldSwapLinksTest(), TestCase::QUICK);
3127 
3128  for (const auto& [baseParams,
3129  setupLinks,
3130  apNegSupport,
3131  dlTidLinkMapping,
3132  ulTidLinkMapping] :
3133  {// matching channels: setup all links
3134  ParamsTuple({{"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3135  {"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3136  {}},
3137  {0, 1, 2},
3138  0, // AP MLD does not support TID-to-Link Mapping negotiation
3139  "0,1,2,3 0,1,2; 4,5 0,1", // default mapping used instead
3140  "0,1,2,3 1,2; 6,7 0,1" // default mapping used instead
3141  ),
3142  // non-matching channels, matching PHY bands: setup all links
3143  ParamsTuple({{"{108, 0, BAND_5GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3144  {"{36, 0, BAND_5GHZ, 0}", "{120, 0, BAND_5GHZ, 0}", "{5, 0, BAND_6GHZ, 0}"},
3145  {}},
3146  {0, 1, 2},
3147  1, // AP MLD does not support distinct link sets for TIDs
3148  "0,1,2,3 0,1,2; 4,5 0,1", // default mapping used instead
3149  ""),
3150  // non-AP MLD switches band on some links to setup 3 links
3151  ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3152  {"{36, 0, BAND_5GHZ, 0}", "{9, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3153  {}},
3154  {0, 1, 2},
3155  3,
3156  "0,1,2,3 0; 4,5,6,7 1,2", // frames of two TIDs are generated
3157  "0,2,3 1,2; 1,4,5,6,7 0" // frames of two TIDs are generated
3158  ),
3159  // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3160  // that band, hence only 2 links are setup
3161  ParamsTuple(
3162  {{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{8, 20, BAND_2_4GHZ, 0}"},
3163  {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3164  {0}},
3165  {0, 1},
3166  1, // AP MLD does not support distinct link sets for TIDs
3167  "0,1,2,3,4,5,6,7 0",
3168  "0,1,2,3,4,5,6,7 0"),
3169  // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3170  // that band; the second link of the non-AP MLD cannot change PHY band and there is
3171  // an AP operating on the same channel; hence 2 links are setup
3172  ParamsTuple(
3173  {{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{8, 20, BAND_2_4GHZ, 0}"},
3174  {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3175  {0, 1}},
3176  {0, 1},
3177  3,
3178  "0,1,2,3 1",
3179  "0,1,2,3 1"),
3180  // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3181  // that band; the second link of the non-AP MLD cannot change PHY band and there is
3182  // an AP operating on the same channel; the third link of the non-AP MLD cannot
3183  // change PHY band and there is an AP operating on the same band (different channel);
3184  // hence 2 links are setup by switching channel (not band) on the third link
3185  ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{60, 0, BAND_5GHZ, 0}"},
3186  {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3187  {0, 1, 2}},
3188  {0, 2},
3189  3,
3190  "",
3191  ""),
3192  // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3193  // that band; the second link of the non-AP MLD cannot change PHY band and there is
3194  // an AP operating on the same channel; hence one link only is setup
3195  ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3196  {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3197  {0, 1}},
3198  {2},
3199  3,
3200  "",
3201  ""),
3202  // non-AP MLD has only two STAs and setups two links
3203  ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3204  {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3205  {}},
3206  {1, 0},
3207  3,
3208  "0,1,2,3 1",
3209  ""),
3210  // single link non-AP STA associates with an AP affiliated with an AP MLD
3211  ParamsTuple({{"{120, 0, BAND_5GHZ, 0}"},
3212  {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3213  {}},
3214  {2}, // link ID of AP MLD only (non-AP STA is single link)
3215  3,
3216  "",
3217  ""),
3218  // a STA affiliated with a non-AP MLD associates with a single link AP
3219  ParamsTuple({{"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3220  {"{120, 0, BAND_5GHZ, 0}"},
3221  {}},
3222  {2}, // link ID of non-AP MLD only (AP is single link)
3223  0,
3224  "0,1,2,3 0,1; 4,5,6,7 0,1", // ignored by single link AP
3225  "")})
3226  {
3227  AddTestCase(new MultiLinkSetupTest(baseParams,
3228  WifiScanType::PASSIVE,
3229  setupLinks,
3230  apNegSupport,
3231  dlTidLinkMapping,
3232  ulTidLinkMapping),
3233  TestCase::QUICK);
3234  AddTestCase(new MultiLinkSetupTest(baseParams,
3235  WifiScanType::ACTIVE,
3236  setupLinks,
3237  apNegSupport,
3238  dlTidLinkMapping,
3239  ulTidLinkMapping),
3240  TestCase::QUICK);
3241 
3242  for (const auto& trafficPattern : {WifiTrafficPattern::STA_TO_STA,
3247  {
3248  // No Block Ack agreement
3249  AddTestCase(new MultiLinkTxTest(baseParams,
3250  trafficPattern,
3253  1),
3254  TestCase::QUICK);
3255  for (const auto& useBarAfterMissedBa :
3257  {
3258  // Block Ack agreement with nMaxInflight=1
3259  AddTestCase(new MultiLinkTxTest(baseParams,
3260  trafficPattern,
3262  useBarAfterMissedBa,
3263  1),
3264  TestCase::QUICK);
3265  // Block Ack agreement with nMaxInflight=2
3266  AddTestCase(new MultiLinkTxTest(baseParams,
3267  trafficPattern,
3269  useBarAfterMissedBa,
3270  2),
3271  TestCase::QUICK);
3272  }
3273  }
3274 
3275  for (const auto& muTrafficPattern : {WifiMuTrafficPattern::DL_MU_BAR_BA_SEQUENCE,
3279  {
3280  for (const auto& useBarAfterMissedBa :
3282  {
3283  // Block Ack agreement with nMaxInflight=1
3284  AddTestCase(
3285  new MultiLinkMuTxTest(baseParams, muTrafficPattern, useBarAfterMissedBa, 1),
3286  TestCase::QUICK);
3287  // Block Ack agreement with nMaxInflight=2
3288  AddTestCase(
3289  new MultiLinkMuTxTest(baseParams, muTrafficPattern, useBarAfterMissedBa, 2),
3290  TestCase::QUICK);
3291  }
3292  }
3293  }
3294 
3295  AddTestCase(new ReleaseSeqNoAfterCtsTimeoutTest(), TestCase::QUICK);
3296 }
3297 
#define min(a, b)
Definition: 80211b.c:41
#define max(a, b)
Definition: 80211b.c:42
Test release of sequence numbers upon CTS timeout in multi-link operations.
PacketSocketAddress m_sockAddr
packet socket address
Ptr< ListErrorModel > m_errorModel
error rate model to corrupt first RTS frame
std::size_t m_nQosDataFrames
counter for transmitted QoS data frames
void StartTraffic() override
Start the generation of traffic (needs to be overridden)
bool m_rtsCorrupted
whether the first RTS frame has been corrupted
void DoSetup() override
Implementation to do any local setup required for this TestCase.
~ReleaseSeqNoAfterCtsTimeoutTest() override=default
void Transmit(Ptr< WifiMac > mac, uint8_t phyId, WifiConstPsduMap psduMap, WifiTxVector txVector, double txPowerW) override
Callback invoked when a FEM passes PSDUs to the PHY.
void DoRun() override
Implementation to actually run this TestCase.
a polymophic address class
Definition: address.h:101
uint16_t GetAssociationId(Mac48Address addr, uint8_t linkId) const
const std::map< uint16_t, Mac48Address > & GetStaList(uint8_t linkId) const
Get a const reference to the map of associated stations on the given link.
Ptr< WifiMacQueue > GetTxopQueue(AcIndex ac) const override
Get the wifi MAC queue of the (Qos)Txop associated with the given AC, if such (Qos)Txop is installed,...
Definition: ap-wifi-mac.cc:171
Headers for BlockAck response.
Definition: ctrl-headers.h:203
bool IsPacketReceived(uint16_t seq, std::size_t index=0) const
Check if the packet with the given sequence number was acknowledged in this BlockAck response.
std::vector< uint32_t > FindPerAidTidInfoWithAid(uint16_t aid) const
For Multi-STA Block Acks, get the indices of the Per AID TID Info subfields carrying the given AID in...
bool IsMultiSta() const
Check if the BlockAck frame variant is Multi-STA Block Ack.
Headers for Trigger frames.
Definition: ctrl-headers.h:942
bool IsBasic() const
Check if this is a Basic Trigger frame.
Hold variables of type enum.
Definition: enum.h:62
void SetList(const std::list< uint64_t > &packetlist)
Definition: error-model.cc:456
an EUI-48 address
Definition: mac48-address.h:46
bool IsGroup() const
Implement the header for management frames of type association request.
Definition: mgt-headers.h:157
Implement the header for management frames of type association and reassociation response.
Definition: mgt-headers.h:334
Implement the header for management frames of type beacon.
Definition: mgt-headers.h:512
Implement the header for management frames of type probe response.
Definition: mgt-headers.h:451
Helper class used to assign positions and mobility models to nodes.
MultiUserScheduler is an abstract base class defining the API that APs supporting at least VHT can us...
holds a vector of ns3::NetDevice pointers
keep track of a set of node pointers.
uint32_t AddApplication(Ptr< Application > application)
Associate an Application to this Node.
Definition: node.cc:169
uint32_t GetId() const
Definition: node.cc:117
bool TraceConnectWithoutContext(std::string name, const CallbackBase &cb)
Connect a TraceSource to a Callback without a context.
Definition: object-base.cc:315
Ptr< T > GetObject() const
Get a pointer to the requested aggregated Object.
Definition: object.h:471
void AggregateObject(Ptr< Object > other)
Aggregate two Objects together.
Definition: object.cc:259
an address for a packet socket
void SetProtocol(uint16_t protocol)
Set the protocol.
void SetPhysicalAddress(const Address address)
Set the destination address.
void SetSingleDevice(uint32_t device)
Set the address to match only a specified NetDevice.
Give ns3::PacketSocket powers to ns3::Node.
void Install(Ptr< Node > node) const
Aggregate an instance of a ns3::PacketSocketFactory onto the provided node.
The Reduced Neighbor Report element.
std::size_t GetNNbrApInfoFields() const
Get the number of Neighbor AP Information fields.
void SetMldParameters(std::size_t nbrApInfoId, std::size_t index, uint8_t mldId, uint8_t linkId, uint8_t changeSequence)
Set the MLD Parameters subfield of the i-th TBTT Information field of the given Neighbor AP Informati...
std::size_t GetNTbttInformationFields(std::size_t nbrApInfoId) const
Get the number of TBTT Information fields included in the TBTT Information Set field of the given Nei...
void AddNbrApInfoField()
Add a Neighbor AP Information field.
void AddTbttInformationField(std::size_t nbrApInfoId)
Add a TBTT Information fields to the TBTT Information Set field of the given Neighbor AP Information ...
Make it easy to create and manage PHY objects for the spectrum model.
void AddChannel(const Ptr< SpectrumChannel > channel, const FrequencyRange &freqRange=WHOLE_WIFI_SPECTRUM)
The IEEE 802.11 SSID Information Element.
Definition: ssid.h:36
Hold variables of type string.
Definition: string.h:56
encapsulates test code
Definition: test.h:1060
void AddTestCase(TestCase *testCase, TestDuration duration=QUICK)
Add an individual child TestCase to this test suite.
Definition: test.cc:301
A suite of tests to run.
Definition: test.h:1256
Simulation virtual time values and global simulation resolution.
Definition: nstime.h:105
Hold an unsigned integer type.
Definition: uinteger.h:45
helps to create WifiNetDevice objects
Definition: wifi-helper.h:324
bool IsQosData() const
Return true if the Type is DATA and Subtype is one of the possible values for QoS Data.
create MAC layers for a ns3::WifiNetDevice.
base class for all MAC-level wifi objects.
Definition: wifi-mac.h:96
Ptr< FrameExchangeManager > GetFrameExchangeManager(uint8_t linkId=SINGLE_LINK_OP_ID) const
Get the Frame Exchange Manager associated with the given link.
Definition: wifi-mac.cc:864
std::optional< Mac48Address > GetMldAddress(const Mac48Address &remoteAddr) const
Definition: wifi-mac.cc:1632
uint8_t GetNLinks() const
Get the number of links (can be greater than 1 for 11be devices only).
Definition: wifi-mac.cc:933
Ptr< WifiPhy > GetWifiPhy(uint8_t linkId=SINGLE_LINK_OP_ID) const
Definition: wifi-mac.cc:1171
virtual std::optional< uint8_t > GetLinkIdByAddress(const Mac48Address &address) const
Get the ID of the link having the given MAC address, if any.
Definition: wifi-mac.cc:961
Ptr< EhtConfiguration > GetEhtConfiguration() const
Definition: wifi-mac.cc:1755
std::optional< std::reference_wrapper< const WifiTidLinkMapping > > GetTidToLinkMapping(Mac48Address mldAddr, WifiDirection dir) const
Get the TID-to-Link Mapping negotiated with the given MLD (if any) for the given direction.
Definition: wifi-mac.cc:1100
Ptr< WifiNetDevice > GetDevice() const
Return the device this PHY is associated with.
Definition: wifi-mac.cc:439
virtual Ptr< WifiMacQueue > GetTxopQueue(AcIndex ac) const
Get the wifi MAC queue of the (Qos)Txop associated with the given AC, if such (Qos)Txop is installed,...
Definition: wifi-mac.cc:545
Ptr< WifiRemoteStationManager > GetWifiRemoteStationManager(uint8_t linkId=0) const
Definition: wifi-mac.cc:906
const std::set< uint8_t > & GetLinkIds() const
Definition: wifi-mac.cc:939
Mac48Address GetAddress() const
Definition: wifi-mac.cc:452
uint32_t GetIfIndex() const override
Address GetAddress() const override
Ptr< Node > GetNode() const override
void SetPcapDataLinkType(SupportedPcapDataLinkTypes dlt)
Set the data link type of PCAP traces to be used.
Definition: wifi-helper.cc:543
void Set(std::string name, const AttributeValue &v)
Definition: wifi-helper.cc:163
void SetPostReceptionErrorModel(const Ptr< ErrorModel > em)
Attach a receive ErrorModel to the WifiPhy.
Definition: wifi-phy.cc:646
WifiPhyBand GetPhyBand() const
Get the configured Wi-Fi band.
Definition: wifi-phy.cc:1021
const WifiPhyOperatingChannel & GetOperatingChannel() const
Get a const reference to the operating channel.
Definition: wifi-phy.cc:1033
uint8_t GetPrimaryChannelIndex(uint16_t primaryChannelWidth) const
If the operating channel width is a multiple of 20 MHz, return the index of the primary channel of th...
uint16_t GetWidth() const
Return the width of the whole operating channel (in MHz).
WifiPhyBand GetPhyBand() const
Return the PHY band of the operating channel.
uint8_t GetNumber() const
Return the channel number identifying the whole operating channel.
uint16_t GetFrequency() const
Return the center frequency of the operating channel (in MHz).
const WifiMacHeader & GetHeader(std::size_t i) const
Get the header of the i-th MPDU.
Definition: wifi-psdu.cc:279
std::vector< Ptr< WifiMpdu > >::const_iterator begin() const
Return a const iterator to the first MPDU.
Definition: wifi-psdu.cc:333
Mac48Address GetAddr2() const
Get the Transmitter Address (TA), which is common to all the MPDUs.
Definition: wifi-psdu.cc:128
Mac48Address GetAddr1() const
Get the Receiver Address (RA), which is common to all the MPDUs.
Definition: wifi-psdu.cc:113
This class mimics the TXVECTOR which is to be passed to the PHY in order to define the parameters whi...
const HeMuUserInfoMap & GetHeMuUserInfoMap() const
Get a const reference to the map HE MU user-specific transmission information indexed by STA-ID.
bool IsUlMu() const
#define NS_ASSERT(condition)
At runtime, in debugging builds, if this condition is not true, the program prints the source file,...
Definition: assert.h:66
#define NS_ASSERT_MSG(condition, message)
At runtime, in debugging builds, if this condition is not true, the program prints the message to out...
Definition: assert.h:86
void SetDefault(std::string name, const AttributeValue &value)
Definition: config.cc:890
void ConnectWithoutContext(std::string path, const CallbackBase &cb)
Definition: config.cc:950
#define NS_FATAL_ERROR(msg)
Report a fatal error with a message and terminate.
Definition: fatal-error.h:179
#define NS_ABORT_MSG(msg)
Unconditional abnormal program termination with a message.
Definition: abort.h:49
#define NS_ABORT_IF(cond)
Abnormal program termination if a condition is true.
Definition: abort.h:76
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition: log.h:202
#define NS_LOG_INFO(msg)
Use NS_LOG to output a message of level LOG_INFO.
Definition: log.h:275
Time Now()
create an ns3::Time instance which contains the current simulation time.
Definition: simulator.cc:305
#define NS_TEST_ASSERT_MSG_LT(actual, limit, msg)
Test that an actual value is less than a limit and report and abort if not.
Definition: test.h:709
#define NS_TEST_ASSERT_MSG_EQ(actual, limit, msg)
Test that an actual and expected (limit) value are equal and report and abort if not.
Definition: test.h:144
#define NS_TEST_EXPECT_MSG_LT_OR_EQ(actual, limit, msg)
Test that an actual value is less than or equal to a limit and report if not.
Definition: test.h:830
#define NS_TEST_EXPECT_MSG_LT(actual, limit, msg)
Test that an actual value is less than a limit and report if not.
Definition: test.h:790
#define NS_TEST_EXPECT_MSG_GT(actual, limit, msg)
Test that an actual value is greater than a limit and report if not.
Definition: test.h:956
#define NS_TEST_EXPECT_MSG_NE(actual, limit, msg)
Test that an actual and expected (limit) value are not equal and report if not.
Definition: test.h:666
#define NS_TEST_EXPECT_MSG_EQ(actual, limit, msg)
Test that an actual and expected (limit) value are equal and report if not.
Definition: test.h:251
#define NS_TEST_ASSERT_MSG_NE(actual, limit, msg)
Test that an actual and expected (limit) value are not equal and report and abort if not.
Definition: test.h:564
Time MicroSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition: nstime.h:1350
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition: nstime.h:1326
Time MilliSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition: nstime.h:1338
WifiScanType
Scan type (active or passive)
Definition: sta-wifi-mac.h:49
AcIndex QosUtilsMapTidToAc(uint8_t tid)
Maps TID (Traffic ID) to Access classes.
Definition: qos-utils.cc:134
@ AP
Definition: wifi-mac.h:66
@ WIFI_STANDARD_80211be
@ AC_BE
Best Effort.
Definition: qos-utils.h:75
@ AC_VO
Voice.
Definition: qos-utils.h:81
@ AC_VI
Video.
Definition: qos-utils.h:79
@ AC_BK
Background.
Definition: qos-utils.h:77
address
Definition: first.py:47
NLOHMANN_BASIC_JSON_TPL_DECLARATION std::string to_string(const NLOHMANN_BASIC_JSON_TPL &j)
user-defined to_string function for JSON values
Definition: json.h:25255
Every class exported by the ns3 library is enclosed in the ns3 namespace.
constexpr FrequencyRange WIFI_SPECTRUM_6_GHZ
Identifier for the frequency range covering the wifi spectrum in the 6 GHz band.
std::unordered_map< uint16_t, Ptr< const WifiPsdu > > WifiConstPsduMap
Map of const PSDUs indexed by STA-ID.
Callback< R, Args... > MakeCallback(R(T::*memPtr)(Args...), OBJ objPtr)
Build Callbacks for class method members which take varying numbers of arguments and potentially retu...
Definition: callback.h:704
WifiTidToLinkMappingNegSupport
TID-to-Link Mapping Negotiation Support.
@ WIFI_TID_TO_LINK_MAPPING_ANY_LINK_SET
static constexpr uint8_t SINGLE_LINK_OP_ID
Link ID for single link operations (helps tracking places where correct link ID is to be used to supp...
Definition: wifi-utils.h:192
constexpr FrequencyRange WIFI_SPECTRUM_5_GHZ
Identifier for the frequency range covering the wifi spectrum in the 5 GHz band.
@ WIFI_MAC_CTL_TRIGGER
@ WIFI_MAC_CTL_BACKREQ
@ WIFI_MAC_MGT_BEACON
@ WIFI_MAC_MGT_ACTION
@ WIFI_MAC_MGT_ASSOCIATION_RESPONSE
@ WIFI_MAC_MGT_ASSOCIATION_REQUEST
@ WIFI_MAC_CTL_BACKRESP
@ WIFI_MAC_MGT_PROBE_RESPONSE
@ WIFI_MAC_QOSDATA
WifiDirection
Wifi direction.
Definition: wifi-utils.h:43
bool TidToLinkMappingValidForNegType1(const WifiTidLinkMapping &dlLinkMapping, const WifiTidLinkMapping &ulLinkMapping)
Check if the given TID-to-Link Mappings are valid for a negotiation type of 1.
Definition: wifi-utils.cc:148
std::map< uint8_t, std::set< uint8_t > > WifiTidLinkMapping
TID-indexed map of the link set to which the TID is mapped.
Definition: wifi-utils.h:74
constexpr FrequencyRange WIFI_SPECTRUM_2_4_GHZ
Identifier for the frequency range covering the wifi spectrum in the 2.4 GHz band.
U * PeekPointer(const Ptr< U > &p)
Definition: ptr.h:449
staDevices
Definition: third.py:100
channel
Definition: third.py:88
mac
Definition: third.py:92
wifi
Definition: third.py:95
apDevices
Definition: third.py:103
wifiApNode
Definition: third.py:86
mobility
Definition: third.py:105
wifiStaNodes
Definition: third.py:84
Function object to compute the hash of a MAC address.
Definition: qos-utils.h:56
uint32_t prev
std::string dir
uint32_t pktSize
packet size used for the simulation (in bytes)
WifiMuTrafficPattern
Tested MU traffic patterns.
WifiTrafficPattern
Tested traffic patterns.
WifiUseBarAfterMissedBa
Whether to send a BlockAckReq after a missed BlockAck.
static WifiMultiLinkOperationsTestSuite g_wifiMultiLinkOperationsTestSuite
the test suite
WifiBaEnabled
Block Ack agreement enabled/disabled.