A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
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
59using namespace ns3;
60
61NS_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
89void
91{
93 std::size_t nbrId;
94 std::size_t tbttId;
95
96 // Add a first Neighbor AP Information field without MLD Parameters
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
250void
252 std::size_t nLinks,
253 const std::map<uint8_t, uint8_t>& links,
254 const std::map<uint8_t, uint8_t>& expected)
255{
256 TestWifiMac mac;
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
285void
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:
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
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:
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
475void
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 }
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 }
542 true,
543 "Address " << *staAddr << " is not a STA device address. "
544 << "PSDU: " << *psdu);
545 }
546}
547
548void
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
583void
585{
586 NS_LOG_INFO("Packet received by NODE " << +nodeId << "\n");
587 m_rxPkts[nodeId]++;
588}
589
590void
592 const std::vector<std::string>& channels,
593 const ChannelMap& channelMap)
594{
595 helper = SpectrumWifiPhyHelper(channels.size());
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
612void
614{
617 int64_t streamNumber = 30;
618
619 NodeContainer wifiApNode;
620 wifiApNode.Create(1);
621
622 NodeContainer wifiStaNodes;
623 wifiStaNodes.Create(m_nStations);
624
625 WifiHelper wifi;
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
648 WifiMacHelper mac;
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
671 MobilityHelper mobility;
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
763void
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
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;
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
929void
931{
933
934 m_staMacs[0]->SetAttribute("ActiveProbing", BooleanValue(m_scanType == WifiScanType::ACTIVE));
935 m_apMac->GetEhtConfiguration()->SetAttribute("TidToLinkMappingNegSupport",
937 // For non-AP MLD, it does not make sense to set the negotiation type to 0 (unless the AP MLD
938 // also advertises 0) or 1 (the AP MLD is discarded if it advertises a support of 3)
939 auto staEhtConfig = m_staMacs[0]->GetEhtConfiguration();
940 staEhtConfig->SetAttribute("TidToLinkMappingNegSupport", EnumValue(3));
941 staEhtConfig->SetAttribute("TidToLinkMappingDl", StringValue(m_dlTidLinkMappingStr));
942 staEhtConfig->SetAttribute("TidToLinkMappingUl", StringValue(m_ulTidLinkMappingStr));
943
944 // the negotiated link mapping matches the one configured in EHT configuration, unless
945 // the AP MLD does not support TID-to-link mapping negotiation or the AP MLD supports
946 // the negotiation type 1 and the non-AP MLD is configured with a link mapping that
947 // maps distinct link sets to the TIDs, in which case the default link mapping is used
948 m_dlTidLinkMapping = staEhtConfig->GetTidLinkMapping(WifiDirection::DOWNLINK);
949 m_ulTidLinkMapping = staEhtConfig->GetTidLinkMapping(WifiDirection::UPLINK);
950
951 if (m_apNegSupport == 0 ||
952 (m_apNegSupport == 1 &&
954 {
955 m_dlTidLinkMapping.clear(); // default link mapping
956 m_ulTidLinkMapping.clear(); // default link mapping
957 }
958
959 // find (if any) a TID that is not mapped to all setup links
960 using TupleRefs = std::tuple<std::reference_wrapper<const WifiTidLinkMapping>,
961 std::reference_wrapper<uint8_t>,
962 std::reference_wrapper<std::optional<uint8_t>>,
964 for (auto& [mappingRef, tid1Ref, tid2Ref, mac] :
967 {
968 tid1Ref.get() = 0;
969 for (uint8_t tid1 = 0; tid1 < 8; tid1++)
970 {
971 if (auto it1 = mappingRef.get().find(tid1);
972 it1 != mappingRef.get().cend() && it1->second.size() != m_setupLinks.size())
973 {
974 // found. Now search for another TID with a disjoint mapped link set
975 for (uint8_t tid2 = tid1 + 1; tid2 < 8; tid2++)
976 {
977 if (auto it2 = mappingRef.get().find(tid2);
978 it2 != mappingRef.get().cend() && it2->second.size() != m_setupLinks.size())
979 {
980 std::list<uint8_t> intersection;
981 std::set_intersection(it1->second.cbegin(),
982 it1->second.cend(),
983 it2->second.cbegin(),
984 it2->second.cend(),
985 std::back_inserter(intersection));
986 if (intersection.empty())
987 {
988 // found a second TID
989 tid2Ref.get() = tid2;
990 break;
991 }
992 }
993 }
994 tid1Ref.get() = tid1;
995 break;
996 }
997 }
998
999 std::list<uint8_t> tids = {tid1Ref.get()};
1000 if (tid2Ref.get())
1001 {
1002 tids.emplace_back(*tid2Ref.get());
1003 }
1004
1005 // prevent aggregation of MPDUs
1006 for (auto tid : tids)
1007 {
1008 std::string attrName;
1009 switch (QosUtilsMapTidToAc(tid))
1010 {
1011 case AC_VI:
1012 attrName = "VI_MaxAmpduSize";
1013 break;
1014 case AC_VO:
1015 attrName = "VO_MaxAmpduSize";
1016 break;
1017 case AC_BE:
1018 attrName = "BE_MaxAmpduSize";
1019 break;
1020 case AC_BK:
1021 attrName = "BK_MaxAmpduSize";
1022 break;
1023 default:
1024 NS_FATAL_ERROR("Invalid TID " << +tid);
1025 }
1026
1027 mac->SetAttribute(attrName, UintegerValue(100));
1028 }
1029 }
1030}
1031
1032void
1034{
1035 // DL traffic
1036 {
1037 PacketSocketAddress sockAddr;
1039 sockAddr.SetPhysicalAddress(m_staMacs[0]->GetDevice()->GetAddress());
1040 sockAddr.SetProtocol(1);
1041
1043 GetApplication(sockAddr, m_setupLinks.size(), 500, Seconds(0), m_dlTid1));
1044 if (m_dlTid2)
1045 {
1047 GetApplication(sockAddr, m_setupLinks.size(), 500, Seconds(0), *m_dlTid2));
1048 }
1049 }
1050
1051 // UL Traffic
1052 {
1053 PacketSocketAddress sockAddr;
1054 sockAddr.SetSingleDevice(m_staMacs[0]->GetDevice()->GetIfIndex());
1056 sockAddr.SetProtocol(1);
1057
1058 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
1059 GetApplication(sockAddr, m_setupLinks.size(), 500, MilliSeconds(500), m_ulTid1));
1060 if (m_ulTid2)
1061 {
1062 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
1063 GetApplication(sockAddr, m_setupLinks.size(), 500, MilliSeconds(500), *m_ulTid2));
1064 }
1065 }
1066}
1067
1068void
1070{
1072
1075
1079 std::size_t index = 0;
1080
1081 for (const auto& frameInfo : m_txPsdus)
1082 {
1083 const auto& mpdu = *frameInfo.psduMap.begin()->second->begin();
1084 const auto& linkId = frameInfo.linkId;
1085
1086 switch (mpdu->GetHeader().GetType())
1087 {
1089 CheckBeacon(mpdu, linkId);
1090 break;
1091
1093 CheckProbeResponse(mpdu, linkId);
1094 m_nProbeResp++;
1095 break;
1096
1098 CheckAssocRequest(mpdu, linkId);
1099 break;
1100
1102 CheckAssocResponse(mpdu, linkId);
1103 break;
1104
1105 case WIFI_MAC_QOSDATA:
1106 CheckQosData(mpdu, linkId, index);
1107 break;
1108
1109 default:
1110 break;
1111 }
1112
1113 index++;
1114 }
1115
1117
1118 std::size_t expectedProbeResp = 0;
1119 if (m_scanType == WifiScanType::ACTIVE)
1120 {
1121 // the number of Probe Response frames that we expect to receive in active mode equals
1122 // the number of channels in common between AP MLD and non-AP MLD at initialization
1123 for (const auto& staChannel : m_staChannels)
1124 {
1125 for (const auto& apChannel : m_apChannels)
1126 {
1127 if (staChannel == apChannel)
1128 {
1129 expectedProbeResp++;
1130 break;
1131 }
1132 }
1133 }
1134 }
1135
1136 NS_TEST_EXPECT_MSG_EQ(m_nProbeResp, expectedProbeResp, "Unexpected number of Probe Responses");
1137
1138 std::size_t expectedRxDlPkts = m_setupLinks.size();
1139 if (m_dlTid2)
1140 {
1141 expectedRxDlPkts *= 2;
1142 }
1143 NS_TEST_EXPECT_MSG_EQ(m_rxPkts[m_staMacs[0]->GetDevice()->GetNode()->GetId()],
1144 expectedRxDlPkts,
1145 "Unexpected number of DL packets received");
1146
1147 std::size_t expectedRxUlPkts = m_setupLinks.size();
1148 if (m_ulTid2)
1149 {
1150 expectedRxUlPkts *= 2;
1151 }
1153 expectedRxUlPkts,
1154 "Unexpected number of UL packets received");
1155
1157}
1158
1159void
1161{
1162 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_BEACON);
1163
1164 CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::DL);
1165
1167 mpdu->GetHeader().GetAddr2(),
1168 "TA of Beacon frame is not the address of the link it is transmitted on");
1169 MgtBeaconHeader beacon;
1170 mpdu->GetPacket()->PeekHeader(beacon);
1171 const auto& rnr = beacon.Get<ReducedNeighborReport>();
1172 const auto& mle = beacon.Get<MultiLinkElement>();
1173
1174 if (m_apMac->GetNLinks() == 1)
1175 {
1176 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(),
1177 false,
1178 "RNR Element in Beacon frame from single link AP");
1179 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1180 false,
1181 "Multi-Link Element in Beacon frame from single link AP");
1182 return;
1183 }
1184
1185 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(), true, "No RNR Element in Beacon frame");
1186 // All the other APs affiliated with the same AP MLD as the AP sending
1187 // the Beacon frame must be reported in a separate Neighbor AP Info field
1188 NS_TEST_EXPECT_MSG_EQ(rnr->GetNNbrApInfoFields(),
1189 static_cast<std::size_t>(m_apMac->GetNLinks() - 1),
1190 "Unexpected number of Neighbor AP Info fields in RNR");
1191 for (std::size_t nbrApInfoId = 0; nbrApInfoId < rnr->GetNNbrApInfoFields(); nbrApInfoId++)
1192 {
1193 NS_TEST_EXPECT_MSG_EQ(rnr->HasMldParameters(nbrApInfoId),
1194 true,
1195 "MLD Parameters not present");
1196 NS_TEST_EXPECT_MSG_EQ(rnr->GetNTbttInformationFields(nbrApInfoId),
1197 1,
1198 "Expected only one TBTT Info subfield per Neighbor AP Info");
1199 uint8_t nbrLinkId = rnr->GetLinkId(nbrApInfoId, 0);
1200 NS_TEST_EXPECT_MSG_EQ(rnr->GetBssid(nbrApInfoId, 0),
1201 m_apMac->GetFrameExchangeManager(nbrLinkId)->GetAddress(),
1202 "BSSID advertised in Neighbor AP Info field "
1203 << nbrApInfoId
1204 << " does not match the address configured on the link "
1205 "advertised in the same field");
1206 }
1207
1208 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Beacon frame");
1209 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1211 "Incorrect MLD address advertised in Multi-Link Element");
1212 NS_TEST_EXPECT_MSG_EQ(mle->GetLinkIdInfo(),
1213 +linkId,
1214 "Incorrect Link ID advertised in Multi-Link Element");
1215}
1216
1217void
1219{
1220 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_PROBE_RESPONSE);
1221
1222 CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::DL);
1223
1225 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1226 mpdu->GetHeader().GetAddr2(),
1227 "TA of Probe Response is not the address of the link it is transmitted on");
1228 MgtProbeResponseHeader probeResp;
1229 mpdu->GetPacket()->PeekHeader(probeResp);
1230 const auto& rnr = probeResp.Get<ReducedNeighborReport>();
1231 const auto& mle = probeResp.Get<MultiLinkElement>();
1232
1233 if (m_apMac->GetNLinks() == 1)
1234 {
1235 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(),
1236 false,
1237 "RNR Element in Probe Response frame from single link AP");
1238 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1239 false,
1240 "Multi-Link Element in Probe Response frame from single link AP");
1241 return;
1242 }
1243
1244 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(), true, "No RNR Element in Probe Response frame");
1245 // All the other APs affiliated with the same AP MLD as the AP sending
1246 // the Probe Response frame must be reported in a separate Neighbor AP Info field
1247 NS_TEST_EXPECT_MSG_EQ(rnr->GetNNbrApInfoFields(),
1248 static_cast<std::size_t>(m_apMac->GetNLinks() - 1),
1249 "Unexpected number of Neighbor AP Info fields in RNR");
1250 for (std::size_t nbrApInfoId = 0; nbrApInfoId < rnr->GetNNbrApInfoFields(); nbrApInfoId++)
1251 {
1252 NS_TEST_EXPECT_MSG_EQ(rnr->HasMldParameters(nbrApInfoId),
1253 true,
1254 "MLD Parameters not present");
1255 NS_TEST_EXPECT_MSG_EQ(rnr->GetNTbttInformationFields(nbrApInfoId),
1256 1,
1257 "Expected only one TBTT Info subfield per Neighbor AP Info");
1258 uint8_t nbrLinkId = rnr->GetLinkId(nbrApInfoId, 0);
1259 NS_TEST_EXPECT_MSG_EQ(rnr->GetBssid(nbrApInfoId, 0),
1260 m_apMac->GetFrameExchangeManager(nbrLinkId)->GetAddress(),
1261 "BSSID advertised in Neighbor AP Info field "
1262 << nbrApInfoId
1263 << " does not match the address configured on the link "
1264 "advertised in the same field");
1265 }
1266
1267 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Probe Response frame");
1268 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1270 "Incorrect MLD address advertised in Multi-Link Element");
1271 NS_TEST_EXPECT_MSG_EQ(mle->GetLinkIdInfo(),
1272 +linkId,
1273 "Incorrect Link ID advertised in Multi-Link Element");
1274}
1275
1276void
1278{
1279 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_ASSOCIATION_REQUEST);
1280
1281 CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::UL);
1282
1284 m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress(),
1285 mpdu->GetHeader().GetAddr2(),
1286 "TA of Assoc Request frame is not the address of the link it is transmitted on");
1288 mpdu->GetPacket()->PeekHeader(assoc);
1289 const auto& mle = assoc.Get<MultiLinkElement>();
1290
1291 if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetNLinks() == 1)
1292 {
1293 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1294 false,
1295 "Multi-Link Element in Assoc Request frame from single link STA");
1296 }
1297 else
1298 {
1299 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1300 true,
1301 "No Multi-Link Element in Assoc Request frame");
1302 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1303 m_staMacs[0]->GetAddress(),
1304 "Incorrect MLD Address advertised in Multi-Link Element");
1306 mle->GetNPerStaProfileSubelements(),
1307 m_setupLinks.size() - 1,
1308 "Incorrect number of Per-STA Profile subelements in Multi-Link Element");
1309 for (std::size_t i = 0; i < mle->GetNPerStaProfileSubelements(); i++)
1310 {
1311 auto& perStaProfile = mle->GetPerStaProfile(i);
1312 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasStaMacAddress(),
1313 true,
1314 "Per-STA Profile must contain STA MAC address");
1315 // find ID of the local link corresponding to this subelement
1316 auto staLinkId = m_staMacs[0]->GetLinkIdByAddress(perStaProfile.GetStaMacAddress());
1318 staLinkId.has_value(),
1319 true,
1320 "No link found with the STA MAC address advertised in Per-STA Profile");
1322 +staLinkId.value(),
1323 +linkId,
1324 "The STA that sent the Assoc Request should not be included in a Per-STA Profile");
1325 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), staLinkId.value());
1326 NS_TEST_EXPECT_MSG_EQ((it != m_setupLinks.end()),
1327 true,
1328 "Not expecting to setup STA link ID " << +staLinkId.value());
1330 +staLinkId.value(),
1331 +perStaProfile.GetLinkId(),
1332 "Not expecting to request association to AP Link ID in Per-STA Profile");
1333 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasAssocRequest(),
1334 true,
1335 "Missing Association Request in Per-STA Profile");
1336 }
1337 }
1338
1339 const auto& tlm = assoc.Get<TidToLinkMapping>();
1340
1341 // A TID-to-Link Mapping IE is included in the Association Request if and only if the AP MLD
1342 // and the non-AP MLD are performing ML setup (i.e., they both have multiple links) and the
1343 // AP MLD advertises a non-null negotiation support type
1344 if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetNLinks() == 1 || m_apNegSupport == 0)
1345 {
1346 NS_TEST_EXPECT_MSG_EQ(tlm.empty(),
1347 true,
1348 "Didn't expect a TID-to-Link Mapping IE in Assoc Request frame");
1349 }
1350 else
1351 {
1352 std::size_t expectedNTlm = (m_dlTidLinkMapping == m_ulTidLinkMapping ? 1 : 2);
1353
1354 NS_TEST_ASSERT_MSG_EQ(tlm.size(),
1355 expectedNTlm,
1356 "Unexpected number of TID-to-Link Mapping IE in Assoc Request");
1357
1358 // lambda to check content of TID-to-Link Mapping IE(s)
1359 auto checkTlm = [&](std::size_t tlmId, WifiDirection dir) {
1360 NS_TEST_EXPECT_MSG_EQ(+static_cast<uint8_t>(tlm[tlmId].m_control.direction),
1361 +static_cast<uint8_t>(dir),
1362 "Unexpected direction in TID-to-Link Mapping IE " << tlmId);
1363 auto& expectedMapping =
1364 (dir == WifiDirection::UPLINK ? m_ulTidLinkMapping : m_dlTidLinkMapping);
1365
1366 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].m_control.defaultMapping,
1367 expectedMapping.empty(),
1368 "Default Link Mapping bit not set correctly");
1369 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].m_linkMapping.size(),
1370 expectedMapping.size(),
1371 "Unexpected number of Link Mapping Of TID n fields");
1372 for (uint8_t tid = 0; tid < 8; tid++)
1373 {
1374 if (auto it = expectedMapping.find(tid); it != expectedMapping.cend())
1375 {
1376 NS_TEST_EXPECT_MSG_EQ((tlm[tlmId].GetLinkMappingOfTid(tid) == it->second),
1377 true,
1378 "Unexpected link mapping for TID "
1379 << +tid << " direction " << dir);
1380 }
1381 else
1382 {
1383 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].GetLinkMappingOfTid(tid).empty(),
1384 true,
1385 "Expecting no Link Mapping Of TID n field for TID "
1386 << +tid << " direction " << dir);
1387 }
1388 }
1389 };
1390
1391 if (tlm.size() == 1)
1392 {
1393 checkTlm(0, WifiDirection::BOTH_DIRECTIONS);
1394 }
1395 else
1396 {
1397 std::size_t dlId = (tlm[0].m_control.direction == WifiDirection::DOWNLINK ? 0 : 1);
1398 std::size_t ulId = (dlId == 0 ? 1 : 0);
1399
1400 checkTlm(dlId, WifiDirection::DOWNLINK);
1401 checkTlm(ulId, WifiDirection::UPLINK);
1402 }
1403 }
1404}
1405
1406void
1408{
1409 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_ASSOCIATION_RESPONSE);
1410
1411 CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::DL);
1412
1414 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1415 mpdu->GetHeader().GetAddr2(),
1416 "TA of Assoc Response frame is not the address of the link it is transmitted on");
1418 mpdu->GetPacket()->PeekHeader(assoc);
1419 const auto& mle = assoc.Get<MultiLinkElement>();
1420
1421 if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetNLinks() == 1)
1422 {
1424 mle.has_value(),
1425 false,
1426 "Multi-Link Element in Assoc Response frame with single link AP or single link STA");
1427 return;
1428 }
1429
1430 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Assoc Request frame");
1431 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1433 "Incorrect MLD Address advertised in Multi-Link Element");
1434 NS_TEST_EXPECT_MSG_EQ(mle->GetNPerStaProfileSubelements(),
1435 m_setupLinks.size() - 1,
1436 "Incorrect number of Per-STA Profile subelements in Multi-Link Element");
1437 for (std::size_t i = 0; i < mle->GetNPerStaProfileSubelements(); i++)
1438 {
1439 auto& perStaProfile = mle->GetPerStaProfile(i);
1440 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasStaMacAddress(),
1441 true,
1442 "Per-STA Profile must contain STA MAC address");
1443 // find ID of the local link corresponding to this subelement
1444 auto apLinkId = m_apMac->GetLinkIdByAddress(perStaProfile.GetStaMacAddress());
1446 apLinkId.has_value(),
1447 true,
1448 "No link found with the STA MAC address advertised in Per-STA Profile");
1449 NS_TEST_EXPECT_MSG_EQ(+apLinkId.value(),
1450 +perStaProfile.GetLinkId(),
1451 "Link ID and MAC address advertised in Per-STA Profile do not match");
1453 +apLinkId.value(),
1454 +linkId,
1455 "The AP that sent the Assoc Response should not be included in a Per-STA Profile");
1456 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), apLinkId.value());
1457 NS_TEST_EXPECT_MSG_EQ((it != m_setupLinks.end()),
1458 true,
1459 "Not expecting to setup AP link ID " << +apLinkId.value());
1460 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasAssocResponse(),
1461 true,
1462 "Missing Association Response in Per-STA Profile");
1463 }
1464
1465 // For the moment, the AP MLD always accepts a valid TID-to-Link Mapping request, hence
1466 // in every case there is no TID-to-Link Mapping IE in the Association Response
1467 NS_TEST_EXPECT_MSG_EQ(assoc.Get<TidToLinkMapping>().empty(),
1468 true,
1469 "Didn't expect to find a TID-to-Link Mapping IE in Association Response");
1470}
1471
1472void
1474{
1478 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->IsAssociated(), true, "Expected the STA to be associated");
1479
1480 for (const auto linkId : m_setupLinks)
1481 {
1482 auto staLinkId = (m_staMacs[0]->GetNLinks() > 1 ? linkId : SINGLE_LINK_OP_ID);
1483 auto apLinkId = (m_apMac->GetNLinks() > 1 ? linkId : SINGLE_LINK_OP_ID);
1484
1485 auto staAddr = m_staMacs[0]->GetFrameExchangeManager(staLinkId)->GetAddress();
1486 auto apAddr = m_apMac->GetFrameExchangeManager(apLinkId)->GetAddress();
1487
1488 auto staRemoteMgr = m_staMacs[0]->GetWifiRemoteStationManager(staLinkId);
1489 auto apRemoteMgr = m_apMac->GetWifiRemoteStationManager(apLinkId);
1490
1491 // STA side
1492 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetFrameExchangeManager(staLinkId)->GetBssid(),
1493 apAddr,
1494 "Unexpected BSSID for STA link ID " << +staLinkId);
1495 if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1496 {
1497 NS_TEST_EXPECT_MSG_EQ((staRemoteMgr->GetMldAddress(apAddr) == m_apMac->GetAddress()),
1498 true,
1499 "Incorrect MLD address stored by STA on link ID " << +staLinkId);
1501 (staRemoteMgr->GetAffiliatedStaAddress(m_apMac->GetAddress()) == apAddr),
1502 true,
1503 "Incorrect affiliated address stored by STA on link ID " << +staLinkId);
1504 }
1505
1506 // AP side
1507 NS_TEST_EXPECT_MSG_EQ(apRemoteMgr->IsAssociated(staAddr),
1508 true,
1509 "Expecting STA " << staAddr << " to be associated on link "
1510 << +apLinkId);
1511 if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1512 {
1514 (apRemoteMgr->GetMldAddress(staAddr) == m_staMacs[0]->GetAddress()),
1515 true,
1516 "Incorrect MLD address stored by AP on link ID " << +apLinkId);
1518 (apRemoteMgr->GetAffiliatedStaAddress(m_staMacs[0]->GetAddress()) == staAddr),
1519 true,
1520 "Incorrect affiliated address stored by AP on link ID " << +apLinkId);
1521 }
1522 auto aid = m_apMac->GetAssociationId(staAddr, apLinkId);
1523 const auto& staList = m_apMac->GetStaList(apLinkId);
1524 NS_TEST_EXPECT_MSG_EQ((staList.find(aid) != staList.end()),
1525 true,
1526 "STA " << staAddr << " not found in list of associated STAs");
1527
1528 // STA of non-AP MLD operate on the same channel as the AP
1530 +m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetNumber(),
1532 "Incorrect operating channel number for STA on link " << +staLinkId);
1534 m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetFrequency(),
1536 "Incorrect operating channel frequency for STA on link " << +staLinkId);
1537 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetWidth(),
1539 "Incorrect operating channel width for STA on link " << +staLinkId);
1541 +m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetPhyBand(),
1543 "Incorrect operating PHY band for STA on link " << +staLinkId);
1545 +m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetPrimaryChannelIndex(20),
1547 "Incorrect operating primary channel index for STA on link " << +staLinkId);
1548 }
1549
1550 // lambda to check the link mapping stored at wifi MAC
1551 auto checkStoredMapping =
1552 [this](Ptr<WifiMac> mac, Ptr<WifiMac> dest, WifiDirection dir, bool present) {
1553 NS_TEST_ASSERT_MSG_EQ(mac->GetTidToLinkMapping(dest->GetAddress(), dir).has_value(),
1554 present,
1555 "Link mapping stored by "
1556 << (mac->GetTypeOfStation() == AP ? "AP" : "non-AP")
1557 << " MLD for " << dir << " direction "
1558 << (present ? "expected" : "not expected"));
1559 if (present)
1560 {
1561 const auto& mapping =
1562 (dir == WifiDirection::DOWNLINK ? m_dlTidLinkMapping : m_ulTidLinkMapping);
1564 (mac->GetTidToLinkMapping(dest->GetAddress(), dir)->get() == mapping),
1565 true,
1566 "Incorrect link mapping stored by "
1567 << (mac->GetTypeOfStation() == AP ? "AP" : "non-AP") << " MLD for " << dir
1568 << " direction");
1569 }
1570 };
1571
1572 auto storedMapping =
1573 m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1 && m_apNegSupport > 0;
1574 checkStoredMapping(m_apMac, m_staMacs[0], WifiDirection::DOWNLINK, storedMapping);
1575 checkStoredMapping(m_apMac, m_staMacs[0], WifiDirection::UPLINK, storedMapping);
1576 checkStoredMapping(m_staMacs[0], m_apMac, WifiDirection::DOWNLINK, storedMapping);
1577 checkStoredMapping(m_staMacs[0], m_apMac, WifiDirection::UPLINK, storedMapping);
1578}
1579
1580void
1582{
1583 if (m_staMacs[0]->GetNLinks() == 1)
1584 {
1585 // no link is disabled on a single link device
1586 return;
1587 }
1588
1589 for (const auto& linkId : m_staMacs[0]->GetLinkIds())
1590 {
1591 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), linkId);
1592 if (it == m_setupLinks.end())
1593 {
1594 // the link has not been setup
1595 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId)->GetState()->IsStateOff(),
1596 true,
1597 "Link " << +linkId << " has not been setup but is not disabled");
1598 continue;
1599 }
1600
1601 // the link has been setup and must be active
1602 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId)->GetState()->IsStateOff(),
1603 false,
1604 "Expecting link " << +linkId << " to be active");
1605 }
1606}
1607
1608void
1609MultiLinkSetupTest::CheckQosData(Ptr<WifiMpdu> mpdu, uint8_t linkId, std::size_t index)
1610{
1612 const auto& hdr = mpdu->GetHeader();
1613
1614 NS_TEST_ASSERT_MSG_EQ(hdr.IsQosData(), true, "Expected a QoS data frame");
1615
1616 if (!hdr.IsToDs() && hdr.IsFromDs())
1617 {
1618 dir = WifiDirection::DOWNLINK;
1619 }
1620 else if (hdr.IsToDs() && !hdr.IsFromDs())
1621 {
1622 dir = WifiDirection::UPLINK;
1623 }
1624 else
1625 {
1626 NS_ABORT_MSG("Invalid combination for QoS data frame: ToDS(" << hdr.IsToDs() << ") FromDS("
1627 << hdr.IsFromDs() << ")");
1628 }
1629
1630 const auto& tid1 = (dir == WifiDirection::DOWNLINK) ? m_dlTid1 : m_ulTid1;
1631 const auto& tid2 = (dir == WifiDirection::DOWNLINK) ? m_dlTid2 : m_ulTid2;
1632 uint8_t tid = hdr.GetQosTid();
1633
1634 NS_TEST_ASSERT_MSG_NE((tid == tid1),
1635 (tid2.has_value() && tid == *tid2),
1636 "QoS frame with unexpected TID " << +tid);
1637
1638 // lambda to find the link set the given TID is mapped to
1639 auto findLinkSet = [this, dir](uint8_t tid) -> std::set<uint8_t> {
1640 std::set<uint8_t> linkSet(m_setupLinks.cbegin(), m_setupLinks.cend());
1641 if (auto mappingOptRef = m_apMac->GetTidToLinkMapping(m_staMacs[0]->GetAddress(), dir))
1642 {
1643 // if the TID is not present in the mapping, it is mapped to all setup links
1644 if (auto it = mappingOptRef->get().find(tid); it != mappingOptRef->get().cend())
1645 {
1646 linkSet = it->second;
1647 NS_ASSERT_MSG(!linkSet.empty(), "TID " << +tid << " mapped to no link");
1648 }
1649 }
1650 return linkSet;
1651 };
1652
1653 auto linkSet = findLinkSet(tid);
1654 auto& qosFrames = (tid == tid1) ? m_qosFrames1 : m_qosFrames2;
1655
1656 // Let N the size of the link set, the first N QoS data frames are sent simultaneously
1657 // on the links of the set, the others (if any) will be sent afterwards on such links
1658
1659 // number of concurrent frames of the same TID transmitted so far (excluding current frame)
1660 std::size_t nConcurFrames = std::min(qosFrames.size(), linkSet.size());
1661
1662 // iterate over the concurrent frames of the same TID transmitted so far
1663 for (std::size_t i = 0; i < nConcurFrames; i++)
1664 {
1665 auto prev = qosFrames[i];
1666
1667 // TX duration of i-th frame
1668 auto band = m_apMac->GetWifiPhy(m_txPsdus[prev].linkId)->GetPhyBand();
1669 Time txDuration =
1670 WifiPhy::CalculateTxDuration(m_txPsdus[prev].psduMap, m_txPsdus[prev].txVector, band);
1671
1672 // the current frame is transmitted concurrently with this previous frame if it is
1673 // within the first N (size of the link set) frames, otherwise it is transmitted after
1674 // this previous frame if they have been transmitted on the same link
1675 if (qosFrames.size() < linkSet.size())
1676 {
1677 // the current frame can be sent concurrently with this previous frame
1678 NS_TEST_EXPECT_MSG_LT(m_txPsdus[index].startTx,
1679 m_txPsdus[prev].startTx + txDuration,
1680 "The " << dir << " QoS frame number " << qosFrames.size()
1681 << " was not sent concurrently with others on link "
1682 << +linkId << " which TID " << +tid << " is mapped to");
1683 }
1684 else if (m_txPsdus[prev].linkId == linkId)
1685 {
1686 // the current frame is sent afterwards
1687 NS_TEST_EXPECT_MSG_GT(m_txPsdus[index].startTx,
1688 m_txPsdus[prev].startTx + txDuration,
1689 "The " << dir << " QoS frame number " << qosFrames.size()
1690 << " was sent concurrently with others on a link "
1691 << +linkId << " which TID " << +tid << " is mapped to");
1692 }
1693 }
1694
1695 if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1696 {
1697 NS_TEST_EXPECT_MSG_EQ(std::count(linkSet.cbegin(), linkSet.cend(), linkId),
1698 1,
1699 "QoS frame sent on Link ID "
1700 << +linkId << " that does not belong to the link set of TID "
1701 << +tid);
1702 }
1703
1704 if (tid2)
1705 {
1706 // QoS frames of two distinct TIDs are sent.
1707 auto otherTid = (tid == tid1) ? *tid2 : tid1;
1708 const auto& otherQosFrames = (tid == tid1) ? m_qosFrames2 : m_qosFrames1;
1709 auto otherLinkSet = findLinkSet(otherTid);
1710
1711 // number of concurrent frames of the other TID transmitted so far
1712 std::size_t nOtherConcurFrames = std::min(otherQosFrames.size(), otherLinkSet.size());
1713
1714 // iterate over the concurrent frames of the other TID
1715 for (std::size_t i = 0; i < nOtherConcurFrames; i++)
1716 {
1717 auto prev = otherQosFrames[i];
1718
1719 // TX duration of i-th frame
1720 auto band = m_apMac->GetWifiPhy(m_txPsdus[prev].linkId)->GetPhyBand();
1721 Time txDuration = WifiPhy::CalculateTxDuration(m_txPsdus[prev].psduMap,
1722 m_txPsdus[prev].txVector,
1723 band);
1724
1725 // the current frame is transmitted concurrently with this previous frame of the
1726 // other TID if it is within the first N (size of the link set) frames of its TID
1727 if (qosFrames.size() < linkSet.size())
1728 {
1729 // the current frame can be sent concurrently with this previous frame
1730 NS_TEST_EXPECT_MSG_LT(m_txPsdus[index].startTx,
1731 m_txPsdus[prev].startTx + txDuration,
1732 "The " << dir << " QoS frame number " << qosFrames.size()
1733 << " was not sent concurrently with others with TID "
1734 << +otherTid);
1735 }
1736 }
1737 }
1738
1739 // insert the frame
1740 qosFrames.emplace_back(index);
1741
1742 if (qosFrames.size() == m_setupLinks.size())
1743 {
1744 qosFrames.clear();
1745 }
1746}
1747
1751enum class WifiTrafficPattern : uint8_t
1752{
1753 STA_TO_STA = 0,
1754 STA_TO_AP,
1755 AP_TO_STA,
1758};
1759
1763enum class WifiBaEnabled : uint8_t
1764{
1765 NO = 0,
1766 YES
1767};
1768
1772enum class WifiUseBarAfterMissedBa : uint8_t
1773{
1774 NO = 0,
1775 YES
1776};
1777
1807{
1808 public:
1819 MultiLinkTxTest(const BaseParams& baseParams,
1820 WifiTrafficPattern trafficPattern,
1821 WifiBaEnabled baEnabled,
1822 WifiUseBarAfterMissedBa useBarAfterMissedBa,
1823 uint8_t nMaxInflight);
1824 ~MultiLinkTxTest() override = default;
1825
1826 protected:
1835 void CheckBlockAck(Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId);
1836
1837 void Transmit(Ptr<WifiMac> mac,
1838 uint8_t phyId,
1839 WifiConstPsduMap psduMap,
1840 WifiTxVector txVector,
1841 double txPowerW) override;
1842 void DoSetup() override;
1843 void DoRun() override;
1844
1845 private:
1846 void StartTraffic() override;
1847
1849 using RxErrorModelMap = std::unordered_map<Mac48Address, Ptr<ListErrorModel>, WifiAddressHash>;
1850
1852 std::list<uint64_t> m_uidList;
1853 bool m_dataCorrupted{false};
1857 std::size_t m_nMaxInflight;
1858 std::size_t m_nPackets;
1859 std::size_t m_blockAckCount{0};
1860 std::size_t m_blockAckReqCount{0};
1861 std::map<uint16_t, std::size_t> m_inflightCount;
1864};
1865
1867 WifiTrafficPattern trafficPattern,
1868 WifiBaEnabled baEnabled,
1869 WifiUseBarAfterMissedBa useBarAfterMissedBa,
1870 uint8_t nMaxInflight)
1872 std::string("Check data transmission between MLDs ") +
1873 (baEnabled == WifiBaEnabled::YES
1874 ? (useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES
1875 ? "with BA agreement, send BAR after BlockAck timeout"
1876 : "with BA agreement, send Data frames after BlockAck timeout")
1877 : "without BA agreement") +
1878 " (Traffic pattern: " + std::to_string(static_cast<uint8_t>(trafficPattern)) +
1879 (baEnabled == WifiBaEnabled::YES ? ", nMaxInflight=" + std::to_string(nMaxInflight)
1880 : "") +
1881 ")",
1882 2,
1883 baseParams),
1884 m_trafficPattern(trafficPattern),
1885 m_baEnabled(baEnabled == WifiBaEnabled::YES),
1886 m_useBarAfterMissedBa(useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES),
1887 m_nMaxInflight(nMaxInflight),
1888 m_nPackets(trafficPattern == WifiTrafficPattern::STA_TO_BCAST ||
1889 trafficPattern == WifiTrafficPattern::STA_TO_STA
1890 ? 4
1891 : 8)
1892{
1893}
1894
1895void
1897 uint8_t phyId,
1898 WifiConstPsduMap psduMap,
1899 WifiTxVector txVector,
1900 double txPowerW)
1901{
1902 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
1903 auto linkId = m_txPsdus.back().linkId;
1904
1905 auto psdu = psduMap.begin()->second;
1906
1907 switch (psdu->GetHeader(0).GetType())
1908 {
1910 // a management frame is a DL frame if TA equals BSSID
1911 CheckAddresses(psdu,
1912 psdu->GetHeader(0).GetAddr2() == psdu->GetHeader(0).GetAddr3() ? DL : UL);
1913 if (!m_baEnabled)
1914 {
1915 // corrupt all management action frames (ADDBA Request frames) to prevent
1916 // the establishment of a BA agreement
1917 m_uidList.push_front(psdu->GetPacket()->GetUid());
1918 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1919 NS_LOG_INFO("CORRUPTED");
1920 }
1921 break;
1922 case WIFI_MAC_QOSDATA:
1923 CheckAddresses(psdu);
1924
1925 for (const auto& mpdu : *psdu)
1926 {
1927 // determine the max number of simultaneous transmissions for this MPDU
1928 // (only if sent by the traffic source and this is not a broadcast frame)
1929 if (m_baEnabled && m_sourceMac->GetLinkIds().count(linkId) == 1 &&
1930 m_sourceMac->GetFrameExchangeManager(linkId)->GetAddress() ==
1931 mpdu->GetHeader().GetAddr2() &&
1932 !mpdu->GetHeader().GetAddr1().IsGroup())
1933 {
1934 auto seqNo = mpdu->GetHeader().GetSequenceNumber();
1935 auto [it, success] =
1936 m_inflightCount.insert({seqNo, mpdu->GetInFlightLinkIds().size()});
1937 if (!success)
1938 {
1939 it->second = std::max(it->second, mpdu->GetInFlightLinkIds().size());
1940 }
1941 }
1942 }
1943 for (std::size_t i = 0; i < psdu->GetNMpdus(); i++)
1944 {
1945 // corrupt QoS data frame with sequence number equal to 1 (only once) if we are
1946 // not in the AP to broadcast traffic pattern (broadcast frames are not retransmitted)
1947 // nor in the STA to broadcast or STA to STA traffic patterns (retransmissions from
1948 // STA 1 could collide with frames forwarded by the AP)
1949 if (psdu->GetHeader(i).GetSequenceNumber() != 1 ||
1950 m_trafficPattern == WifiTrafficPattern::AP_TO_BCAST ||
1951 m_trafficPattern == WifiTrafficPattern::STA_TO_BCAST ||
1952 m_trafficPattern == WifiTrafficPattern::STA_TO_STA)
1953 {
1954 continue;
1955 }
1956 auto uid = psdu->GetPayload(i)->GetUid();
1957 if (!m_dataCorrupted)
1958 {
1959 m_uidList.push_front(uid);
1960 m_dataCorrupted = true;
1961 NS_LOG_INFO("CORRUPTED");
1962 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1963 }
1964 else
1965 {
1966 // do not corrupt the QoS data frame anymore
1967 if (auto it = std::find(m_uidList.cbegin(), m_uidList.cend(), uid);
1968 it != m_uidList.cend())
1969 {
1970 m_uidList.erase(it);
1971 }
1972 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1973 }
1974 break;
1975 }
1976 break;
1977 case WIFI_MAC_CTL_BACKRESP: {
1978 // ignore BlockAck frames not addressed to the source of the application packets
1979 if (!m_sourceMac->GetLinkIdByAddress(psdu->GetHeader(0).GetAddr1()))
1980 {
1981 break;
1982 }
1983 if (m_nMaxInflight > 1)
1984 {
1985 // we do not check the content of BlockAck when m_nMaxInflight is greater than 1
1986 break;
1987 }
1988 CheckBlockAck(psdu, txVector, linkId);
1990 if (m_blockAckCount == 2)
1991 {
1992 // corrupt the second BlockAck frame to simulate a missed BlockAck
1993 m_uidList.push_front(psdu->GetPacket()->GetUid());
1994 NS_LOG_INFO("CORRUPTED");
1995 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1996 }
1997 break;
1999 // ignore BlockAckReq frames not transmitted by the source of the application packets
2000 if (m_sourceMac->GetLinkIdByAddress(psdu->GetHeader(0).GetAddr2()))
2001 {
2003 }
2004 break;
2005 }
2006 default:;
2007 }
2008}
2009
2010void
2012 const WifiTxVector& txVector,
2013 uint8_t linkId)
2014{
2015 NS_TEST_ASSERT_MSG_EQ(m_baEnabled, true, "No BlockAck expected without BA agreement");
2016 NS_TEST_ASSERT_MSG_EQ((m_trafficPattern != WifiTrafficPattern::AP_TO_BCAST),
2017 true,
2018 "No BlockAck expected in AP to broadcast traffic pattern");
2019
2020 /*
2021 * ┌───────┬───────X ┌───────┐
2022 * link 0 │ 0 │ 1 │ │ 1 │
2023 * ───────┴───────┴───────┴┬──┬────┴───────┴┬───┬────────────────────────
2024 * │BA│ │ACK│
2025 * └──┘ └───┘
2026 * ┌───────┬───────┐ ┌───────┬───────┐
2027 * link 1 │ 2 │ 3 │ │ 2 │ 3 │
2028 * ────────────────────┴───────┴───────┴┬──X───┴───────┴───────┴┬──┬─────
2029 * │BA│ │BA│
2030 * └──┘ └──┘
2031 */
2032 auto mpdu = *psdu->begin();
2033 CtrlBAckResponseHeader blockAck;
2034 mpdu->GetPacket()->PeekHeader(blockAck);
2035 bool isMpdu1corrupted = (m_trafficPattern == WifiTrafficPattern::STA_TO_AP ||
2036 m_trafficPattern == WifiTrafficPattern::AP_TO_STA);
2037
2038 switch (m_blockAckCount)
2039 {
2040 case 0: // first BlockAck frame (all traffic patterns)
2042 true,
2043 "MPDU 0 expected to be successfully received");
2045 blockAck.IsPacketReceived(1),
2046 !isMpdu1corrupted,
2047 "MPDU 1 expected to be received only in STA_TO_STA/STA_TO_BCAST scenarios");
2048 // if there are at least two links setup, we expect all MPDUs to be inflight
2049 // (on distinct links)
2050 if (m_staMacs[0]->GetSetupLinkIds().size() > 1)
2051 {
2052 auto queue = m_sourceMac->GetTxopQueue(AC_BE);
2053 auto rcvMac = m_sourceMac == m_staMacs[0] ? StaticCast<WifiMac>(m_apMac)
2054 : StaticCast<WifiMac>(m_staMacs[1]);
2055 auto item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress());
2056 std::size_t nQueuedPkt = 0;
2057 auto delay = WifiPhy::CalculateTxDuration(psdu,
2058 txVector,
2059 rcvMac->GetWifiPhy(linkId)->GetPhyBand()) +
2060 MicroSeconds(1); // to account for propagation delay
2061
2062 while (item)
2063 {
2064 auto seqNo = item->GetHeader().GetSequenceNumber();
2065 NS_TEST_EXPECT_MSG_EQ(item->IsInFlight(),
2066 true,
2067 "MPDU with seqNo=" << seqNo << " is not in flight");
2068 auto linkIds = item->GetInFlightLinkIds();
2069 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
2070 1,
2071 "MPDU with seqNo=" << seqNo
2072 << " is in flight on multiple links");
2073 // The first two MPDUs are in flight on the same link on which the BlockAck
2074 // is sent. The other two MPDUs (only for AP to STA/STA to AP scenarios) are
2075 // in flight on a different link.
2076 auto srcLinkId = m_sourceMac->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1());
2077 NS_TEST_ASSERT_MSG_EQ(srcLinkId.has_value(),
2078 true,
2079 "Addr1 of BlockAck is not an originator's link address");
2080 NS_TEST_EXPECT_MSG_EQ((*linkIds.begin() == *srcLinkId),
2081 (seqNo <= 1),
2082 "MPDU with seqNo=" << seqNo
2083 << " in flight on unexpected link");
2084 // check the Retry subfield and whether this MPDU is still queued
2085 // after the originator has processed this BlockAck
2086
2087 // MPDUs acknowledged via this BlockAck are no longer queued
2088 bool isQueued = (seqNo > (isMpdu1corrupted ? 0 : 1));
2089 // The Retry subfield is set if the MPDU has not been acknowledged (i.e., it
2090 // is still queued) and has been transmitted on the same link as the BlockAck
2091 // (i.e., its sequence number is less than or equal to 1)
2092 bool isRetry = isQueued && seqNo <= 1;
2093
2094 Simulator::Schedule(delay, [this, item, isQueued, isRetry]() {
2095 NS_TEST_EXPECT_MSG_EQ(item->IsQueued(),
2096 isQueued,
2097 "MPDU with seqNo="
2098 << item->GetHeader().GetSequenceNumber() << " should "
2099 << (isQueued ? "" : "not") << " be queued");
2101 item->GetHeader().IsRetry(),
2102 isRetry,
2103 "Unexpected value for the Retry subfield of the MPDU with seqNo="
2104 << item->GetHeader().GetSequenceNumber());
2105 });
2106
2107 nQueuedPkt++;
2108 item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress(), item);
2109 }
2110 // Each MPDU contains an A-MSDU consisting of two MSDUs
2111 NS_TEST_EXPECT_MSG_EQ(nQueuedPkt, m_nPackets / 2, "Unexpected number of queued MPDUs");
2112 }
2113 break;
2114 case 1: // second BlockAck frame (STA to AP and AP to STA traffic patterns only)
2115 case 2: // third BlockAck frame (STA to AP and AP to STA traffic patterns only)
2116 NS_TEST_EXPECT_MSG_EQ((m_trafficPattern == WifiTrafficPattern::AP_TO_STA ||
2117 m_trafficPattern == WifiTrafficPattern::STA_TO_AP),
2118 true,
2119 "Did not expect to receive a second BlockAck");
2120 // the second BlockAck is corrupted, but the data frames have been received successfully
2121 std::pair<uint16_t, uint16_t> seqNos;
2122 // if multiple links were setup, the transmission of the second A-MPDU started before
2123 // the end of the first one, so the second A-MPDU includes MPDUs with sequence numbers
2124 // 2 and 3. Otherwise, MPDU with sequence number 1 is retransmitted along with the MPDU
2125 // with sequence number 2.
2126 if (m_staMacs[0]->GetSetupLinkIds().size() > 1)
2127 {
2128 seqNos = {2, 3};
2129 }
2130 else
2131 {
2132 seqNos = {1, 2};
2133 }
2134 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(seqNos.first),
2135 true,
2136 "MPDU " << seqNos.first << " expected to be successfully received");
2137 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(seqNos.second),
2138 true,
2139 "MPDU " << seqNos.second << " expected to be successfully received");
2140 break;
2141 }
2142}
2143
2144void
2146{
2148
2149 if (m_baEnabled)
2150 {
2151 // Enable A-MSDU aggregation. Max A-MSDU size is set such that two MSDUs can be aggregated
2152 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
2153 {
2154 mac->SetAttribute("BE_MaxAmsduSize", UintegerValue(2100));
2155 mac->GetQosTxop(AC_BE)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2157 mac->GetQosTxop(AC_BE)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2158 }
2159 }
2160
2161 // install post reception error model on all devices
2162 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
2163 {
2164 auto errorModel = CreateObject<ListErrorModel>();
2165 m_errorModels[m_apMac->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2166 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2167 }
2168 for (std::size_t i : {0, 1})
2169 {
2170 for (const auto linkId : m_staMacs[i]->GetLinkIds())
2171 {
2172 auto errorModel = CreateObject<ListErrorModel>();
2173 m_errorModels[m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2174 m_staMacs[i]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2175 }
2176 }
2177}
2178
2179void
2181{
2182 Address destAddr;
2183
2184 switch (m_trafficPattern)
2185 {
2186 case WifiTrafficPattern::STA_TO_STA:
2188 destAddr = m_staMacs[1]->GetDevice()->GetAddress();
2189 break;
2190 case WifiTrafficPattern::STA_TO_AP:
2192 destAddr = m_apMac->GetDevice()->GetAddress();
2193 break;
2194 case WifiTrafficPattern::AP_TO_STA:
2196 destAddr = m_staMacs[1]->GetDevice()->GetAddress();
2197 break;
2198 case WifiTrafficPattern::AP_TO_BCAST:
2200 destAddr = Mac48Address::GetBroadcast();
2201 break;
2202 case WifiTrafficPattern::STA_TO_BCAST:
2204 destAddr = Mac48Address::GetBroadcast();
2205 break;
2206 }
2207
2208 PacketSocketAddress sockAddr;
2210 sockAddr.SetPhysicalAddress(destAddr);
2211 sockAddr.SetProtocol(1);
2212
2213 // install first client application generating at most 4 packets
2215 GetApplication(sockAddr, std::min<std::size_t>(m_nPackets, 4), 1000));
2216
2217 if (m_nPackets > 4)
2218 {
2219 // install a second client application generating the remaining packets and
2220 // starting during transmission of first A-MPDU, if multiple links are setup
2222 GetApplication(sockAddr, m_nPackets - 4, 1000, MilliSeconds(4)));
2223 }
2224
2226}
2227
2228void
2230{
2232
2233 // Expected number of packets received by each node (AP, STA 0, STA 1) at application layer
2234 std::array<std::size_t, 3> expectedRxPkts{};
2235
2236 switch (m_trafficPattern)
2237 {
2238 case WifiTrafficPattern::STA_TO_STA:
2239 case WifiTrafficPattern::AP_TO_STA:
2240 // only STA 1 receives the m_nPackets packets that have been transmitted
2241 expectedRxPkts[2] = m_nPackets;
2242 break;
2243 case WifiTrafficPattern::STA_TO_AP:
2244 // only the AP receives the m_nPackets packets that have been transmitted
2245 expectedRxPkts[0] = m_nPackets;
2246 break;
2247 case WifiTrafficPattern::AP_TO_BCAST:
2248 // the AP replicates the broadcast frames on all the links, hence each station
2249 // receives the m_nPackets packets N times, where N is the number of setup link
2250 expectedRxPkts[1] = m_nPackets * m_staMacs[0]->GetSetupLinkIds().size();
2251 expectedRxPkts[2] = m_nPackets * m_staMacs[1]->GetSetupLinkIds().size();
2252 break;
2253 case WifiTrafficPattern::STA_TO_BCAST:
2254 // the AP receives the m_nPackets packets and then replicates them on all the links,
2255 // hence STA 1 receives m_nPackets packets N times, where N is the number of setup link
2256 expectedRxPkts[0] = m_nPackets;
2257 expectedRxPkts[2] = m_nPackets * m_staMacs[1]->GetSetupLinkIds().size();
2258 break;
2259 }
2260
2262 +expectedRxPkts[0],
2263 "Unexpected number of packets received by the AP");
2265 +expectedRxPkts[1],
2266 "Unexpected number of packets received by STA 0");
2268 +expectedRxPkts[2],
2269 "Unexpected number of packets received by STA 1");
2270
2271 // check that the expected number of BlockAck frames are transmitted
2272 if (m_baEnabled && m_nMaxInflight == 1)
2273 {
2274 std::size_t expectedBaCount = 0;
2275 std::size_t expectedBarCount = 0;
2276
2277 switch (m_trafficPattern)
2278 {
2279 case WifiTrafficPattern::STA_TO_AP:
2280 case WifiTrafficPattern::AP_TO_STA:
2281 // two A-MPDUs are transmitted and one BlockAck is corrupted
2282 expectedBaCount = 3;
2283 // one BlockAckReq is sent if m_useBarAfterMissedBa is true
2284 expectedBarCount = m_useBarAfterMissedBa ? 1 : 0;
2285 break;
2286 case WifiTrafficPattern::STA_TO_STA:
2287 case WifiTrafficPattern::STA_TO_BCAST:
2288 // only one A-MPDU is transmitted and the BlockAck is not corrupted
2289 expectedBaCount = 1;
2290 break;
2291 default:;
2292 }
2294 expectedBaCount,
2295 "Unexpected number of BlockAck frames");
2297 expectedBarCount,
2298 "Unexpected number of BlockAckReq frames");
2299 }
2300
2301 // check that setting the QosTxop::NMaxInflights attribute has the expected effect.
2302 // We do not support sending an MPDU multiple times concurrently without Block Ack
2303 // agreement. Also, broadcast frames are already duplicated and sent on all links.
2304 if (m_baEnabled && m_trafficPattern != WifiTrafficPattern::AP_TO_BCAST)
2305 {
2307 m_inflightCount.size(),
2308 m_nPackets / 2,
2309 "Did not collect number of simultaneous transmissions for all data frames");
2310
2311 auto nMaxInflight = std::min(m_nMaxInflight, m_staMacs[0]->GetSetupLinkIds().size());
2312 std::size_t maxCount = 0;
2313 for (const auto& [seqNo, count] : m_inflightCount)
2314 {
2316 count,
2317 nMaxInflight,
2318 "MPDU with seqNo=" << seqNo
2319 << " transmitted simultaneously more times than allowed");
2320 maxCount = std::max(maxCount, count);
2321 }
2322
2324 maxCount,
2325 nMaxInflight,
2326 "Expected that at least one data frame was transmitted simultaneously a number of "
2327 "times equal to the NMaxInflights attribute");
2328 }
2329
2331}
2332
2336enum class WifiMuTrafficPattern : uint8_t
2337{
2341 UL_MU
2342};
2343
2368{
2369 public:
2379 MultiLinkMuTxTest(const BaseParams& baseParams,
2380 WifiMuTrafficPattern muTrafficPattern,
2381 WifiUseBarAfterMissedBa useBarAfterMissedBa,
2382 uint8_t nMaxInflight);
2383 ~MultiLinkMuTxTest() override = default;
2384
2385 protected:
2394 void CheckBlockAck(Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId);
2395
2396 void Transmit(Ptr<WifiMac> mac,
2397 uint8_t phyId,
2398 WifiConstPsduMap psduMap,
2399 WifiTxVector txVector,
2400 double txPowerW) override;
2401 void DoSetup() override;
2402 void DoRun() override;
2403
2404 private:
2405 void StartTraffic() override;
2406
2408 using RxErrorModelMap = std::unordered_map<Mac48Address, Ptr<ListErrorModel>, WifiAddressHash>;
2409
2412 using AddrSeqNoPair = std::pair<Mac48Address, uint16_t>;
2413
2415 std::list<uint64_t> m_uidList;
2416 std::optional<Mac48Address> m_dataCorruptedSta;
2418 bool m_waitFirstTf{true};
2421 std::size_t m_nMaxInflight;
2422 std::vector<PacketSocketAddress> m_sockets;
2423 std::size_t m_nPackets;
2424 std::size_t m_blockAckCount{0};
2425 // std::size_t m_blockAckReqCount{0}; ///< transmitted BlockAckReq counter
2426 std::map<AddrSeqNoPair, std::size_t> m_inflightCount;
2429};
2430
2432 WifiMuTrafficPattern muTrafficPattern,
2433 WifiUseBarAfterMissedBa useBarAfterMissedBa,
2434 uint8_t nMaxInflight)
2436 std::string("Check MU data transmission between MLDs ") +
2437 (useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES
2438 ? "(send BAR after BlockAck timeout,"
2439 : "(send Data frames after BlockAck timeout,") +
2440 " MU Traffic pattern: " + std::to_string(static_cast<uint8_t>(muTrafficPattern)) +
2441 ", nMaxInflight=" + std::to_string(nMaxInflight) + ")",
2442 2,
2443 baseParams),
2444 m_muTrafficPattern(muTrafficPattern),
2445 m_useBarAfterMissedBa(useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES),
2446 m_nMaxInflight(nMaxInflight),
2447 m_sockets(m_nStations),
2448 m_nPackets(muTrafficPattern == WifiMuTrafficPattern::UL_MU ? 4 : 8)
2449{
2450}
2451
2452void
2454 uint8_t phyId,
2455 WifiConstPsduMap psduMap,
2456 WifiTxVector txVector,
2457 double txPowerW)
2458{
2459 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2460 auto linkId = m_txPsdus.back().linkId;
2461
2462 CtrlTriggerHeader trigger;
2463
2464 for (const auto& [staId, psdu] : psduMap)
2465 {
2466 switch (psdu->GetHeader(0).GetType())
2467 {
2468 case WIFI_MAC_QOSDATA:
2469 CheckAddresses(psdu);
2470 if (psdu->GetHeader(0).HasData())
2471 {
2472 bool isDl = psdu->GetHeader(0).IsFromDs();
2473 auto linkAddress =
2474 isDl ? psdu->GetHeader(0).GetAddr1() : psdu->GetHeader(0).GetAddr2();
2475 auto address = m_apMac->GetMldAddress(linkAddress).value_or(linkAddress);
2476
2477 for (const auto& mpdu : *psdu)
2478 {
2479 // determine the max number of simultaneous transmissions for this MPDU
2480 auto seqNo = mpdu->GetHeader().GetSequenceNumber();
2481 auto [it, success] = m_inflightCount.insert(
2482 {{address, seqNo}, mpdu->GetInFlightLinkIds().size()});
2483 if (!success)
2484 {
2485 it->second = std::max(it->second, mpdu->GetInFlightLinkIds().size());
2486 }
2487 }
2488 for (std::size_t i = 0; i < psdu->GetNMpdus(); i++)
2489 {
2490 // MPDUs with seqNo=2 are always transmitted in an MU PPDU
2491 if (psdu->GetHeader(i).GetSequenceNumber() == 2)
2492 {
2493 if (m_muTrafficPattern == WifiMuTrafficPattern::UL_MU)
2494 {
2495 NS_TEST_EXPECT_MSG_EQ(txVector.IsUlMu(),
2496 true,
2497 "MPDU " << **std::next(psdu->begin(), i)
2498 << " not transmitted in a TB PPDU");
2499 }
2500 else
2501 {
2502 NS_TEST_EXPECT_MSG_EQ(txVector.GetHeMuUserInfoMap().size(),
2503 2,
2504 "MPDU " << **std::next(psdu->begin(), i)
2505 << " not transmitted in a DL MU PPDU");
2506 }
2507 }
2508 // corrupt QoS data frame with sequence number equal to 3 (only once)
2509 if (psdu->GetHeader(i).GetSequenceNumber() != 3)
2510 {
2511 continue;
2512 }
2513 auto uid = psdu->GetPayload(i)->GetUid();
2514 if (!m_dataCorruptedSta)
2515 {
2516 m_uidList.push_front(uid);
2517 m_dataCorruptedSta = isDl ? psdu->GetAddr1() : psdu->GetAddr2();
2518 NS_LOG_INFO("CORRUPTED");
2519 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2520 }
2521 else if ((isDl && m_dataCorruptedSta == psdu->GetAddr1()) ||
2522 (!isDl && m_dataCorruptedSta == psdu->GetAddr2()))
2523 {
2524 // do not corrupt the QoS data frame anymore
2525 if (auto it = std::find(m_uidList.cbegin(), m_uidList.cend(), uid);
2526 it != m_uidList.cend())
2527 {
2528 m_uidList.erase(it);
2529 }
2530 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2531 }
2532 break;
2533 }
2534 }
2535 break;
2537 if (m_nMaxInflight > 1)
2538 {
2539 // we do not check the content of BlockAck when m_nMaxInflight is greater than 1
2540 break;
2541 }
2542 CheckBlockAck(psdu, txVector, linkId);
2544 // to simulate a missed BlockAck, corrupt the fifth BlockAck frame (the first
2545 // two BlockAck frames are sent to acknowledge the QoS data frames that triggered
2546 // the establishment of Block Ack agreements)
2547 if (m_blockAckCount == 5)
2548 {
2549 // corrupt the third BlockAck frame to simulate a missed BlockAck
2550 m_uidList.push_front(psdu->GetPacket()->GetUid());
2551 NS_LOG_INFO("CORRUPTED");
2552 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2553 }
2554 break;
2556 psdu->GetPayload(0)->PeekHeader(trigger);
2557 // the MU scheduler requests channel access on all links but we have to perform the
2558 // following actions only once (hence why we only consider TF transmitted on link 0)
2559 if (trigger.IsBasic() && m_waitFirstTf)
2560 {
2561 m_waitFirstTf = false;
2562 // the AP is starting the transmission of the Basic Trigger frame, so generate
2563 // the configured number of packets at STAs, which are sent in TB PPDUs, when
2564 // transmission of the Trigger Frame ends
2565 auto band = mac->GetWifiPhy(linkId)->GetPhyBand();
2566 Time txDuration = WifiPhy::CalculateTxDuration(psduMap, txVector, band);
2567 for (uint8_t i = 0; i < m_nStations; i++)
2568 {
2569 m_staMacs[i]->GetDevice()->GetNode()->AddApplication(
2570 GetApplication(m_sockets[i], m_nPackets, 450, txDuration, i * 4));
2571 }
2572 }
2573 break;
2574 default:;
2575 }
2576 }
2577}
2578
2579void
2581 const WifiTxVector& txVector,
2582 uint8_t linkId)
2583{
2584 /*
2585 * Example sequence with DL_MU_BAR_BA_SEQUENCE
2586 * ┌───────┬───────X
2587 * (To:1) │ 2 │ 3 │
2588 * ├───────┼───────┤ ┌───┐ ┌───────┐
2589 * [link 0] (To:0) │ 2 │ 3 │ │BAR│ (To:1) │ 3 │
2590 * ────────────────┴───────┴───────┴┬──┼───┼──┬──────────┴───────┴┬───┬────────
2591 * │BA│ │BA│ │ACK│
2592 * └──┘ └──┘ └───┘
2593 * ┌───────┬───────┐
2594 * (To:1) │ 4 │ 5 │
2595 * ├───────┼───────┤ ┌───┐ ┌───┐
2596 * [link 1] (To:0) │ 4 │ 5 │ │BAR│ │BAR│
2597 * ────────────────────────────┴───────┴───────┴┬──X────┴───┴┬──┼───┼──┬───────
2598 * │BA│ │BA│ │BA│
2599 * └──┘ └──┘ └──┘
2600 *
2601 * Example sequence with UL_MU
2602 *
2603 * ┌──┐ ┌────┐ ┌───┐
2604 * [link 0] │TF│ │M-BA│ │ACK│
2605 * ─────────┴──┴──┬───────┬───────┬──┴────┴────────────┬───────┬─┴───┴─────────
2606 * (From:0) │ 2 │ 3 │ (From:1) │ 3 │
2607 * ├───────┼───────┤ └───────┘
2608 * (From:1) │ 2 │ 3 │
2609 * └───────┴───────X
2610 * ┌──┐
2611 * [link 1] │TF│
2612 * ─────────┴──┴──┬───────────────┬────────────────────────────────────────────
2613 * (From:0) │ QoS Null │
2614 * ├───────────────┤
2615 * (From:1) │ QoS Null │
2616 * └───────────────┘
2617 */
2618 auto mpdu = *psdu->begin();
2619 CtrlBAckResponseHeader blockAck;
2620 mpdu->GetPacket()->PeekHeader(blockAck);
2621 bool isMpdu3corrupted;
2622
2623 switch (m_blockAckCount)
2624 {
2625 case 0:
2626 case 1: // Ignore the first two BlockAck frames that acknowledged frames sent to establish BA
2627 break;
2628 case 2:
2629 if (m_muTrafficPattern == WifiMuTrafficPattern::UL_MU)
2630 {
2631 NS_TEST_EXPECT_MSG_EQ(blockAck.IsMultiSta(), true, "Expected a Multi-STA BlockAck");
2632 for (uint8_t i = 0; i < m_nStations; i++)
2633 {
2634 auto indices = blockAck.FindPerAidTidInfoWithAid(m_staMacs[i]->GetAssociationId());
2635 NS_TEST_ASSERT_MSG_EQ(indices.size(), 1, "Expected one Per AID TID Info per STA");
2636 auto index = indices.front();
2638 true,
2639 "Expected that a QoS data frame was corrupted");
2640 isMpdu3corrupted =
2641 m_staMacs[i]->GetLinkIdByAddress(*m_dataCorruptedSta).has_value();
2642 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(2, index),
2643 true,
2644 "MPDU 2 expected to be successfully received");
2645 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(3, index),
2646 !isMpdu3corrupted,
2647 "Unexpected reception status for MPDU 3");
2648 }
2649
2650 break;
2651 }
2652 case 3:
2653 // BlockAck frames in response to the first DL MU PPDU
2654 isMpdu3corrupted = (mpdu->GetHeader().GetAddr2() == m_dataCorruptedSta);
2656 true,
2657 "MPDU 2 expected to be successfully received");
2659 !isMpdu3corrupted,
2660 "Unexpected reception status for MPDU 3");
2661 // in case of DL MU, if there are at least two links setup, we expect all MPDUs to
2662 // be inflight (on distinct links)
2663 if (m_muTrafficPattern != WifiMuTrafficPattern::UL_MU &&
2664 m_staMacs[0]->GetSetupLinkIds().size() > 1)
2665 {
2666 auto queue = m_apMac->GetTxopQueue(AC_BE);
2667 Ptr<StaWifiMac> rcvMac;
2668 if (m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress() ==
2669 mpdu->GetHeader().GetAddr2())
2670 {
2671 rcvMac = m_staMacs[0];
2672 }
2673 else if (m_staMacs[1]->GetFrameExchangeManager(linkId)->GetAddress() ==
2674 mpdu->GetHeader().GetAddr2())
2675 {
2676 rcvMac = m_staMacs[1];
2677 }
2678 else
2679 {
2680 NS_ABORT_MSG("BlockAck frame not sent by a station in DL scenario");
2681 }
2682 auto item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress());
2683 std::size_t nQueuedPkt = 0;
2684 auto delay = WifiPhy::CalculateTxDuration(psdu,
2685 txVector,
2686 rcvMac->GetWifiPhy(linkId)->GetPhyBand()) +
2687 MicroSeconds(1); // to account for propagation delay
2688
2689 while (item)
2690 {
2691 auto seqNo = item->GetHeader().GetSequenceNumber();
2692 NS_TEST_EXPECT_MSG_EQ(item->IsInFlight(),
2693 true,
2694 "MPDU with seqNo=" << seqNo << " is not in flight");
2695 auto linkIds = item->GetInFlightLinkIds();
2696 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
2697 1,
2698 "MPDU with seqNo=" << seqNo
2699 << " is in flight on multiple links");
2700 // The first two MPDUs are in flight on the same link on which the BlockAck
2701 // is sent. The other two MPDUs (only for AP to STA/STA to AP scenarios) are
2702 // in flight on a different link.
2703 auto srcLinkId = m_apMac->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1());
2704 NS_TEST_ASSERT_MSG_EQ(srcLinkId.has_value(),
2705 true,
2706 "Addr1 of BlockAck is not an originator's link address");
2707 NS_TEST_EXPECT_MSG_EQ((*linkIds.begin() == *srcLinkId),
2708 (seqNo <= 3),
2709 "MPDU with seqNo=" << seqNo
2710 << " in flight on unexpected link");
2711 // check the Retry subfield and whether this MPDU is still queued
2712 // after the originator has processed this BlockAck
2713
2714 // MPDUs acknowledged via this BlockAck are no longer queued
2715 bool isQueued = (seqNo > (isMpdu3corrupted ? 2 : 3));
2716 // The Retry subfield is set if the MPDU has not been acknowledged (i.e., it
2717 // is still queued) and has been transmitted on the same link as the BlockAck
2718 // (i.e., its sequence number is less than or equal to 2)
2719 bool isRetry = isQueued && seqNo <= 3;
2720
2721 Simulator::Schedule(delay, [this, item, isQueued, isRetry]() {
2722 NS_TEST_EXPECT_MSG_EQ(item->IsQueued(),
2723 isQueued,
2724 "MPDU with seqNo="
2725 << item->GetHeader().GetSequenceNumber() << " should "
2726 << (isQueued ? "" : "not") << " be queued");
2728 item->GetHeader().IsRetry(),
2729 isRetry,
2730 "Unexpected value for the Retry subfield of the MPDU with seqNo="
2731 << item->GetHeader().GetSequenceNumber());
2732 });
2733
2734 nQueuedPkt++;
2735 item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress(), item);
2736 }
2737 // Each MPDU contains an A-MSDU consisting of two MSDUs
2738 NS_TEST_EXPECT_MSG_EQ(nQueuedPkt, m_nPackets / 2, "Unexpected number of queued MPDUs");
2739 }
2740 break;
2741 }
2742}
2743
2744void
2746{
2747 switch (m_muTrafficPattern)
2748 {
2749 case WifiMuTrafficPattern::DL_MU_BAR_BA_SEQUENCE:
2750 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2752 break;
2753 case WifiMuTrafficPattern::DL_MU_MU_BAR:
2754 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2756 break;
2757 case WifiMuTrafficPattern::DL_MU_AGGR_MU_BAR:
2758 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2760 break;
2761 default:;
2762 }
2763
2765
2766 // Enable A-MSDU aggregation. Max A-MSDU size is set such that two MSDUs can be aggregated
2767 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
2768 {
2769 mac->SetAttribute("BE_MaxAmsduSize", UintegerValue(1050));
2770 mac->GetQosTxop(AC_BE)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2772 mac->GetQosTxop(AC_BE)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2773
2774 mac->SetAttribute("VI_MaxAmsduSize", UintegerValue(1050));
2775 mac->GetQosTxop(AC_VI)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2777 mac->GetQosTxop(AC_VI)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2778 }
2779
2780 // aggregate MU scheduler
2781 auto muScheduler = CreateObjectWithAttributes<RrMultiUserScheduler>(
2782 "EnableUlOfdma",
2783 BooleanValue(m_muTrafficPattern == WifiMuTrafficPattern::UL_MU),
2784 "EnableBsrp",
2785 BooleanValue(false),
2786 "UlPsduSize",
2787 UintegerValue(2000));
2788 m_apMac->AggregateObject(muScheduler);
2789
2790 // install post reception error model on all devices
2791 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
2792 {
2793 auto errorModel = CreateObject<ListErrorModel>();
2794 m_errorModels[m_apMac->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2795 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2796 }
2797 for (std::size_t i : {0, 1})
2798 {
2799 for (const auto linkId : m_staMacs[i]->GetLinkIds())
2800 {
2801 auto errorModel = CreateObject<ListErrorModel>();
2802 m_errorModels[m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2803 m_staMacs[i]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2804 }
2805 }
2806}
2807
2808void
2810{
2811 if (m_muTrafficPattern < WifiMuTrafficPattern::UL_MU)
2812 {
2813 // DL Traffic
2814 for (uint8_t i = 0; i < m_nStations; i++)
2815 {
2816 PacketSocketAddress sockAddr;
2818 sockAddr.SetPhysicalAddress(m_staMacs[i]->GetDevice()->GetAddress());
2819 sockAddr.SetProtocol(1);
2820
2821 // the first client application generates three packets in order
2822 // to trigger the establishment of a Block Ack agreement
2824 GetApplication(sockAddr, 3, 450, i * MilliSeconds(50)));
2825
2826 // the second client application generates the first half of the selected number
2827 // of packets, which are sent in DL MU PPDUs, and starts after all BA agreements
2828 // are established
2830 GetApplication(sockAddr, m_nPackets / 2, 450, m_nStations * MilliSeconds(50)));
2831
2832 // the third client application generates the second half of the selected number
2833 // of packets, which are sent in DL MU PPDUs, and starts during transmission of
2834 // first A-MPDU, if multiple links are setup
2836 GetApplication(sockAddr,
2837 m_nPackets / 2,
2838 450,
2840 }
2841 }
2842 else
2843 {
2844 // UL Traffic
2845 for (uint8_t i = 0; i < m_nStations; i++)
2846 {
2847 m_sockets[i].SetSingleDevice(m_staMacs[i]->GetDevice()->GetIfIndex());
2848 m_sockets[i].SetPhysicalAddress(m_apMac->GetDevice()->GetAddress());
2849 m_sockets[i].SetProtocol(1);
2850
2851 // the first client application generates three packets in order
2852 // to trigger the establishment of a Block Ack agreement
2853 m_staMacs[i]->GetDevice()->GetNode()->AddApplication(
2854 GetApplication(m_sockets[i], 3, 450, i * MilliSeconds(50), i * 4));
2855
2856 // packets to be included in TB PPDUs are generated (by Transmit()) when
2857 // the first Basic Trigger Frame is sent by the AP
2858 }
2859
2860 // MU scheduler starts requesting channel access when we are done with BA agreements
2862 auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
2863 NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "Expected an aggregated MU scheduler");
2864 muScheduler->SetAccessReqInterval(MilliSeconds(3));
2865 // channel access is requested only once
2866 muScheduler->SetAccessReqInterval(Seconds(0));
2867 });
2868 }
2869
2871}
2872
2873void
2875{
2877
2878 // Expected number of packets received by each node (AP, STA 0, STA 1) at application layer
2879 std::array<std::size_t, 3> expectedRxPkts{};
2880
2881 switch (m_muTrafficPattern)
2882 {
2883 case WifiMuTrafficPattern::DL_MU_BAR_BA_SEQUENCE:
2884 case WifiMuTrafficPattern::DL_MU_MU_BAR:
2885 case WifiMuTrafficPattern::DL_MU_AGGR_MU_BAR:
2886 // both STA 0 and STA 1 receive m_nPackets + 3 (sent to trigger BA establishment) packets
2887 expectedRxPkts[1] = m_nPackets + 3;
2888 expectedRxPkts[2] = m_nPackets + 3;
2889 break;
2890 case WifiMuTrafficPattern::UL_MU:
2891 // AP receives m_nPackets + 3 (sent to trigger BA establishment) packets from each station
2892 expectedRxPkts[0] = 2 * (m_nPackets + 3);
2893 break;
2894 }
2895
2897 +expectedRxPkts[0],
2898 "Unexpected number of packets received by the AP");
2900 +expectedRxPkts[1],
2901 "Unexpected number of packets received by STA 0");
2903 +expectedRxPkts[2],
2904 "Unexpected number of packets received by STA 1");
2905
2906 // check that setting the QosTxop::NMaxInflights attribute has the expected effect.
2907 // For DL, for each station we send 2 MPDUs to trigger BA agreement and m_nPackets / 2 MPDUs
2908 // For UL, each station sends 2 MPDUs to trigger BA agreement and m_nPackets / 2 MPDUs
2910 m_inflightCount.size(),
2911 2 * (2 + m_nPackets / 2),
2912 "Did not collect number of simultaneous transmissions for all data frames");
2913
2914 auto nMaxInflight = std::min(m_nMaxInflight, m_staMacs[0]->GetSetupLinkIds().size());
2915 std::size_t maxCount = 0;
2916 for (const auto& [txSeqNoPair, count] : m_inflightCount)
2917 {
2919 nMaxInflight,
2920 "MPDU with seqNo="
2921 << txSeqNoPair.second
2922 << " transmitted simultaneously more times than allowed");
2923 maxCount = std::max(maxCount, count);
2924 }
2925
2927 maxCount,
2928 nMaxInflight,
2929 "Expected that at least one data frame was transmitted simultaneously a number of "
2930 "times equal to the NMaxInflights attribute");
2931
2933}
2934
2954{
2955 public:
2958
2959 protected:
2960 void DoSetup() override;
2961 void DoRun() override;
2962 void Transmit(Ptr<WifiMac> mac,
2963 uint8_t phyId,
2964 WifiConstPsduMap psduMap,
2965 WifiTxVector txVector,
2966 double txPowerW) override;
2967
2968 private:
2969 void StartTraffic() override;
2970
2972 std::size_t m_nQosDataFrames;
2975};
2976
2979 "Check sequence numbers after CTS timeout",
2980 1,
2981 BaseParams{{"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2982 {"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2983 {}}),
2984 m_nQosDataFrames(0),
2985 m_errorModel(CreateObject<ListErrorModel>()),
2986 m_rtsCorrupted(false)
2987{
2988}
2989
2990void
2992{
2993 // Enable RTS/CTS
2994 Config::SetDefault("ns3::WifiRemoteStationManager::RtsCtsThreshold", StringValue("1000"));
2995
2997
2998 // install post reception error model on all STAs affiliated with non-AP MLD
2999 for (const auto linkId : m_staMacs[0]->GetLinkIds())
3000 {
3001 m_staMacs[0]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel);
3002 }
3003}
3004
3005void
3007{
3009 m_sockAddr.SetPhysicalAddress(m_staMacs[0]->GetAddress());
3011
3012 // install client application generating 4 packets
3014}
3015
3016void
3018 uint8_t phyId,
3019 WifiConstPsduMap psduMap,
3020 WifiTxVector txVector,
3021 double txPowerW)
3022{
3023 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
3024
3025 auto psdu = psduMap.begin()->second;
3026
3027 if (psdu->GetHeader(0).IsRts() && !m_rtsCorrupted)
3028 {
3029 m_errorModel->SetList({psdu->GetPacket()->GetUid()});
3030 m_rtsCorrupted = true;
3031 // generate other packets when the first RTS is transmitted
3033 }
3034 else if (psdu->GetHeader(0).IsQosData())
3035 {
3037
3038 if (m_nQosDataFrames == 2)
3039 {
3040 // generate other packets when the second QoS data frame is transmitted
3042 }
3043 }
3044}
3045
3046void
3048{
3051
3052 NS_TEST_EXPECT_MSG_EQ(m_nQosDataFrames, 3, "Unexpected number of transmitted QoS data frames");
3053
3054 std::size_t count{};
3055
3056 for (const auto& txPsdu : m_txPsdus)
3057 {
3058 auto psdu = txPsdu.psduMap.begin()->second;
3059
3060 if (!psdu->GetHeader(0).IsQosData())
3061 {
3062 continue;
3063 }
3064
3065 NS_TEST_EXPECT_MSG_EQ(psdu->GetNMpdus(), 4, "Unexpected number of MPDUs in A-MPDU");
3066
3067 count++;
3068 uint16_t expectedSeqNo{};
3069
3070 switch (count)
3071 {
3072 case 1:
3073 expectedSeqNo = 4;
3074 break;
3075 case 2:
3076 expectedSeqNo = 0;
3077 break;
3078 case 3:
3079 expectedSeqNo = 8;
3080 break;
3081 }
3082
3083 for (const auto& mpdu : *PeekPointer(psdu))
3084 {
3085 NS_TEST_EXPECT_MSG_EQ(mpdu->GetHeader().GetSequenceNumber(),
3086 expectedSeqNo++,
3087 "Unexpected sequence number");
3088 }
3089 }
3090
3092}
3093
3101{
3102 public:
3104};
3105
3107 : TestSuite("wifi-mlo", UNIT)
3108{
3109 using ParamsTuple = std::tuple<MultiLinkOperationsTestBase::BaseParams, // base config params
3110 std::vector<uint8_t>, // link ID of setup links
3111 uint8_t, // AP negotiation support
3112 std::string, // DL TID-to-Link Mapping
3113 std::string>; // UL TID-to-Link Mapping
3114
3117
3118 for (const auto& [baseParams,
3119 setupLinks,
3120 apNegSupport,
3121 dlTidLinkMapping,
3122 ulTidLinkMapping] :
3123 {// matching channels: setup all links
3124 ParamsTuple({{"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3125 {"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3126 {}},
3127 {0, 1, 2},
3128 0, // AP MLD does not support TID-to-Link Mapping negotiation
3129 "0,1,2,3 0,1,2; 4,5 0,1", // default mapping used instead
3130 "0,1,2,3 1,2; 6,7 0,1" // default mapping used instead
3131 ),
3132 // non-matching channels, matching PHY bands: setup all links
3133 ParamsTuple({{"{108, 0, BAND_5GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3134 {"{36, 0, BAND_5GHZ, 0}", "{120, 0, BAND_5GHZ, 0}", "{5, 0, BAND_6GHZ, 0}"},
3135 {}},
3136 {0, 1, 2},
3137 1, // AP MLD does not support distinct link sets for TIDs
3138 "0,1,2,3 0,1,2; 4,5 0,1", // default mapping used instead
3139 ""),
3140 // non-AP MLD switches band on some links to setup 3 links
3141 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3142 {"{36, 0, BAND_5GHZ, 0}", "{9, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3143 {}},
3144 {0, 1, 2},
3145 3,
3146 "0,1,2,3 0; 4,5,6,7 1,2", // frames of two TIDs are generated
3147 "0,2,3 1,2; 1,4,5,6,7 0" // frames of two TIDs are generated
3148 ),
3149 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3150 // that band, hence only 2 links are setup
3151 ParamsTuple(
3152 {{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{8, 20, BAND_2_4GHZ, 0}"},
3153 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3154 {0}},
3155 {0, 1},
3156 1, // AP MLD does not support distinct link sets for TIDs
3157 "0,1,2,3,4,5,6,7 0",
3158 "0,1,2,3,4,5,6,7 0"),
3159 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3160 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3161 // an AP operating on the same channel; hence 2 links are setup
3162 ParamsTuple(
3163 {{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{8, 20, BAND_2_4GHZ, 0}"},
3164 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3165 {0, 1}},
3166 {0, 1},
3167 3,
3168 "0,1,2,3 1",
3169 "0,1,2,3 1"),
3170 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3171 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3172 // an AP operating on the same channel; the third link of the non-AP MLD cannot
3173 // change PHY band and there is an AP operating on the same band (different channel);
3174 // hence 2 links are setup by switching channel (not band) on the third link
3175 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{60, 0, BAND_5GHZ, 0}"},
3176 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3177 {0, 1, 2}},
3178 {0, 2},
3179 3,
3180 "",
3181 ""),
3182 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3183 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3184 // an AP operating on the same channel; hence one link only is setup
3185 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3186 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3187 {0, 1}},
3188 {2},
3189 3,
3190 "",
3191 ""),
3192 // non-AP MLD has only two STAs and setups two links
3193 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3194 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3195 {}},
3196 {1, 0},
3197 3,
3198 "0,1,2,3 1",
3199 ""),
3200 // single link non-AP STA associates with an AP affiliated with an AP MLD
3201 ParamsTuple({{"{120, 0, BAND_5GHZ, 0}"},
3202 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3203 {}},
3204 {2}, // link ID of AP MLD only (non-AP STA is single link)
3205 3,
3206 "",
3207 ""),
3208 // a STA affiliated with a non-AP MLD associates with a single link AP
3209 ParamsTuple({{"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3210 {"{120, 0, BAND_5GHZ, 0}"},
3211 {}},
3212 {2}, // link ID of non-AP MLD only (AP is single link)
3213 0,
3214 "0,1,2,3 0,1; 4,5,6,7 0,1", // ignored by single link AP
3215 "")})
3216 {
3217 AddTestCase(new MultiLinkSetupTest(baseParams,
3218 WifiScanType::PASSIVE,
3219 setupLinks,
3220 apNegSupport,
3221 dlTidLinkMapping,
3222 ulTidLinkMapping),
3224 AddTestCase(new MultiLinkSetupTest(baseParams,
3225 WifiScanType::ACTIVE,
3226 setupLinks,
3227 apNegSupport,
3228 dlTidLinkMapping,
3229 ulTidLinkMapping),
3231
3232 for (const auto& trafficPattern : {WifiTrafficPattern::STA_TO_STA,
3233 WifiTrafficPattern::STA_TO_AP,
3234 WifiTrafficPattern::AP_TO_STA,
3235 WifiTrafficPattern::AP_TO_BCAST,
3236 WifiTrafficPattern::STA_TO_BCAST})
3237 {
3238 // No Block Ack agreement
3239 AddTestCase(new MultiLinkTxTest(baseParams,
3240 trafficPattern,
3241 WifiBaEnabled::NO,
3242 WifiUseBarAfterMissedBa::NO,
3243 1),
3245 for (const auto& useBarAfterMissedBa :
3246 {WifiUseBarAfterMissedBa::YES, WifiUseBarAfterMissedBa::NO})
3247 {
3248 // Block Ack agreement with nMaxInflight=1
3249 AddTestCase(new MultiLinkTxTest(baseParams,
3250 trafficPattern,
3251 WifiBaEnabled::YES,
3252 useBarAfterMissedBa,
3253 1),
3255 // Block Ack agreement with nMaxInflight=2
3256 AddTestCase(new MultiLinkTxTest(baseParams,
3257 trafficPattern,
3258 WifiBaEnabled::YES,
3259 useBarAfterMissedBa,
3260 2),
3262 }
3263 }
3264
3265 for (const auto& muTrafficPattern : {WifiMuTrafficPattern::DL_MU_BAR_BA_SEQUENCE,
3266 WifiMuTrafficPattern::DL_MU_MU_BAR,
3267 WifiMuTrafficPattern::DL_MU_AGGR_MU_BAR,
3268 WifiMuTrafficPattern::UL_MU})
3269 {
3270 for (const auto& useBarAfterMissedBa :
3271 {WifiUseBarAfterMissedBa::YES, WifiUseBarAfterMissedBa::NO})
3272 {
3273 // Block Ack agreement with nMaxInflight=1
3275 new MultiLinkMuTxTest(baseParams, muTrafficPattern, useBarAfterMissedBa, 1),
3277 // Block Ack agreement with nMaxInflight=2
3279 new MultiLinkMuTxTest(baseParams, muTrafficPattern, useBarAfterMissedBa, 2),
3281 }
3282 }
3283 }
3284
3286}
3287
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:170
AttributeValue implementation for Boolean.
Definition: boolean.h:37
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:56
void SetList(const std::list< uint64_t > &packetlist)
Definition: error-model.cc:456
an EUI-48 address
Definition: mac48-address.h:46
static Mac48Address GetBroadcast()
Implement the header for management frames of type association request.
Definition: mgt-headers.h:161
Implement the header for management frames of type association and reassociation response.
Definition: mgt-headers.h:338
Implement the header for management frames of type beacon.
Definition: mgt-headers.h:516
Implement the header for management frames of type probe response.
Definition: mgt-headers.h:455
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
static Iterator Begin()
Definition: node-list.cc:237
static uint32_t GetNNodes()
Definition: node-list.cc:258
static Iterator End()
Definition: node-list.cc:244
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.
Smart pointer class similar to boost::intrusive_ptr.
Definition: ptr.h:78
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 ...
static void SetRun(uint64_t run)
Set the run number of simulation.
static void SetSeed(uint32_t seed)
Set the seed.
static EventId Schedule(const Time &delay, FUNC f, Ts &&... args)
Schedule an event to expire after delay.
Definition: simulator.h:558
static void Destroy()
Execute the events scheduled with ScheduleDestroy().
Definition: simulator.cc:140
static Time Now()
Return the current simulation virtual time.
Definition: simulator.cc:199
static void Run()
Run the simulation.
Definition: simulator.cc:176
static void Stop()
Tell the Simulator the calling event should be the last one executed.
Definition: simulator.cc:184
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
AttributeValue implementation for Ssid.
Hold variables of type string.
Definition: string.h:56
encapsulates test code
Definition: test.h:1060
@ QUICK
Fast test.
Definition: test.h:1065
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
AttributeValue implementation for Time.
Definition: nstime.h:1412
Hold an unsigned integer type.
Definition: uinteger.h:45
static std::optional< WifiAssocManager::RnrLinkInfo > GetNextAffiliatedAp(const ReducedNeighborReport &rnr, std::size_t nbrApInfoId)
Search the given RNR element for APs affiliated to the same AP MLD as the reporting AP.
static std::list< WifiAssocManager::RnrLinkInfo > GetAllAffiliatedAps(const ReducedNeighborReport &rnr)
Find all the APs affiliated to the same AP MLD as the reporting AP that sent the given RNR element.
helps to create WifiNetDevice objects
Definition: wifi-helper.h:324
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:866
std::optional< Mac48Address > GetMldAddress(const Mac48Address &remoteAddr) const
Definition: wifi-mac.cc:1634
const std::map< uint8_t, std::unique_ptr< LinkEntity > > & GetLinks() const
Definition: wifi-mac.cc:920
uint8_t GetNLinks() const
Get the number of links (can be greater than 1 for 11be devices only).
Definition: wifi-mac.cc:935
void SwapLinks(std::map< uint8_t, uint8_t > links)
Swap the links based on the information included in the given map.
Definition: wifi-mac.cc:997
Ptr< WifiPhy > GetWifiPhy(uint8_t linkId=SINGLE_LINK_OP_ID) const
Definition: wifi-mac.cc:1173
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:963
Ptr< EhtConfiguration > GetEhtConfiguration() const
Definition: wifi-mac.cc:1757
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:1102
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:908
const std::set< uint8_t > & GetLinkIds() const
Definition: wifi-mac.cc:941
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
@ DLT_IEEE802_11_RADIO
Include Radiotap link layer information.
Definition: wifi-helper.h:178
void SetPostReceptionErrorModel(const Ptr< ErrorModel > em)
Attach a receive ErrorModel to the WifiPhy.
Definition: wifi-phy.cc:648
static Time CalculateTxDuration(uint32_t size, const WifiTxVector &txVector, WifiPhyBand band, uint16_t staId=SU_STA_ID)
Definition: wifi-phy.cc:1496
WifiPhyBand GetPhyBand() const
Get the configured Wi-Fi band.
Definition: wifi-phy.cc:1005
const WifiPhyOperatingChannel & GetOperatingChannel() const
Get a const reference to the operating channel.
Definition: wifi-phy.cc:1017
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).
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
#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:1349
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition: nstime.h:1325
Time MilliSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition: nstime.h:1337
WifiScanType
Scan type (active or passive)
Definition: sta-wifi-mac.h:51
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:74
@ AC_VO
Voice.
Definition: qos-utils.h:80
@ AC_VI
Video.
Definition: qos-utils.h:78
@ AC_BK
Background.
Definition: qos-utils.h:76
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.
U * PeekPointer(const Ptr< U > &p)
Definition: ptr.h:484
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
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.
STL namespace.
Function object to compute the hash of a MAC address.
Definition: qos-utils.h:55
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.