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
63/**
64 * \ingroup wifi-test
65 * \ingroup tests
66 *
67 * \brief Test the implementation of WifiAssocManager::GetNextAffiliatedAp(), which
68 * searches a given RNR element for APs affiliated to the same AP MLD as the
69 * reporting AP that sent the frame containing the element.
70 */
72{
73 public:
74 /**
75 * Constructor
76 */
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
192/**
193 * \ingroup wifi-test
194 * \ingroup tests
195 *
196 * Test the WifiMac::SwapLinks() method.
197 */
199{
200 /**
201 * Test WifiMac subclass used to access the SwapLinks method.
202 */
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:
229 /**
230 * Run a single test case.
231 *
232 * \param text string identifying the test case
233 * \param nLinks the number of links of the MLD
234 * \param links a set of pairs (from, to) each mapping a current link ID to the
235 * link ID it has to become (i.e., link 'from' becomes link 'to')
236 * \param expected maps each link ID to the id of the PHY that is expected
237 * to operate on that link after the swap
238 */
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
307/**
308 * \ingroup wifi-test
309 * \ingroup tests
310 *
311 * \brief Base class for Multi-Link Operations tests
312 *
313 * Three spectrum channels are created, one for each band (2.4 GHz, 5 GHz and 6 GHz).
314 * Each PHY object is attached to the spectrum channel corresponding to the PHY band
315 * in which it is operating.
316 */
318{
319 public:
320 /**
321 * Configuration parameters common to all subclasses
322 */
324 {
325 std::vector<std::string>
326 staChannels; //!< the strings specifying the operating channels for the non-AP MLD
327 std::vector<std::string>
328 apChannels; //!< the strings specifying the operating channels for the AP MLD
329 std::vector<uint8_t>
330 fixedPhyBands; //!< list of IDs of non-AP MLD PHYs that cannot switch band
331 };
332
333 /**
334 * Constructor
335 *
336 * \param name The name of the new TestCase created
337 * \param nStations the number of stations to create
338 * \param baseParams common configuration parameters
339 */
340 MultiLinkOperationsTestBase(const std::string& name,
341 uint8_t nStations,
342 const BaseParams& baseParams);
343 ~MultiLinkOperationsTestBase() override = default;
344
345 protected:
346 /**
347 * Callback invoked when a FEM passes PSDUs to the PHY.
348 *
349 * \param mac the MAC transmitting the PSDUs
350 * \param phyId the ID of the PHY transmitting the PSDUs
351 * \param psduMap the PSDU map
352 * \param txVector the TX vector
353 * \param txPowerW the tx power in Watts
354 */
355 virtual void Transmit(Ptr<WifiMac> mac,
356 uint8_t phyId,
357 WifiConstPsduMap psduMap,
358 WifiTxVector txVector,
359 double txPowerW);
360
361 /**
362 * Function to trace packets received by the server application
363 * \param nodeId the ID of the node that received the packet
364 * \param p the packet
365 * \param addr the address
366 */
367 virtual void L7Receive(uint8_t nodeId, Ptr<const Packet> p, const Address& addr);
368
369 /**
370 * \param sockAddr the packet socket address identifying local outgoing interface
371 * and remote address
372 * \param count the number of packets to generate
373 * \param pktSize the size of the packets to generate
374 * \param delay the delay with which traffic generation starts
375 * \param priority user priority for generated packets
376 * \return an application generating the given number packets of the given size destined
377 * to the given packet socket address
378 */
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
387 /// PHY band-indexed map of spectrum channels
388 using ChannelMap = std::map<FrequencyRange, Ptr<MultiModelSpectrumChannel>>;
389
390 /**
391 * Uplink or Downlink direction
392 */
394 {
395 DL = 0,
396 UL
397 };
398
399 /**
400 * Check that the Address 1 and Address 2 fields of the given PSDU contain device MAC addresses.
401 *
402 * \param psdu the given PSDU
403 * \param direction indicates direction for management frames (DL or UL)
404 */
406 std::optional<Direction> direction = std::nullopt);
407
408 /// Information about transmitted frames
410 {
411 Time startTx; ///< TX start time
412 WifiConstPsduMap psduMap; ///< transmitted PSDU map
413 WifiTxVector txVector; ///< TXVECTOR
414 uint8_t linkId; ///< link ID
415 uint8_t phyId; ///< ID of the transmitting PHY
416 };
417
418 std::vector<FrameInfo> m_txPsdus; ///< transmitted PSDUs
419 const std::vector<std::string> m_staChannels; ///< strings specifying channels for STA
420 const std::vector<std::string> m_apChannels; ///< strings specifying channels for AP
421 const std::vector<uint8_t> m_fixedPhyBands; ///< links on non-AP MLD with fixed PHY band
422 Ptr<ApWifiMac> m_apMac; ///< AP wifi MAC
423 std::vector<Ptr<StaWifiMac>> m_staMacs; ///< STA wifi MACs
424 uint8_t m_nStations; ///< number of stations to create
425 uint16_t m_lastAid; ///< AID of last associated station
426 Time m_duration{Seconds(1)}; ///< simulation duration
427 std::vector<std::size_t> m_rxPkts; ///< number of packets received at application layer
428 ///< by each node (index is node ID)
429
430 private:
431 /**
432 * Reset the given PHY helper, use the given strings to set the ChannelSettings
433 * attribute of the PHY objects to create, and attach them to the given spectrum
434 * channels appropriately.
435 *
436 * \param helper the given PHY helper
437 * \param channels the strings specifying the operating channels to configure
438 * \param channelMap the created spectrum channels
439 */
441 const std::vector<std::string>& channels,
442 const ChannelMap& channelMap);
443
444 /**
445 * Set the SSID on the next station that needs to start the association procedure.
446 * This method is connected to the ApWifiMac's AssociatedSta trace source.
447 * Start generating traffic (if needed) when all stations are associated.
448 *
449 * \param aid the AID assigned to the previous associated STA
450 */
451 void SetSsid(uint16_t aid, Mac48Address /* addr */);
452
453 /**
454 * Start the generation of traffic (needs to be overridden)
455 */
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
783/**
784 * \ingroup wifi-test
785 * \ingroup tests
786 *
787 * \brief Multi-Link Discovery & Setup test.
788 *
789 * This test sets up an AP MLD and a non-AP MLD having a variable number of links.
790 * The RF channels to set each link to are provided as input parameters through the test
791 * case constructor, along with the identifiers (starting at 0) of the links that cannot
792 * switch PHY band (if any). The links that are expected to be setup are also provided as input
793 * parameters. This test verifies that the management frames exchanged during ML discovery
794 * and ML setup contain the expected values and that the two MLDs setup the expected links.
795 *
796 * The negotiated TID-to-link mapping is tested by verifying that generated QoS data frames of
797 * a given TID are transmitted on links which the TID is mapped to. Specifically, the following
798 * operations are performed separately for each direction (downlink and uplink). A first TID
799 * is searched such that it is not mapped on all the setup links. If no such TID is found, only
800 * QoS frames of TID 0 are generated. Otherwise, we also search for a second TID that is mapped
801 * to a link set that is disjoint with the link set to which the first TID is mapped. If such a
802 * TID is found, QoS frames of both the first TID and the second TID are generated; otherwise,
803 * only QoS frames of the first TID are generated. For each TID, a number of QoS frames equal
804 * to the number of setup links is generated. For each TID, we check that the first N QoS frames,
805 * where N is the size of the link set to which the TID is mapped, are transmitted concurrently,
806 * while the following QoS frames are sent after the first QoS frame sent on the same link. We
807 * also check that all the QoS frames are sent on a link belonging to the link set to which the
808 * TID is mapped. If QoS frames of two TIDs are generated, we also check that the first N QoS
809 * frames of a TID, where N is the size of the link set to which that TID is mapped, are sent
810 * concurrently with the first M QoS frames of the other TID, where M is the size of the link
811 * set to which the other TID is mapped.
812 */
814{
815 public:
816 /**
817 * Constructor
818 *
819 * \param baseParams common configuration parameters
820 * \param scanType the scan type (active or passive)
821 * \param setupLinks a list of links that are expected to be setup. In case one of the two
822 * devices has a single link, the ID of the link on the MLD is indicated
823 * \param apNegSupport TID-to-Link Mapping negotiation supported by the AP MLD (0, 1, or 3)
824 * \param dlTidToLinkMapping DL TID-to-Link Mapping for EHT configuration of non-AP MLD
825 * \param ulTidToLinkMapping UL TID-to-Link Mapping for EHT configuration of non-AP MLD
826 */
827 MultiLinkSetupTest(const BaseParams& baseParams,
828 WifiScanType scanType,
829 const std::vector<uint8_t>& setupLinks,
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
842 /**
843 * Check correctness of Multi-Link Setup procedure.
844 */
845 void CheckMlSetup();
846
847 /**
848 * Check that links that are not setup on the non-AP MLD are disabled.
849 */
850 void CheckDisabledLinks();
851
852 /**
853 * Check correctness of the given Beacon frame.
854 *
855 * \param mpdu the given Beacon frame
856 * \param linkId the ID of the link on which the Beacon frame was transmitted
857 */
858 void CheckBeacon(Ptr<WifiMpdu> mpdu, uint8_t linkId);
859
860 /**
861 * Check correctness of the given Probe Response frame.
862 *
863 * \param mpdu the given Probe Response frame
864 * \param linkId the ID of the link on which the Probe Response frame was transmitted
865 */
866 void CheckProbeResponse(Ptr<WifiMpdu> mpdu, uint8_t linkId);
867
868 /**
869 * Check correctness of the given Association Request frame.
870 *
871 * \param mpdu the given Association Request frame
872 * \param linkId the ID of the link on which the Association Request frame was transmitted
873 */
874 void CheckAssocRequest(Ptr<WifiMpdu> mpdu, uint8_t linkId);
875
876 /**
877 * Check correctness of the given Association Response frame.
878 *
879 * \param mpdu the given Association Response frame
880 * \param linkId the ID of the link on which the Association Response frame was transmitted
881 */
882 void CheckAssocResponse(Ptr<WifiMpdu> mpdu, uint8_t linkId);
883
884 /**
885 * Check that QoS data frames are sent on links their TID is mapped to.
886 *
887 * \param mpdu the given QoS data frame
888 * \param linkId the ID of the link on which the QoS data frame was transmitted
889 * \param index index of the QoS data frame in the vector of transmitted PSDUs
890 */
891 void CheckQosData(Ptr<WifiMpdu> mpdu, uint8_t linkId, std::size_t index);
892
893 const std::vector<uint8_t> m_setupLinks; //!< IDs of the expected links to setup
894 WifiScanType m_scanType; //!< the scan type (active or passive)
895 std::size_t m_nProbeResp; //!< number of Probe Responses received by the non-AP MLD
897 m_apNegSupport; //!< TID-to-Link Mapping negotiation supported by the AP MLD
898 std::string m_dlTidLinkMappingStr; //!< DL TID-to-Link Mapping for non-AP MLD EHT configuration
899 std::string m_ulTidLinkMappingStr; //!< UL TID-to-Link Mapping for non-AP MLD EHT configuration
900 WifiTidLinkMapping m_dlTidLinkMapping; //!< expected DL TID-to-Link Mapping requested by non-AP
901 //!< MLD and accepted by AP MLD
902 WifiTidLinkMapping m_ulTidLinkMapping; //!< expected UL TID-to-Link Mapping requested by non-AP
903 //!< MLD and accepted by AP MLD
904 uint8_t m_dlTid1; //!< the TID of the first set of DL QoS data frames
905 uint8_t m_ulTid1; //!< the TID of the first set of UL QoS data frames
906 std::optional<uint8_t> m_dlTid2; //!< the TID of the optional set of DL QoS data frames
907 std::optional<uint8_t> m_ulTid2; //!< the TID of the optional set of UL QoS data frames
908 std::vector<std::size_t>
909 m_qosFrames1; //!< indices of QoS frames of the first set in the vector of TX PSDUs
910 std::vector<std::size_t>
911 m_qosFrames2; //!< indices of QoS frames of the optional set in the vector of TX PSDUs
912};
913
915 WifiScanType scanType,
916 const std::vector<uint8_t>& setupLinks,
918 const std::string& dlTidToLinkMapping,
919 const std::string& ulTidToLinkMapping)
920 : MultiLinkOperationsTestBase("Check correctness of Multi-Link Setup", 1, baseParams),
921 m_setupLinks(setupLinks),
922 m_scanType(scanType),
923 m_nProbeResp(0),
924 m_apNegSupport(apNegSupport),
925 m_dlTidLinkMappingStr(dlTidToLinkMapping),
926 m_ulTidLinkMappingStr(ulTidToLinkMapping)
927{
928}
929
930void
932{
934
935 m_staMacs[0]->SetAttribute("ActiveProbing", BooleanValue(m_scanType == WifiScanType::ACTIVE));
936 m_apMac->GetEhtConfiguration()->SetAttribute("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",
942 EnumValue(WifiTidToLinkMappingNegSupport::ANY_LINK_SET));
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 == WifiTidToLinkMappingNegSupport::NOT_SUPPORTED ||
954 (m_apNegSupport == WifiTidToLinkMappingNegSupport::SAME_LINK_SET &&
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
1078 /**
1079 * Check content of management frames
1080 */
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 ||
1347 m_apNegSupport == WifiTidToLinkMappingNegSupport::NOT_SUPPORTED)
1348 {
1349 NS_TEST_EXPECT_MSG_EQ(tlm.empty(),
1350 true,
1351 "Didn't expect a TID-to-Link Mapping IE in Assoc Request frame");
1352 }
1353 else
1354 {
1355 std::size_t expectedNTlm = (m_dlTidLinkMapping == m_ulTidLinkMapping ? 1 : 2);
1356
1357 NS_TEST_ASSERT_MSG_EQ(tlm.size(),
1358 expectedNTlm,
1359 "Unexpected number of TID-to-Link Mapping IE in Assoc Request");
1360
1361 // lambda to check content of TID-to-Link Mapping IE(s)
1362 auto checkTlm = [&](std::size_t tlmId, WifiDirection dir) {
1363 NS_TEST_EXPECT_MSG_EQ(+static_cast<uint8_t>(tlm[tlmId].m_control.direction),
1364 +static_cast<uint8_t>(dir),
1365 "Unexpected direction in TID-to-Link Mapping IE " << tlmId);
1366 auto& expectedMapping =
1367 (dir == WifiDirection::UPLINK ? m_ulTidLinkMapping : m_dlTidLinkMapping);
1368
1369 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].m_control.defaultMapping,
1370 expectedMapping.empty(),
1371 "Default Link Mapping bit not set correctly");
1372 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].m_linkMapping.size(),
1373 expectedMapping.size(),
1374 "Unexpected number of Link Mapping Of TID n fields");
1375 for (uint8_t tid = 0; tid < 8; tid++)
1376 {
1377 if (auto it = expectedMapping.find(tid); it != expectedMapping.cend())
1378 {
1379 NS_TEST_EXPECT_MSG_EQ((tlm[tlmId].GetLinkMappingOfTid(tid) == it->second),
1380 true,
1381 "Unexpected link mapping for TID "
1382 << +tid << " direction " << dir);
1383 }
1384 else
1385 {
1386 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].GetLinkMappingOfTid(tid).empty(),
1387 true,
1388 "Expecting no Link Mapping Of TID n field for TID "
1389 << +tid << " direction " << dir);
1390 }
1391 }
1392 };
1393
1394 if (tlm.size() == 1)
1395 {
1396 checkTlm(0, WifiDirection::BOTH_DIRECTIONS);
1397 }
1398 else
1399 {
1400 std::size_t dlId = (tlm[0].m_control.direction == WifiDirection::DOWNLINK ? 0 : 1);
1401 std::size_t ulId = (dlId == 0 ? 1 : 0);
1402
1403 checkTlm(dlId, WifiDirection::DOWNLINK);
1404 checkTlm(ulId, WifiDirection::UPLINK);
1405 }
1406 }
1407}
1408
1409void
1411{
1412 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_ASSOCIATION_RESPONSE);
1413
1414 CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::DL);
1415
1417 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1418 mpdu->GetHeader().GetAddr2(),
1419 "TA of Assoc Response frame is not the address of the link it is transmitted on");
1421 mpdu->GetPacket()->PeekHeader(assoc);
1422 const auto& mle = assoc.Get<MultiLinkElement>();
1423
1424 if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetNLinks() == 1)
1425 {
1427 mle.has_value(),
1428 false,
1429 "Multi-Link Element in Assoc Response frame with single link AP or single link STA");
1430 return;
1431 }
1432
1433 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Assoc Request frame");
1434 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1436 "Incorrect MLD Address advertised in Multi-Link Element");
1437 NS_TEST_EXPECT_MSG_EQ(mle->GetNPerStaProfileSubelements(),
1438 m_setupLinks.size() - 1,
1439 "Incorrect number of Per-STA Profile subelements in Multi-Link Element");
1440 for (std::size_t i = 0; i < mle->GetNPerStaProfileSubelements(); i++)
1441 {
1442 auto& perStaProfile = mle->GetPerStaProfile(i);
1443 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasStaMacAddress(),
1444 true,
1445 "Per-STA Profile must contain STA MAC address");
1446 // find ID of the local link corresponding to this subelement
1447 auto apLinkId = m_apMac->GetLinkIdByAddress(perStaProfile.GetStaMacAddress());
1449 apLinkId.has_value(),
1450 true,
1451 "No link found with the STA MAC address advertised in Per-STA Profile");
1452 NS_TEST_EXPECT_MSG_EQ(+apLinkId.value(),
1453 +perStaProfile.GetLinkId(),
1454 "Link ID and MAC address advertised in Per-STA Profile do not match");
1456 +apLinkId.value(),
1457 +linkId,
1458 "The AP that sent the Assoc Response should not be included in a Per-STA Profile");
1459 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), apLinkId.value());
1460 NS_TEST_EXPECT_MSG_EQ((it != m_setupLinks.end()),
1461 true,
1462 "Not expecting to setup AP link ID " << +apLinkId.value());
1463 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasAssocResponse(),
1464 true,
1465 "Missing Association Response in Per-STA Profile");
1466 }
1467
1468 // For the moment, the AP MLD always accepts a valid TID-to-Link Mapping request, hence
1469 // in every case there is no TID-to-Link Mapping IE in the Association Response
1470 NS_TEST_EXPECT_MSG_EQ(assoc.Get<TidToLinkMapping>().empty(),
1471 true,
1472 "Didn't expect to find a TID-to-Link Mapping IE in Association Response");
1473}
1474
1475void
1477{
1478 /**
1479 * Check outcome of Multi-Link Setup
1480 */
1481 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->IsAssociated(), true, "Expected the STA to be associated");
1482
1483 for (const auto linkId : m_setupLinks)
1484 {
1485 auto staLinkId = (m_staMacs[0]->GetNLinks() > 1 ? linkId : SINGLE_LINK_OP_ID);
1486 auto apLinkId = (m_apMac->GetNLinks() > 1 ? linkId : SINGLE_LINK_OP_ID);
1487
1488 auto staAddr = m_staMacs[0]->GetFrameExchangeManager(staLinkId)->GetAddress();
1489 auto apAddr = m_apMac->GetFrameExchangeManager(apLinkId)->GetAddress();
1490
1491 auto staRemoteMgr = m_staMacs[0]->GetWifiRemoteStationManager(staLinkId);
1492 auto apRemoteMgr = m_apMac->GetWifiRemoteStationManager(apLinkId);
1493
1494 // STA side
1495 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetFrameExchangeManager(staLinkId)->GetBssid(),
1496 apAddr,
1497 "Unexpected BSSID for STA link ID " << +staLinkId);
1498 if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1499 {
1500 NS_TEST_EXPECT_MSG_EQ((staRemoteMgr->GetMldAddress(apAddr) == m_apMac->GetAddress()),
1501 true,
1502 "Incorrect MLD address stored by STA on link ID " << +staLinkId);
1504 (staRemoteMgr->GetAffiliatedStaAddress(m_apMac->GetAddress()) == apAddr),
1505 true,
1506 "Incorrect affiliated address stored by STA on link ID " << +staLinkId);
1507 }
1508
1509 // AP side
1510 NS_TEST_EXPECT_MSG_EQ(apRemoteMgr->IsAssociated(staAddr),
1511 true,
1512 "Expecting STA " << staAddr << " to be associated on link "
1513 << +apLinkId);
1514 if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1515 {
1517 (apRemoteMgr->GetMldAddress(staAddr) == m_staMacs[0]->GetAddress()),
1518 true,
1519 "Incorrect MLD address stored by AP on link ID " << +apLinkId);
1521 (apRemoteMgr->GetAffiliatedStaAddress(m_staMacs[0]->GetAddress()) == staAddr),
1522 true,
1523 "Incorrect affiliated address stored by AP on link ID " << +apLinkId);
1524 }
1525 auto aid = m_apMac->GetAssociationId(staAddr, apLinkId);
1526 const auto& staList = m_apMac->GetStaList(apLinkId);
1527 NS_TEST_EXPECT_MSG_EQ((staList.find(aid) != staList.end()),
1528 true,
1529 "STA " << staAddr << " not found in list of associated STAs");
1530
1531 // STA of non-AP MLD operate on the same channel as the AP
1533 +m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetNumber(),
1535 "Incorrect operating channel number for STA on link " << +staLinkId);
1537 m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetFrequency(),
1539 "Incorrect operating channel frequency for STA on link " << +staLinkId);
1540 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetWidth(),
1542 "Incorrect operating channel width for STA on link " << +staLinkId);
1544 +m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetPhyBand(),
1546 "Incorrect operating PHY band for STA on link " << +staLinkId);
1548 +m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetPrimaryChannelIndex(20),
1550 "Incorrect operating primary channel index for STA on link " << +staLinkId);
1551 }
1552
1553 // lambda to check the link mapping stored at wifi MAC
1554 auto checkStoredMapping =
1555 [this](Ptr<WifiMac> mac, Ptr<WifiMac> dest, WifiDirection dir, bool present) {
1556 NS_TEST_ASSERT_MSG_EQ(mac->GetTidToLinkMapping(dest->GetAddress(), dir).has_value(),
1557 present,
1558 "Link mapping stored by "
1559 << (mac->GetTypeOfStation() == AP ? "AP" : "non-AP")
1560 << " MLD for " << dir << " direction "
1561 << (present ? "expected" : "not expected"));
1562 if (present)
1563 {
1564 const auto& mapping =
1565 (dir == WifiDirection::DOWNLINK ? m_dlTidLinkMapping : m_ulTidLinkMapping);
1567 (mac->GetTidToLinkMapping(dest->GetAddress(), dir)->get() == mapping),
1568 true,
1569 "Incorrect link mapping stored by "
1570 << (mac->GetTypeOfStation() == AP ? "AP" : "non-AP") << " MLD for " << dir
1571 << " direction");
1572 }
1573 };
1574
1575 auto storedMapping = m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1 &&
1576 m_apNegSupport > WifiTidToLinkMappingNegSupport::NOT_SUPPORTED;
1577 checkStoredMapping(m_apMac, m_staMacs[0], WifiDirection::DOWNLINK, storedMapping);
1578 checkStoredMapping(m_apMac, m_staMacs[0], WifiDirection::UPLINK, storedMapping);
1579 checkStoredMapping(m_staMacs[0], m_apMac, WifiDirection::DOWNLINK, storedMapping);
1580 checkStoredMapping(m_staMacs[0], m_apMac, WifiDirection::UPLINK, storedMapping);
1581}
1582
1583void
1585{
1586 if (m_staMacs[0]->GetNLinks() == 1)
1587 {
1588 // no link is disabled on a single link device
1589 return;
1590 }
1591
1592 for (const auto& linkId : m_staMacs[0]->GetLinkIds())
1593 {
1594 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), linkId);
1595 if (it == m_setupLinks.end())
1596 {
1597 // the link has not been setup
1598 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId)->GetState()->IsStateOff(),
1599 true,
1600 "Link " << +linkId << " has not been setup but is not disabled");
1601 continue;
1602 }
1603
1604 // the link has been setup and must be active
1605 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId)->GetState()->IsStateOff(),
1606 false,
1607 "Expecting link " << +linkId << " to be active");
1608 }
1609}
1610
1611void
1612MultiLinkSetupTest::CheckQosData(Ptr<WifiMpdu> mpdu, uint8_t linkId, std::size_t index)
1613{
1615 const auto& hdr = mpdu->GetHeader();
1616
1617 NS_TEST_ASSERT_MSG_EQ(hdr.IsQosData(), true, "Expected a QoS data frame");
1618
1619 if (!hdr.IsToDs() && hdr.IsFromDs())
1620 {
1621 dir = WifiDirection::DOWNLINK;
1622 }
1623 else if (hdr.IsToDs() && !hdr.IsFromDs())
1624 {
1625 dir = WifiDirection::UPLINK;
1626 }
1627 else
1628 {
1629 NS_ABORT_MSG("Invalid combination for QoS data frame: ToDS(" << hdr.IsToDs() << ") FromDS("
1630 << hdr.IsFromDs() << ")");
1631 }
1632
1633 const auto& tid1 = (dir == WifiDirection::DOWNLINK) ? m_dlTid1 : m_ulTid1;
1634 const auto& tid2 = (dir == WifiDirection::DOWNLINK) ? m_dlTid2 : m_ulTid2;
1635 uint8_t tid = hdr.GetQosTid();
1636
1637 NS_TEST_ASSERT_MSG_NE((tid == tid1),
1638 (tid2.has_value() && tid == *tid2),
1639 "QoS frame with unexpected TID " << +tid);
1640
1641 // lambda to find the link set the given TID is mapped to
1642 auto findLinkSet = [this, dir](uint8_t tid) -> std::set<uint8_t> {
1643 std::set<uint8_t> linkSet(m_setupLinks.cbegin(), m_setupLinks.cend());
1644 if (auto mappingOptRef = m_apMac->GetTidToLinkMapping(m_staMacs[0]->GetAddress(), dir))
1645 {
1646 // if the TID is not present in the mapping, it is mapped to all setup links
1647 if (auto it = mappingOptRef->get().find(tid); it != mappingOptRef->get().cend())
1648 {
1649 linkSet = it->second;
1650 NS_ASSERT_MSG(!linkSet.empty(), "TID " << +tid << " mapped to no link");
1651 }
1652 }
1653 return linkSet;
1654 };
1655
1656 auto linkSet = findLinkSet(tid);
1657 auto& qosFrames = (tid == tid1) ? m_qosFrames1 : m_qosFrames2;
1658
1659 // Let N the size of the link set, the first N QoS data frames are sent simultaneously
1660 // on the links of the set, the others (if any) will be sent afterwards on such links
1661
1662 // number of concurrent frames of the same TID transmitted so far (excluding current frame)
1663 std::size_t nConcurFrames = std::min(qosFrames.size(), linkSet.size());
1664
1665 // iterate over the concurrent frames of the same TID transmitted so far
1666 for (std::size_t i = 0; i < nConcurFrames; i++)
1667 {
1668 auto prev = qosFrames[i];
1669
1670 // TX duration of i-th frame
1671 auto band = m_apMac->GetWifiPhy(m_txPsdus[prev].linkId)->GetPhyBand();
1672 Time txDuration =
1673 WifiPhy::CalculateTxDuration(m_txPsdus[prev].psduMap, m_txPsdus[prev].txVector, band);
1674
1675 // the current frame is transmitted concurrently with this previous frame if it is
1676 // within the first N (size of the link set) frames, otherwise it is transmitted after
1677 // this previous frame if they have been transmitted on the same link
1678 if (qosFrames.size() < linkSet.size())
1679 {
1680 // the current frame can be sent concurrently with this previous frame
1681 NS_TEST_EXPECT_MSG_LT(m_txPsdus[index].startTx,
1682 m_txPsdus[prev].startTx + txDuration,
1683 "The " << dir << " QoS frame number " << qosFrames.size()
1684 << " was not sent concurrently with others on link "
1685 << +linkId << " which TID " << +tid << " is mapped to");
1686 }
1687 else if (m_txPsdus[prev].linkId == linkId)
1688 {
1689 // the current frame is sent afterwards
1690 NS_TEST_EXPECT_MSG_GT(m_txPsdus[index].startTx,
1691 m_txPsdus[prev].startTx + txDuration,
1692 "The " << dir << " QoS frame number " << qosFrames.size()
1693 << " was sent concurrently with others on a link "
1694 << +linkId << " which TID " << +tid << " is mapped to");
1695 }
1696 }
1697
1698 if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1699 {
1700 NS_TEST_EXPECT_MSG_EQ(std::count(linkSet.cbegin(), linkSet.cend(), linkId),
1701 1,
1702 "QoS frame sent on Link ID "
1703 << +linkId << " that does not belong to the link set of TID "
1704 << +tid);
1705 }
1706
1707 if (tid2)
1708 {
1709 // QoS frames of two distinct TIDs are sent.
1710 auto otherTid = (tid == tid1) ? *tid2 : tid1;
1711 const auto& otherQosFrames = (tid == tid1) ? m_qosFrames2 : m_qosFrames1;
1712 auto otherLinkSet = findLinkSet(otherTid);
1713
1714 // number of concurrent frames of the other TID transmitted so far
1715 std::size_t nOtherConcurFrames = std::min(otherQosFrames.size(), otherLinkSet.size());
1716
1717 // iterate over the concurrent frames of the other TID
1718 for (std::size_t i = 0; i < nOtherConcurFrames; i++)
1719 {
1720 auto prev = otherQosFrames[i];
1721
1722 // TX duration of i-th frame
1723 auto band = m_apMac->GetWifiPhy(m_txPsdus[prev].linkId)->GetPhyBand();
1724 Time txDuration = WifiPhy::CalculateTxDuration(m_txPsdus[prev].psduMap,
1725 m_txPsdus[prev].txVector,
1726 band);
1727
1728 // the current frame is transmitted concurrently with this previous frame of the
1729 // other TID if it is within the first N (size of the link set) frames of its TID
1730 if (qosFrames.size() < linkSet.size())
1731 {
1732 // the current frame can be sent concurrently with this previous frame
1733 NS_TEST_EXPECT_MSG_LT(m_txPsdus[index].startTx,
1734 m_txPsdus[prev].startTx + txDuration,
1735 "The " << dir << " QoS frame number " << qosFrames.size()
1736 << " was not sent concurrently with others with TID "
1737 << +otherTid);
1738 }
1739 }
1740 }
1741
1742 // insert the frame
1743 qosFrames.emplace_back(index);
1744
1745 if (qosFrames.size() == m_setupLinks.size())
1746 {
1747 qosFrames.clear();
1748 }
1749}
1750
1751/**
1752 * Tested traffic patterns.
1753 */
1754enum class WifiTrafficPattern : uint8_t
1755{
1756 STA_TO_STA = 0,
1757 STA_TO_AP,
1758 AP_TO_STA,
1761};
1762
1763/**
1764 * Block Ack agreement enabled/disabled
1765 */
1766enum class WifiBaEnabled : uint8_t
1767{
1768 NO = 0,
1769 YES
1770};
1771
1772/**
1773 * Whether to send a BlockAckReq after a missed BlockAck
1774 */
1775enum class WifiUseBarAfterMissedBa : uint8_t
1776{
1777 NO = 0,
1778 YES
1779};
1780
1781/**
1782 * \ingroup wifi-test
1783 * \ingroup tests
1784 *
1785 * \brief Test data transmission between two MLDs.
1786 *
1787 * This test sets up an AP MLD and two non-AP MLDs having a variable number of links.
1788 * The RF channels to set each link to are provided as input parameters through the test
1789 * case constructor, along with the identifiers (starting at 0) of the links that cannot
1790 * switch PHY band (if any). This test aims at veryfing the successful transmission of both
1791 * unicast QoS data frames (from one station to another, from one station to the AP, from
1792 * the AP to the station) and broadcast QoS data frames (from the AP or from one station).
1793 * In the scenarios in which the AP forwards frames (i.e., from one station to another and
1794 * from one station to broadcast) the client application generates only 4 packets, in order
1795 * to limit the probability of collisions. In the other scenarios, 8 packets are generated.
1796 * When BlockAck agreements are enabled, the maximum A-MSDU size is set such that two
1797 * packets can be aggregated in an A-MSDU. The MPDU with sequence number equal to 1 is
1798 * corrupted (once, by using a post reception error model) to test its successful
1799 * re-transmission, unless the traffic scenario is from the AP to broadcast (broadcast frames
1800 * are not retransmitted) or is a scenario where the AP forwards frame (to limit the
1801 * probability of collisions).
1802 *
1803 * When BlockAck agreements are enabled, we also corrupt a BlockAck frame, so as to simulate
1804 * the case of BlockAck timeout. Both the case where a BlockAckReq is sent and the case where
1805 * data frame are retransmitted are tested. Finally, when BlockAck agreements are enabled, we
1806 * also enable the concurrent transmission of data frames over two links and check that at
1807 * least one MPDU is concurrently transmitted over two links.
1808 */
1810{
1811 public:
1812 /**
1813 * Constructor
1814 *
1815 * \param baseParams common configuration parameters
1816 * \param trafficPattern the pattern of traffic to generate
1817 * \param baEnabled whether BA agreement is enabled or disabled
1818 * \param useBarAfterMissedBa whether a BAR or Data frames are sent after missed BlockAck
1819 * \param nMaxInflight the max number of links on which an MPDU can be simultaneously inflight
1820 * (unused if Block Ack agreements are not established)
1821 */
1822 MultiLinkTxTest(const BaseParams& baseParams,
1823 WifiTrafficPattern trafficPattern,
1824 WifiBaEnabled baEnabled,
1825 WifiUseBarAfterMissedBa useBarAfterMissedBa,
1826 uint8_t nMaxInflight);
1827 ~MultiLinkTxTest() override = default;
1828
1829 protected:
1830 /**
1831 * Check the content of a received BlockAck frame when the max number of links on which
1832 * an MPDU can be inflight is one.
1833 *
1834 * \param psdu the PSDU containing the BlockAck
1835 * \param txVector the TXVECTOR used to transmit the BlockAck
1836 * \param linkId the ID of the link on which the BlockAck was transmitted
1837 */
1838 void CheckBlockAck(Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId);
1839
1840 void Transmit(Ptr<WifiMac> mac,
1841 uint8_t phyId,
1842 WifiConstPsduMap psduMap,
1843 WifiTxVector txVector,
1844 double txPowerW) override;
1845 void DoSetup() override;
1846 void DoRun() override;
1847
1848 private:
1849 void StartTraffic() override;
1850
1851 /// Receiver address-indexed map of list error models
1852 using RxErrorModelMap = std::unordered_map<Mac48Address, Ptr<ListErrorModel>, WifiAddressHash>;
1853
1854 RxErrorModelMap m_errorModels; ///< error rate models to corrupt packets
1855 std::list<uint64_t> m_uidList; ///< list of UIDs of packets to corrupt
1856 bool m_dataCorrupted{false}; ///< whether second data frame has been already corrupted
1857 WifiTrafficPattern m_trafficPattern; ///< the pattern of traffic to generate
1858 bool m_baEnabled; ///< whether BA agreement is enabled or disabled
1859 bool m_useBarAfterMissedBa; ///< whether to send BAR after missed BlockAck
1860 std::size_t m_nMaxInflight; ///< max number of links on which an MPDU can be inflight
1861 std::size_t m_nPackets; ///< number of application packets to generate
1862 std::size_t m_blockAckCount{0}; ///< transmitted BlockAck counter
1863 std::size_t m_blockAckReqCount{0}; ///< transmitted BlockAckReq counter
1864 std::map<uint16_t, std::size_t> m_inflightCount; ///< seqNo-indexed max number of simultaneous
1865 ///< transmissions of a data frame
1866 Ptr<WifiMac> m_sourceMac; ///< MAC of the node sending application packets
1867};
1868
1870 WifiTrafficPattern trafficPattern,
1871 WifiBaEnabled baEnabled,
1872 WifiUseBarAfterMissedBa useBarAfterMissedBa,
1873 uint8_t nMaxInflight)
1875 std::string("Check data transmission between MLDs ") +
1876 (baEnabled == WifiBaEnabled::YES
1877 ? (useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES
1878 ? "with BA agreement, send BAR after BlockAck timeout"
1879 : "with BA agreement, send Data frames after BlockAck timeout")
1880 : "without BA agreement") +
1881 " (Traffic pattern: " + std::to_string(static_cast<uint8_t>(trafficPattern)) +
1882 (baEnabled == WifiBaEnabled::YES ? ", nMaxInflight=" + std::to_string(nMaxInflight)
1883 : "") +
1884 ")",
1885 2,
1886 baseParams),
1887 m_trafficPattern(trafficPattern),
1888 m_baEnabled(baEnabled == WifiBaEnabled::YES),
1889 m_useBarAfterMissedBa(useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES),
1890 m_nMaxInflight(nMaxInflight),
1891 m_nPackets(trafficPattern == WifiTrafficPattern::STA_TO_BCAST ||
1892 trafficPattern == WifiTrafficPattern::STA_TO_STA
1893 ? 4
1894 : 8)
1895{
1896}
1897
1898void
1900 uint8_t phyId,
1901 WifiConstPsduMap psduMap,
1902 WifiTxVector txVector,
1903 double txPowerW)
1904{
1905 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
1906 auto linkId = m_txPsdus.back().linkId;
1907
1908 auto psdu = psduMap.begin()->second;
1909
1910 switch (psdu->GetHeader(0).GetType())
1911 {
1913 // a management frame is a DL frame if TA equals BSSID
1914 CheckAddresses(psdu,
1915 psdu->GetHeader(0).GetAddr2() == psdu->GetHeader(0).GetAddr3() ? DL : UL);
1916 if (!m_baEnabled)
1917 {
1918 // corrupt all management action frames (ADDBA Request frames) to prevent
1919 // the establishment of a BA agreement
1920 m_uidList.push_front(psdu->GetPacket()->GetUid());
1921 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1922 NS_LOG_INFO("CORRUPTED");
1923 }
1924 break;
1925 case WIFI_MAC_QOSDATA:
1926 CheckAddresses(psdu);
1927
1928 for (const auto& mpdu : *psdu)
1929 {
1930 // determine the max number of simultaneous transmissions for this MPDU
1931 // (only if sent by the traffic source and this is not a broadcast frame)
1932 if (m_baEnabled && m_sourceMac->GetLinkIds().count(linkId) == 1 &&
1933 m_sourceMac->GetFrameExchangeManager(linkId)->GetAddress() ==
1934 mpdu->GetHeader().GetAddr2() &&
1935 !mpdu->GetHeader().GetAddr1().IsGroup())
1936 {
1937 auto seqNo = mpdu->GetHeader().GetSequenceNumber();
1938 auto [it, success] =
1939 m_inflightCount.insert({seqNo, mpdu->GetInFlightLinkIds().size()});
1940 if (!success)
1941 {
1942 it->second = std::max(it->second, mpdu->GetInFlightLinkIds().size());
1943 }
1944 }
1945 }
1946 for (std::size_t i = 0; i < psdu->GetNMpdus(); i++)
1947 {
1948 // corrupt QoS data frame with sequence number equal to 1 (only once) if we are
1949 // not in the AP to broadcast traffic pattern (broadcast frames are not retransmitted)
1950 // nor in the STA to broadcast or STA to STA traffic patterns (retransmissions from
1951 // STA 1 could collide with frames forwarded by the AP)
1952 if (psdu->GetHeader(i).GetSequenceNumber() != 1 ||
1953 m_trafficPattern == WifiTrafficPattern::AP_TO_BCAST ||
1954 m_trafficPattern == WifiTrafficPattern::STA_TO_BCAST ||
1955 m_trafficPattern == WifiTrafficPattern::STA_TO_STA)
1956 {
1957 continue;
1958 }
1959 auto uid = psdu->GetPayload(i)->GetUid();
1960 if (!m_dataCorrupted)
1961 {
1962 m_uidList.push_front(uid);
1963 m_dataCorrupted = true;
1964 NS_LOG_INFO("CORRUPTED");
1965 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1966 }
1967 else
1968 {
1969 // do not corrupt the QoS data frame anymore
1970 if (auto it = std::find(m_uidList.cbegin(), m_uidList.cend(), uid);
1971 it != m_uidList.cend())
1972 {
1973 m_uidList.erase(it);
1974 }
1975 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1976 }
1977 break;
1978 }
1979 break;
1980 case WIFI_MAC_CTL_BACKRESP: {
1981 // ignore BlockAck frames not addressed to the source of the application packets
1982 if (!m_sourceMac->GetLinkIdByAddress(psdu->GetHeader(0).GetAddr1()))
1983 {
1984 break;
1985 }
1986 if (m_nMaxInflight > 1)
1987 {
1988 // we do not check the content of BlockAck when m_nMaxInflight is greater than 1
1989 break;
1990 }
1991 CheckBlockAck(psdu, txVector, linkId);
1993 if (m_blockAckCount == 2)
1994 {
1995 // corrupt the second BlockAck frame to simulate a missed BlockAck
1996 m_uidList.push_front(psdu->GetPacket()->GetUid());
1997 NS_LOG_INFO("CORRUPTED");
1998 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1999 }
2000 break;
2002 // ignore BlockAckReq frames not transmitted by the source of the application packets
2003 if (m_sourceMac->GetLinkIdByAddress(psdu->GetHeader(0).GetAddr2()))
2004 {
2006 }
2007 break;
2008 }
2009 default:;
2010 }
2011}
2012
2013void
2015 const WifiTxVector& txVector,
2016 uint8_t linkId)
2017{
2018 NS_TEST_ASSERT_MSG_EQ(m_baEnabled, true, "No BlockAck expected without BA agreement");
2019 NS_TEST_ASSERT_MSG_EQ((m_trafficPattern != WifiTrafficPattern::AP_TO_BCAST),
2020 true,
2021 "No BlockAck expected in AP to broadcast traffic pattern");
2022
2023 /*
2024 * ┌───────┬───────X ┌───────┐
2025 * link 0 │ 0 │ 1 │ │ 1 │
2026 * ───────┴───────┴───────┴┬──┬────┴───────┴┬───┬────────────────────────
2027 * │BA│ │ACK│
2028 * └──┘ └───┘
2029 * ┌───────┬───────┐ ┌───────┬───────┐
2030 * link 1 │ 2 │ 3 │ │ 2 │ 3 │
2031 * ────────────────────┴───────┴───────┴┬──X───┴───────┴───────┴┬──┬─────
2032 * │BA│ │BA│
2033 * └──┘ └──┘
2034 */
2035 auto mpdu = *psdu->begin();
2036 CtrlBAckResponseHeader blockAck;
2037 mpdu->GetPacket()->PeekHeader(blockAck);
2038 bool isMpdu1corrupted = (m_trafficPattern == WifiTrafficPattern::STA_TO_AP ||
2039 m_trafficPattern == WifiTrafficPattern::AP_TO_STA);
2040
2041 switch (m_blockAckCount)
2042 {
2043 case 0: // first BlockAck frame (all traffic patterns)
2045 true,
2046 "MPDU 0 expected to be successfully received");
2048 blockAck.IsPacketReceived(1),
2049 !isMpdu1corrupted,
2050 "MPDU 1 expected to be received only in STA_TO_STA/STA_TO_BCAST scenarios");
2051 // if there are at least two links setup, we expect all MPDUs to be inflight
2052 // (on distinct links)
2053 if (m_staMacs[0]->GetSetupLinkIds().size() > 1)
2054 {
2055 auto queue = m_sourceMac->GetTxopQueue(AC_BE);
2056 auto rcvMac = m_sourceMac == m_staMacs[0] ? StaticCast<WifiMac>(m_apMac)
2057 : StaticCast<WifiMac>(m_staMacs[1]);
2058 auto item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress());
2059 std::size_t nQueuedPkt = 0;
2060 auto delay = WifiPhy::CalculateTxDuration(psdu,
2061 txVector,
2062 rcvMac->GetWifiPhy(linkId)->GetPhyBand()) +
2063 MicroSeconds(1); // to account for propagation delay
2064
2065 while (item)
2066 {
2067 auto seqNo = item->GetHeader().GetSequenceNumber();
2068 NS_TEST_EXPECT_MSG_EQ(item->IsInFlight(),
2069 true,
2070 "MPDU with seqNo=" << seqNo << " is not in flight");
2071 auto linkIds = item->GetInFlightLinkIds();
2072 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
2073 1,
2074 "MPDU with seqNo=" << seqNo
2075 << " is in flight on multiple links");
2076 // The first two MPDUs are in flight on the same link on which the BlockAck
2077 // is sent. The other two MPDUs (only for AP to STA/STA to AP scenarios) are
2078 // in flight on a different link.
2079 auto srcLinkId = m_sourceMac->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1());
2080 NS_TEST_ASSERT_MSG_EQ(srcLinkId.has_value(),
2081 true,
2082 "Addr1 of BlockAck is not an originator's link address");
2083 NS_TEST_EXPECT_MSG_EQ((*linkIds.begin() == *srcLinkId),
2084 (seqNo <= 1),
2085 "MPDU with seqNo=" << seqNo
2086 << " in flight on unexpected link");
2087 // check the Retry subfield and whether this MPDU is still queued
2088 // after the originator has processed this BlockAck
2089
2090 // MPDUs acknowledged via this BlockAck are no longer queued
2091 bool isQueued = (seqNo > (isMpdu1corrupted ? 0 : 1));
2092 // The Retry subfield is set if the MPDU has not been acknowledged (i.e., it
2093 // is still queued) and has been transmitted on the same link as the BlockAck
2094 // (i.e., its sequence number is less than or equal to 1)
2095 bool isRetry = isQueued && seqNo <= 1;
2096
2097 Simulator::Schedule(delay, [this, item, isQueued, isRetry]() {
2098 NS_TEST_EXPECT_MSG_EQ(item->IsQueued(),
2099 isQueued,
2100 "MPDU with seqNo="
2101 << item->GetHeader().GetSequenceNumber() << " should "
2102 << (isQueued ? "" : "not") << " be queued");
2104 item->GetHeader().IsRetry(),
2105 isRetry,
2106 "Unexpected value for the Retry subfield of the MPDU with seqNo="
2107 << item->GetHeader().GetSequenceNumber());
2108 });
2109
2110 nQueuedPkt++;
2111 item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress(), item);
2112 }
2113 // Each MPDU contains an A-MSDU consisting of two MSDUs
2114 NS_TEST_EXPECT_MSG_EQ(nQueuedPkt, m_nPackets / 2, "Unexpected number of queued MPDUs");
2115 }
2116 break;
2117 case 1: // second BlockAck frame (STA to AP and AP to STA traffic patterns only)
2118 case 2: // third BlockAck frame (STA to AP and AP to STA traffic patterns only)
2119 NS_TEST_EXPECT_MSG_EQ((m_trafficPattern == WifiTrafficPattern::AP_TO_STA ||
2120 m_trafficPattern == WifiTrafficPattern::STA_TO_AP),
2121 true,
2122 "Did not expect to receive a second BlockAck");
2123 // the second BlockAck is corrupted, but the data frames have been received successfully
2124 std::pair<uint16_t, uint16_t> seqNos;
2125 // if multiple links were setup, the transmission of the second A-MPDU started before
2126 // the end of the first one, so the second A-MPDU includes MPDUs with sequence numbers
2127 // 2 and 3. Otherwise, MPDU with sequence number 1 is retransmitted along with the MPDU
2128 // with sequence number 2.
2129 if (m_staMacs[0]->GetSetupLinkIds().size() > 1)
2130 {
2131 seqNos = {2, 3};
2132 }
2133 else
2134 {
2135 seqNos = {1, 2};
2136 }
2137 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(seqNos.first),
2138 true,
2139 "MPDU " << seqNos.first << " expected to be successfully received");
2140 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(seqNos.second),
2141 true,
2142 "MPDU " << seqNos.second << " expected to be successfully received");
2143 break;
2144 }
2145}
2146
2147void
2149{
2151
2152 if (m_baEnabled)
2153 {
2154 // Enable A-MSDU aggregation. Max A-MSDU size is set such that two MSDUs can be aggregated
2155 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
2156 {
2157 mac->SetAttribute("BE_MaxAmsduSize", UintegerValue(2100));
2158 mac->GetQosTxop(AC_BE)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2160 mac->GetQosTxop(AC_BE)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2161 }
2162 }
2163
2164 // install post reception error model on all devices
2165 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
2166 {
2167 auto errorModel = CreateObject<ListErrorModel>();
2168 m_errorModels[m_apMac->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2169 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2170 }
2171 for (std::size_t i : {0, 1})
2172 {
2173 for (const auto linkId : m_staMacs[i]->GetLinkIds())
2174 {
2175 auto errorModel = CreateObject<ListErrorModel>();
2176 m_errorModels[m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2177 m_staMacs[i]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2178 }
2179 }
2180}
2181
2182void
2184{
2185 Address destAddr;
2186
2187 switch (m_trafficPattern)
2188 {
2189 case WifiTrafficPattern::STA_TO_STA:
2191 destAddr = m_staMacs[1]->GetDevice()->GetAddress();
2192 break;
2193 case WifiTrafficPattern::STA_TO_AP:
2195 destAddr = m_apMac->GetDevice()->GetAddress();
2196 break;
2197 case WifiTrafficPattern::AP_TO_STA:
2199 destAddr = m_staMacs[1]->GetDevice()->GetAddress();
2200 break;
2201 case WifiTrafficPattern::AP_TO_BCAST:
2203 destAddr = Mac48Address::GetBroadcast();
2204 break;
2205 case WifiTrafficPattern::STA_TO_BCAST:
2207 destAddr = Mac48Address::GetBroadcast();
2208 break;
2209 }
2210
2211 PacketSocketAddress sockAddr;
2213 sockAddr.SetPhysicalAddress(destAddr);
2214 sockAddr.SetProtocol(1);
2215
2216 // install first client application generating at most 4 packets
2218 GetApplication(sockAddr, std::min<std::size_t>(m_nPackets, 4), 1000));
2219
2220 if (m_nPackets > 4)
2221 {
2222 // install a second client application generating the remaining packets and
2223 // starting during transmission of first A-MPDU, if multiple links are setup
2225 GetApplication(sockAddr, m_nPackets - 4, 1000, MilliSeconds(4)));
2226 }
2227
2229}
2230
2231void
2233{
2235
2236 // Expected number of packets received by each node (AP, STA 0, STA 1) at application layer
2237 std::array<std::size_t, 3> expectedRxPkts{};
2238
2239 switch (m_trafficPattern)
2240 {
2241 case WifiTrafficPattern::STA_TO_STA:
2242 case WifiTrafficPattern::AP_TO_STA:
2243 // only STA 1 receives the m_nPackets packets that have been transmitted
2244 expectedRxPkts[2] = m_nPackets;
2245 break;
2246 case WifiTrafficPattern::STA_TO_AP:
2247 // only the AP receives the m_nPackets packets that have been transmitted
2248 expectedRxPkts[0] = m_nPackets;
2249 break;
2250 case WifiTrafficPattern::AP_TO_BCAST:
2251 // the AP replicates the broadcast frames on all the links, hence each station
2252 // receives the m_nPackets packets N times, where N is the number of setup link
2253 expectedRxPkts[1] = m_nPackets * m_staMacs[0]->GetSetupLinkIds().size();
2254 expectedRxPkts[2] = m_nPackets * m_staMacs[1]->GetSetupLinkIds().size();
2255 break;
2256 case WifiTrafficPattern::STA_TO_BCAST:
2257 // the AP receives the m_nPackets packets and then replicates them on all the links,
2258 // hence STA 1 receives m_nPackets packets N times, where N is the number of setup link
2259 expectedRxPkts[0] = m_nPackets;
2260 expectedRxPkts[2] = m_nPackets * m_staMacs[1]->GetSetupLinkIds().size();
2261 break;
2262 }
2263
2265 +expectedRxPkts[0],
2266 "Unexpected number of packets received by the AP");
2268 +expectedRxPkts[1],
2269 "Unexpected number of packets received by STA 0");
2271 +expectedRxPkts[2],
2272 "Unexpected number of packets received by STA 1");
2273
2274 // check that the expected number of BlockAck frames are transmitted
2275 if (m_baEnabled && m_nMaxInflight == 1)
2276 {
2277 std::size_t expectedBaCount = 0;
2278 std::size_t expectedBarCount = 0;
2279
2280 switch (m_trafficPattern)
2281 {
2282 case WifiTrafficPattern::STA_TO_AP:
2283 case WifiTrafficPattern::AP_TO_STA:
2284 // two A-MPDUs are transmitted and one BlockAck is corrupted
2285 expectedBaCount = 3;
2286 // one BlockAckReq is sent if m_useBarAfterMissedBa is true
2287 expectedBarCount = m_useBarAfterMissedBa ? 1 : 0;
2288 break;
2289 case WifiTrafficPattern::STA_TO_STA:
2290 case WifiTrafficPattern::STA_TO_BCAST:
2291 // only one A-MPDU is transmitted and the BlockAck is not corrupted
2292 expectedBaCount = 1;
2293 break;
2294 default:;
2295 }
2297 expectedBaCount,
2298 "Unexpected number of BlockAck frames");
2300 expectedBarCount,
2301 "Unexpected number of BlockAckReq frames");
2302 }
2303
2304 // check that setting the QosTxop::NMaxInflights attribute has the expected effect.
2305 // We do not support sending an MPDU multiple times concurrently without Block Ack
2306 // agreement. Also, broadcast frames are already duplicated and sent on all links.
2307 if (m_baEnabled && m_trafficPattern != WifiTrafficPattern::AP_TO_BCAST)
2308 {
2310 m_inflightCount.size(),
2311 m_nPackets / 2,
2312 "Did not collect number of simultaneous transmissions for all data frames");
2313
2314 auto nMaxInflight = std::min(m_nMaxInflight, m_staMacs[0]->GetSetupLinkIds().size());
2315 std::size_t maxCount = 0;
2316 for (const auto& [seqNo, count] : m_inflightCount)
2317 {
2319 count,
2320 nMaxInflight,
2321 "MPDU with seqNo=" << seqNo
2322 << " transmitted simultaneously more times than allowed");
2323 maxCount = std::max(maxCount, count);
2324 }
2325
2327 maxCount,
2328 nMaxInflight,
2329 "Expected that at least one data frame was transmitted simultaneously a number of "
2330 "times equal to the NMaxInflights attribute");
2331 }
2332
2334}
2335
2336/**
2337 * Tested MU traffic patterns.
2338 */
2339enum class WifiMuTrafficPattern : uint8_t
2340{
2344 UL_MU
2345};
2346
2347/**
2348 * \ingroup wifi-test
2349 * \ingroup tests
2350 *
2351 * \brief Test data transmission between MLDs using OFDMA MU transmissions
2352 *
2353 * This test sets up an AP MLD and two non-AP MLDs having a variable number of links.
2354 * The RF channels to set each link to are provided as input parameters through the test
2355 * case constructor, along with the identifiers (starting at 0) of the links that cannot
2356 * switch PHY band (if any). This test aims at veryfing the successful transmission of both
2357 * DL MU and UL MU frames. In the DL MU scenarios, the client applications installed on the
2358 * AP generate 8 packets addressed to each of the stations (plus 3 packets to trigger the
2359 * establishment of BlockAck agreements). In the UL MU scenario, client applications
2360 * installed on the stations generate 4 packets each (plus 3 packets to trigger the
2361 * establishment of BlockAck agreements).
2362 *
2363 * The maximum A-MSDU size is set such that two packets can be aggregated in an A-MSDU.
2364 * The MPDU with sequence number equal to 3 is corrupted (by using a post reception error
2365 * model) once and for a single station, to test its successful re-transmission.
2366 *
2367 * Also, we enable the concurrent transmission of data frames over two links and check that at
2368 * least one MPDU is concurrently transmitted over two links.
2369 */
2371{
2372 public:
2373 /**
2374 * Constructor
2375 *
2376 * \param baseParams common configuration parameters
2377 * \param muTrafficPattern the pattern of traffic to generate
2378 * \param useBarAfterMissedBa whether a BAR or Data frames are sent after missed BlockAck
2379 * \param nMaxInflight the max number of links on which an MPDU can be simultaneously inflight
2380 * (unused if Block Ack agreements are not established)
2381 */
2382 MultiLinkMuTxTest(const BaseParams& baseParams,
2383 WifiMuTrafficPattern muTrafficPattern,
2384 WifiUseBarAfterMissedBa useBarAfterMissedBa,
2385 uint8_t nMaxInflight);
2386 ~MultiLinkMuTxTest() override = default;
2387
2388 protected:
2389 /**
2390 * Check the content of a received BlockAck frame when the max number of links on which
2391 * an MPDU can be inflight is one.
2392 *
2393 * \param psdu the PSDU containing the BlockAck
2394 * \param txVector the TXVECTOR used to transmit the BlockAck
2395 * \param linkId the ID of the link on which the BlockAck was transmitted
2396 */
2397 void CheckBlockAck(Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId);
2398
2399 void Transmit(Ptr<WifiMac> mac,
2400 uint8_t phyId,
2401 WifiConstPsduMap psduMap,
2402 WifiTxVector txVector,
2403 double txPowerW) override;
2404 void DoSetup() override;
2405 void DoRun() override;
2406
2407 private:
2408 void StartTraffic() override;
2409
2410 /// Receiver address-indexed map of list error models
2411 using RxErrorModelMap = std::unordered_map<Mac48Address, Ptr<ListErrorModel>, WifiAddressHash>;
2412
2413 /// A pair of a MAC address (the address of the receiver for DL frames and the address of
2414 /// the sender for UL frames) and a sequence number identifying a transmitted QoS data frame
2415 using AddrSeqNoPair = std::pair<Mac48Address, uint16_t>;
2416
2417 RxErrorModelMap m_errorModels; ///< error rate models to corrupt packets
2418 std::list<uint64_t> m_uidList; ///< list of UIDs of packets to corrupt
2419 std::optional<Mac48Address> m_dataCorruptedSta; ///< MAC address of the station that received
2420 ///< MPDU with SeqNo=2 corrupted
2421 bool m_waitFirstTf{true}; ///< whether we are waiting for the first Basic Trigger Frame
2422 WifiMuTrafficPattern m_muTrafficPattern; ///< the pattern of traffic to generate
2423 bool m_useBarAfterMissedBa; ///< whether to send BAR after missed BlockAck
2424 std::size_t m_nMaxInflight; ///< max number of links on which an MPDU can be inflight
2425 std::vector<PacketSocketAddress> m_sockets; ///< packet socket addresses for STAs
2426 std::size_t m_nPackets; ///< number of application packets to generate
2427 std::size_t m_blockAckCount{0}; ///< transmitted BlockAck counter
2428 std::size_t m_tfCount{0}; ///< transmitted Trigger Frame counter
2429 // std::size_t m_blockAckReqCount{0}; ///< transmitted BlockAckReq counter
2430 std::map<AddrSeqNoPair, std::size_t> m_inflightCount; ///< max number of simultaneous
2431 ///< transmissions of each data frame
2432 Ptr<WifiMac> m_sourceMac; ///< MAC of the node sending application packets
2433};
2434
2436 WifiMuTrafficPattern muTrafficPattern,
2437 WifiUseBarAfterMissedBa useBarAfterMissedBa,
2438 uint8_t nMaxInflight)
2440 std::string("Check MU data transmission between MLDs ") +
2441 (useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES
2442 ? "(send BAR after BlockAck timeout,"
2443 : "(send Data frames after BlockAck timeout,") +
2444 " MU Traffic pattern: " + std::to_string(static_cast<uint8_t>(muTrafficPattern)) +
2445 ", nMaxInflight=" + std::to_string(nMaxInflight) + ")",
2446 2,
2447 baseParams),
2448 m_muTrafficPattern(muTrafficPattern),
2449 m_useBarAfterMissedBa(useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES),
2450 m_nMaxInflight(nMaxInflight),
2451 m_sockets(m_nStations),
2452 m_nPackets(muTrafficPattern == WifiMuTrafficPattern::UL_MU ? 4 : 8)
2453{
2454}
2455
2456void
2458 uint8_t phyId,
2459 WifiConstPsduMap psduMap,
2460 WifiTxVector txVector,
2461 double txPowerW)
2462{
2463 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2464 auto linkId = m_txPsdus.back().linkId;
2465
2466 CtrlTriggerHeader trigger;
2467
2468 for (const auto& [staId, psdu] : psduMap)
2469 {
2470 switch (psdu->GetHeader(0).GetType())
2471 {
2472 case WIFI_MAC_QOSDATA:
2473 CheckAddresses(psdu);
2474 if (psdu->GetHeader(0).HasData())
2475 {
2476 bool isDl = psdu->GetHeader(0).IsFromDs();
2477 auto linkAddress =
2478 isDl ? psdu->GetHeader(0).GetAddr1() : psdu->GetHeader(0).GetAddr2();
2479 auto address = m_apMac->GetMldAddress(linkAddress).value_or(linkAddress);
2480
2481 for (const auto& mpdu : *psdu)
2482 {
2483 // determine the max number of simultaneous transmissions for this MPDU
2484 auto seqNo = mpdu->GetHeader().GetSequenceNumber();
2485 auto [it, success] = m_inflightCount.insert(
2486 {{address, seqNo}, mpdu->GetInFlightLinkIds().size()});
2487 if (!success)
2488 {
2489 it->second = std::max(it->second, mpdu->GetInFlightLinkIds().size());
2490 }
2491 }
2492 for (std::size_t i = 0; i < psdu->GetNMpdus(); i++)
2493 {
2494 // MPDUs with seqNo=2 are always transmitted in an MU PPDU
2495 if (psdu->GetHeader(i).GetSequenceNumber() == 2)
2496 {
2497 if (m_muTrafficPattern == WifiMuTrafficPattern::UL_MU)
2498 {
2499 NS_TEST_EXPECT_MSG_EQ(txVector.IsUlMu(),
2500 true,
2501 "MPDU " << **std::next(psdu->begin(), i)
2502 << " not transmitted in a TB PPDU");
2503 }
2504 else
2505 {
2506 NS_TEST_EXPECT_MSG_EQ(txVector.GetHeMuUserInfoMap().size(),
2507 2,
2508 "MPDU " << **std::next(psdu->begin(), i)
2509 << " not transmitted in a DL MU PPDU");
2510 }
2511 }
2512 // corrupt QoS data frame with sequence number equal to 3 (only once)
2513 if (psdu->GetHeader(i).GetSequenceNumber() != 3)
2514 {
2515 continue;
2516 }
2517 auto uid = psdu->GetPayload(i)->GetUid();
2518 if (!m_dataCorruptedSta)
2519 {
2520 m_uidList.push_front(uid);
2521 m_dataCorruptedSta = isDl ? psdu->GetAddr1() : psdu->GetAddr2();
2522 NS_LOG_INFO("CORRUPTED");
2523 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2524 }
2525 else if ((isDl && m_dataCorruptedSta == psdu->GetAddr1()) ||
2526 (!isDl && m_dataCorruptedSta == psdu->GetAddr2()))
2527 {
2528 // do not corrupt the QoS data frame anymore
2529 if (auto it = std::find(m_uidList.cbegin(), m_uidList.cend(), uid);
2530 it != m_uidList.cend())
2531 {
2532 m_uidList.erase(it);
2533 }
2534 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2535 }
2536 break;
2537 }
2538 }
2539 break;
2541 if (m_nMaxInflight > 1)
2542 {
2543 // we do not check the content of BlockAck when m_nMaxInflight is greater than 1
2544 break;
2545 }
2546 CheckBlockAck(psdu, txVector, linkId);
2548 // to simulate a missed BlockAck, corrupt the fifth BlockAck frame (the first
2549 // two BlockAck frames are sent to acknowledge the QoS data frames that triggered
2550 // the establishment of Block Ack agreements)
2551 if (m_blockAckCount == 5)
2552 {
2553 // corrupt the third BlockAck frame to simulate a missed BlockAck
2554 m_uidList.push_front(psdu->GetPacket()->GetUid());
2555 NS_LOG_INFO("CORRUPTED");
2556 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2557 }
2558 break;
2560 psdu->GetPayload(0)->PeekHeader(trigger);
2561 // the MU scheduler requests channel access on all links but we have to perform the
2562 // following actions only once (hence why we only consider TF transmitted on link 0)
2563 if (trigger.IsBasic() && m_waitFirstTf)
2564 {
2565 m_waitFirstTf = false;
2566 // the AP is starting the transmission of the Basic Trigger frame, so generate
2567 // the configured number of packets at STAs, which are sent in TB PPDUs, when
2568 // transmission of the Trigger Frame ends
2569 auto band = mac->GetWifiPhy(linkId)->GetPhyBand();
2570 Time txDuration = WifiPhy::CalculateTxDuration(psduMap, txVector, band);
2571 for (uint8_t i = 0; i < m_nStations; i++)
2572 {
2573 m_staMacs[i]->GetDevice()->GetNode()->AddApplication(
2574 GetApplication(m_sockets[i], m_nPackets, 450, txDuration, i * 4));
2575 }
2576 }
2577 if (++m_tfCount == m_staMacs[0]->GetSetupLinkIds().size())
2578 {
2579 // a TF has been sent on all the setup links, we can now disable UL OFDMA
2580 auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
2581 NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "Expected an aggregated MU scheduler");
2582 muScheduler->SetAttribute("EnableUlOfdma", BooleanValue(false));
2583 }
2584 break;
2585 default:;
2586 }
2587 }
2588}
2589
2590void
2592 const WifiTxVector& txVector,
2593 uint8_t linkId)
2594{
2595 /*
2596 * Example sequence with DL_MU_BAR_BA_SEQUENCE
2597 * ┌───────┬───────X
2598 * (To:1) │ 2 │ 3 │
2599 * ├───────┼───────┤ ┌───┐ ┌───────┐
2600 * [link 0] (To:0) │ 2 │ 3 │ │BAR│ (To:1) │ 3 │
2601 * ────────────────┴───────┴───────┴┬──┼───┼──┬──────────┴───────┴┬───┬────────
2602 * │BA│ │BA│ │ACK│
2603 * └──┘ └──┘ └───┘
2604 * ┌───────┬───────┐
2605 * (To:1) │ 4 │ 5 │
2606 * ├───────┼───────┤ ┌───┐ ┌───┐
2607 * [link 1] (To:0) │ 4 │ 5 │ │BAR│ │BAR│
2608 * ────────────────────────────┴───────┴───────┴┬──X────┴───┴┬──┼───┼──┬───────
2609 * │BA│ │BA│ │BA│
2610 * └──┘ └──┘ └──┘
2611 *
2612 * Example sequence with UL_MU
2613 *
2614 * ┌──┐ ┌────┐ ┌───┐
2615 * [link 0] │TF│ │M-BA│ │ACK│
2616 * ─────────┴──┴──┬───────┬───────┬──┴────┴────────────┬───────┬─┴───┴─────────
2617 * (From:0) │ 2 │ 3 │ (From:1) │ 3 │
2618 * ├───────┼───────┤ └───────┘
2619 * (From:1) │ 2 │ 3 │
2620 * └───────┴───────X
2621 * ┌──┐
2622 * [link 1] │TF│
2623 * ─────────┴──┴──┬───────────────┬────────────────────────────────────────────
2624 * (From:0) │ QoS Null │
2625 * ├───────────────┤
2626 * (From:1) │ QoS Null │
2627 * └───────────────┘
2628 */
2629 auto mpdu = *psdu->begin();
2630 CtrlBAckResponseHeader blockAck;
2631 mpdu->GetPacket()->PeekHeader(blockAck);
2632 bool isMpdu3corrupted;
2633
2634 switch (m_blockAckCount)
2635 {
2636 case 0:
2637 case 1: // Ignore the first two BlockAck frames that acknowledged frames sent to establish BA
2638 break;
2639 case 2:
2640 if (m_muTrafficPattern == WifiMuTrafficPattern::UL_MU)
2641 {
2642 NS_TEST_EXPECT_MSG_EQ(blockAck.IsMultiSta(), true, "Expected a Multi-STA BlockAck");
2643 for (uint8_t i = 0; i < m_nStations; i++)
2644 {
2645 auto indices = blockAck.FindPerAidTidInfoWithAid(m_staMacs[i]->GetAssociationId());
2646 NS_TEST_ASSERT_MSG_EQ(indices.size(), 1, "Expected one Per AID TID Info per STA");
2647 auto index = indices.front();
2649 true,
2650 "Expected that a QoS data frame was corrupted");
2651 isMpdu3corrupted =
2652 m_staMacs[i]->GetLinkIdByAddress(*m_dataCorruptedSta).has_value();
2653 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(2, index),
2654 true,
2655 "MPDU 2 expected to be successfully received");
2656 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(3, index),
2657 !isMpdu3corrupted,
2658 "Unexpected reception status for MPDU 3");
2659 }
2660
2661 break;
2662 }
2663 case 3:
2664 // BlockAck frames in response to the first DL MU PPDU
2665 isMpdu3corrupted = (mpdu->GetHeader().GetAddr2() == m_dataCorruptedSta);
2667 true,
2668 "MPDU 2 expected to be successfully received");
2670 !isMpdu3corrupted,
2671 "Unexpected reception status for MPDU 3");
2672 // in case of DL MU, if there are at least two links setup, we expect all MPDUs to
2673 // be inflight (on distinct links)
2674 if (m_muTrafficPattern != WifiMuTrafficPattern::UL_MU &&
2675 m_staMacs[0]->GetSetupLinkIds().size() > 1)
2676 {
2677 auto queue = m_apMac->GetTxopQueue(AC_BE);
2678 Ptr<StaWifiMac> rcvMac;
2679 if (m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress() ==
2680 mpdu->GetHeader().GetAddr2())
2681 {
2682 rcvMac = m_staMacs[0];
2683 }
2684 else if (m_staMacs[1]->GetFrameExchangeManager(linkId)->GetAddress() ==
2685 mpdu->GetHeader().GetAddr2())
2686 {
2687 rcvMac = m_staMacs[1];
2688 }
2689 else
2690 {
2691 NS_ABORT_MSG("BlockAck frame not sent by a station in DL scenario");
2692 }
2693 auto item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress());
2694 std::size_t nQueuedPkt = 0;
2695 auto delay = WifiPhy::CalculateTxDuration(psdu,
2696 txVector,
2697 rcvMac->GetWifiPhy(linkId)->GetPhyBand()) +
2698 MicroSeconds(1); // to account for propagation delay
2699
2700 while (item)
2701 {
2702 auto seqNo = item->GetHeader().GetSequenceNumber();
2703 NS_TEST_EXPECT_MSG_EQ(item->IsInFlight(),
2704 true,
2705 "MPDU with seqNo=" << seqNo << " is not in flight");
2706 auto linkIds = item->GetInFlightLinkIds();
2707 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
2708 1,
2709 "MPDU with seqNo=" << seqNo
2710 << " is in flight on multiple links");
2711 // The first two MPDUs are in flight on the same link on which the BlockAck
2712 // is sent. The other two MPDUs (only for AP to STA/STA to AP scenarios) are
2713 // in flight on a different link.
2714 auto srcLinkId = m_apMac->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1());
2715 NS_TEST_ASSERT_MSG_EQ(srcLinkId.has_value(),
2716 true,
2717 "Addr1 of BlockAck is not an originator's link address");
2718 NS_TEST_EXPECT_MSG_EQ((*linkIds.begin() == *srcLinkId),
2719 (seqNo <= 3),
2720 "MPDU with seqNo=" << seqNo
2721 << " in flight on unexpected link");
2722 // check the Retry subfield and whether this MPDU is still queued
2723 // after the originator has processed this BlockAck
2724
2725 // MPDUs acknowledged via this BlockAck are no longer queued
2726 bool isQueued = (seqNo > (isMpdu3corrupted ? 2 : 3));
2727 // The Retry subfield is set if the MPDU has not been acknowledged (i.e., it
2728 // is still queued) and has been transmitted on the same link as the BlockAck
2729 // (i.e., its sequence number is less than or equal to 2)
2730 bool isRetry = isQueued && seqNo <= 3;
2731
2732 Simulator::Schedule(delay, [this, item, isQueued, isRetry]() {
2733 NS_TEST_EXPECT_MSG_EQ(item->IsQueued(),
2734 isQueued,
2735 "MPDU with seqNo="
2736 << item->GetHeader().GetSequenceNumber() << " should "
2737 << (isQueued ? "" : "not") << " be queued");
2739 item->GetHeader().IsRetry(),
2740 isRetry,
2741 "Unexpected value for the Retry subfield of the MPDU with seqNo="
2742 << item->GetHeader().GetSequenceNumber());
2743 });
2744
2745 nQueuedPkt++;
2746 item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress(), item);
2747 }
2748 // Each MPDU contains an A-MSDU consisting of two MSDUs
2749 NS_TEST_EXPECT_MSG_EQ(nQueuedPkt, m_nPackets / 2, "Unexpected number of queued MPDUs");
2750 }
2751 break;
2752 }
2753}
2754
2755void
2757{
2758 switch (m_muTrafficPattern)
2759 {
2760 case WifiMuTrafficPattern::DL_MU_BAR_BA_SEQUENCE:
2761 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2763 break;
2764 case WifiMuTrafficPattern::DL_MU_MU_BAR:
2765 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2767 break;
2768 case WifiMuTrafficPattern::DL_MU_AGGR_MU_BAR:
2769 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2771 break;
2772 default:;
2773 }
2774
2776
2777 // Enable A-MSDU aggregation. Max A-MSDU size is set such that two MSDUs can be aggregated
2778 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
2779 {
2780 mac->SetAttribute("BE_MaxAmsduSize", UintegerValue(1050));
2781 mac->GetQosTxop(AC_BE)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2783 mac->GetQosTxop(AC_BE)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2784
2785 mac->SetAttribute("VI_MaxAmsduSize", UintegerValue(1050));
2786 mac->GetQosTxop(AC_VI)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2788 mac->GetQosTxop(AC_VI)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2789 }
2790
2791 // aggregate MU scheduler
2792 auto muScheduler = CreateObjectWithAttributes<RrMultiUserScheduler>("EnableUlOfdma",
2793 BooleanValue(false),
2794 "EnableBsrp",
2795 BooleanValue(false),
2796 "UlPsduSize",
2797 UintegerValue(2000));
2798 m_apMac->AggregateObject(muScheduler);
2799
2800 // install post reception error model on all devices
2801 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
2802 {
2803 auto errorModel = CreateObject<ListErrorModel>();
2804 m_errorModels[m_apMac->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2805 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2806 }
2807 for (std::size_t i : {0, 1})
2808 {
2809 for (const auto linkId : m_staMacs[i]->GetLinkIds())
2810 {
2811 auto errorModel = CreateObject<ListErrorModel>();
2812 m_errorModels[m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2813 m_staMacs[i]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2814 }
2815 }
2816}
2817
2818void
2820{
2821 if (m_muTrafficPattern < WifiMuTrafficPattern::UL_MU)
2822 {
2823 // DL Traffic
2824 for (uint8_t i = 0; i < m_nStations; i++)
2825 {
2826 PacketSocketAddress sockAddr;
2828 sockAddr.SetPhysicalAddress(m_staMacs[i]->GetDevice()->GetAddress());
2829 sockAddr.SetProtocol(1);
2830
2831 // the first client application generates three packets in order
2832 // to trigger the establishment of a Block Ack agreement
2834 GetApplication(sockAddr, 3, 450, i * MilliSeconds(50)));
2835
2836 // the second client application generates the first half of the selected number
2837 // of packets, which are sent in DL MU PPDUs, and starts after all BA agreements
2838 // are established
2840 GetApplication(sockAddr, m_nPackets / 2, 450, m_nStations * MilliSeconds(50)));
2841
2842 // the third client application generates the second half of the selected number
2843 // of packets, which are sent in DL MU PPDUs, and starts during transmission of
2844 // first A-MPDU, if multiple links are setup
2846 GetApplication(sockAddr,
2847 m_nPackets / 2,
2848 450,
2850 }
2851 }
2852 else
2853 {
2854 // UL Traffic
2855 for (uint8_t i = 0; i < m_nStations; i++)
2856 {
2857 m_sockets[i].SetSingleDevice(m_staMacs[i]->GetDevice()->GetIfIndex());
2858 m_sockets[i].SetPhysicalAddress(m_apMac->GetDevice()->GetAddress());
2859 m_sockets[i].SetProtocol(1);
2860
2861 // the first client application generates three packets in order
2862 // to trigger the establishment of a Block Ack agreement
2863 m_staMacs[i]->GetDevice()->GetNode()->AddApplication(
2864 GetApplication(m_sockets[i], 3, 450, i * MilliSeconds(50), i * 4));
2865
2866 // packets to be included in TB PPDUs are generated (by Transmit()) when
2867 // the first Basic Trigger Frame is sent by the AP
2868 }
2869
2870 // MU scheduler starts requesting channel access when we are done with BA agreements
2872 auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
2873 NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "Expected an aggregated MU scheduler");
2874 muScheduler->SetAttribute("EnableUlOfdma", BooleanValue(true));
2875 muScheduler->SetAccessReqInterval(MilliSeconds(3));
2876 // channel access is requested only once
2877 muScheduler->SetAccessReqInterval(Seconds(0));
2878 });
2879 }
2880
2882}
2883
2884void
2886{
2888
2889 // Expected number of packets received by each node (AP, STA 0, STA 1) at application layer
2890 std::array<std::size_t, 3> expectedRxPkts{};
2891
2892 switch (m_muTrafficPattern)
2893 {
2894 case WifiMuTrafficPattern::DL_MU_BAR_BA_SEQUENCE:
2895 case WifiMuTrafficPattern::DL_MU_MU_BAR:
2896 case WifiMuTrafficPattern::DL_MU_AGGR_MU_BAR:
2897 // both STA 0 and STA 1 receive m_nPackets + 3 (sent to trigger BA establishment) packets
2898 expectedRxPkts[1] = m_nPackets + 3;
2899 expectedRxPkts[2] = m_nPackets + 3;
2900 break;
2901 case WifiMuTrafficPattern::UL_MU:
2902 // AP receives m_nPackets + 3 (sent to trigger BA establishment) packets from each station
2903 expectedRxPkts[0] = 2 * (m_nPackets + 3);
2904 break;
2905 }
2906
2908 +expectedRxPkts[0],
2909 "Unexpected number of packets received by the AP");
2911 +expectedRxPkts[1],
2912 "Unexpected number of packets received by STA 0");
2914 +expectedRxPkts[2],
2915 "Unexpected number of packets received by STA 1");
2916
2917 // check that setting the QosTxop::NMaxInflights attribute has the expected effect.
2918 // For DL, for each station we send 2 MPDUs to trigger BA agreement and m_nPackets / 2 MPDUs
2919 // For UL, each station sends 2 MPDUs to trigger BA agreement and m_nPackets / 2 MPDUs
2921 m_inflightCount.size(),
2922 2 * (2 + m_nPackets / 2),
2923 "Did not collect number of simultaneous transmissions for all data frames");
2924
2925 auto nMaxInflight = std::min(m_nMaxInflight, m_staMacs[0]->GetSetupLinkIds().size());
2926 std::size_t maxCount = 0;
2927 for (const auto& [txSeqNoPair, count] : m_inflightCount)
2928 {
2930 nMaxInflight,
2931 "MPDU with seqNo="
2932 << txSeqNoPair.second
2933 << " transmitted simultaneously more times than allowed");
2934 maxCount = std::max(maxCount, count);
2935 }
2936
2938 maxCount,
2939 nMaxInflight,
2940 "Expected that at least one data frame was transmitted simultaneously a number of "
2941 "times equal to the NMaxInflights attribute");
2942
2944}
2945
2946/**
2947 * \ingroup wifi-test
2948 * \ingroup tests
2949 *
2950 * \brief Test release of sequence numbers upon CTS timeout in multi-link operations
2951 *
2952 * In this test, an AP MLD and a non-AP MLD setup 3 links. Usage of RTS/CTS protection is
2953 * enabled for frames whose length is at least 1000 bytes. The AP MLD receives a first set
2954 * of 4 packets from the upper layer and sends an RTS frame, which is corrupted at the
2955 * receiver, on a first link. When the RTS frame is transmitted, the AP MLD receives another
2956 * set of 4 packets, which are transmitted after a successful RTS/CTS exchange on a second
2957 * link. In the meantime, a new RTS/CTS exchange is successfully carried out (on the first
2958 * link or on the third link) to transmit the first set of 4 packets. When the transmission
2959 * of the first set of 4 packets starts, the AP MLD receives the third set of 4 packets from
2960 * the upper layer, which are transmitted after a successful RTS/CTS exchange.
2961 *
2962 * This test checks that sequence numbers are correctly assigned to all the MPDUs carrying data.
2963 */
2965{
2966 public:
2969
2970 protected:
2971 void DoSetup() override;
2972 void DoRun() override;
2973 void Transmit(Ptr<WifiMac> mac,
2974 uint8_t phyId,
2975 WifiConstPsduMap psduMap,
2976 WifiTxVector txVector,
2977 double txPowerW) override;
2978
2979 private:
2980 void StartTraffic() override;
2981
2982 PacketSocketAddress m_sockAddr; //!< packet socket address
2983 std::size_t m_nQosDataFrames; //!< counter for transmitted QoS data frames
2984 Ptr<ListErrorModel> m_errorModel; //!< error rate model to corrupt first RTS frame
2985 bool m_rtsCorrupted; //!< whether the first RTS frame has been corrupted
2986};
2987
2990 "Check sequence numbers after CTS timeout",
2991 1,
2992 BaseParams{{"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2993 {"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2994 {}}),
2995 m_nQosDataFrames(0),
2996 m_errorModel(CreateObject<ListErrorModel>()),
2997 m_rtsCorrupted(false)
2998{
2999}
3000
3001void
3003{
3004 // Enable RTS/CTS
3005 Config::SetDefault("ns3::WifiRemoteStationManager::RtsCtsThreshold", StringValue("1000"));
3006
3008
3009 // install post reception error model on all STAs affiliated with non-AP MLD
3010 for (const auto linkId : m_staMacs[0]->GetLinkIds())
3011 {
3012 m_staMacs[0]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel);
3013 }
3014}
3015
3016void
3018{
3020 m_sockAddr.SetPhysicalAddress(m_staMacs[0]->GetAddress());
3022
3023 // install client application generating 4 packets
3025}
3026
3027void
3029 uint8_t phyId,
3030 WifiConstPsduMap psduMap,
3031 WifiTxVector txVector,
3032 double txPowerW)
3033{
3034 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
3035
3036 auto psdu = psduMap.begin()->second;
3037
3038 if (psdu->GetHeader(0).IsRts() && !m_rtsCorrupted)
3039 {
3040 m_errorModel->SetList({psdu->GetPacket()->GetUid()});
3041 m_rtsCorrupted = true;
3042 // generate other packets when the first RTS is transmitted
3044 }
3045 else if (psdu->GetHeader(0).IsQosData())
3046 {
3048
3049 if (m_nQosDataFrames == 2)
3050 {
3051 // generate other packets when the second QoS data frame is transmitted
3053 }
3054 }
3055}
3056
3057void
3059{
3062
3063 NS_TEST_EXPECT_MSG_EQ(m_nQosDataFrames, 3, "Unexpected number of transmitted QoS data frames");
3064
3065 std::size_t count{};
3066
3067 for (const auto& txPsdu : m_txPsdus)
3068 {
3069 auto psdu = txPsdu.psduMap.begin()->second;
3070
3071 if (!psdu->GetHeader(0).IsQosData())
3072 {
3073 continue;
3074 }
3075
3076 NS_TEST_EXPECT_MSG_EQ(psdu->GetNMpdus(), 4, "Unexpected number of MPDUs in A-MPDU");
3077
3078 count++;
3079 uint16_t expectedSeqNo{};
3080
3081 switch (count)
3082 {
3083 case 1:
3084 expectedSeqNo = 4;
3085 break;
3086 case 2:
3087 expectedSeqNo = 0;
3088 break;
3089 case 3:
3090 expectedSeqNo = 8;
3091 break;
3092 }
3093
3094 for (const auto& mpdu : *PeekPointer(psdu))
3095 {
3096 NS_TEST_EXPECT_MSG_EQ(mpdu->GetHeader().GetSequenceNumber(),
3097 expectedSeqNo++,
3098 "Unexpected sequence number");
3099 }
3100 }
3101
3103}
3104
3105/**
3106 * \ingroup wifi-test
3107 * \ingroup tests
3108 *
3109 * \brief wifi 11be MLD Test Suite
3110 */
3112{
3113 public:
3115};
3116
3118 : TestSuite("wifi-mlo", Type::UNIT)
3119{
3120 using ParamsTuple = std::tuple<MultiLinkOperationsTestBase::BaseParams, // base config params
3121 std::vector<uint8_t>, // link ID of setup links
3122 WifiTidToLinkMappingNegSupport, // AP negotiation support
3123 std::string, // DL TID-to-Link Mapping
3124 std::string>; // UL TID-to-Link Mapping
3125
3126 AddTestCase(new GetRnrLinkInfoTest(), TestCase::Duration::QUICK);
3127 AddTestCase(new MldSwapLinksTest(), TestCase::Duration::QUICK);
3128
3129 for (const auto& [baseParams,
3130 setupLinks,
3131 apNegSupport,
3132 dlTidLinkMapping,
3133 ulTidLinkMapping] :
3134 {// matching channels: setup all links
3135 ParamsTuple(
3136 {{"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3137 {"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3138 {}},
3139 {0, 1, 2},
3140 WifiTidToLinkMappingNegSupport::NOT_SUPPORTED, // AP MLD does not support TID-to-Link
3141 // Mapping negotiation
3142 "0,1,2,3 0,1,2; 4,5 0,1", // default mapping used instead
3143 "0,1,2,3 1,2; 6,7 0,1" // default mapping used instead
3144 ),
3145 // non-matching channels, matching PHY bands: setup all links
3146 ParamsTuple({{"{108, 0, BAND_5GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3147 {"{36, 0, BAND_5GHZ, 0}", "{120, 0, BAND_5GHZ, 0}", "{5, 0, BAND_6GHZ, 0}"},
3148 {}},
3149 {0, 1, 2},
3150 WifiTidToLinkMappingNegSupport::SAME_LINK_SET, // AP MLD does not support
3151 // distinct link sets for TIDs
3152 "0,1,2,3 0,1,2; 4,5 0,1", // default mapping used instead
3153 ""),
3154 // non-AP MLD switches band on some links to setup 3 links
3155 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3156 {"{36, 0, BAND_5GHZ, 0}", "{9, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3157 {}},
3158 {0, 1, 2},
3159 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3160 "0,1,2,3 0; 4,5,6,7 1,2", // frames of two TIDs are generated
3161 "0,2,3 1,2; 1,4,5,6,7 0" // frames of two TIDs are generated
3162 ),
3163 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3164 // that band, hence only 2 links are setup
3165 ParamsTuple(
3166 {{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{8, 20, BAND_2_4GHZ, 0}"},
3167 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3168 {0}},
3169 {0, 1},
3170 WifiTidToLinkMappingNegSupport::SAME_LINK_SET, // AP MLD does not support distinct
3171 // link sets for TIDs
3172 "0,1,2,3,4,5,6,7 0",
3173 "0,1,2,3,4,5,6,7 0"),
3174 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3175 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3176 // an AP operating on the same channel; hence 2 links are setup
3177 ParamsTuple(
3178 {{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{8, 20, BAND_2_4GHZ, 0}"},
3179 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3180 {0, 1}},
3181 {0, 1},
3182 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3183 "0,1,2,3 1",
3184 "0,1,2,3 1"),
3185 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3186 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3187 // an AP operating on the same channel; the third link of the non-AP MLD cannot
3188 // change PHY band and there is an AP operating on the same band (different channel);
3189 // hence 2 links are setup by switching channel (not band) on the third link
3190 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{60, 0, BAND_5GHZ, 0}"},
3191 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3192 {0, 1, 2}},
3193 {0, 2},
3194 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3195 "",
3196 ""),
3197 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3198 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3199 // an AP operating on the same channel; hence one link only is setup
3200 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3201 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3202 {0, 1}},
3203 {2},
3204 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3205 "",
3206 ""),
3207 // non-AP MLD has only two STAs and setups two links
3208 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3209 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3210 {}},
3211 {1, 0},
3212 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3213 "0,1,2,3 1",
3214 ""),
3215 // single link non-AP STA associates with an AP affiliated with an AP MLD
3216 ParamsTuple({{"{120, 0, BAND_5GHZ, 0}"},
3217 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3218 {}},
3219 {2}, // link ID of AP MLD only (non-AP STA is single link)
3220 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3221 "",
3222 ""),
3223 // a STA affiliated with a non-AP MLD associates with a single link AP
3224 ParamsTuple({{"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3225 {"{120, 0, BAND_5GHZ, 0}"},
3226 {}},
3227 {2}, // link ID of non-AP MLD only (AP is single link)
3228 WifiTidToLinkMappingNegSupport::NOT_SUPPORTED,
3229 "0,1,2,3 0,1; 4,5,6,7 0,1", // ignored by single link AP
3230 "")})
3231 {
3232 AddTestCase(new MultiLinkSetupTest(baseParams,
3233 WifiScanType::PASSIVE,
3234 setupLinks,
3235 apNegSupport,
3236 dlTidLinkMapping,
3237 ulTidLinkMapping),
3238 TestCase::Duration::QUICK);
3239 AddTestCase(new MultiLinkSetupTest(baseParams,
3240 WifiScanType::ACTIVE,
3241 setupLinks,
3242 apNegSupport,
3243 dlTidLinkMapping,
3244 ulTidLinkMapping),
3245 TestCase::Duration::QUICK);
3246
3247 for (const auto& trafficPattern : {WifiTrafficPattern::STA_TO_STA,
3248 WifiTrafficPattern::STA_TO_AP,
3249 WifiTrafficPattern::AP_TO_STA,
3250 WifiTrafficPattern::AP_TO_BCAST,
3251 WifiTrafficPattern::STA_TO_BCAST})
3252 {
3253 // No Block Ack agreement
3254 AddTestCase(new MultiLinkTxTest(baseParams,
3255 trafficPattern,
3256 WifiBaEnabled::NO,
3257 WifiUseBarAfterMissedBa::NO,
3258 1),
3259 TestCase::Duration::QUICK);
3260 for (const auto& useBarAfterMissedBa :
3261 {WifiUseBarAfterMissedBa::YES, WifiUseBarAfterMissedBa::NO})
3262 {
3263 // Block Ack agreement with nMaxInflight=1
3264 AddTestCase(new MultiLinkTxTest(baseParams,
3265 trafficPattern,
3266 WifiBaEnabled::YES,
3267 useBarAfterMissedBa,
3268 1),
3269 TestCase::Duration::QUICK);
3270 // Block Ack agreement with nMaxInflight=2
3271 AddTestCase(new MultiLinkTxTest(baseParams,
3272 trafficPattern,
3273 WifiBaEnabled::YES,
3274 useBarAfterMissedBa,
3275 2),
3276 TestCase::Duration::QUICK);
3277 }
3278 }
3279
3280 for (const auto& muTrafficPattern : {WifiMuTrafficPattern::DL_MU_BAR_BA_SEQUENCE,
3281 WifiMuTrafficPattern::DL_MU_MU_BAR,
3282 WifiMuTrafficPattern::DL_MU_AGGR_MU_BAR,
3283 WifiMuTrafficPattern::UL_MU})
3284 {
3285 for (const auto& useBarAfterMissedBa :
3286 {WifiUseBarAfterMissedBa::YES, WifiUseBarAfterMissedBa::NO})
3287 {
3288 // Block Ack agreement with nMaxInflight=1
3290 new MultiLinkMuTxTest(baseParams, muTrafficPattern, useBarAfterMissedBa, 1),
3291 TestCase::Duration::QUICK);
3292 // Block Ack agreement with nMaxInflight=2
3294 new MultiLinkMuTxTest(baseParams, muTrafficPattern, useBarAfterMissedBa, 2),
3295 TestCase::Duration::QUICK);
3296 }
3297 }
3298 }
3299
3300 AddTestCase(new ReleaseSeqNoAfterCtsTimeoutTest(), TestCase::Duration::QUICK);
3301}
3302
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:164
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:322
Ptr< T > GetObject() const
Get a pointer to the requested aggregated Object.
Definition: object.h:522
void AggregateObject(Ptr< Object > other)
Aggregate two Objects together.
Definition: object.cc:309
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.
Definition: ssid.h:96
Hold variables of type string.
Definition: string.h:56
encapsulates test code
Definition: test.h:1061
void AddTestCase(TestCase *testCase, Duration duration=Duration::QUICK)
Add an individual child TestCase to this test suite.
Definition: test.cc:301
A suite of tests to run.
Definition: test.h:1268
Type
Type of test.
Definition: test.h:1275
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:669
static Time CalculateTxDuration(uint32_t size, const WifiTxVector &txVector, WifiPhyBand band, uint16_t staId=SU_STA_ID)
Definition: wifi-phy.cc:1529
WifiPhyBand GetPhyBand() const
Get the configured Wi-Fi band.
Definition: wifi-phy.cc:1043
const WifiPhyOperatingChannel & GetOperatingChannel() const
Get a const reference to the operating channel.
Definition: wifi-phy.cc:1055
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:894
void ConnectWithoutContext(std::string path, const CallbackBase &cb)
Definition: config.cc:954
#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:710
#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:145
#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:831
#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:791
#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:957
#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:667
#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:252
#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:565
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:50
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:454
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.
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.