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(
936 "TidToLinkMappingNegSupport",
938 // For non-AP MLD, it does not make sense to set the negotiation type to 0 (unless the AP MLD
939 // also advertises 0) or 1 (the AP MLD is discarded if it advertises a support of 3)
940 auto staEhtConfig = m_staMacs[0]->GetEhtConfiguration();
941 staEhtConfig->SetAttribute("TidToLinkMappingNegSupport",
943 staEhtConfig->SetAttribute("TidToLinkMappingDl", StringValue(m_dlTidLinkMappingStr));
944 staEhtConfig->SetAttribute("TidToLinkMappingUl", StringValue(m_ulTidLinkMappingStr));
945
946 // the negotiated link mapping matches the one configured in EHT configuration, unless
947 // the AP MLD does not support TID-to-link mapping negotiation or the AP MLD supports
948 // the negotiation type 1 and the non-AP MLD is configured with a link mapping that
949 // maps distinct link sets to the TIDs, in which case the default link mapping is used
950 m_dlTidLinkMapping = staEhtConfig->GetTidLinkMapping(WifiDirection::DOWNLINK);
951 m_ulTidLinkMapping = staEhtConfig->GetTidLinkMapping(WifiDirection::UPLINK);
952
953 if (m_apNegSupport == 0 ||
954 (m_apNegSupport == 1 &&
956 {
957 m_dlTidLinkMapping.clear(); // default link mapping
958 m_ulTidLinkMapping.clear(); // default link mapping
959 }
960
961 // find (if any) a TID that is not mapped to all setup links
962 using TupleRefs = std::tuple<std::reference_wrapper<const WifiTidLinkMapping>,
963 std::reference_wrapper<uint8_t>,
964 std::reference_wrapper<std::optional<uint8_t>>,
966 for (auto& [mappingRef, tid1Ref, tid2Ref, mac] :
969 {
970 tid1Ref.get() = 0;
971 for (uint8_t tid1 = 0; tid1 < 8; tid1++)
972 {
973 if (auto it1 = mappingRef.get().find(tid1);
974 it1 != mappingRef.get().cend() && it1->second.size() != m_setupLinks.size())
975 {
976 // found. Now search for another TID with a disjoint mapped link set
977 for (uint8_t tid2 = tid1 + 1; tid2 < 8; tid2++)
978 {
979 if (auto it2 = mappingRef.get().find(tid2);
980 it2 != mappingRef.get().cend() && it2->second.size() != m_setupLinks.size())
981 {
982 std::list<uint8_t> intersection;
983 std::set_intersection(it1->second.cbegin(),
984 it1->second.cend(),
985 it2->second.cbegin(),
986 it2->second.cend(),
987 std::back_inserter(intersection));
988 if (intersection.empty())
989 {
990 // found a second TID
991 tid2Ref.get() = tid2;
992 break;
993 }
994 }
995 }
996 tid1Ref.get() = tid1;
997 break;
998 }
999 }
1000
1001 std::list<uint8_t> tids = {tid1Ref.get()};
1002 if (tid2Ref.get())
1003 {
1004 tids.emplace_back(*tid2Ref.get());
1005 }
1006
1007 // prevent aggregation of MPDUs
1008 for (auto tid : tids)
1009 {
1010 std::string attrName;
1011 switch (QosUtilsMapTidToAc(tid))
1012 {
1013 case AC_VI:
1014 attrName = "VI_MaxAmpduSize";
1015 break;
1016 case AC_VO:
1017 attrName = "VO_MaxAmpduSize";
1018 break;
1019 case AC_BE:
1020 attrName = "BE_MaxAmpduSize";
1021 break;
1022 case AC_BK:
1023 attrName = "BK_MaxAmpduSize";
1024 break;
1025 default:
1026 NS_FATAL_ERROR("Invalid TID " << +tid);
1027 }
1028
1029 mac->SetAttribute(attrName, UintegerValue(100));
1030 }
1031 }
1032}
1033
1034void
1036{
1037 // DL traffic
1038 {
1039 PacketSocketAddress sockAddr;
1041 sockAddr.SetPhysicalAddress(m_staMacs[0]->GetDevice()->GetAddress());
1042 sockAddr.SetProtocol(1);
1043
1045 GetApplication(sockAddr, m_setupLinks.size(), 500, Seconds(0), m_dlTid1));
1046 if (m_dlTid2)
1047 {
1049 GetApplication(sockAddr, m_setupLinks.size(), 500, Seconds(0), *m_dlTid2));
1050 }
1051 }
1052
1053 // UL Traffic
1054 {
1055 PacketSocketAddress sockAddr;
1056 sockAddr.SetSingleDevice(m_staMacs[0]->GetDevice()->GetIfIndex());
1058 sockAddr.SetProtocol(1);
1059
1060 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
1061 GetApplication(sockAddr, m_setupLinks.size(), 500, MilliSeconds(500), m_ulTid1));
1062 if (m_ulTid2)
1063 {
1064 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
1065 GetApplication(sockAddr, m_setupLinks.size(), 500, MilliSeconds(500), *m_ulTid2));
1066 }
1067 }
1068}
1069
1070void
1072{
1074
1077
1081 std::size_t index = 0;
1082
1083 for (const auto& frameInfo : m_txPsdus)
1084 {
1085 const auto& mpdu = *frameInfo.psduMap.begin()->second->begin();
1086 const auto& linkId = frameInfo.linkId;
1087
1088 switch (mpdu->GetHeader().GetType())
1089 {
1091 CheckBeacon(mpdu, linkId);
1092 break;
1093
1095 CheckProbeResponse(mpdu, linkId);
1096 m_nProbeResp++;
1097 break;
1098
1100 CheckAssocRequest(mpdu, linkId);
1101 break;
1102
1104 CheckAssocResponse(mpdu, linkId);
1105 break;
1106
1107 case WIFI_MAC_QOSDATA:
1108 CheckQosData(mpdu, linkId, index);
1109 break;
1110
1111 default:
1112 break;
1113 }
1114
1115 index++;
1116 }
1117
1119
1120 std::size_t expectedProbeResp = 0;
1121 if (m_scanType == WifiScanType::ACTIVE)
1122 {
1123 // the number of Probe Response frames that we expect to receive in active mode equals
1124 // the number of channels in common between AP MLD and non-AP MLD at initialization
1125 for (const auto& staChannel : m_staChannels)
1126 {
1127 for (const auto& apChannel : m_apChannels)
1128 {
1129 if (staChannel == apChannel)
1130 {
1131 expectedProbeResp++;
1132 break;
1133 }
1134 }
1135 }
1136 }
1137
1138 NS_TEST_EXPECT_MSG_EQ(m_nProbeResp, expectedProbeResp, "Unexpected number of Probe Responses");
1139
1140 std::size_t expectedRxDlPkts = m_setupLinks.size();
1141 if (m_dlTid2)
1142 {
1143 expectedRxDlPkts *= 2;
1144 }
1145 NS_TEST_EXPECT_MSG_EQ(m_rxPkts[m_staMacs[0]->GetDevice()->GetNode()->GetId()],
1146 expectedRxDlPkts,
1147 "Unexpected number of DL packets received");
1148
1149 std::size_t expectedRxUlPkts = m_setupLinks.size();
1150 if (m_ulTid2)
1151 {
1152 expectedRxUlPkts *= 2;
1153 }
1155 expectedRxUlPkts,
1156 "Unexpected number of UL packets received");
1157
1159}
1160
1161void
1163{
1164 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_BEACON);
1165
1166 CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::DL);
1167
1169 mpdu->GetHeader().GetAddr2(),
1170 "TA of Beacon frame is not the address of the link it is transmitted on");
1171 MgtBeaconHeader beacon;
1172 mpdu->GetPacket()->PeekHeader(beacon);
1173 const auto& rnr = beacon.Get<ReducedNeighborReport>();
1174 const auto& mle = beacon.Get<MultiLinkElement>();
1175
1176 if (m_apMac->GetNLinks() == 1)
1177 {
1178 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(),
1179 false,
1180 "RNR Element in Beacon frame from single link AP");
1181 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1182 false,
1183 "Multi-Link Element in Beacon frame from single link AP");
1184 return;
1185 }
1186
1187 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(), true, "No RNR Element in Beacon frame");
1188 // All the other APs affiliated with the same AP MLD as the AP sending
1189 // the Beacon frame must be reported in a separate Neighbor AP Info field
1190 NS_TEST_EXPECT_MSG_EQ(rnr->GetNNbrApInfoFields(),
1191 static_cast<std::size_t>(m_apMac->GetNLinks() - 1),
1192 "Unexpected number of Neighbor AP Info fields in RNR");
1193 for (std::size_t nbrApInfoId = 0; nbrApInfoId < rnr->GetNNbrApInfoFields(); nbrApInfoId++)
1194 {
1195 NS_TEST_EXPECT_MSG_EQ(rnr->HasMldParameters(nbrApInfoId),
1196 true,
1197 "MLD Parameters not present");
1198 NS_TEST_EXPECT_MSG_EQ(rnr->GetNTbttInformationFields(nbrApInfoId),
1199 1,
1200 "Expected only one TBTT Info subfield per Neighbor AP Info");
1201 uint8_t nbrLinkId = rnr->GetLinkId(nbrApInfoId, 0);
1202 NS_TEST_EXPECT_MSG_EQ(rnr->GetBssid(nbrApInfoId, 0),
1203 m_apMac->GetFrameExchangeManager(nbrLinkId)->GetAddress(),
1204 "BSSID advertised in Neighbor AP Info field "
1205 << nbrApInfoId
1206 << " does not match the address configured on the link "
1207 "advertised in the same field");
1208 }
1209
1210 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Beacon frame");
1211 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1213 "Incorrect MLD address advertised in Multi-Link Element");
1214 NS_TEST_EXPECT_MSG_EQ(mle->GetLinkIdInfo(),
1215 +linkId,
1216 "Incorrect Link ID advertised in Multi-Link Element");
1217}
1218
1219void
1221{
1222 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_PROBE_RESPONSE);
1223
1224 CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::DL);
1225
1227 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1228 mpdu->GetHeader().GetAddr2(),
1229 "TA of Probe Response is not the address of the link it is transmitted on");
1230 MgtProbeResponseHeader probeResp;
1231 mpdu->GetPacket()->PeekHeader(probeResp);
1232 const auto& rnr = probeResp.Get<ReducedNeighborReport>();
1233 const auto& mle = probeResp.Get<MultiLinkElement>();
1234
1235 if (m_apMac->GetNLinks() == 1)
1236 {
1237 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(),
1238 false,
1239 "RNR Element in Probe Response frame from single link AP");
1240 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1241 false,
1242 "Multi-Link Element in Probe Response frame from single link AP");
1243 return;
1244 }
1245
1246 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(), true, "No RNR Element in Probe Response frame");
1247 // All the other APs affiliated with the same AP MLD as the AP sending
1248 // the Probe Response frame must be reported in a separate Neighbor AP Info field
1249 NS_TEST_EXPECT_MSG_EQ(rnr->GetNNbrApInfoFields(),
1250 static_cast<std::size_t>(m_apMac->GetNLinks() - 1),
1251 "Unexpected number of Neighbor AP Info fields in RNR");
1252 for (std::size_t nbrApInfoId = 0; nbrApInfoId < rnr->GetNNbrApInfoFields(); nbrApInfoId++)
1253 {
1254 NS_TEST_EXPECT_MSG_EQ(rnr->HasMldParameters(nbrApInfoId),
1255 true,
1256 "MLD Parameters not present");
1257 NS_TEST_EXPECT_MSG_EQ(rnr->GetNTbttInformationFields(nbrApInfoId),
1258 1,
1259 "Expected only one TBTT Info subfield per Neighbor AP Info");
1260 uint8_t nbrLinkId = rnr->GetLinkId(nbrApInfoId, 0);
1261 NS_TEST_EXPECT_MSG_EQ(rnr->GetBssid(nbrApInfoId, 0),
1262 m_apMac->GetFrameExchangeManager(nbrLinkId)->GetAddress(),
1263 "BSSID advertised in Neighbor AP Info field "
1264 << nbrApInfoId
1265 << " does not match the address configured on the link "
1266 "advertised in the same field");
1267 }
1268
1269 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Probe Response frame");
1270 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1272 "Incorrect MLD address advertised in Multi-Link Element");
1273 NS_TEST_EXPECT_MSG_EQ(mle->GetLinkIdInfo(),
1274 +linkId,
1275 "Incorrect Link ID advertised in Multi-Link Element");
1276}
1277
1278void
1280{
1281 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_ASSOCIATION_REQUEST);
1282
1283 CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::UL);
1284
1286 m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress(),
1287 mpdu->GetHeader().GetAddr2(),
1288 "TA of Assoc Request frame is not the address of the link it is transmitted on");
1290 mpdu->GetPacket()->PeekHeader(assoc);
1291 const auto& mle = assoc.Get<MultiLinkElement>();
1292
1293 if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetNLinks() == 1)
1294 {
1295 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1296 false,
1297 "Multi-Link Element in Assoc Request frame from single link STA");
1298 }
1299 else
1300 {
1301 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1302 true,
1303 "No Multi-Link Element in Assoc Request frame");
1304 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1305 m_staMacs[0]->GetAddress(),
1306 "Incorrect MLD Address advertised in Multi-Link Element");
1308 mle->GetNPerStaProfileSubelements(),
1309 m_setupLinks.size() - 1,
1310 "Incorrect number of Per-STA Profile subelements in Multi-Link Element");
1311 for (std::size_t i = 0; i < mle->GetNPerStaProfileSubelements(); i++)
1312 {
1313 auto& perStaProfile = mle->GetPerStaProfile(i);
1314 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasStaMacAddress(),
1315 true,
1316 "Per-STA Profile must contain STA MAC address");
1317 // find ID of the local link corresponding to this subelement
1318 auto staLinkId = m_staMacs[0]->GetLinkIdByAddress(perStaProfile.GetStaMacAddress());
1320 staLinkId.has_value(),
1321 true,
1322 "No link found with the STA MAC address advertised in Per-STA Profile");
1324 +staLinkId.value(),
1325 +linkId,
1326 "The STA that sent the Assoc Request should not be included in a Per-STA Profile");
1327 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), staLinkId.value());
1328 NS_TEST_EXPECT_MSG_EQ((it != m_setupLinks.end()),
1329 true,
1330 "Not expecting to setup STA link ID " << +staLinkId.value());
1332 +staLinkId.value(),
1333 +perStaProfile.GetLinkId(),
1334 "Not expecting to request association to AP Link ID in Per-STA Profile");
1335 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasAssocRequest(),
1336 true,
1337 "Missing Association Request in Per-STA Profile");
1338 }
1339 }
1340
1341 const auto& tlm = assoc.Get<TidToLinkMapping>();
1342
1343 // A TID-to-Link Mapping IE is included in the Association Request if and only if the AP MLD
1344 // and the non-AP MLD are performing ML setup (i.e., they both have multiple links) and the
1345 // AP MLD advertises a non-null negotiation support type
1346 if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetNLinks() == 1 || m_apNegSupport == 0)
1347 {
1348 NS_TEST_EXPECT_MSG_EQ(tlm.empty(),
1349 true,
1350 "Didn't expect a TID-to-Link Mapping IE in Assoc Request frame");
1351 }
1352 else
1353 {
1354 std::size_t expectedNTlm = (m_dlTidLinkMapping == m_ulTidLinkMapping ? 1 : 2);
1355
1356 NS_TEST_ASSERT_MSG_EQ(tlm.size(),
1357 expectedNTlm,
1358 "Unexpected number of TID-to-Link Mapping IE in Assoc Request");
1359
1360 // lambda to check content of TID-to-Link Mapping IE(s)
1361 auto checkTlm = [&](std::size_t tlmId, WifiDirection dir) {
1362 NS_TEST_EXPECT_MSG_EQ(+static_cast<uint8_t>(tlm[tlmId].m_control.direction),
1363 +static_cast<uint8_t>(dir),
1364 "Unexpected direction in TID-to-Link Mapping IE " << tlmId);
1365 auto& expectedMapping =
1366 (dir == WifiDirection::UPLINK ? m_ulTidLinkMapping : m_dlTidLinkMapping);
1367
1368 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].m_control.defaultMapping,
1369 expectedMapping.empty(),
1370 "Default Link Mapping bit not set correctly");
1371 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].m_linkMapping.size(),
1372 expectedMapping.size(),
1373 "Unexpected number of Link Mapping Of TID n fields");
1374 for (uint8_t tid = 0; tid < 8; tid++)
1375 {
1376 if (auto it = expectedMapping.find(tid); it != expectedMapping.cend())
1377 {
1378 NS_TEST_EXPECT_MSG_EQ((tlm[tlmId].GetLinkMappingOfTid(tid) == it->second),
1379 true,
1380 "Unexpected link mapping for TID "
1381 << +tid << " direction " << dir);
1382 }
1383 else
1384 {
1385 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].GetLinkMappingOfTid(tid).empty(),
1386 true,
1387 "Expecting no Link Mapping Of TID n field for TID "
1388 << +tid << " direction " << dir);
1389 }
1390 }
1391 };
1392
1393 if (tlm.size() == 1)
1394 {
1395 checkTlm(0, WifiDirection::BOTH_DIRECTIONS);
1396 }
1397 else
1398 {
1399 std::size_t dlId = (tlm[0].m_control.direction == WifiDirection::DOWNLINK ? 0 : 1);
1400 std::size_t ulId = (dlId == 0 ? 1 : 0);
1401
1402 checkTlm(dlId, WifiDirection::DOWNLINK);
1403 checkTlm(ulId, WifiDirection::UPLINK);
1404 }
1405 }
1406}
1407
1408void
1410{
1411 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_ASSOCIATION_RESPONSE);
1412
1413 CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::DL);
1414
1416 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1417 mpdu->GetHeader().GetAddr2(),
1418 "TA of Assoc Response frame is not the address of the link it is transmitted on");
1420 mpdu->GetPacket()->PeekHeader(assoc);
1421 const auto& mle = assoc.Get<MultiLinkElement>();
1422
1423 if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetNLinks() == 1)
1424 {
1426 mle.has_value(),
1427 false,
1428 "Multi-Link Element in Assoc Response frame with single link AP or single link STA");
1429 return;
1430 }
1431
1432 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Assoc Request frame");
1433 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1435 "Incorrect MLD Address advertised in Multi-Link Element");
1436 NS_TEST_EXPECT_MSG_EQ(mle->GetNPerStaProfileSubelements(),
1437 m_setupLinks.size() - 1,
1438 "Incorrect number of Per-STA Profile subelements in Multi-Link Element");
1439 for (std::size_t i = 0; i < mle->GetNPerStaProfileSubelements(); i++)
1440 {
1441 auto& perStaProfile = mle->GetPerStaProfile(i);
1442 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasStaMacAddress(),
1443 true,
1444 "Per-STA Profile must contain STA MAC address");
1445 // find ID of the local link corresponding to this subelement
1446 auto apLinkId = m_apMac->GetLinkIdByAddress(perStaProfile.GetStaMacAddress());
1448 apLinkId.has_value(),
1449 true,
1450 "No link found with the STA MAC address advertised in Per-STA Profile");
1451 NS_TEST_EXPECT_MSG_EQ(+apLinkId.value(),
1452 +perStaProfile.GetLinkId(),
1453 "Link ID and MAC address advertised in Per-STA Profile do not match");
1455 +apLinkId.value(),
1456 +linkId,
1457 "The AP that sent the Assoc Response should not be included in a Per-STA Profile");
1458 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), apLinkId.value());
1459 NS_TEST_EXPECT_MSG_EQ((it != m_setupLinks.end()),
1460 true,
1461 "Not expecting to setup AP link ID " << +apLinkId.value());
1462 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasAssocResponse(),
1463 true,
1464 "Missing Association Response in Per-STA Profile");
1465 }
1466
1467 // For the moment, the AP MLD always accepts a valid TID-to-Link Mapping request, hence
1468 // in every case there is no TID-to-Link Mapping IE in the Association Response
1469 NS_TEST_EXPECT_MSG_EQ(assoc.Get<TidToLinkMapping>().empty(),
1470 true,
1471 "Didn't expect to find a TID-to-Link Mapping IE in Association Response");
1472}
1473
1474void
1476{
1480 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->IsAssociated(), true, "Expected the STA to be associated");
1481
1482 for (const auto linkId : m_setupLinks)
1483 {
1484 auto staLinkId = (m_staMacs[0]->GetNLinks() > 1 ? linkId : SINGLE_LINK_OP_ID);
1485 auto apLinkId = (m_apMac->GetNLinks() > 1 ? linkId : SINGLE_LINK_OP_ID);
1486
1487 auto staAddr = m_staMacs[0]->GetFrameExchangeManager(staLinkId)->GetAddress();
1488 auto apAddr = m_apMac->GetFrameExchangeManager(apLinkId)->GetAddress();
1489
1490 auto staRemoteMgr = m_staMacs[0]->GetWifiRemoteStationManager(staLinkId);
1491 auto apRemoteMgr = m_apMac->GetWifiRemoteStationManager(apLinkId);
1492
1493 // STA side
1494 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetFrameExchangeManager(staLinkId)->GetBssid(),
1495 apAddr,
1496 "Unexpected BSSID for STA link ID " << +staLinkId);
1497 if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1498 {
1499 NS_TEST_EXPECT_MSG_EQ((staRemoteMgr->GetMldAddress(apAddr) == m_apMac->GetAddress()),
1500 true,
1501 "Incorrect MLD address stored by STA on link ID " << +staLinkId);
1503 (staRemoteMgr->GetAffiliatedStaAddress(m_apMac->GetAddress()) == apAddr),
1504 true,
1505 "Incorrect affiliated address stored by STA on link ID " << +staLinkId);
1506 }
1507
1508 // AP side
1509 NS_TEST_EXPECT_MSG_EQ(apRemoteMgr->IsAssociated(staAddr),
1510 true,
1511 "Expecting STA " << staAddr << " to be associated on link "
1512 << +apLinkId);
1513 if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1514 {
1516 (apRemoteMgr->GetMldAddress(staAddr) == m_staMacs[0]->GetAddress()),
1517 true,
1518 "Incorrect MLD address stored by AP on link ID " << +apLinkId);
1520 (apRemoteMgr->GetAffiliatedStaAddress(m_staMacs[0]->GetAddress()) == staAddr),
1521 true,
1522 "Incorrect affiliated address stored by AP on link ID " << +apLinkId);
1523 }
1524 auto aid = m_apMac->GetAssociationId(staAddr, apLinkId);
1525 const auto& staList = m_apMac->GetStaList(apLinkId);
1526 NS_TEST_EXPECT_MSG_EQ((staList.find(aid) != staList.end()),
1527 true,
1528 "STA " << staAddr << " not found in list of associated STAs");
1529
1530 // STA of non-AP MLD operate on the same channel as the AP
1532 +m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetNumber(),
1534 "Incorrect operating channel number for STA on link " << +staLinkId);
1536 m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetFrequency(),
1538 "Incorrect operating channel frequency for STA on link " << +staLinkId);
1539 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetWidth(),
1541 "Incorrect operating channel width for STA on link " << +staLinkId);
1543 +m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetPhyBand(),
1545 "Incorrect operating PHY band for STA on link " << +staLinkId);
1547 +m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetPrimaryChannelIndex(20),
1549 "Incorrect operating primary channel index for STA on link " << +staLinkId);
1550 }
1551
1552 // lambda to check the link mapping stored at wifi MAC
1553 auto checkStoredMapping =
1554 [this](Ptr<WifiMac> mac, Ptr<WifiMac> dest, WifiDirection dir, bool present) {
1555 NS_TEST_ASSERT_MSG_EQ(mac->GetTidToLinkMapping(dest->GetAddress(), dir).has_value(),
1556 present,
1557 "Link mapping stored by "
1558 << (mac->GetTypeOfStation() == AP ? "AP" : "non-AP")
1559 << " MLD for " << dir << " direction "
1560 << (present ? "expected" : "not expected"));
1561 if (present)
1562 {
1563 const auto& mapping =
1564 (dir == WifiDirection::DOWNLINK ? m_dlTidLinkMapping : m_ulTidLinkMapping);
1566 (mac->GetTidToLinkMapping(dest->GetAddress(), dir)->get() == mapping),
1567 true,
1568 "Incorrect link mapping stored by "
1569 << (mac->GetTypeOfStation() == AP ? "AP" : "non-AP") << " MLD for " << dir
1570 << " direction");
1571 }
1572 };
1573
1574 auto storedMapping =
1575 m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1 && m_apNegSupport > 0;
1576 checkStoredMapping(m_apMac, m_staMacs[0], WifiDirection::DOWNLINK, storedMapping);
1577 checkStoredMapping(m_apMac, m_staMacs[0], WifiDirection::UPLINK, storedMapping);
1578 checkStoredMapping(m_staMacs[0], m_apMac, WifiDirection::DOWNLINK, storedMapping);
1579 checkStoredMapping(m_staMacs[0], m_apMac, WifiDirection::UPLINK, storedMapping);
1580}
1581
1582void
1584{
1585 if (m_staMacs[0]->GetNLinks() == 1)
1586 {
1587 // no link is disabled on a single link device
1588 return;
1589 }
1590
1591 for (const auto& linkId : m_staMacs[0]->GetLinkIds())
1592 {
1593 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), linkId);
1594 if (it == m_setupLinks.end())
1595 {
1596 // the link has not been setup
1597 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId)->GetState()->IsStateOff(),
1598 true,
1599 "Link " << +linkId << " has not been setup but is not disabled");
1600 continue;
1601 }
1602
1603 // the link has been setup and must be active
1604 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId)->GetState()->IsStateOff(),
1605 false,
1606 "Expecting link " << +linkId << " to be active");
1607 }
1608}
1609
1610void
1611MultiLinkSetupTest::CheckQosData(Ptr<WifiMpdu> mpdu, uint8_t linkId, std::size_t index)
1612{
1614 const auto& hdr = mpdu->GetHeader();
1615
1616 NS_TEST_ASSERT_MSG_EQ(hdr.IsQosData(), true, "Expected a QoS data frame");
1617
1618 if (!hdr.IsToDs() && hdr.IsFromDs())
1619 {
1620 dir = WifiDirection::DOWNLINK;
1621 }
1622 else if (hdr.IsToDs() && !hdr.IsFromDs())
1623 {
1624 dir = WifiDirection::UPLINK;
1625 }
1626 else
1627 {
1628 NS_ABORT_MSG("Invalid combination for QoS data frame: ToDS(" << hdr.IsToDs() << ") FromDS("
1629 << hdr.IsFromDs() << ")");
1630 }
1631
1632 const auto& tid1 = (dir == WifiDirection::DOWNLINK) ? m_dlTid1 : m_ulTid1;
1633 const auto& tid2 = (dir == WifiDirection::DOWNLINK) ? m_dlTid2 : m_ulTid2;
1634 uint8_t tid = hdr.GetQosTid();
1635
1636 NS_TEST_ASSERT_MSG_NE((tid == tid1),
1637 (tid2.has_value() && tid == *tid2),
1638 "QoS frame with unexpected TID " << +tid);
1639
1640 // lambda to find the link set the given TID is mapped to
1641 auto findLinkSet = [this, dir](uint8_t tid) -> std::set<uint8_t> {
1642 std::set<uint8_t> linkSet(m_setupLinks.cbegin(), m_setupLinks.cend());
1643 if (auto mappingOptRef = m_apMac->GetTidToLinkMapping(m_staMacs[0]->GetAddress(), dir))
1644 {
1645 // if the TID is not present in the mapping, it is mapped to all setup links
1646 if (auto it = mappingOptRef->get().find(tid); it != mappingOptRef->get().cend())
1647 {
1648 linkSet = it->second;
1649 NS_ASSERT_MSG(!linkSet.empty(), "TID " << +tid << " mapped to no link");
1650 }
1651 }
1652 return linkSet;
1653 };
1654
1655 auto linkSet = findLinkSet(tid);
1656 auto& qosFrames = (tid == tid1) ? m_qosFrames1 : m_qosFrames2;
1657
1658 // Let N the size of the link set, the first N QoS data frames are sent simultaneously
1659 // on the links of the set, the others (if any) will be sent afterwards on such links
1660
1661 // number of concurrent frames of the same TID transmitted so far (excluding current frame)
1662 std::size_t nConcurFrames = std::min(qosFrames.size(), linkSet.size());
1663
1664 // iterate over the concurrent frames of the same TID transmitted so far
1665 for (std::size_t i = 0; i < nConcurFrames; i++)
1666 {
1667 auto prev = qosFrames[i];
1668
1669 // TX duration of i-th frame
1670 auto band = m_apMac->GetWifiPhy(m_txPsdus[prev].linkId)->GetPhyBand();
1671 Time txDuration =
1672 WifiPhy::CalculateTxDuration(m_txPsdus[prev].psduMap, m_txPsdus[prev].txVector, band);
1673
1674 // the current frame is transmitted concurrently with this previous frame if it is
1675 // within the first N (size of the link set) frames, otherwise it is transmitted after
1676 // this previous frame if they have been transmitted on the same link
1677 if (qosFrames.size() < linkSet.size())
1678 {
1679 // the current frame can be sent concurrently with this previous frame
1680 NS_TEST_EXPECT_MSG_LT(m_txPsdus[index].startTx,
1681 m_txPsdus[prev].startTx + txDuration,
1682 "The " << dir << " QoS frame number " << qosFrames.size()
1683 << " was not sent concurrently with others on link "
1684 << +linkId << " which TID " << +tid << " is mapped to");
1685 }
1686 else if (m_txPsdus[prev].linkId == linkId)
1687 {
1688 // the current frame is sent afterwards
1689 NS_TEST_EXPECT_MSG_GT(m_txPsdus[index].startTx,
1690 m_txPsdus[prev].startTx + txDuration,
1691 "The " << dir << " QoS frame number " << qosFrames.size()
1692 << " was sent concurrently with others on a link "
1693 << +linkId << " which TID " << +tid << " is mapped to");
1694 }
1695 }
1696
1697 if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1698 {
1699 NS_TEST_EXPECT_MSG_EQ(std::count(linkSet.cbegin(), linkSet.cend(), linkId),
1700 1,
1701 "QoS frame sent on Link ID "
1702 << +linkId << " that does not belong to the link set of TID "
1703 << +tid);
1704 }
1705
1706 if (tid2)
1707 {
1708 // QoS frames of two distinct TIDs are sent.
1709 auto otherTid = (tid == tid1) ? *tid2 : tid1;
1710 const auto& otherQosFrames = (tid == tid1) ? m_qosFrames2 : m_qosFrames1;
1711 auto otherLinkSet = findLinkSet(otherTid);
1712
1713 // number of concurrent frames of the other TID transmitted so far
1714 std::size_t nOtherConcurFrames = std::min(otherQosFrames.size(), otherLinkSet.size());
1715
1716 // iterate over the concurrent frames of the other TID
1717 for (std::size_t i = 0; i < nOtherConcurFrames; i++)
1718 {
1719 auto prev = otherQosFrames[i];
1720
1721 // TX duration of i-th frame
1722 auto band = m_apMac->GetWifiPhy(m_txPsdus[prev].linkId)->GetPhyBand();
1723 Time txDuration = WifiPhy::CalculateTxDuration(m_txPsdus[prev].psduMap,
1724 m_txPsdus[prev].txVector,
1725 band);
1726
1727 // the current frame is transmitted concurrently with this previous frame of the
1728 // other TID if it is within the first N (size of the link set) frames of its TID
1729 if (qosFrames.size() < linkSet.size())
1730 {
1731 // the current frame can be sent concurrently with this previous frame
1732 NS_TEST_EXPECT_MSG_LT(m_txPsdus[index].startTx,
1733 m_txPsdus[prev].startTx + txDuration,
1734 "The " << dir << " QoS frame number " << qosFrames.size()
1735 << " was not sent concurrently with others with TID "
1736 << +otherTid);
1737 }
1738 }
1739 }
1740
1741 // insert the frame
1742 qosFrames.emplace_back(index);
1743
1744 if (qosFrames.size() == m_setupLinks.size())
1745 {
1746 qosFrames.clear();
1747 }
1748}
1749
1753enum class WifiTrafficPattern : uint8_t
1754{
1755 STA_TO_STA = 0,
1756 STA_TO_AP,
1757 AP_TO_STA,
1760};
1761
1765enum class WifiBaEnabled : uint8_t
1766{
1767 NO = 0,
1768 YES
1769};
1770
1774enum class WifiUseBarAfterMissedBa : uint8_t
1775{
1776 NO = 0,
1777 YES
1778};
1779
1809{
1810 public:
1821 MultiLinkTxTest(const BaseParams& baseParams,
1822 WifiTrafficPattern trafficPattern,
1823 WifiBaEnabled baEnabled,
1824 WifiUseBarAfterMissedBa useBarAfterMissedBa,
1825 uint8_t nMaxInflight);
1826 ~MultiLinkTxTest() override = default;
1827
1828 protected:
1837 void CheckBlockAck(Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId);
1838
1839 void Transmit(Ptr<WifiMac> mac,
1840 uint8_t phyId,
1841 WifiConstPsduMap psduMap,
1842 WifiTxVector txVector,
1843 double txPowerW) override;
1844 void DoSetup() override;
1845 void DoRun() override;
1846
1847 private:
1848 void StartTraffic() override;
1849
1851 using RxErrorModelMap = std::unordered_map<Mac48Address, Ptr<ListErrorModel>, WifiAddressHash>;
1852
1854 std::list<uint64_t> m_uidList;
1855 bool m_dataCorrupted{false};
1859 std::size_t m_nMaxInflight;
1860 std::size_t m_nPackets;
1861 std::size_t m_blockAckCount{0};
1862 std::size_t m_blockAckReqCount{0};
1863 std::map<uint16_t, std::size_t> m_inflightCount;
1866};
1867
1869 WifiTrafficPattern trafficPattern,
1870 WifiBaEnabled baEnabled,
1871 WifiUseBarAfterMissedBa useBarAfterMissedBa,
1872 uint8_t nMaxInflight)
1874 std::string("Check data transmission between MLDs ") +
1875 (baEnabled == WifiBaEnabled::YES
1876 ? (useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES
1877 ? "with BA agreement, send BAR after BlockAck timeout"
1878 : "with BA agreement, send Data frames after BlockAck timeout")
1879 : "without BA agreement") +
1880 " (Traffic pattern: " + std::to_string(static_cast<uint8_t>(trafficPattern)) +
1881 (baEnabled == WifiBaEnabled::YES ? ", nMaxInflight=" + std::to_string(nMaxInflight)
1882 : "") +
1883 ")",
1884 2,
1885 baseParams),
1886 m_trafficPattern(trafficPattern),
1887 m_baEnabled(baEnabled == WifiBaEnabled::YES),
1888 m_useBarAfterMissedBa(useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES),
1889 m_nMaxInflight(nMaxInflight),
1890 m_nPackets(trafficPattern == WifiTrafficPattern::STA_TO_BCAST ||
1891 trafficPattern == WifiTrafficPattern::STA_TO_STA
1892 ? 4
1893 : 8)
1894{
1895}
1896
1897void
1899 uint8_t phyId,
1900 WifiConstPsduMap psduMap,
1901 WifiTxVector txVector,
1902 double txPowerW)
1903{
1904 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
1905 auto linkId = m_txPsdus.back().linkId;
1906
1907 auto psdu = psduMap.begin()->second;
1908
1909 switch (psdu->GetHeader(0).GetType())
1910 {
1912 // a management frame is a DL frame if TA equals BSSID
1913 CheckAddresses(psdu,
1914 psdu->GetHeader(0).GetAddr2() == psdu->GetHeader(0).GetAddr3() ? DL : UL);
1915 if (!m_baEnabled)
1916 {
1917 // corrupt all management action frames (ADDBA Request frames) to prevent
1918 // the establishment of a BA agreement
1919 m_uidList.push_front(psdu->GetPacket()->GetUid());
1920 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1921 NS_LOG_INFO("CORRUPTED");
1922 }
1923 break;
1924 case WIFI_MAC_QOSDATA:
1925 CheckAddresses(psdu);
1926
1927 for (const auto& mpdu : *psdu)
1928 {
1929 // determine the max number of simultaneous transmissions for this MPDU
1930 // (only if sent by the traffic source and this is not a broadcast frame)
1931 if (m_baEnabled && m_sourceMac->GetLinkIds().count(linkId) == 1 &&
1932 m_sourceMac->GetFrameExchangeManager(linkId)->GetAddress() ==
1933 mpdu->GetHeader().GetAddr2() &&
1934 !mpdu->GetHeader().GetAddr1().IsGroup())
1935 {
1936 auto seqNo = mpdu->GetHeader().GetSequenceNumber();
1937 auto [it, success] =
1938 m_inflightCount.insert({seqNo, mpdu->GetInFlightLinkIds().size()});
1939 if (!success)
1940 {
1941 it->second = std::max(it->second, mpdu->GetInFlightLinkIds().size());
1942 }
1943 }
1944 }
1945 for (std::size_t i = 0; i < psdu->GetNMpdus(); i++)
1946 {
1947 // corrupt QoS data frame with sequence number equal to 1 (only once) if we are
1948 // not in the AP to broadcast traffic pattern (broadcast frames are not retransmitted)
1949 // nor in the STA to broadcast or STA to STA traffic patterns (retransmissions from
1950 // STA 1 could collide with frames forwarded by the AP)
1951 if (psdu->GetHeader(i).GetSequenceNumber() != 1 ||
1952 m_trafficPattern == WifiTrafficPattern::AP_TO_BCAST ||
1953 m_trafficPattern == WifiTrafficPattern::STA_TO_BCAST ||
1954 m_trafficPattern == WifiTrafficPattern::STA_TO_STA)
1955 {
1956 continue;
1957 }
1958 auto uid = psdu->GetPayload(i)->GetUid();
1959 if (!m_dataCorrupted)
1960 {
1961 m_uidList.push_front(uid);
1962 m_dataCorrupted = true;
1963 NS_LOG_INFO("CORRUPTED");
1964 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1965 }
1966 else
1967 {
1968 // do not corrupt the QoS data frame anymore
1969 if (auto it = std::find(m_uidList.cbegin(), m_uidList.cend(), uid);
1970 it != m_uidList.cend())
1971 {
1972 m_uidList.erase(it);
1973 }
1974 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1975 }
1976 break;
1977 }
1978 break;
1979 case WIFI_MAC_CTL_BACKRESP: {
1980 // ignore BlockAck frames not addressed to the source of the application packets
1981 if (!m_sourceMac->GetLinkIdByAddress(psdu->GetHeader(0).GetAddr1()))
1982 {
1983 break;
1984 }
1985 if (m_nMaxInflight > 1)
1986 {
1987 // we do not check the content of BlockAck when m_nMaxInflight is greater than 1
1988 break;
1989 }
1990 CheckBlockAck(psdu, txVector, linkId);
1992 if (m_blockAckCount == 2)
1993 {
1994 // corrupt the second BlockAck frame to simulate a missed BlockAck
1995 m_uidList.push_front(psdu->GetPacket()->GetUid());
1996 NS_LOG_INFO("CORRUPTED");
1997 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1998 }
1999 break;
2001 // ignore BlockAckReq frames not transmitted by the source of the application packets
2002 if (m_sourceMac->GetLinkIdByAddress(psdu->GetHeader(0).GetAddr2()))
2003 {
2005 }
2006 break;
2007 }
2008 default:;
2009 }
2010}
2011
2012void
2014 const WifiTxVector& txVector,
2015 uint8_t linkId)
2016{
2017 NS_TEST_ASSERT_MSG_EQ(m_baEnabled, true, "No BlockAck expected without BA agreement");
2018 NS_TEST_ASSERT_MSG_EQ((m_trafficPattern != WifiTrafficPattern::AP_TO_BCAST),
2019 true,
2020 "No BlockAck expected in AP to broadcast traffic pattern");
2021
2022 /*
2023 * ┌───────┬───────X ┌───────┐
2024 * link 0 │ 0 │ 1 │ │ 1 │
2025 * ───────┴───────┴───────┴┬──┬────┴───────┴┬───┬────────────────────────
2026 * │BA│ │ACK│
2027 * └──┘ └───┘
2028 * ┌───────┬───────┐ ┌───────┬───────┐
2029 * link 1 │ 2 │ 3 │ │ 2 │ 3 │
2030 * ────────────────────┴───────┴───────┴┬──X───┴───────┴───────┴┬──┬─────
2031 * │BA│ │BA│
2032 * └──┘ └──┘
2033 */
2034 auto mpdu = *psdu->begin();
2035 CtrlBAckResponseHeader blockAck;
2036 mpdu->GetPacket()->PeekHeader(blockAck);
2037 bool isMpdu1corrupted = (m_trafficPattern == WifiTrafficPattern::STA_TO_AP ||
2038 m_trafficPattern == WifiTrafficPattern::AP_TO_STA);
2039
2040 switch (m_blockAckCount)
2041 {
2042 case 0: // first BlockAck frame (all traffic patterns)
2044 true,
2045 "MPDU 0 expected to be successfully received");
2047 blockAck.IsPacketReceived(1),
2048 !isMpdu1corrupted,
2049 "MPDU 1 expected to be received only in STA_TO_STA/STA_TO_BCAST scenarios");
2050 // if there are at least two links setup, we expect all MPDUs to be inflight
2051 // (on distinct links)
2052 if (m_staMacs[0]->GetSetupLinkIds().size() > 1)
2053 {
2054 auto queue = m_sourceMac->GetTxopQueue(AC_BE);
2055 auto rcvMac = m_sourceMac == m_staMacs[0] ? StaticCast<WifiMac>(m_apMac)
2056 : StaticCast<WifiMac>(m_staMacs[1]);
2057 auto item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress());
2058 std::size_t nQueuedPkt = 0;
2059 auto delay = WifiPhy::CalculateTxDuration(psdu,
2060 txVector,
2061 rcvMac->GetWifiPhy(linkId)->GetPhyBand()) +
2062 MicroSeconds(1); // to account for propagation delay
2063
2064 while (item)
2065 {
2066 auto seqNo = item->GetHeader().GetSequenceNumber();
2067 NS_TEST_EXPECT_MSG_EQ(item->IsInFlight(),
2068 true,
2069 "MPDU with seqNo=" << seqNo << " is not in flight");
2070 auto linkIds = item->GetInFlightLinkIds();
2071 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
2072 1,
2073 "MPDU with seqNo=" << seqNo
2074 << " is in flight on multiple links");
2075 // The first two MPDUs are in flight on the same link on which the BlockAck
2076 // is sent. The other two MPDUs (only for AP to STA/STA to AP scenarios) are
2077 // in flight on a different link.
2078 auto srcLinkId = m_sourceMac->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1());
2079 NS_TEST_ASSERT_MSG_EQ(srcLinkId.has_value(),
2080 true,
2081 "Addr1 of BlockAck is not an originator's link address");
2082 NS_TEST_EXPECT_MSG_EQ((*linkIds.begin() == *srcLinkId),
2083 (seqNo <= 1),
2084 "MPDU with seqNo=" << seqNo
2085 << " in flight on unexpected link");
2086 // check the Retry subfield and whether this MPDU is still queued
2087 // after the originator has processed this BlockAck
2088
2089 // MPDUs acknowledged via this BlockAck are no longer queued
2090 bool isQueued = (seqNo > (isMpdu1corrupted ? 0 : 1));
2091 // The Retry subfield is set if the MPDU has not been acknowledged (i.e., it
2092 // is still queued) and has been transmitted on the same link as the BlockAck
2093 // (i.e., its sequence number is less than or equal to 1)
2094 bool isRetry = isQueued && seqNo <= 1;
2095
2096 Simulator::Schedule(delay, [this, item, isQueued, isRetry]() {
2097 NS_TEST_EXPECT_MSG_EQ(item->IsQueued(),
2098 isQueued,
2099 "MPDU with seqNo="
2100 << item->GetHeader().GetSequenceNumber() << " should "
2101 << (isQueued ? "" : "not") << " be queued");
2103 item->GetHeader().IsRetry(),
2104 isRetry,
2105 "Unexpected value for the Retry subfield of the MPDU with seqNo="
2106 << item->GetHeader().GetSequenceNumber());
2107 });
2108
2109 nQueuedPkt++;
2110 item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress(), item);
2111 }
2112 // Each MPDU contains an A-MSDU consisting of two MSDUs
2113 NS_TEST_EXPECT_MSG_EQ(nQueuedPkt, m_nPackets / 2, "Unexpected number of queued MPDUs");
2114 }
2115 break;
2116 case 1: // second BlockAck frame (STA to AP and AP to STA traffic patterns only)
2117 case 2: // third BlockAck frame (STA to AP and AP to STA traffic patterns only)
2118 NS_TEST_EXPECT_MSG_EQ((m_trafficPattern == WifiTrafficPattern::AP_TO_STA ||
2119 m_trafficPattern == WifiTrafficPattern::STA_TO_AP),
2120 true,
2121 "Did not expect to receive a second BlockAck");
2122 // the second BlockAck is corrupted, but the data frames have been received successfully
2123 std::pair<uint16_t, uint16_t> seqNos;
2124 // if multiple links were setup, the transmission of the second A-MPDU started before
2125 // the end of the first one, so the second A-MPDU includes MPDUs with sequence numbers
2126 // 2 and 3. Otherwise, MPDU with sequence number 1 is retransmitted along with the MPDU
2127 // with sequence number 2.
2128 if (m_staMacs[0]->GetSetupLinkIds().size() > 1)
2129 {
2130 seqNos = {2, 3};
2131 }
2132 else
2133 {
2134 seqNos = {1, 2};
2135 }
2136 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(seqNos.first),
2137 true,
2138 "MPDU " << seqNos.first << " expected to be successfully received");
2139 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(seqNos.second),
2140 true,
2141 "MPDU " << seqNos.second << " expected to be successfully received");
2142 break;
2143 }
2144}
2145
2146void
2148{
2150
2151 if (m_baEnabled)
2152 {
2153 // Enable A-MSDU aggregation. Max A-MSDU size is set such that two MSDUs can be aggregated
2154 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
2155 {
2156 mac->SetAttribute("BE_MaxAmsduSize", UintegerValue(2100));
2157 mac->GetQosTxop(AC_BE)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2159 mac->GetQosTxop(AC_BE)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2160 }
2161 }
2162
2163 // install post reception error model on all devices
2164 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
2165 {
2166 auto errorModel = CreateObject<ListErrorModel>();
2167 m_errorModels[m_apMac->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2168 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2169 }
2170 for (std::size_t i : {0, 1})
2171 {
2172 for (const auto linkId : m_staMacs[i]->GetLinkIds())
2173 {
2174 auto errorModel = CreateObject<ListErrorModel>();
2175 m_errorModels[m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2176 m_staMacs[i]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2177 }
2178 }
2179}
2180
2181void
2183{
2184 Address destAddr;
2185
2186 switch (m_trafficPattern)
2187 {
2188 case WifiTrafficPattern::STA_TO_STA:
2190 destAddr = m_staMacs[1]->GetDevice()->GetAddress();
2191 break;
2192 case WifiTrafficPattern::STA_TO_AP:
2194 destAddr = m_apMac->GetDevice()->GetAddress();
2195 break;
2196 case WifiTrafficPattern::AP_TO_STA:
2198 destAddr = m_staMacs[1]->GetDevice()->GetAddress();
2199 break;
2200 case WifiTrafficPattern::AP_TO_BCAST:
2202 destAddr = Mac48Address::GetBroadcast();
2203 break;
2204 case WifiTrafficPattern::STA_TO_BCAST:
2206 destAddr = Mac48Address::GetBroadcast();
2207 break;
2208 }
2209
2210 PacketSocketAddress sockAddr;
2212 sockAddr.SetPhysicalAddress(destAddr);
2213 sockAddr.SetProtocol(1);
2214
2215 // install first client application generating at most 4 packets
2217 GetApplication(sockAddr, std::min<std::size_t>(m_nPackets, 4), 1000));
2218
2219 if (m_nPackets > 4)
2220 {
2221 // install a second client application generating the remaining packets and
2222 // starting during transmission of first A-MPDU, if multiple links are setup
2224 GetApplication(sockAddr, m_nPackets - 4, 1000, MilliSeconds(4)));
2225 }
2226
2228}
2229
2230void
2232{
2234
2235 // Expected number of packets received by each node (AP, STA 0, STA 1) at application layer
2236 std::array<std::size_t, 3> expectedRxPkts{};
2237
2238 switch (m_trafficPattern)
2239 {
2240 case WifiTrafficPattern::STA_TO_STA:
2241 case WifiTrafficPattern::AP_TO_STA:
2242 // only STA 1 receives the m_nPackets packets that have been transmitted
2243 expectedRxPkts[2] = m_nPackets;
2244 break;
2245 case WifiTrafficPattern::STA_TO_AP:
2246 // only the AP receives the m_nPackets packets that have been transmitted
2247 expectedRxPkts[0] = m_nPackets;
2248 break;
2249 case WifiTrafficPattern::AP_TO_BCAST:
2250 // the AP replicates the broadcast frames on all the links, hence each station
2251 // receives the m_nPackets packets N times, where N is the number of setup link
2252 expectedRxPkts[1] = m_nPackets * m_staMacs[0]->GetSetupLinkIds().size();
2253 expectedRxPkts[2] = m_nPackets * m_staMacs[1]->GetSetupLinkIds().size();
2254 break;
2255 case WifiTrafficPattern::STA_TO_BCAST:
2256 // the AP receives the m_nPackets packets and then replicates them on all the links,
2257 // hence STA 1 receives m_nPackets packets N times, where N is the number of setup link
2258 expectedRxPkts[0] = m_nPackets;
2259 expectedRxPkts[2] = m_nPackets * m_staMacs[1]->GetSetupLinkIds().size();
2260 break;
2261 }
2262
2264 +expectedRxPkts[0],
2265 "Unexpected number of packets received by the AP");
2267 +expectedRxPkts[1],
2268 "Unexpected number of packets received by STA 0");
2270 +expectedRxPkts[2],
2271 "Unexpected number of packets received by STA 1");
2272
2273 // check that the expected number of BlockAck frames are transmitted
2274 if (m_baEnabled && m_nMaxInflight == 1)
2275 {
2276 std::size_t expectedBaCount = 0;
2277 std::size_t expectedBarCount = 0;
2278
2279 switch (m_trafficPattern)
2280 {
2281 case WifiTrafficPattern::STA_TO_AP:
2282 case WifiTrafficPattern::AP_TO_STA:
2283 // two A-MPDUs are transmitted and one BlockAck is corrupted
2284 expectedBaCount = 3;
2285 // one BlockAckReq is sent if m_useBarAfterMissedBa is true
2286 expectedBarCount = m_useBarAfterMissedBa ? 1 : 0;
2287 break;
2288 case WifiTrafficPattern::STA_TO_STA:
2289 case WifiTrafficPattern::STA_TO_BCAST:
2290 // only one A-MPDU is transmitted and the BlockAck is not corrupted
2291 expectedBaCount = 1;
2292 break;
2293 default:;
2294 }
2296 expectedBaCount,
2297 "Unexpected number of BlockAck frames");
2299 expectedBarCount,
2300 "Unexpected number of BlockAckReq frames");
2301 }
2302
2303 // check that setting the QosTxop::NMaxInflights attribute has the expected effect.
2304 // We do not support sending an MPDU multiple times concurrently without Block Ack
2305 // agreement. Also, broadcast frames are already duplicated and sent on all links.
2306 if (m_baEnabled && m_trafficPattern != WifiTrafficPattern::AP_TO_BCAST)
2307 {
2309 m_inflightCount.size(),
2310 m_nPackets / 2,
2311 "Did not collect number of simultaneous transmissions for all data frames");
2312
2313 auto nMaxInflight = std::min(m_nMaxInflight, m_staMacs[0]->GetSetupLinkIds().size());
2314 std::size_t maxCount = 0;
2315 for (const auto& [seqNo, count] : m_inflightCount)
2316 {
2318 count,
2319 nMaxInflight,
2320 "MPDU with seqNo=" << seqNo
2321 << " transmitted simultaneously more times than allowed");
2322 maxCount = std::max(maxCount, count);
2323 }
2324
2326 maxCount,
2327 nMaxInflight,
2328 "Expected that at least one data frame was transmitted simultaneously a number of "
2329 "times equal to the NMaxInflights attribute");
2330 }
2331
2333}
2334
2338enum class WifiMuTrafficPattern : uint8_t
2339{
2343 UL_MU
2344};
2345
2370{
2371 public:
2381 MultiLinkMuTxTest(const BaseParams& baseParams,
2382 WifiMuTrafficPattern muTrafficPattern,
2383 WifiUseBarAfterMissedBa useBarAfterMissedBa,
2384 uint8_t nMaxInflight);
2385 ~MultiLinkMuTxTest() override = default;
2386
2387 protected:
2396 void CheckBlockAck(Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId);
2397
2398 void Transmit(Ptr<WifiMac> mac,
2399 uint8_t phyId,
2400 WifiConstPsduMap psduMap,
2401 WifiTxVector txVector,
2402 double txPowerW) override;
2403 void DoSetup() override;
2404 void DoRun() override;
2405
2406 private:
2407 void StartTraffic() override;
2408
2410 using RxErrorModelMap = std::unordered_map<Mac48Address, Ptr<ListErrorModel>, WifiAddressHash>;
2411
2414 using AddrSeqNoPair = std::pair<Mac48Address, uint16_t>;
2415
2417 std::list<uint64_t> m_uidList;
2418 std::optional<Mac48Address> m_dataCorruptedSta;
2420 bool m_waitFirstTf{true};
2423 std::size_t m_nMaxInflight;
2424 std::vector<PacketSocketAddress> m_sockets;
2425 std::size_t m_nPackets;
2426 std::size_t m_blockAckCount{0};
2427 std::size_t m_tfCount{0};
2428 // std::size_t m_blockAckReqCount{0}; ///< transmitted BlockAckReq counter
2429 std::map<AddrSeqNoPair, std::size_t> m_inflightCount;
2432};
2433
2435 WifiMuTrafficPattern muTrafficPattern,
2436 WifiUseBarAfterMissedBa useBarAfterMissedBa,
2437 uint8_t nMaxInflight)
2439 std::string("Check MU data transmission between MLDs ") +
2440 (useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES
2441 ? "(send BAR after BlockAck timeout,"
2442 : "(send Data frames after BlockAck timeout,") +
2443 " MU Traffic pattern: " + std::to_string(static_cast<uint8_t>(muTrafficPattern)) +
2444 ", nMaxInflight=" + std::to_string(nMaxInflight) + ")",
2445 2,
2446 baseParams),
2447 m_muTrafficPattern(muTrafficPattern),
2448 m_useBarAfterMissedBa(useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES),
2449 m_nMaxInflight(nMaxInflight),
2450 m_sockets(m_nStations),
2451 m_nPackets(muTrafficPattern == WifiMuTrafficPattern::UL_MU ? 4 : 8)
2452{
2453}
2454
2455void
2457 uint8_t phyId,
2458 WifiConstPsduMap psduMap,
2459 WifiTxVector txVector,
2460 double txPowerW)
2461{
2462 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2463 auto linkId = m_txPsdus.back().linkId;
2464
2465 CtrlTriggerHeader trigger;
2466
2467 for (const auto& [staId, psdu] : psduMap)
2468 {
2469 switch (psdu->GetHeader(0).GetType())
2470 {
2471 case WIFI_MAC_QOSDATA:
2472 CheckAddresses(psdu);
2473 if (psdu->GetHeader(0).HasData())
2474 {
2475 bool isDl = psdu->GetHeader(0).IsFromDs();
2476 auto linkAddress =
2477 isDl ? psdu->GetHeader(0).GetAddr1() : psdu->GetHeader(0).GetAddr2();
2478 auto address = m_apMac->GetMldAddress(linkAddress).value_or(linkAddress);
2479
2480 for (const auto& mpdu : *psdu)
2481 {
2482 // determine the max number of simultaneous transmissions for this MPDU
2483 auto seqNo = mpdu->GetHeader().GetSequenceNumber();
2484 auto [it, success] = m_inflightCount.insert(
2485 {{address, seqNo}, mpdu->GetInFlightLinkIds().size()});
2486 if (!success)
2487 {
2488 it->second = std::max(it->second, mpdu->GetInFlightLinkIds().size());
2489 }
2490 }
2491 for (std::size_t i = 0; i < psdu->GetNMpdus(); i++)
2492 {
2493 // MPDUs with seqNo=2 are always transmitted in an MU PPDU
2494 if (psdu->GetHeader(i).GetSequenceNumber() == 2)
2495 {
2496 if (m_muTrafficPattern == WifiMuTrafficPattern::UL_MU)
2497 {
2498 NS_TEST_EXPECT_MSG_EQ(txVector.IsUlMu(),
2499 true,
2500 "MPDU " << **std::next(psdu->begin(), i)
2501 << " not transmitted in a TB PPDU");
2502 }
2503 else
2504 {
2505 NS_TEST_EXPECT_MSG_EQ(txVector.GetHeMuUserInfoMap().size(),
2506 2,
2507 "MPDU " << **std::next(psdu->begin(), i)
2508 << " not transmitted in a DL MU PPDU");
2509 }
2510 }
2511 // corrupt QoS data frame with sequence number equal to 3 (only once)
2512 if (psdu->GetHeader(i).GetSequenceNumber() != 3)
2513 {
2514 continue;
2515 }
2516 auto uid = psdu->GetPayload(i)->GetUid();
2517 if (!m_dataCorruptedSta)
2518 {
2519 m_uidList.push_front(uid);
2520 m_dataCorruptedSta = isDl ? psdu->GetAddr1() : psdu->GetAddr2();
2521 NS_LOG_INFO("CORRUPTED");
2522 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2523 }
2524 else if ((isDl && m_dataCorruptedSta == psdu->GetAddr1()) ||
2525 (!isDl && m_dataCorruptedSta == psdu->GetAddr2()))
2526 {
2527 // do not corrupt the QoS data frame anymore
2528 if (auto it = std::find(m_uidList.cbegin(), m_uidList.cend(), uid);
2529 it != m_uidList.cend())
2530 {
2531 m_uidList.erase(it);
2532 }
2533 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2534 }
2535 break;
2536 }
2537 }
2538 break;
2540 if (m_nMaxInflight > 1)
2541 {
2542 // we do not check the content of BlockAck when m_nMaxInflight is greater than 1
2543 break;
2544 }
2545 CheckBlockAck(psdu, txVector, linkId);
2547 // to simulate a missed BlockAck, corrupt the fifth BlockAck frame (the first
2548 // two BlockAck frames are sent to acknowledge the QoS data frames that triggered
2549 // the establishment of Block Ack agreements)
2550 if (m_blockAckCount == 5)
2551 {
2552 // corrupt the third BlockAck frame to simulate a missed BlockAck
2553 m_uidList.push_front(psdu->GetPacket()->GetUid());
2554 NS_LOG_INFO("CORRUPTED");
2555 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2556 }
2557 break;
2559 psdu->GetPayload(0)->PeekHeader(trigger);
2560 // the MU scheduler requests channel access on all links but we have to perform the
2561 // following actions only once (hence why we only consider TF transmitted on link 0)
2562 if (trigger.IsBasic() && m_waitFirstTf)
2563 {
2564 m_waitFirstTf = false;
2565 // the AP is starting the transmission of the Basic Trigger frame, so generate
2566 // the configured number of packets at STAs, which are sent in TB PPDUs, when
2567 // transmission of the Trigger Frame ends
2568 auto band = mac->GetWifiPhy(linkId)->GetPhyBand();
2569 Time txDuration = WifiPhy::CalculateTxDuration(psduMap, txVector, band);
2570 for (uint8_t i = 0; i < m_nStations; i++)
2571 {
2572 m_staMacs[i]->GetDevice()->GetNode()->AddApplication(
2573 GetApplication(m_sockets[i], m_nPackets, 450, txDuration, i * 4));
2574 }
2575 }
2576 if (++m_tfCount == m_staMacs[0]->GetSetupLinkIds().size())
2577 {
2578 // a TF has been sent on all the setup links, we can now disable UL OFDMA
2579 auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
2580 NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "Expected an aggregated MU scheduler");
2581 muScheduler->SetAttribute("EnableUlOfdma", BooleanValue(false));
2582 }
2583 break;
2584 default:;
2585 }
2586 }
2587}
2588
2589void
2591 const WifiTxVector& txVector,
2592 uint8_t linkId)
2593{
2594 /*
2595 * Example sequence with DL_MU_BAR_BA_SEQUENCE
2596 * ┌───────┬───────X
2597 * (To:1) │ 2 │ 3 │
2598 * ├───────┼───────┤ ┌───┐ ┌───────┐
2599 * [link 0] (To:0) │ 2 │ 3 │ │BAR│ (To:1) │ 3 │
2600 * ────────────────┴───────┴───────┴┬──┼───┼──┬──────────┴───────┴┬───┬────────
2601 * │BA│ │BA│ │ACK│
2602 * └──┘ └──┘ └───┘
2603 * ┌───────┬───────┐
2604 * (To:1) │ 4 │ 5 │
2605 * ├───────┼───────┤ ┌───┐ ┌───┐
2606 * [link 1] (To:0) │ 4 │ 5 │ │BAR│ │BAR│
2607 * ────────────────────────────┴───────┴───────┴┬──X────┴───┴┬──┼───┼──┬───────
2608 * │BA│ │BA│ │BA│
2609 * └──┘ └──┘ └──┘
2610 *
2611 * Example sequence with UL_MU
2612 *
2613 * ┌──┐ ┌────┐ ┌───┐
2614 * [link 0] │TF│ │M-BA│ │ACK│
2615 * ─────────┴──┴──┬───────┬───────┬──┴────┴────────────┬───────┬─┴───┴─────────
2616 * (From:0) │ 2 │ 3 │ (From:1) │ 3 │
2617 * ├───────┼───────┤ └───────┘
2618 * (From:1) │ 2 │ 3 │
2619 * └───────┴───────X
2620 * ┌──┐
2621 * [link 1] │TF│
2622 * ─────────┴──┴──┬───────────────┬────────────────────────────────────────────
2623 * (From:0) │ QoS Null │
2624 * ├───────────────┤
2625 * (From:1) │ QoS Null │
2626 * └───────────────┘
2627 */
2628 auto mpdu = *psdu->begin();
2629 CtrlBAckResponseHeader blockAck;
2630 mpdu->GetPacket()->PeekHeader(blockAck);
2631 bool isMpdu3corrupted;
2632
2633 switch (m_blockAckCount)
2634 {
2635 case 0:
2636 case 1: // Ignore the first two BlockAck frames that acknowledged frames sent to establish BA
2637 break;
2638 case 2:
2639 if (m_muTrafficPattern == WifiMuTrafficPattern::UL_MU)
2640 {
2641 NS_TEST_EXPECT_MSG_EQ(blockAck.IsMultiSta(), true, "Expected a Multi-STA BlockAck");
2642 for (uint8_t i = 0; i < m_nStations; i++)
2643 {
2644 auto indices = blockAck.FindPerAidTidInfoWithAid(m_staMacs[i]->GetAssociationId());
2645 NS_TEST_ASSERT_MSG_EQ(indices.size(), 1, "Expected one Per AID TID Info per STA");
2646 auto index = indices.front();
2648 true,
2649 "Expected that a QoS data frame was corrupted");
2650 isMpdu3corrupted =
2651 m_staMacs[i]->GetLinkIdByAddress(*m_dataCorruptedSta).has_value();
2652 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(2, index),
2653 true,
2654 "MPDU 2 expected to be successfully received");
2655 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(3, index),
2656 !isMpdu3corrupted,
2657 "Unexpected reception status for MPDU 3");
2658 }
2659
2660 break;
2661 }
2662 case 3:
2663 // BlockAck frames in response to the first DL MU PPDU
2664 isMpdu3corrupted = (mpdu->GetHeader().GetAddr2() == m_dataCorruptedSta);
2666 true,
2667 "MPDU 2 expected to be successfully received");
2669 !isMpdu3corrupted,
2670 "Unexpected reception status for MPDU 3");
2671 // in case of DL MU, if there are at least two links setup, we expect all MPDUs to
2672 // be inflight (on distinct links)
2673 if (m_muTrafficPattern != WifiMuTrafficPattern::UL_MU &&
2674 m_staMacs[0]->GetSetupLinkIds().size() > 1)
2675 {
2676 auto queue = m_apMac->GetTxopQueue(AC_BE);
2677 Ptr<StaWifiMac> rcvMac;
2678 if (m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress() ==
2679 mpdu->GetHeader().GetAddr2())
2680 {
2681 rcvMac = m_staMacs[0];
2682 }
2683 else if (m_staMacs[1]->GetFrameExchangeManager(linkId)->GetAddress() ==
2684 mpdu->GetHeader().GetAddr2())
2685 {
2686 rcvMac = m_staMacs[1];
2687 }
2688 else
2689 {
2690 NS_ABORT_MSG("BlockAck frame not sent by a station in DL scenario");
2691 }
2692 auto item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress());
2693 std::size_t nQueuedPkt = 0;
2694 auto delay = WifiPhy::CalculateTxDuration(psdu,
2695 txVector,
2696 rcvMac->GetWifiPhy(linkId)->GetPhyBand()) +
2697 MicroSeconds(1); // to account for propagation delay
2698
2699 while (item)
2700 {
2701 auto seqNo = item->GetHeader().GetSequenceNumber();
2702 NS_TEST_EXPECT_MSG_EQ(item->IsInFlight(),
2703 true,
2704 "MPDU with seqNo=" << seqNo << " is not in flight");
2705 auto linkIds = item->GetInFlightLinkIds();
2706 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
2707 1,
2708 "MPDU with seqNo=" << seqNo
2709 << " is in flight on multiple links");
2710 // The first two MPDUs are in flight on the same link on which the BlockAck
2711 // is sent. The other two MPDUs (only for AP to STA/STA to AP scenarios) are
2712 // in flight on a different link.
2713 auto srcLinkId = m_apMac->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1());
2714 NS_TEST_ASSERT_MSG_EQ(srcLinkId.has_value(),
2715 true,
2716 "Addr1 of BlockAck is not an originator's link address");
2717 NS_TEST_EXPECT_MSG_EQ((*linkIds.begin() == *srcLinkId),
2718 (seqNo <= 3),
2719 "MPDU with seqNo=" << seqNo
2720 << " in flight on unexpected link");
2721 // check the Retry subfield and whether this MPDU is still queued
2722 // after the originator has processed this BlockAck
2723
2724 // MPDUs acknowledged via this BlockAck are no longer queued
2725 bool isQueued = (seqNo > (isMpdu3corrupted ? 2 : 3));
2726 // The Retry subfield is set if the MPDU has not been acknowledged (i.e., it
2727 // is still queued) and has been transmitted on the same link as the BlockAck
2728 // (i.e., its sequence number is less than or equal to 2)
2729 bool isRetry = isQueued && seqNo <= 3;
2730
2731 Simulator::Schedule(delay, [this, item, isQueued, isRetry]() {
2732 NS_TEST_EXPECT_MSG_EQ(item->IsQueued(),
2733 isQueued,
2734 "MPDU with seqNo="
2735 << item->GetHeader().GetSequenceNumber() << " should "
2736 << (isQueued ? "" : "not") << " be queued");
2738 item->GetHeader().IsRetry(),
2739 isRetry,
2740 "Unexpected value for the Retry subfield of the MPDU with seqNo="
2741 << item->GetHeader().GetSequenceNumber());
2742 });
2743
2744 nQueuedPkt++;
2745 item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress(), item);
2746 }
2747 // Each MPDU contains an A-MSDU consisting of two MSDUs
2748 NS_TEST_EXPECT_MSG_EQ(nQueuedPkt, m_nPackets / 2, "Unexpected number of queued MPDUs");
2749 }
2750 break;
2751 }
2752}
2753
2754void
2756{
2757 switch (m_muTrafficPattern)
2758 {
2759 case WifiMuTrafficPattern::DL_MU_BAR_BA_SEQUENCE:
2760 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2762 break;
2763 case WifiMuTrafficPattern::DL_MU_MU_BAR:
2764 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2766 break;
2767 case WifiMuTrafficPattern::DL_MU_AGGR_MU_BAR:
2768 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2770 break;
2771 default:;
2772 }
2773
2775
2776 // Enable A-MSDU aggregation. Max A-MSDU size is set such that two MSDUs can be aggregated
2777 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
2778 {
2779 mac->SetAttribute("BE_MaxAmsduSize", UintegerValue(1050));
2780 mac->GetQosTxop(AC_BE)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2782 mac->GetQosTxop(AC_BE)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2783
2784 mac->SetAttribute("VI_MaxAmsduSize", UintegerValue(1050));
2785 mac->GetQosTxop(AC_VI)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2787 mac->GetQosTxop(AC_VI)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2788 }
2789
2790 // aggregate MU scheduler
2791 auto muScheduler = CreateObjectWithAttributes<RrMultiUserScheduler>("EnableUlOfdma",
2792 BooleanValue(false),
2793 "EnableBsrp",
2794 BooleanValue(false),
2795 "UlPsduSize",
2796 UintegerValue(2000));
2797 m_apMac->AggregateObject(muScheduler);
2798
2799 // install post reception error model on all devices
2800 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
2801 {
2802 auto errorModel = CreateObject<ListErrorModel>();
2803 m_errorModels[m_apMac->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2804 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2805 }
2806 for (std::size_t i : {0, 1})
2807 {
2808 for (const auto linkId : m_staMacs[i]->GetLinkIds())
2809 {
2810 auto errorModel = CreateObject<ListErrorModel>();
2811 m_errorModels[m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2812 m_staMacs[i]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2813 }
2814 }
2815}
2816
2817void
2819{
2820 if (m_muTrafficPattern < WifiMuTrafficPattern::UL_MU)
2821 {
2822 // DL Traffic
2823 for (uint8_t i = 0; i < m_nStations; i++)
2824 {
2825 PacketSocketAddress sockAddr;
2827 sockAddr.SetPhysicalAddress(m_staMacs[i]->GetDevice()->GetAddress());
2828 sockAddr.SetProtocol(1);
2829
2830 // the first client application generates three packets in order
2831 // to trigger the establishment of a Block Ack agreement
2833 GetApplication(sockAddr, 3, 450, i * MilliSeconds(50)));
2834
2835 // the second client application generates the first half of the selected number
2836 // of packets, which are sent in DL MU PPDUs, and starts after all BA agreements
2837 // are established
2839 GetApplication(sockAddr, m_nPackets / 2, 450, m_nStations * MilliSeconds(50)));
2840
2841 // the third client application generates the second half of the selected number
2842 // of packets, which are sent in DL MU PPDUs, and starts during transmission of
2843 // first A-MPDU, if multiple links are setup
2845 GetApplication(sockAddr,
2846 m_nPackets / 2,
2847 450,
2849 }
2850 }
2851 else
2852 {
2853 // UL Traffic
2854 for (uint8_t i = 0; i < m_nStations; i++)
2855 {
2856 m_sockets[i].SetSingleDevice(m_staMacs[i]->GetDevice()->GetIfIndex());
2857 m_sockets[i].SetPhysicalAddress(m_apMac->GetDevice()->GetAddress());
2858 m_sockets[i].SetProtocol(1);
2859
2860 // the first client application generates three packets in order
2861 // to trigger the establishment of a Block Ack agreement
2862 m_staMacs[i]->GetDevice()->GetNode()->AddApplication(
2863 GetApplication(m_sockets[i], 3, 450, i * MilliSeconds(50), i * 4));
2864
2865 // packets to be included in TB PPDUs are generated (by Transmit()) when
2866 // the first Basic Trigger Frame is sent by the AP
2867 }
2868
2869 // MU scheduler starts requesting channel access when we are done with BA agreements
2871 auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
2872 NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "Expected an aggregated MU scheduler");
2873 muScheduler->SetAttribute("EnableUlOfdma", BooleanValue(true));
2874 muScheduler->SetAccessReqInterval(MilliSeconds(3));
2875 // channel access is requested only once
2876 muScheduler->SetAccessReqInterval(Seconds(0));
2877 });
2878 }
2879
2881}
2882
2883void
2885{
2887
2888 // Expected number of packets received by each node (AP, STA 0, STA 1) at application layer
2889 std::array<std::size_t, 3> expectedRxPkts{};
2890
2891 switch (m_muTrafficPattern)
2892 {
2893 case WifiMuTrafficPattern::DL_MU_BAR_BA_SEQUENCE:
2894 case WifiMuTrafficPattern::DL_MU_MU_BAR:
2895 case WifiMuTrafficPattern::DL_MU_AGGR_MU_BAR:
2896 // both STA 0 and STA 1 receive m_nPackets + 3 (sent to trigger BA establishment) packets
2897 expectedRxPkts[1] = m_nPackets + 3;
2898 expectedRxPkts[2] = m_nPackets + 3;
2899 break;
2900 case WifiMuTrafficPattern::UL_MU:
2901 // AP receives m_nPackets + 3 (sent to trigger BA establishment) packets from each station
2902 expectedRxPkts[0] = 2 * (m_nPackets + 3);
2903 break;
2904 }
2905
2907 +expectedRxPkts[0],
2908 "Unexpected number of packets received by the AP");
2910 +expectedRxPkts[1],
2911 "Unexpected number of packets received by STA 0");
2913 +expectedRxPkts[2],
2914 "Unexpected number of packets received by STA 1");
2915
2916 // check that setting the QosTxop::NMaxInflights attribute has the expected effect.
2917 // For DL, for each station we send 2 MPDUs to trigger BA agreement and m_nPackets / 2 MPDUs
2918 // For UL, each station sends 2 MPDUs to trigger BA agreement and m_nPackets / 2 MPDUs
2920 m_inflightCount.size(),
2921 2 * (2 + m_nPackets / 2),
2922 "Did not collect number of simultaneous transmissions for all data frames");
2923
2924 auto nMaxInflight = std::min(m_nMaxInflight, m_staMacs[0]->GetSetupLinkIds().size());
2925 std::size_t maxCount = 0;
2926 for (const auto& [txSeqNoPair, count] : m_inflightCount)
2927 {
2929 nMaxInflight,
2930 "MPDU with seqNo="
2931 << txSeqNoPair.second
2932 << " transmitted simultaneously more times than allowed");
2933 maxCount = std::max(maxCount, count);
2934 }
2935
2937 maxCount,
2938 nMaxInflight,
2939 "Expected that at least one data frame was transmitted simultaneously a number of "
2940 "times equal to the NMaxInflights attribute");
2941
2943}
2944
2964{
2965 public:
2968
2969 protected:
2970 void DoSetup() override;
2971 void DoRun() override;
2972 void Transmit(Ptr<WifiMac> mac,
2973 uint8_t phyId,
2974 WifiConstPsduMap psduMap,
2975 WifiTxVector txVector,
2976 double txPowerW) override;
2977
2978 private:
2979 void StartTraffic() override;
2980
2982 std::size_t m_nQosDataFrames;
2985};
2986
2989 "Check sequence numbers after CTS timeout",
2990 1,
2991 BaseParams{{"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2992 {"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2993 {}}),
2994 m_nQosDataFrames(0),
2995 m_errorModel(CreateObject<ListErrorModel>()),
2996 m_rtsCorrupted(false)
2997{
2998}
2999
3000void
3002{
3003 // Enable RTS/CTS
3004 Config::SetDefault("ns3::WifiRemoteStationManager::RtsCtsThreshold", StringValue("1000"));
3005
3007
3008 // install post reception error model on all STAs affiliated with non-AP MLD
3009 for (const auto linkId : m_staMacs[0]->GetLinkIds())
3010 {
3011 m_staMacs[0]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel);
3012 }
3013}
3014
3015void
3017{
3019 m_sockAddr.SetPhysicalAddress(m_staMacs[0]->GetAddress());
3021
3022 // install client application generating 4 packets
3024}
3025
3026void
3028 uint8_t phyId,
3029 WifiConstPsduMap psduMap,
3030 WifiTxVector txVector,
3031 double txPowerW)
3032{
3033 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
3034
3035 auto psdu = psduMap.begin()->second;
3036
3037 if (psdu->GetHeader(0).IsRts() && !m_rtsCorrupted)
3038 {
3039 m_errorModel->SetList({psdu->GetPacket()->GetUid()});
3040 m_rtsCorrupted = true;
3041 // generate other packets when the first RTS is transmitted
3043 }
3044 else if (psdu->GetHeader(0).IsQosData())
3045 {
3047
3048 if (m_nQosDataFrames == 2)
3049 {
3050 // generate other packets when the second QoS data frame is transmitted
3052 }
3053 }
3054}
3055
3056void
3058{
3061
3062 NS_TEST_EXPECT_MSG_EQ(m_nQosDataFrames, 3, "Unexpected number of transmitted QoS data frames");
3063
3064 std::size_t count{};
3065
3066 for (const auto& txPsdu : m_txPsdus)
3067 {
3068 auto psdu = txPsdu.psduMap.begin()->second;
3069
3070 if (!psdu->GetHeader(0).IsQosData())
3071 {
3072 continue;
3073 }
3074
3075 NS_TEST_EXPECT_MSG_EQ(psdu->GetNMpdus(), 4, "Unexpected number of MPDUs in A-MPDU");
3076
3077 count++;
3078 uint16_t expectedSeqNo{};
3079
3080 switch (count)
3081 {
3082 case 1:
3083 expectedSeqNo = 4;
3084 break;
3085 case 2:
3086 expectedSeqNo = 0;
3087 break;
3088 case 3:
3089 expectedSeqNo = 8;
3090 break;
3091 }
3092
3093 for (const auto& mpdu : *PeekPointer(psdu))
3094 {
3095 NS_TEST_EXPECT_MSG_EQ(mpdu->GetHeader().GetSequenceNumber(),
3096 expectedSeqNo++,
3097 "Unexpected sequence number");
3098 }
3099 }
3100
3102}
3103
3111{
3112 public:
3114};
3115
3117 : TestSuite("wifi-mlo", UNIT)
3118{
3119 using ParamsTuple = std::tuple<MultiLinkOperationsTestBase::BaseParams, // base config params
3120 std::vector<uint8_t>, // link ID of setup links
3121 uint8_t, // AP negotiation support
3122 std::string, // DL TID-to-Link Mapping
3123 std::string>; // UL TID-to-Link Mapping
3124
3127
3128 for (const auto& [baseParams,
3129 setupLinks,
3130 apNegSupport,
3131 dlTidLinkMapping,
3132 ulTidLinkMapping] :
3133 {// matching channels: setup all links
3134 ParamsTuple({{"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3135 {"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3136 {}},
3137 {0, 1, 2},
3138 0, // AP MLD does not support TID-to-Link Mapping negotiation
3139 "0,1,2,3 0,1,2; 4,5 0,1", // default mapping used instead
3140 "0,1,2,3 1,2; 6,7 0,1" // default mapping used instead
3141 ),
3142 // non-matching channels, matching PHY bands: setup all links
3143 ParamsTuple({{"{108, 0, BAND_5GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3144 {"{36, 0, BAND_5GHZ, 0}", "{120, 0, BAND_5GHZ, 0}", "{5, 0, BAND_6GHZ, 0}"},
3145 {}},
3146 {0, 1, 2},
3147 1, // AP MLD does not support distinct link sets for TIDs
3148 "0,1,2,3 0,1,2; 4,5 0,1", // default mapping used instead
3149 ""),
3150 // non-AP MLD switches band on some links to setup 3 links
3151 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3152 {"{36, 0, BAND_5GHZ, 0}", "{9, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3153 {}},
3154 {0, 1, 2},
3155 3,
3156 "0,1,2,3 0; 4,5,6,7 1,2", // frames of two TIDs are generated
3157 "0,2,3 1,2; 1,4,5,6,7 0" // frames of two TIDs are generated
3158 ),
3159 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3160 // that band, hence only 2 links are setup
3161 ParamsTuple(
3162 {{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{8, 20, BAND_2_4GHZ, 0}"},
3163 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3164 {0}},
3165 {0, 1},
3166 1, // AP MLD does not support distinct link sets for TIDs
3167 "0,1,2,3,4,5,6,7 0",
3168 "0,1,2,3,4,5,6,7 0"),
3169 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3170 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3171 // an AP operating on the same channel; hence 2 links are setup
3172 ParamsTuple(
3173 {{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{8, 20, BAND_2_4GHZ, 0}"},
3174 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3175 {0, 1}},
3176 {0, 1},
3177 3,
3178 "0,1,2,3 1",
3179 "0,1,2,3 1"),
3180 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3181 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3182 // an AP operating on the same channel; the third link of the non-AP MLD cannot
3183 // change PHY band and there is an AP operating on the same band (different channel);
3184 // hence 2 links are setup by switching channel (not band) on the third link
3185 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{60, 0, BAND_5GHZ, 0}"},
3186 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3187 {0, 1, 2}},
3188 {0, 2},
3189 3,
3190 "",
3191 ""),
3192 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3193 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3194 // an AP operating on the same channel; hence one link only is setup
3195 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3196 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3197 {0, 1}},
3198 {2},
3199 3,
3200 "",
3201 ""),
3202 // non-AP MLD has only two STAs and setups two links
3203 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3204 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3205 {}},
3206 {1, 0},
3207 3,
3208 "0,1,2,3 1",
3209 ""),
3210 // single link non-AP STA associates with an AP affiliated with an AP MLD
3211 ParamsTuple({{"{120, 0, BAND_5GHZ, 0}"},
3212 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3213 {}},
3214 {2}, // link ID of AP MLD only (non-AP STA is single link)
3215 3,
3216 "",
3217 ""),
3218 // a STA affiliated with a non-AP MLD associates with a single link AP
3219 ParamsTuple({{"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3220 {"{120, 0, BAND_5GHZ, 0}"},
3221 {}},
3222 {2}, // link ID of non-AP MLD only (AP is single link)
3223 0,
3224 "0,1,2,3 0,1; 4,5,6,7 0,1", // ignored by single link AP
3225 "")})
3226 {
3227 AddTestCase(new MultiLinkSetupTest(baseParams,
3228 WifiScanType::PASSIVE,
3229 setupLinks,
3230 apNegSupport,
3231 dlTidLinkMapping,
3232 ulTidLinkMapping),
3234 AddTestCase(new MultiLinkSetupTest(baseParams,
3235 WifiScanType::ACTIVE,
3236 setupLinks,
3237 apNegSupport,
3238 dlTidLinkMapping,
3239 ulTidLinkMapping),
3241
3242 for (const auto& trafficPattern : {WifiTrafficPattern::STA_TO_STA,
3243 WifiTrafficPattern::STA_TO_AP,
3244 WifiTrafficPattern::AP_TO_STA,
3245 WifiTrafficPattern::AP_TO_BCAST,
3246 WifiTrafficPattern::STA_TO_BCAST})
3247 {
3248 // No Block Ack agreement
3249 AddTestCase(new MultiLinkTxTest(baseParams,
3250 trafficPattern,
3251 WifiBaEnabled::NO,
3252 WifiUseBarAfterMissedBa::NO,
3253 1),
3255 for (const auto& useBarAfterMissedBa :
3256 {WifiUseBarAfterMissedBa::YES, WifiUseBarAfterMissedBa::NO})
3257 {
3258 // Block Ack agreement with nMaxInflight=1
3259 AddTestCase(new MultiLinkTxTest(baseParams,
3260 trafficPattern,
3261 WifiBaEnabled::YES,
3262 useBarAfterMissedBa,
3263 1),
3265 // Block Ack agreement with nMaxInflight=2
3266 AddTestCase(new MultiLinkTxTest(baseParams,
3267 trafficPattern,
3268 WifiBaEnabled::YES,
3269 useBarAfterMissedBa,
3270 2),
3272 }
3273 }
3274
3275 for (const auto& muTrafficPattern : {WifiMuTrafficPattern::DL_MU_BAR_BA_SEQUENCE,
3276 WifiMuTrafficPattern::DL_MU_MU_BAR,
3277 WifiMuTrafficPattern::DL_MU_AGGR_MU_BAR,
3278 WifiMuTrafficPattern::UL_MU})
3279 {
3280 for (const auto& useBarAfterMissedBa :
3281 {WifiUseBarAfterMissedBa::YES, WifiUseBarAfterMissedBa::NO})
3282 {
3283 // Block Ack agreement with nMaxInflight=1
3285 new MultiLinkMuTxTest(baseParams, muTrafficPattern, useBarAfterMissedBa, 1),
3287 // Block Ack agreement with nMaxInflight=2
3289 new MultiLinkMuTxTest(baseParams, muTrafficPattern, useBarAfterMissedBa, 2),
3291 }
3292 }
3293 }
3294
3296}
3297
Test release of sequence numbers upon CTS timeout in multi-link operations.
PacketSocketAddress m_sockAddr
packet socket address
Ptr< ListErrorModel > m_errorModel
error rate model to corrupt first RTS frame
std::size_t m_nQosDataFrames
counter for transmitted QoS data frames
void StartTraffic() override
Start the generation of traffic (needs to be overridden)
bool m_rtsCorrupted
whether the first RTS frame has been corrupted
void DoSetup() override
Implementation to do any local setup required for this TestCase.
~ReleaseSeqNoAfterCtsTimeoutTest() override=default
void Transmit(Ptr< WifiMac > mac, uint8_t phyId, WifiConstPsduMap psduMap, WifiTxVector txVector, double txPowerW) override
Callback invoked when a FEM passes PSDUs to the PHY.
void DoRun() override
Implementation to actually run this TestCase.
a polymophic address class
Definition: address.h:101
uint16_t GetAssociationId(Mac48Address addr, uint8_t linkId) const
const std::map< uint16_t, Mac48Address > & GetStaList(uint8_t linkId) const
Get a const reference to the map of associated stations on the given link.
Ptr< WifiMacQueue > GetTxopQueue(AcIndex ac) const override
Get the wifi MAC queue of the (Qos)Txop associated with the given AC, if such (Qos)Txop is installed,...
Definition: ap-wifi-mac.cc:171
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:62
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:157
Implement the header for management frames of type association and reassociation response.
Definition: mgt-headers.h:334
Implement the header for management frames of type beacon.
Definition: mgt-headers.h:512
Implement the header for management frames of type probe response.
Definition: mgt-headers.h:451
Helper class used to assign positions and mobility models to nodes.
MultiUserScheduler is an abstract base class defining the API that APs supporting at least VHT can us...
holds a vector of ns3::NetDevice pointers
keep track of a set of node pointers.
uint32_t AddApplication(Ptr< Application > application)
Associate an Application to this Node.
Definition: node.cc:169
uint32_t GetId() const
Definition: node.cc:117
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:77
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:571
static void Destroy()
Execute the events scheduled with ScheduleDestroy().
Definition: simulator.cc:142
static Time Now()
Return the current simulation virtual time.
Definition: simulator.cc:208
static void Run()
Run the simulation.
Definition: simulator.cc:178
static void Stop()
Tell the Simulator the calling event should be the last one executed.
Definition: simulator.cc:186
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:1413
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:864
std::optional< Mac48Address > GetMldAddress(const Mac48Address &remoteAddr) const
Definition: wifi-mac.cc:1632
const std::map< uint8_t, std::unique_ptr< LinkEntity > > & GetLinks() const
Definition: wifi-mac.cc:918
uint8_t GetNLinks() const
Get the number of links (can be greater than 1 for 11be devices only).
Definition: wifi-mac.cc:933
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:995
Ptr< WifiPhy > GetWifiPhy(uint8_t linkId=SINGLE_LINK_OP_ID) const
Definition: wifi-mac.cc:1171
virtual std::optional< uint8_t > GetLinkIdByAddress(const Mac48Address &address) const
Get the ID of the link having the given MAC address, if any.
Definition: wifi-mac.cc:961
Ptr< EhtConfiguration > GetEhtConfiguration() const
Definition: wifi-mac.cc:1755
std::optional< std::reference_wrapper< const WifiTidLinkMapping > > GetTidToLinkMapping(Mac48Address mldAddr, WifiDirection dir) const
Get the TID-to-Link Mapping negotiated with the given MLD (if any) for the given direction.
Definition: wifi-mac.cc:1100
Ptr< WifiNetDevice > GetDevice() const
Return the device this PHY is associated with.
Definition: wifi-mac.cc:439
virtual Ptr< WifiMacQueue > GetTxopQueue(AcIndex ac) const
Get the wifi MAC queue of the (Qos)Txop associated with the given AC, if such (Qos)Txop is installed,...
Definition: wifi-mac.cc:545
Ptr< WifiRemoteStationManager > GetWifiRemoteStationManager(uint8_t linkId=0) const
Definition: wifi-mac.cc:906
const std::set< uint8_t > & GetLinkIds() const
Definition: wifi-mac.cc:939
Mac48Address GetAddress() const
Definition: wifi-mac.cc:452
uint32_t GetIfIndex() const override
Address GetAddress() const override
Ptr< Node > GetNode() const override
void SetPcapDataLinkType(SupportedPcapDataLinkTypes dlt)
Set the data link type of PCAP traces to be used.
Definition: wifi-helper.cc:543
void Set(std::string name, const AttributeValue &v)
Definition: wifi-helper.cc:163
@ 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:646
static Time CalculateTxDuration(uint32_t size, const WifiTxVector &txVector, WifiPhyBand band, uint16_t staId=SU_STA_ID)
Definition: wifi-phy.cc:1507
WifiPhyBand GetPhyBand() const
Get the configured Wi-Fi band.
Definition: wifi-phy.cc:1021
const WifiPhyOperatingChannel & GetOperatingChannel() const
Get a const reference to the operating channel.
Definition: wifi-phy.cc:1033
uint8_t GetPrimaryChannelIndex(uint16_t primaryChannelWidth) const
If the operating channel width is a multiple of 20 MHz, return the index of the primary channel of th...
uint16_t GetWidth() const
Return the width of the whole operating channel (in MHz).
WifiPhyBand GetPhyBand() const
Return the PHY band of the operating channel.
uint8_t GetNumber() const
Return the channel number identifying the whole operating channel.
uint16_t GetFrequency() const
Return the center frequency of the operating channel (in MHz).
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:1350
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition: nstime.h:1326
Time MilliSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition: nstime.h:1338
WifiScanType
Scan type (active or passive)
Definition: sta-wifi-mac.h:49
AcIndex QosUtilsMapTidToAc(uint8_t tid)
Maps TID (Traffic ID) to Access classes.
Definition: qos-utils.cc:134
@ AP
Definition: wifi-mac.h:66
@ WIFI_STANDARD_80211be
@ AC_BE
Best Effort.
Definition: qos-utils.h:75
@ AC_VO
Voice.
Definition: qos-utils.h:81
@ AC_VI
Video.
Definition: qos-utils.h:79
@ AC_BK
Background.
Definition: qos-utils.h:77
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:449
std::unordered_map< uint16_t, Ptr< const WifiPsdu > > WifiConstPsduMap
Map of const PSDUs indexed by STA-ID.
Callback< R, Args... > MakeCallback(R(T::*memPtr)(Args...), OBJ objPtr)
Build Callbacks for class method members which take varying numbers of arguments and potentially retu...
Definition: callback.h:704
WifiTidToLinkMappingNegSupport
TID-to-Link Mapping Negotiation Support.
@ WIFI_TID_TO_LINK_MAPPING_ANY_LINK_SET
static constexpr uint8_t SINGLE_LINK_OP_ID
Link ID for single link operations (helps tracking places where correct link ID is to be used to supp...
Definition: wifi-utils.h:192
constexpr FrequencyRange WIFI_SPECTRUM_5_GHZ
Identifier for the frequency range covering the wifi spectrum in the 5 GHz band.
@ WIFI_MAC_CTL_TRIGGER
@ WIFI_MAC_CTL_BACKREQ
@ WIFI_MAC_MGT_BEACON
@ WIFI_MAC_MGT_ACTION
@ WIFI_MAC_MGT_ASSOCIATION_RESPONSE
@ WIFI_MAC_MGT_ASSOCIATION_REQUEST
@ WIFI_MAC_CTL_BACKRESP
@ WIFI_MAC_MGT_PROBE_RESPONSE
@ WIFI_MAC_QOSDATA
WifiDirection
Wifi direction.
Definition: wifi-utils.h:43
bool TidToLinkMappingValidForNegType1(const WifiTidLinkMapping &dlLinkMapping, const WifiTidLinkMapping &ulLinkMapping)
Check if the given TID-to-Link Mappings are valid for a negotiation type of 1.
Definition: wifi-utils.cc:148
std::map< uint8_t, std::set< uint8_t > > WifiTidLinkMapping
TID-indexed map of the link set to which the TID is mapped.
Definition: wifi-utils.h:74
constexpr FrequencyRange WIFI_SPECTRUM_2_4_GHZ
Identifier for the frequency range covering the wifi spectrum in the 2.4 GHz band.
STL namespace.
Function object to compute the hash of a MAC address.
Definition: qos-utils.h:56
uint32_t prev
std::string dir
uint32_t pktSize
packet size used for the simulation (in bytes)
WifiMuTrafficPattern
Tested MU traffic patterns.
WifiTrafficPattern
Tested traffic patterns.
WifiUseBarAfterMissedBa
Whether to send a BlockAckReq after a missed BlockAck.
static WifiMultiLinkOperationsTestSuite g_wifiMultiLinkOperationsTestSuite
the test suite
WifiBaEnabled
Block Ack agreement enabled/disabled.