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 * SPDX-License-Identifier: GPL-2.0-only
5 *
6 * Author: Stefano Avallone <stavallo@unina.it>
7 */
8
9#include "wifi-mlo-test.h"
10
11#include "ns3/config.h"
12#include "ns3/eht-configuration.h"
13#include "ns3/ht-frame-exchange-manager.h"
14#include "ns3/log.h"
15#include "ns3/mgt-action-headers.h"
16#include "ns3/mgt-headers.h"
17#include "ns3/mobility-helper.h"
18#include "ns3/multi-link-element.h"
19#include "ns3/node-list.h"
20#include "ns3/packet-socket-helper.h"
21#include "ns3/packet.h"
22#include "ns3/pointer.h"
23#include "ns3/rng-seed-manager.h"
24#include "ns3/rr-multi-user-scheduler.h"
25#include "ns3/spectrum-wifi-phy.h"
26#include "ns3/string.h"
27#include "ns3/vht-configuration.h"
28#include "ns3/wifi-acknowledgment.h"
29#include "ns3/wifi-assoc-manager.h"
30#include "ns3/wifi-mac-header.h"
31#include "ns3/wifi-mac-queue.h"
32#include "ns3/wifi-net-device.h"
33#include "ns3/wifi-protection.h"
34
35#include <algorithm>
36#include <array>
37#include <iomanip>
38#include <sstream>
39#include <tuple>
40
41using namespace ns3;
42
43NS_LOG_COMPONENT_DEFINE("WifiMloTest");
44
46 : TestCase("Check the implementation of WifiAssocManager::GetNextAffiliatedAp()")
47{
48}
49
50void
52{
54 std::size_t nbrId;
55 std::size_t tbttId;
56
57 // Add a first Neighbor AP Information field without MLD Parameters
59 nbrId = rnr.GetNNbrApInfoFields() - 1;
60
61 rnr.AddTbttInformationField(nbrId);
62 rnr.AddTbttInformationField(nbrId);
63
64 // Add a second Neighbor AP Information field with MLD Parameters; the first
65 // TBTT Information field is related to an AP affiliated to the same AP MLD
66 // as the reported AP; the second TBTT Information field is not (it does not
67 // make sense that two APs affiliated to the same AP MLD are using the same
68 // channel).
70 nbrId = rnr.GetNNbrApInfoFields() - 1;
71
72 rnr.AddTbttInformationField(nbrId);
73 tbttId = rnr.GetNTbttInformationFields(nbrId) - 1;
74 rnr.SetMldParameters(nbrId, tbttId, {0, 0, 0, 1, 1});
75
76 rnr.AddTbttInformationField(nbrId);
77 tbttId = rnr.GetNTbttInformationFields(nbrId) - 1;
78 rnr.SetMldParameters(nbrId, tbttId, {5, 0, 0, 1, 0});
79
80 // Add a third Neighbor AP Information field with MLD Parameters; none of the
81 // TBTT Information fields is related to an AP affiliated to the same AP MLD
82 // as the reported AP.
84 nbrId = rnr.GetNNbrApInfoFields() - 1;
85
86 rnr.AddTbttInformationField(nbrId);
87 tbttId = rnr.GetNTbttInformationFields(nbrId) - 1;
88 rnr.SetMldParameters(nbrId, tbttId, {3, 0, 0, 0, 1});
89
90 rnr.AddTbttInformationField(nbrId);
91 tbttId = rnr.GetNTbttInformationFields(nbrId) - 1;
92 rnr.SetMldParameters(nbrId, tbttId, {4, 0, 0, 0, 0});
93
94 // Add a fourth Neighbor AP Information field with MLD Parameters; the first
95 // TBTT Information field is not related to an AP affiliated to the same AP MLD
96 // as the reported AP; the second TBTT Information field is.
98 nbrId = rnr.GetNNbrApInfoFields() - 1;
99
100 rnr.AddTbttInformationField(nbrId);
101 tbttId = rnr.GetNTbttInformationFields(nbrId) - 1;
102 rnr.SetMldParameters(nbrId, tbttId, {6, 0, 0, 1, 1});
103
104 rnr.AddTbttInformationField(nbrId);
105 tbttId = rnr.GetNTbttInformationFields(nbrId) - 1;
106 rnr.SetMldParameters(nbrId, tbttId, {0, 0, 0, 0, 0});
107
108 // check implementation of WifiAssocManager::GetNextAffiliatedAp()
109 auto ret = WifiAssocManager::GetNextAffiliatedAp(rnr, 0);
110
111 NS_TEST_EXPECT_MSG_EQ(ret.has_value(), true, "Expected to find a suitable reported AP");
112 NS_TEST_EXPECT_MSG_EQ(ret->m_nbrApInfoId, 1, "Unexpected neighbor ID of the first reported AP");
113 NS_TEST_EXPECT_MSG_EQ(ret->m_tbttInfoFieldId, 0, "Unexpected tbtt ID of the first reported AP");
114
115 ret = WifiAssocManager::GetNextAffiliatedAp(rnr, ret->m_nbrApInfoId + 1);
116
117 NS_TEST_EXPECT_MSG_EQ(ret.has_value(), true, "Expected to find a second suitable reported AP");
118 NS_TEST_EXPECT_MSG_EQ(ret->m_nbrApInfoId,
119 3,
120 "Unexpected neighbor ID of the second reported AP");
121 NS_TEST_EXPECT_MSG_EQ(ret->m_tbttInfoFieldId,
122 1,
123 "Unexpected tbtt ID of the second reported AP");
124
125 ret = WifiAssocManager::GetNextAffiliatedAp(rnr, ret->m_nbrApInfoId + 1);
126
127 NS_TEST_EXPECT_MSG_EQ(ret.has_value(),
128 false,
129 "Did not expect to find a third suitable reported AP");
130
131 // check implementation of WifiAssocManager::GetAllAffiliatedAps()
132 auto allAps = WifiAssocManager::GetAllAffiliatedAps(rnr);
133
134 NS_TEST_EXPECT_MSG_EQ(allAps.size(), 2, "Expected to find two suitable reported APs");
135
136 auto apIt = allAps.begin();
137 NS_TEST_EXPECT_MSG_EQ(apIt->m_nbrApInfoId,
138 1,
139 "Unexpected neighbor ID of the first reported AP");
140 NS_TEST_EXPECT_MSG_EQ(apIt->m_tbttInfoFieldId,
141 0,
142 "Unexpected tbtt ID of the first reported AP");
143
144 apIt++;
145 NS_TEST_EXPECT_MSG_EQ(apIt->m_nbrApInfoId,
146 3,
147 "Unexpected neighbor ID of the second reported AP");
148 NS_TEST_EXPECT_MSG_EQ(apIt->m_tbttInfoFieldId,
149 1,
150 "Unexpected tbtt ID of the second reported AP");
151}
152
153TypeId
155{
156 static TypeId tid = TypeId("ns3::TestWifiMac")
158 .SetGroupName("Wifi")
159 .AddConstructor<TestWifiMac>();
160 return tid;
161}
162
164 : TestCase("Test the WifiMac::SwapLinks() method")
165{
166}
167
168void
170 std::size_t nLinks,
171 const std::map<uint8_t, uint8_t>& links,
172 const std::map<uint8_t, uint8_t>& expected)
173{
174 auto mac = CreateObjectWithAttributes<TestWifiMac>("QosSupported",
175 BooleanValue(false),
176 "Txop",
178
179 std::vector<Ptr<WifiPhy>> phys;
180 std::vector<Ptr<FrameExchangeManager>> feManagers;
181 std::vector<Ptr<WifiRemoteStationManager>> rsManagers;
182
183 for (std::size_t i = 0; i < nLinks; ++i)
184 {
186 phy->SetPhyId(i);
187 phys.emplace_back(phy);
188 feManagers.emplace_back(CreateObject<TestFrameExchangeManager>());
189 rsManagers.emplace_back(CreateObject<TestRemoteStationManager>());
190 }
191 mac->SetWifiPhys(phys); // create links containing the given PHYs
192 mac->SetFrameExchangeManagers(feManagers);
193 mac->SetWifiRemoteStationManagers(rsManagers);
194 mac->GetTxop()->SetWifiMac(mac);
195
196 // set CWmin of each Txop LinkEntity to the link ID, so that we can check where it has moved
197 for (std::size_t id = 0; id < nLinks; ++id)
198 {
199 mac->GetTxop()->SetMinCw(id, id);
200 }
201
202 mac->SwapLinks(links);
203
204 NS_TEST_EXPECT_MSG_EQ(mac->GetNLinks(), nLinks, "Number of links changed after swapping");
205
206 for (const auto& [linkId, phyId] : expected)
207 {
208 NS_TEST_ASSERT_MSG_EQ(mac->GetLinks().contains(linkId),
209 true,
210 "Link ID " << +linkId << " does not exist");
211
212 NS_TEST_ASSERT_MSG_LT(+phyId, nLinks, "Invalid PHY ID");
213
214 // the id of the PHY operating on a link is the original ID of the link
215 NS_TEST_EXPECT_MSG_EQ(+mac->GetWifiPhy(linkId)->GetPhyId(),
216 +phyId,
217 text << ": Link " << +phyId << " has not been moved to link "
218 << +linkId);
219
221 +DynamicCast<TestFrameExchangeManager>(mac->GetFrameExchangeManager(linkId))
222 ->GetLinkId(),
223 +linkId,
224 text << ": Link ID stored by FrameExchangeManager has not been updated");
225
227 +DynamicCast<TestRemoteStationManager>(mac->GetWifiRemoteStationManager(linkId))
228 ->GetLinkId(),
229 +linkId,
230 text << ": Link ID stored by RemoteStationManager has not been updated");
231
232 NS_TEST_EXPECT_MSG_EQ(mac->GetTxop()->GetMinCw(linkId),
233 +phyId,
234 text << ": Txop Link entity " << +phyId
235 << " has not been moved to link " << +linkId);
236 }
237
238 mac->Dispose();
239}
240
241void
243{
244 RunOne("No change needed", 3, {{0, 0}, {1, 1}, {2, 2}}, {{0, 0}, {1, 1}, {2, 2}});
245 RunOne("Circular swapping", 3, {{0, 2}, {1, 0}, {2, 1}}, {{0, 1}, {1, 2}, {2, 0}});
246 RunOne("Swapping two links, one unchanged", 3, {{0, 2}, {2, 0}}, {{0, 2}, {1, 1}, {2, 0}});
247 RunOne("Non-circular swapping, autodetect how to close the loop",
248 3,
249 {{0, 2}, {2, 1}},
250 {{0, 1}, {1, 2}, {2, 0}});
251 RunOne("A different non-circular swapping, same result",
252 3,
253 {{1, 0}, {2, 1}},
254 {{0, 1}, {1, 2}, {2, 0}});
255 RunOne("One move only, autodetect how to complete the swapping",
256 3,
257 {{2, 0}},
258 {{0, 2}, {1, 1}, {2, 0}});
259 RunOne("Create a new link ID (2), remove the unused one (0)",
260 2,
261 {{0, 1}, {1, 2}},
262 {{1, 0}, {2, 1}});
263 RunOne("One move only that creates a new link ID (2)", 2, {{0, 2}}, {{1, 1}, {2, 0}});
264 RunOne("Move all links to a new set of IDs", 2, {{0, 2}, {1, 3}}, {{2, 0}, {3, 1}});
265}
266
267AidAssignmentTest::AidAssignmentTest(const std::vector<std::set<uint8_t>>& linkIds)
268 : TestCase("Test the assignment of AIDs"),
269 m_linkChannels({"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}"}),
270 m_linkIds(linkIds),
271 m_expectedAid(1) // AID for first station
272{
273}
274
275void
277{
280 int64_t streamNumber{1};
281
282 NodeContainer wifiApNode;
283 wifiApNode.Create(1);
284 NodeContainer wifiStaNodes;
285
286 WifiHelper wifi;
287 wifi.SetStandard(WIFI_STANDARD_80211be);
288 wifi.SetRemoteStationManager("ns3::ConstantRateWifiManager",
289 "DataMode",
290 StringValue("EhtMcs0"),
291 "ControlMode",
292 StringValue("HtMcs0"));
293
295
296 // AP MLD
297 SpectrumWifiPhyHelper phyHelper(3);
299 phyHelper.SetPcapCaptureType(WifiPhyHelper::PcapCaptureType::PCAP_PER_LINK);
300 uint8_t linkId = 0;
301 for (const auto& str : m_linkChannels)
302 {
303 phyHelper.Set(linkId++, "ChannelSettings", StringValue(str));
304 }
305 phyHelper.SetChannel(channel);
306
307 WifiMacHelper mac;
308 mac.SetType("ns3::ApWifiMac",
309 "Ssid",
310 SsidValue(Ssid("ns-3-ssid")),
311 "BeaconGeneration",
312 BooleanValue(true));
313
314 auto apDevice = wifi.Install(phyHelper, mac, wifiApNode);
315
316 // non-AP STAs/MLDs
317 for (const auto& links : m_linkIds)
318 {
319 phyHelper = SpectrumWifiPhyHelper(links.size());
321 phyHelper.SetPcapCaptureType(WifiPhyHelper::PcapCaptureType::PCAP_PER_LINK);
322 linkId = 0;
323 for (const auto& id : links)
324 {
325 phyHelper.Set(linkId++, "ChannelSettings", StringValue(m_linkChannels.at(id)));
326 }
327 phyHelper.SetChannel(channel);
328 phyHelper.Set("FixedPhyBand", BooleanValue(true));
329
330 WifiMacHelper mac;
331 mac.SetType("ns3::StaWifiMac",
332 "Ssid", // first non-AP STA/MLD only starts associating
333 SsidValue(Ssid(m_staDevices.GetN() == 0 ? "ns-3-ssid" : "default")),
334 "ActiveProbing",
335 BooleanValue(false));
336
337 auto staNode = CreateObject<Node>();
338 auto staDevice = wifi.Install(phyHelper, mac, staNode);
339 wifiStaNodes.Add(staNode);
340 m_staDevices.Add(staDevice);
341 }
342
343 // Assign fixed streams to random variables in use
344 streamNumber += WifiHelper::AssignStreams(apDevice, streamNumber);
345 streamNumber += WifiHelper::AssignStreams(m_staDevices, streamNumber);
346
347 auto positionAlloc = CreateObject<ListPositionAllocator>();
348 positionAlloc->Add(Vector(0.0, 0.0, 0.0));
349 MobilityHelper mobility;
350 mobility.SetPositionAllocator(positionAlloc);
351 mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
352 mobility.Install(wifiApNode);
353 mobility.Install(wifiStaNodes);
354
355 for (uint32_t i = 0; i < m_staDevices.GetN(); ++i)
356 {
357 auto mac = StaticCast<WifiNetDevice>(m_staDevices.Get(i))->GetMac();
358 mac->TraceConnectWithoutContext(
359 "Assoc",
361 }
362}
363
364void
366{
367 const auto aid = staMac->GetAssociationId();
368
369 std::stringstream linksStr;
370 const auto setupLinks = staMac->GetSetupLinkIds();
371 std::copy(setupLinks.cbegin(), setupLinks.cend(), std::ostream_iterator<int>(linksStr, " "));
372
373 NS_LOG_INFO("STA " << staMac->GetAddress() << " associated with AID " << aid << " links "
374 << linksStr.str());
375
376 NS_TEST_EXPECT_MSG_EQ(aid, m_expectedAid, "Unexpected AID for STA " << staMac->GetAddress());
377 // For non-AP MLDs, check that the requested links have been setup (for non-AP STAs, link ID
378 // as seen by the non-AP STAs is always zero and could not match link ID as seen by the AP MLD)
379 if (m_linkIds.at(aid - 1).size() > 1)
380 {
381 NS_TEST_EXPECT_MSG_EQ((staMac->GetSetupLinkIds() == m_linkIds.at(aid - 1)),
382 true,
383 "Unexpected set of setup links " << linksStr.str());
384 }
385
387 {
388 // let the next STA associate with the AP
390 ->GetMac()
391 ->SetSsid(Ssid("ns-3-ssid"));
393 }
394 else
395 {
396 Simulator::Stop(MilliSeconds(5)); // allow sending Ack response to Association Response
397 }
398}
399
400void
402{
403 Simulator::Stop(Seconds(5)); // simulation will stop earlier if all STAs complete association
405
406 NS_TEST_EXPECT_MSG_EQ(m_expectedAid, m_staDevices.GetN(), "Not all STAs completed association");
407
408 for (uint32_t i = 0; i < m_staDevices.GetN(); ++i)
409 {
410 auto mac = StaticCast<WifiNetDevice>(m_staDevices.Get(i))->GetMac();
411 mac->TraceDisconnectWithoutContext(
412 "Assoc",
414 }
415
417}
418
420 uint8_t nStations,
421 const BaseParams& baseParams)
422 : TestCase(name),
423 m_staChannels(baseParams.staChannels),
424 m_apChannels(baseParams.apChannels),
425 m_fixedPhyBands(baseParams.fixedPhyBands),
426 m_staMacs(nStations),
427 m_nStations(nStations),
428 m_lastAid(0),
429 m_rxPkts(nStations + 1)
430{
431}
432
433void
435 std::optional<Direction> direction)
436{
437 std::optional<Mac48Address> apAddr;
438 std::optional<Mac48Address> staAddr;
439
440 // direction for Data frames is derived from ToDS/FromDS flags
441 if (psdu->GetHeader(0).IsQosData())
442 {
443 direction = (!psdu->GetHeader(0).IsToDs() && psdu->GetHeader(0).IsFromDs()) ? DL : UL;
444 }
445 NS_ASSERT(direction);
446
447 if (direction == DL)
448 {
449 if (!psdu->GetAddr1().IsGroup())
450 {
451 staAddr = psdu->GetAddr1();
452 }
453 apAddr = psdu->GetAddr2();
454 }
455 else
456 {
457 if (!psdu->GetAddr1().IsGroup())
458 {
459 apAddr = psdu->GetAddr1();
460 }
461 staAddr = psdu->GetAddr2();
462 }
463
464 if (apAddr)
465 {
466 bool found = false;
467 for (uint8_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
468 {
469 if (m_apMac->GetFrameExchangeManager(linkId)->GetAddress() == *apAddr)
470 {
471 found = true;
472 break;
473 }
474 }
476 true,
477 "Address " << *apAddr << " is not an AP device address. "
478 << "PSDU: " << *psdu);
479 }
480
481 if (staAddr)
482 {
483 bool found = false;
484 for (uint8_t i = 0; i < m_nStations; i++)
485 {
486 for (const auto& linkId : m_staMacs[i]->GetLinkIds())
487 {
488 if (m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress() == *staAddr)
489 {
490 found = true;
491 break;
492 }
493 }
494 if (found)
495 {
496 break;
497 }
498 }
500 true,
501 "Address " << *staAddr << " is not a STA device address. "
502 << "PSDU: " << *psdu);
503 }
504}
505
506void
508 uint8_t phyId,
509 WifiConstPsduMap psduMap,
510 WifiTxVector txVector,
511 double txPowerW)
512{
513 auto linkId = mac->GetLinkForPhy(phyId);
514 NS_TEST_ASSERT_MSG_EQ(linkId.has_value(), true, "No link found for PHY ID " << +phyId);
515 m_txPsdus.push_back({Simulator::Now(), psduMap, txVector, *linkId, phyId});
516
517 for (const auto& [aid, psdu] : psduMap)
518 {
519 std::stringstream ss;
520 ss << std::setprecision(10) << "PSDU #" << m_txPsdus.size() << " Link ID "
521 << +linkId.value() << " Phy ID " << +phyId << " #MPDUs " << psdu->GetNMpdus();
522 for (auto it = psdu->begin(); it != psdu->end(); ++it)
523 {
524 ss << "\n" << **it;
525 }
526 NS_LOG_INFO(ss.str());
527
528 CheckCapabilities(*psdu->begin(), mac, phyId);
529 }
530 NS_LOG_INFO("TXVECTOR = " << txVector << "\n");
531}
532
533void
535{
536 auto band = mac->GetDevice()->GetPhy(phyId)->GetPhyBand();
537 bool hasHtCapabilities;
538 bool hasVhtCapabilities;
539 bool hasHeCapabilities;
540 bool hasHe6GhzCapabilities;
541 bool hasEhtCapabilities;
542
543 auto findCapabilities = [&](auto&& frame) {
544 hasHtCapabilities = frame.template Get<HtCapabilities>().has_value();
545 hasVhtCapabilities = frame.template Get<VhtCapabilities>().has_value();
546 hasHeCapabilities = frame.template Get<HeCapabilities>().has_value();
547 hasHe6GhzCapabilities = frame.template Get<He6GhzBandCapabilities>().has_value();
548 hasEhtCapabilities = frame.template Get<EhtCapabilities>().has_value();
549 };
550
551 switch (mpdu->GetHeader().GetType())
552 {
553 case WIFI_MAC_MGT_BEACON: {
554 MgtBeaconHeader beacon;
555 mpdu->GetPacket()->PeekHeader(beacon);
556 findCapabilities(beacon);
557 }
558 break;
559
561 MgtProbeRequestHeader probeReq;
562 mpdu->GetPacket()->PeekHeader(probeReq);
563 findCapabilities(probeReq);
564 }
565 break;
566
568 MgtProbeResponseHeader probeResp;
569 mpdu->GetPacket()->PeekHeader(probeResp);
570 findCapabilities(probeResp);
571 }
572 break;
573
575 MgtAssocRequestHeader assocReq;
576 mpdu->GetPacket()->PeekHeader(assocReq);
577 findCapabilities(assocReq);
578 }
579 break;
580
582 MgtAssocResponseHeader assocResp;
583 mpdu->GetPacket()->PeekHeader(assocResp);
584 findCapabilities(assocResp);
585 }
586 break;
587
588 default:
589 return;
590 }
591
593 hasHtCapabilities,
594 (band != WIFI_PHY_BAND_6GHZ),
595 "HT Capabilities should not be present in a mgt frame sent in 6 GHz band");
597 hasVhtCapabilities,
598 (band == WIFI_PHY_BAND_5GHZ),
599 "VHT Capabilities should only be present in a mgt frame sent in 5 GHz band");
600 NS_TEST_EXPECT_MSG_EQ(hasHeCapabilities,
601 true,
602 "HE Capabilities should always be present in a mgt frame");
604 hasHe6GhzCapabilities,
605 (band == WIFI_PHY_BAND_6GHZ),
606 "HE 6GHz Band Capabilities should only be present in a mgt frame sent in 6 GHz band");
607 NS_TEST_EXPECT_MSG_EQ(hasEhtCapabilities,
608 true,
609 "EHT Capabilities should always be present in a mgt frame");
610}
611
612void
614{
615 NS_LOG_INFO("Packet received by NODE " << +nodeId << "\n");
616 m_rxPkts[nodeId]++;
617}
618
619void
621 const std::vector<std::string>& channels,
622 const ChannelMap& channelMap)
623{
624 helper = SpectrumWifiPhyHelper(channels.size());
626 helper.SetPcapCaptureType(WifiPhyHelper::PcapCaptureType::PCAP_PER_LINK);
627
628 uint8_t linkId = 0;
629 for (const auto& str : channels)
630 {
631 helper.Set(linkId++, "ChannelSettings", StringValue(str));
632 }
633
634 // NOTE replace this for loop with the line below to use a single spectrum channel
635 // helper.SetChannel(channelMap.begin()->second);
636 for (const auto& [band, channel] : channelMap)
637 {
638 helper.AddChannel(channel, band);
639 }
640}
641
642void
644{
647 int64_t streamNumber = 30;
648
649 NodeContainer wifiApNode;
650 wifiApNode.Create(1);
651
652 NodeContainer wifiStaNodes;
653 wifiStaNodes.Create(m_nStations);
654
655 WifiHelper wifi;
656 // wifi.EnableLogComponents ();
657 wifi.SetStandard(WIFI_STANDARD_80211be);
658 wifi.SetRemoteStationManager("ns3::ConstantRateWifiManager",
659 "DataMode",
660 StringValue("EhtMcs0"),
661 "ControlMode",
662 StringValue("HtMcs0"));
663
667
668 SpectrumWifiPhyHelper staPhyHelper;
669 SpectrumWifiPhyHelper apPhyHelper;
670 SetChannels(staPhyHelper, m_staChannels, channelMap);
671 SetChannels(apPhyHelper, m_apChannels, channelMap);
672
673 for (const auto& linkId : m_fixedPhyBands)
674 {
675 staPhyHelper.Set(linkId, "FixedPhyBand", BooleanValue(true));
676 }
677
678 WifiMacHelper mac;
679 mac.SetType("ns3::StaWifiMac", // default SSID
680 "MaxMissedBeacons",
681 UintegerValue(1e6), // do not deassociate
682 "ActiveProbing",
683 BooleanValue(false));
684
685 NetDeviceContainer staDevices = wifi.Install(staPhyHelper, mac, wifiStaNodes);
686
687 mac.SetType("ns3::ApWifiMac",
688 "Ssid",
689 SsidValue(Ssid("ns-3-ssid")),
690 "BeaconGeneration",
691 BooleanValue(true));
692
693 NetDeviceContainer apDevices = wifi.Install(apPhyHelper, mac, wifiApNode);
694
695 // Uncomment the lines below to write PCAP files
696 // apPhyHelper.EnablePcap("wifi-mlo_AP", apDevices);
697 // staPhyHelper.EnablePcap("wifi-mlo_STA", staDevices);
698
699 // Assign fixed streams to random variables in use
700 streamNumber += WifiHelper::AssignStreams(apDevices, streamNumber);
701 streamNumber += WifiHelper::AssignStreams(staDevices, streamNumber);
702
703 MobilityHelper mobility;
705
706 positionAlloc->Add(Vector(0.0, 0.0, 0.0));
707 positionAlloc->Add(Vector(1.0, 0.0, 0.0));
708 mobility.SetPositionAllocator(positionAlloc);
709
710 mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
711 mobility.Install(wifiApNode);
712 mobility.Install(wifiStaNodes);
713
714 m_apMac = DynamicCast<ApWifiMac>(DynamicCast<WifiNetDevice>(apDevices.Get(0))->GetMac());
715 for (uint8_t i = 0; i < m_nStations; i++)
716 {
717 m_staMacs[i] =
718 DynamicCast<StaWifiMac>(DynamicCast<WifiNetDevice>(staDevices.Get(i))->GetMac());
719 }
720
721 // Trace PSDUs passed to the PHY on all devices
722 for (uint8_t phyId = 0; phyId < m_apMac->GetDevice()->GetNPhys(); phyId++)
723 {
725 "/NodeList/0/DeviceList/*/$ns3::WifiNetDevice/Phys/" + std::to_string(phyId) +
726 "/PhyTxPsduBegin",
728 }
729 for (uint8_t i = 0; i < m_nStations; i++)
730 {
731 for (uint8_t phyId = 0; phyId < m_staMacs[i]->GetDevice()->GetNPhys(); phyId++)
732 {
733 Config::ConnectWithoutContext("/NodeList/" + std::to_string(i + 1) +
734 "/DeviceList/*/$ns3::WifiNetDevice/Phys/" +
735 std::to_string(phyId) + "/PhyTxPsduBegin",
737 .Bind(m_staMacs[i], phyId));
738 }
739 }
740
741 // install packet socket on all nodes
742 PacketSocketHelper packetSocket;
743 packetSocket.Install(wifiApNode);
744 packetSocket.Install(wifiStaNodes);
745
746 // install a packet socket server on all nodes
747 for (auto nodeIt = NodeList::Begin(); nodeIt != NodeList::End(); ++nodeIt)
748 {
749 PacketSocketAddress srvAddr;
750 auto device = DynamicCast<WifiNetDevice>((*nodeIt)->GetDevice(0));
751 NS_TEST_ASSERT_MSG_NE(device, nullptr, "Expected a WifiNetDevice");
752 srvAddr.SetSingleDevice(device->GetIfIndex());
753 srvAddr.SetProtocol(1);
754
755 auto server = CreateObject<PacketSocketServer>();
756 server->SetLocal(srvAddr);
757 (*nodeIt)->AddApplication(server);
758 server->SetStartTime(Seconds(0)); // now
759 server->SetStopTime(m_duration);
760 }
761
762 for (std::size_t nodeId = 0; nodeId < NodeList::GetNNodes(); nodeId++)
763 {
765 "/NodeList/" + std::to_string(nodeId) +
766 "/ApplicationList/*/$ns3::PacketSocketServer/Rx",
768 }
769
770 // schedule ML setup for one station at a time
771 m_apMac->TraceConnectWithoutContext("AssociatedSta",
773 m_staMacs[0]->SetSsid(Ssid("ns-3-ssid"));
774}
775
778 std::size_t count,
779 std::size_t pktSize,
780 Time delay,
781 uint8_t priority) const
782{
783 auto client = CreateObject<PacketSocketClient>();
784 client->SetAttribute("PacketSize", UintegerValue(pktSize));
785 client->SetAttribute("MaxPackets", UintegerValue(count));
786 client->SetAttribute("Interval", TimeValue(MicroSeconds(0)));
787 client->SetAttribute("Priority", UintegerValue(priority));
788 client->SetRemote(sockAddr);
789 client->SetStartTime(delay);
790 client->SetStopTime(m_duration - Simulator::Now());
791
792 return client;
793}
794
795void
797{
798 if (m_lastAid == aid)
799 {
800 // another STA of this non-AP MLD has already fired this callback
801 return;
802 }
803 m_lastAid = aid;
804
805 // make the next STA to start ML discovery & setup
806 if (aid < m_nStations)
807 {
808 m_staMacs[aid]->SetSsid(Ssid("ns-3-ssid"));
809 return;
810 }
811 // stop generation of beacon frames in order to avoid interference
812 m_apMac->SetAttribute("BeaconGeneration", BooleanValue(false));
813
814 // wait some time (5ms) to allow the completion of association before generating traffic
816}
817
819 WifiScanType scanType,
820 const std::vector<uint8_t>& setupLinks,
822 const std::string& dlTidToLinkMapping,
823 const std::string& ulTidToLinkMapping,
824 bool support160MHzOp)
825 : MultiLinkOperationsTestBase("Check correctness of Multi-Link Setup", 1, baseParams),
826 m_setupLinks(setupLinks),
827 m_scanType(scanType),
828 m_nProbeResp(0),
829 m_apNegSupport(apNegSupport),
830 m_dlTidLinkMappingStr(dlTidToLinkMapping),
831 m_ulTidLinkMappingStr(ulTidToLinkMapping),
832 m_support160MHzOp(support160MHzOp)
833{
834}
835
836void
838{
840
841 m_staMacs[0]->SetAttribute("ActiveProbing", BooleanValue(m_scanType == WifiScanType::ACTIVE));
842 m_apMac->GetEhtConfiguration()->SetAttribute("TidToLinkMappingNegSupport",
844 // For non-AP MLD, it does not make sense to set the negotiation type to 0 (unless the AP MLD
845 // also advertises 0) or 1 (the AP MLD is discarded if it advertises a support of 3)
846 auto staEhtConfig = m_staMacs[0]->GetEhtConfiguration();
847 staEhtConfig->SetAttribute("TidToLinkMappingNegSupport",
848 EnumValue(WifiTidToLinkMappingNegSupport::ANY_LINK_SET));
849 staEhtConfig->SetAttribute("TidToLinkMappingDl", StringValue(m_dlTidLinkMappingStr));
850 staEhtConfig->SetAttribute("TidToLinkMappingUl", StringValue(m_ulTidLinkMappingStr));
851
852 // the negotiated link mapping matches the one configured in EHT configuration, unless
853 // the AP MLD does not support TID-to-link mapping negotiation or the AP MLD supports
854 // the negotiation type 1 and the non-AP MLD is configured with a link mapping that
855 // maps distinct link sets to the TIDs, in which case the default link mapping is used
856 m_dlTidLinkMapping = staEhtConfig->GetTidLinkMapping(WifiDirection::DOWNLINK);
857 m_ulTidLinkMapping = staEhtConfig->GetTidLinkMapping(WifiDirection::UPLINK);
858
859 if (m_apNegSupport == WifiTidToLinkMappingNegSupport::NOT_SUPPORTED ||
860 (m_apNegSupport == WifiTidToLinkMappingNegSupport::SAME_LINK_SET &&
862 {
863 m_dlTidLinkMapping.clear(); // default link mapping
864 m_ulTidLinkMapping.clear(); // default link mapping
865 }
866
867 // find (if any) a TID that is not mapped to all setup links
868 using TupleRefs = std::tuple<std::reference_wrapper<const WifiTidLinkMapping>,
869 std::reference_wrapper<uint8_t>,
870 std::reference_wrapper<std::optional<uint8_t>>,
872 for (auto& [mappingRef, tid1Ref, tid2Ref, mac] :
875 {
876 tid1Ref.get() = 0;
877 for (uint8_t tid1 = 0; tid1 < 8; tid1++)
878 {
879 if (auto it1 = mappingRef.get().find(tid1);
880 it1 != mappingRef.get().cend() && it1->second.size() != m_setupLinks.size())
881 {
882 // found. Now search for another TID with a disjoint mapped link set
883 for (uint8_t tid2 = tid1 + 1; tid2 < 8; tid2++)
884 {
885 if (auto it2 = mappingRef.get().find(tid2);
886 it2 != mappingRef.get().cend() && it2->second.size() != m_setupLinks.size())
887 {
888 std::list<uint8_t> intersection;
889 std::set_intersection(it1->second.cbegin(),
890 it1->second.cend(),
891 it2->second.cbegin(),
892 it2->second.cend(),
893 std::back_inserter(intersection));
894 if (intersection.empty())
895 {
896 // found a second TID
897 tid2Ref.get() = tid2;
898 break;
899 }
900 }
901 }
902 tid1Ref.get() = tid1;
903 break;
904 }
905 }
906
907 std::list<uint8_t> tids = {tid1Ref.get()};
908 if (tid2Ref.get())
909 {
910 tids.emplace_back(*tid2Ref.get());
911 }
912
913 // prevent aggregation of MPDUs
914 for (auto tid : tids)
915 {
916 std::string attrName;
917 switch (QosUtilsMapTidToAc(tid))
918 {
919 case AC_VI:
920 attrName = "VI_MaxAmpduSize";
921 break;
922 case AC_VO:
923 attrName = "VO_MaxAmpduSize";
924 break;
925 case AC_BE:
926 attrName = "BE_MaxAmpduSize";
927 break;
928 case AC_BK:
929 attrName = "BK_MaxAmpduSize";
930 break;
931 default:
932 NS_FATAL_ERROR("Invalid TID " << +tid);
933 }
934
935 mac->SetAttribute(attrName, UintegerValue(100));
936 }
937 }
938
939 // configure support for 160 MHz operations and set the channels again to check that they
940 // are compatible
941 for (auto staMac : m_staMacs)
942 {
943 staMac->GetVhtConfiguration()->SetAttribute("Support160MHzOperation",
945 uint8_t linkId = 0;
946 for (const auto& str : m_staChannels)
947 {
948 staMac->GetWifiPhy(linkId++)->SetAttribute("ChannelSettings", StringValue(str));
949 }
950 }
951}
952
953void
955{
956 // DL traffic
957 {
958 PacketSocketAddress sockAddr;
959 sockAddr.SetSingleDevice(m_apMac->GetDevice()->GetIfIndex());
960 sockAddr.SetPhysicalAddress(m_staMacs[0]->GetDevice()->GetAddress());
961 sockAddr.SetProtocol(1);
962
963 m_apMac->GetDevice()->GetNode()->AddApplication(
964 GetApplication(sockAddr, m_setupLinks.size(), 500, Seconds(0), m_dlTid1));
965 if (m_dlTid2)
966 {
967 m_apMac->GetDevice()->GetNode()->AddApplication(
968 GetApplication(sockAddr, m_setupLinks.size(), 500, Seconds(0), *m_dlTid2));
969 }
970 }
971
972 // UL Traffic
973 {
974 PacketSocketAddress sockAddr;
975 sockAddr.SetSingleDevice(m_staMacs[0]->GetDevice()->GetIfIndex());
976 sockAddr.SetPhysicalAddress(m_apMac->GetDevice()->GetAddress());
977 sockAddr.SetProtocol(1);
978
979 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
980 GetApplication(sockAddr, m_setupLinks.size(), 500, MilliSeconds(500), m_ulTid1));
981 if (m_ulTid2)
982 {
983 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
984 GetApplication(sockAddr, m_setupLinks.size(), 500, MilliSeconds(500), *m_ulTid2));
985 }
986 }
987}
988
989void
991{
993
996
997 /**
998 * Check content of management frames
999 */
1000 std::size_t index = 0;
1001
1002 for (const auto& frameInfo : m_txPsdus)
1003 {
1004 const auto& mpdu = *frameInfo.psduMap.begin()->second->begin();
1005 const auto& linkId = frameInfo.linkId;
1006
1007 switch (mpdu->GetHeader().GetType())
1008 {
1010 CheckBeacon(mpdu, linkId);
1011 break;
1012
1014 CheckProbeResponse(mpdu, linkId);
1015 m_nProbeResp++;
1016 break;
1017
1019 CheckAssocRequest(mpdu, linkId);
1020 break;
1021
1023 CheckAssocResponse(mpdu, linkId);
1024 break;
1025
1026 case WIFI_MAC_QOSDATA:
1027 CheckQosData(mpdu, frameInfo.txVector, linkId, index);
1028 break;
1029
1030 default:
1031 break;
1032 }
1033
1034 index++;
1035 }
1036
1038
1039 std::size_t expectedProbeResp = 0;
1040 if (m_scanType == WifiScanType::ACTIVE)
1041 {
1042 // the number of Probe Response frames that we expect to receive in active mode equals
1043 // the number of channels in common between AP MLD and non-AP MLD at initialization
1044 for (const auto& staChannel : m_staChannels)
1045 {
1046 for (const auto& apChannel : m_apChannels)
1047 {
1048 if (staChannel == apChannel)
1049 {
1050 expectedProbeResp++;
1051 break;
1052 }
1053 }
1054 }
1055 }
1056
1057 NS_TEST_EXPECT_MSG_EQ(m_nProbeResp, expectedProbeResp, "Unexpected number of Probe Responses");
1058
1059 std::size_t expectedRxDlPkts = m_setupLinks.size();
1060 if (m_dlTid2)
1061 {
1062 expectedRxDlPkts *= 2;
1063 }
1064 NS_TEST_EXPECT_MSG_EQ(m_rxPkts[m_staMacs[0]->GetDevice()->GetNode()->GetId()],
1065 expectedRxDlPkts,
1066 "Unexpected number of DL packets received");
1067
1068 std::size_t expectedRxUlPkts = m_setupLinks.size();
1069 if (m_ulTid2)
1070 {
1071 expectedRxUlPkts *= 2;
1072 }
1073 NS_TEST_EXPECT_MSG_EQ(m_rxPkts[m_apMac->GetDevice()->GetNode()->GetId()],
1074 expectedRxUlPkts,
1075 "Unexpected number of UL packets received");
1076
1078}
1079
1080void
1082{
1083 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_BEACON);
1084
1086
1087 NS_TEST_EXPECT_MSG_EQ(m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1088 mpdu->GetHeader().GetAddr2(),
1089 "TA of Beacon frame is not the address of the link it is transmitted on");
1090 MgtBeaconHeader beacon;
1091 mpdu->GetPacket()->PeekHeader(beacon);
1092 const auto& rnr = beacon.Get<ReducedNeighborReport>();
1093 const auto& mle = beacon.Get<MultiLinkElement>();
1094
1095 if (m_apMac->GetNLinks() == 1)
1096 {
1097 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(),
1098 false,
1099 "RNR Element in Beacon frame from single link AP");
1100 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1101 false,
1102 "Multi-Link Element in Beacon frame from single link AP");
1103 return;
1104 }
1105
1106 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(), true, "No RNR Element in Beacon frame");
1107 // All the other APs affiliated with the same AP MLD as the AP sending
1108 // the Beacon frame must be reported in a separate Neighbor AP Info field
1109 NS_TEST_EXPECT_MSG_EQ(rnr->GetNNbrApInfoFields(),
1110 static_cast<std::size_t>(m_apMac->GetNLinks() - 1),
1111 "Unexpected number of Neighbor AP Info fields in RNR");
1112 for (std::size_t nbrApInfoId = 0; nbrApInfoId < rnr->GetNNbrApInfoFields(); nbrApInfoId++)
1113 {
1114 NS_TEST_EXPECT_MSG_EQ(rnr->HasMldParameters(nbrApInfoId),
1115 true,
1116 "MLD Parameters not present");
1117 NS_TEST_EXPECT_MSG_EQ(rnr->GetNTbttInformationFields(nbrApInfoId),
1118 1,
1119 "Expected only one TBTT Info subfield per Neighbor AP Info");
1120 uint8_t nbrLinkId = rnr->GetMldParameters(nbrApInfoId, 0).linkId;
1121 NS_TEST_EXPECT_MSG_EQ(rnr->GetBssid(nbrApInfoId, 0),
1122 m_apMac->GetFrameExchangeManager(nbrLinkId)->GetAddress(),
1123 "BSSID advertised in Neighbor AP Info field "
1124 << nbrApInfoId
1125 << " does not match the address configured on the link "
1126 "advertised in the same field");
1127 }
1128
1129 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Beacon frame");
1130 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1131 m_apMac->GetAddress(),
1132 "Incorrect MLD address advertised in Multi-Link Element");
1133 NS_TEST_EXPECT_MSG_EQ(mle->GetLinkIdInfo(),
1134 +linkId,
1135 "Incorrect Link ID advertised in Multi-Link Element");
1136}
1137
1138void
1140{
1141 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_PROBE_RESPONSE);
1142
1144
1146 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1147 mpdu->GetHeader().GetAddr2(),
1148 "TA of Probe Response is not the address of the link it is transmitted on");
1149 MgtProbeResponseHeader probeResp;
1150 mpdu->GetPacket()->PeekHeader(probeResp);
1151 const auto& rnr = probeResp.Get<ReducedNeighborReport>();
1152 const auto& mle = probeResp.Get<MultiLinkElement>();
1153
1154 if (m_apMac->GetNLinks() == 1)
1155 {
1156 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(),
1157 false,
1158 "RNR Element in Probe Response frame from single link AP");
1159 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1160 false,
1161 "Multi-Link Element in Probe Response frame from single link AP");
1162 return;
1163 }
1164
1165 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(), true, "No RNR Element in Probe Response frame");
1166 // All the other APs affiliated with the same AP MLD as the AP sending
1167 // the Probe Response frame must be reported in a separate Neighbor AP Info field
1168 NS_TEST_EXPECT_MSG_EQ(rnr->GetNNbrApInfoFields(),
1169 static_cast<std::size_t>(m_apMac->GetNLinks() - 1),
1170 "Unexpected number of Neighbor AP Info fields in RNR");
1171 for (std::size_t nbrApInfoId = 0; nbrApInfoId < rnr->GetNNbrApInfoFields(); nbrApInfoId++)
1172 {
1173 NS_TEST_EXPECT_MSG_EQ(rnr->HasMldParameters(nbrApInfoId),
1174 true,
1175 "MLD Parameters not present");
1176 NS_TEST_EXPECT_MSG_EQ(rnr->GetNTbttInformationFields(nbrApInfoId),
1177 1,
1178 "Expected only one TBTT Info subfield per Neighbor AP Info");
1179 uint8_t nbrLinkId = rnr->GetMldParameters(nbrApInfoId, 0).linkId;
1180 NS_TEST_EXPECT_MSG_EQ(rnr->GetBssid(nbrApInfoId, 0),
1181 m_apMac->GetFrameExchangeManager(nbrLinkId)->GetAddress(),
1182 "BSSID advertised in Neighbor AP Info field "
1183 << nbrApInfoId
1184 << " does not match the address configured on the link "
1185 "advertised in the same field");
1186 }
1187
1188 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Probe Response frame");
1189 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1190 m_apMac->GetAddress(),
1191 "Incorrect MLD address advertised in Multi-Link Element");
1192 NS_TEST_EXPECT_MSG_EQ(mle->GetLinkIdInfo(),
1193 +linkId,
1194 "Incorrect Link ID advertised in Multi-Link Element");
1195}
1196
1197void
1199{
1200 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_ASSOCIATION_REQUEST);
1201
1203
1205 m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress(),
1206 mpdu->GetHeader().GetAddr2(),
1207 "TA of Assoc Request frame is not the address of the link it is transmitted on");
1209 mpdu->GetPacket()->PeekHeader(assoc);
1210 const auto& mle = assoc.Get<MultiLinkElement>();
1211
1212 if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetNLinks() == 1)
1213 {
1214 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1215 false,
1216 "Multi-Link Element in Assoc Request frame from single link STA");
1217 }
1218 else
1219 {
1220 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1221 true,
1222 "No Multi-Link Element in Assoc Request frame");
1223 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1224 m_staMacs[0]->GetAddress(),
1225 "Incorrect MLD Address advertised in Multi-Link Element");
1227 mle->GetNPerStaProfileSubelements(),
1228 m_setupLinks.size() - 1,
1229 "Incorrect number of Per-STA Profile subelements in Multi-Link Element");
1230 for (std::size_t i = 0; i < mle->GetNPerStaProfileSubelements(); i++)
1231 {
1232 auto& perStaProfile = mle->GetPerStaProfile(i);
1233 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasStaMacAddress(),
1234 true,
1235 "Per-STA Profile must contain STA MAC address");
1236 // find ID of the local link corresponding to this subelement
1237 auto staLinkId = m_staMacs[0]->GetLinkIdByAddress(perStaProfile.GetStaMacAddress());
1239 staLinkId.has_value(),
1240 true,
1241 "No link found with the STA MAC address advertised in Per-STA Profile");
1243 +staLinkId.value(),
1244 +linkId,
1245 "The STA that sent the Assoc Request should not be included in a Per-STA Profile");
1246 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), staLinkId.value());
1247 NS_TEST_EXPECT_MSG_EQ((it != m_setupLinks.end()),
1248 true,
1249 "Not expecting to setup STA link ID " << +staLinkId.value());
1251 +staLinkId.value(),
1252 +perStaProfile.GetLinkId(),
1253 "Not expecting to request association to AP Link ID in Per-STA Profile");
1254 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasAssocRequest(),
1255 true,
1256 "Missing Association Request in Per-STA Profile");
1257 }
1258 }
1259
1260 const auto& tlm = assoc.Get<TidToLinkMapping>();
1261
1262 // A TID-to-Link Mapping IE is included in the Association Request if and only if the AP MLD
1263 // and the non-AP MLD are performing ML setup (i.e., they both have multiple links) and the
1264 // AP MLD advertises a non-null negotiation support type
1265 if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetNLinks() == 1 ||
1266 m_apNegSupport == WifiTidToLinkMappingNegSupport::NOT_SUPPORTED)
1267 {
1268 NS_TEST_EXPECT_MSG_EQ(tlm.empty(),
1269 true,
1270 "Didn't expect a TID-to-Link Mapping IE in Assoc Request frame");
1271 }
1272 else
1273 {
1274 std::size_t expectedNTlm = (m_dlTidLinkMapping == m_ulTidLinkMapping ? 1 : 2);
1275
1276 NS_TEST_ASSERT_MSG_EQ(tlm.size(),
1277 expectedNTlm,
1278 "Unexpected number of TID-to-Link Mapping IE in Assoc Request");
1279
1280 // lambda to check content of TID-to-Link Mapping IE(s)
1281 auto checkTlm = [&](std::size_t tlmId, WifiDirection dir) {
1282 NS_TEST_EXPECT_MSG_EQ(+static_cast<uint8_t>(tlm[tlmId].m_control.direction),
1283 +static_cast<uint8_t>(dir),
1284 "Unexpected direction in TID-to-Link Mapping IE " << tlmId);
1285 auto& expectedMapping =
1286 (dir == WifiDirection::UPLINK ? m_ulTidLinkMapping : m_dlTidLinkMapping);
1287
1288 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].m_control.defaultMapping,
1289 expectedMapping.empty(),
1290 "Default Link Mapping bit not set correctly");
1291 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].m_linkMapping.size(),
1292 expectedMapping.size(),
1293 "Unexpected number of Link Mapping Of TID n fields");
1294 for (uint8_t tid = 0; tid < 8; tid++)
1295 {
1296 if (auto it = expectedMapping.find(tid); it != expectedMapping.cend())
1297 {
1298 NS_TEST_EXPECT_MSG_EQ((tlm[tlmId].GetLinkMappingOfTid(tid) == it->second),
1299 true,
1300 "Unexpected link mapping for TID "
1301 << +tid << " direction " << dir);
1302 }
1303 else
1304 {
1305 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].GetLinkMappingOfTid(tid).empty(),
1306 true,
1307 "Expecting no Link Mapping Of TID n field for TID "
1308 << +tid << " direction " << dir);
1309 }
1310 }
1311 };
1312
1313 if (tlm.size() == 1)
1314 {
1315 checkTlm(0, WifiDirection::BOTH_DIRECTIONS);
1316 }
1317 else
1318 {
1319 std::size_t dlId = (tlm[0].m_control.direction == WifiDirection::DOWNLINK ? 0 : 1);
1320 std::size_t ulId = (dlId == 0 ? 1 : 0);
1321
1322 checkTlm(dlId, WifiDirection::DOWNLINK);
1323 checkTlm(ulId, WifiDirection::UPLINK);
1324 }
1325 }
1326}
1327
1328void
1330{
1331 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_ASSOCIATION_RESPONSE);
1332
1334
1336 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1337 mpdu->GetHeader().GetAddr2(),
1338 "TA of Assoc Response frame is not the address of the link it is transmitted on");
1340 mpdu->GetPacket()->PeekHeader(assoc);
1341 const auto& mle = assoc.Get<MultiLinkElement>();
1342
1343 if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetNLinks() == 1)
1344 {
1346 mle.has_value(),
1347 false,
1348 "Multi-Link Element in Assoc Response frame with single link AP or single link STA");
1349 return;
1350 }
1351
1352 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Assoc Request frame");
1353 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1354 m_apMac->GetAddress(),
1355 "Incorrect MLD Address advertised in Multi-Link Element");
1356 NS_TEST_EXPECT_MSG_EQ(mle->GetNPerStaProfileSubelements(),
1357 m_setupLinks.size() - 1,
1358 "Incorrect number of Per-STA Profile subelements in Multi-Link Element");
1359 for (std::size_t i = 0; i < mle->GetNPerStaProfileSubelements(); i++)
1360 {
1361 auto& perStaProfile = mle->GetPerStaProfile(i);
1362 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasStaMacAddress(),
1363 true,
1364 "Per-STA Profile must contain STA MAC address");
1365 // find ID of the local link corresponding to this subelement
1366 auto apLinkId = m_apMac->GetLinkIdByAddress(perStaProfile.GetStaMacAddress());
1368 apLinkId.has_value(),
1369 true,
1370 "No link found with the STA MAC address advertised in Per-STA Profile");
1371 NS_TEST_EXPECT_MSG_EQ(+apLinkId.value(),
1372 +perStaProfile.GetLinkId(),
1373 "Link ID and MAC address advertised in Per-STA Profile do not match");
1375 +apLinkId.value(),
1376 +linkId,
1377 "The AP that sent the Assoc Response should not be included in a Per-STA Profile");
1378 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), apLinkId.value());
1379 NS_TEST_EXPECT_MSG_EQ((it != m_setupLinks.end()),
1380 true,
1381 "Not expecting to setup AP link ID " << +apLinkId.value());
1382 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasAssocResponse(),
1383 true,
1384 "Missing Association Response in Per-STA Profile");
1385 }
1386
1387 // For the moment, the AP MLD always accepts a valid TID-to-Link Mapping request, hence
1388 // in every case there is no TID-to-Link Mapping IE in the Association Response
1389 NS_TEST_EXPECT_MSG_EQ(assoc.Get<TidToLinkMapping>().empty(),
1390 true,
1391 "Didn't expect to find a TID-to-Link Mapping IE in Association Response");
1392}
1393
1394void
1396{
1397 /**
1398 * Check outcome of Multi-Link Setup
1399 */
1400 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->IsAssociated(), true, "Expected the STA to be associated");
1401
1402 for (const auto linkId : m_setupLinks)
1403 {
1404 auto staLinkId = (m_staMacs[0]->GetNLinks() > 1 ? linkId : SINGLE_LINK_OP_ID);
1405 auto apLinkId = (m_apMac->GetNLinks() > 1 ? linkId : SINGLE_LINK_OP_ID);
1406
1407 auto staAddr = m_staMacs[0]->GetFrameExchangeManager(staLinkId)->GetAddress();
1408 auto apAddr = m_apMac->GetFrameExchangeManager(apLinkId)->GetAddress();
1409
1410 auto staRemoteMgr = m_staMacs[0]->GetWifiRemoteStationManager(staLinkId);
1411 auto apRemoteMgr = m_apMac->GetWifiRemoteStationManager(apLinkId);
1412
1413 // STA side
1414 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetFrameExchangeManager(staLinkId)->GetBssid(),
1415 apAddr,
1416 "Unexpected BSSID for STA link ID " << +staLinkId);
1417 if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1418 {
1419 NS_TEST_EXPECT_MSG_EQ((staRemoteMgr->GetMldAddress(apAddr) == m_apMac->GetAddress()),
1420 true,
1421 "Incorrect MLD address stored by STA on link ID " << +staLinkId);
1423 (staRemoteMgr->GetAffiliatedStaAddress(m_apMac->GetAddress()) == apAddr),
1424 true,
1425 "Incorrect affiliated address stored by STA on link ID " << +staLinkId);
1426 }
1427
1428 // AP side
1429 NS_TEST_EXPECT_MSG_EQ(apRemoteMgr->IsAssociated(staAddr),
1430 true,
1431 "Expecting STA " << staAddr << " to be associated on link "
1432 << +apLinkId);
1433 if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1434 {
1436 (apRemoteMgr->GetMldAddress(staAddr) == m_staMacs[0]->GetAddress()),
1437 true,
1438 "Incorrect MLD address stored by AP on link ID " << +apLinkId);
1440 (apRemoteMgr->GetAffiliatedStaAddress(m_staMacs[0]->GetAddress()) == staAddr),
1441 true,
1442 "Incorrect affiliated address stored by AP on link ID " << +apLinkId);
1443 }
1444 auto aid = m_apMac->GetAssociationId(staAddr, apLinkId);
1445 const auto& staList = m_apMac->GetStaList(apLinkId);
1446 NS_TEST_EXPECT_MSG_EQ((staList.find(aid) != staList.end()),
1447 true,
1448 "STA " << staAddr << " not found in list of associated STAs");
1449
1450 // STA of non-AP MLD operate on the same channel as the AP (or on its primary80 if the AP
1451 // operates on a 160 MHz channel and non-AP MLD does not support 160 MHz operations)
1452 const auto& staChannel = m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel();
1453 const auto& apChannel = m_apMac->GetWifiPhy(apLinkId)->GetOperatingChannel();
1454
1455 auto width = apChannel.GetTotalWidth();
1456 auto primary20 = apChannel.GetPrimaryChannelIndex(MHz_u{20});
1457
1458 if (width > MHz_u{80} && !m_support160MHzOp)
1459 {
1460 width = MHz_u{80};
1461 primary20 -= apChannel.GetPrimaryChannelIndex(MHz_u{80}) * 4;
1462 }
1463
1464 NS_TEST_EXPECT_MSG_EQ(+staChannel.GetNumber(),
1465 +apChannel.GetPrimaryChannelNumber(width, WIFI_STANDARD_80211be),
1466 "Incorrect operating channel number for STA on link " << +staLinkId);
1467 NS_TEST_EXPECT_MSG_EQ(staChannel.GetFrequency(),
1468 apChannel.GetPrimaryChannelCenterFrequency(width),
1469 "Incorrect operating channel frequency for STA on link "
1470 << +staLinkId);
1471 NS_TEST_EXPECT_MSG_EQ(staChannel.GetWidth(),
1472 width,
1473 "Incorrect operating channel width for STA on link " << +staLinkId);
1474 NS_TEST_EXPECT_MSG_EQ(+staChannel.GetPhyBand(),
1475 +apChannel.GetPhyBand(),
1476 "Incorrect operating PHY band for STA on link " << +staLinkId);
1477 NS_TEST_EXPECT_MSG_EQ(+staChannel.GetPrimaryChannelIndex(MHz_u{20}),
1478 +primary20,
1479 "Incorrect operating primary channel index for STA on link "
1480 << +staLinkId);
1481 }
1482
1483 // lambda to check the link mapping stored at wifi MAC
1484 auto checkStoredMapping =
1485 [this](Ptr<WifiMac> mac, Ptr<WifiMac> dest, WifiDirection dir, bool present) {
1486 NS_TEST_ASSERT_MSG_EQ(mac->GetTidToLinkMapping(dest->GetAddress(), dir).has_value(),
1487 present,
1488 "Link mapping stored by "
1489 << (mac->GetTypeOfStation() == AP ? "AP" : "non-AP")
1490 << " MLD for " << dir << " direction "
1491 << (present ? "expected" : "not expected"));
1492 if (present)
1493 {
1494 const auto& mapping =
1495 (dir == WifiDirection::DOWNLINK ? m_dlTidLinkMapping : m_ulTidLinkMapping);
1497 (mac->GetTidToLinkMapping(dest->GetAddress(), dir)->get() == mapping),
1498 true,
1499 "Incorrect link mapping stored by "
1500 << (mac->GetTypeOfStation() == AP ? "AP" : "non-AP") << " MLD for " << dir
1501 << " direction");
1502
1503 // check correctness of WifiMac::TidMappedOnLink function
1504 std::set<uint8_t> setupLinks(m_setupLinks.cbegin(), m_setupLinks.cend());
1505 for (uint8_t tid = 0; tid < 8; ++tid)
1506 {
1507 const auto& linkSet = mapping.contains(tid) ? mapping.at(tid) : setupLinks;
1508
1509 for (const auto linkId : setupLinks)
1510 {
1512 mac->TidMappedOnLink(dest->GetAddress(), dir, tid, linkId),
1513 linkSet.contains(linkId),
1514 "Incorrect return value on " << (mac == m_apMac ? "AP" : "STA")
1515 << " direction " << dir << " TID " << +tid
1516 << " linkID " << +linkId);
1517 }
1518 }
1519 }
1520 };
1521
1522 auto storedMapping = m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1 &&
1523 m_apNegSupport > WifiTidToLinkMappingNegSupport::NOT_SUPPORTED;
1524 checkStoredMapping(m_apMac, m_staMacs[0], WifiDirection::DOWNLINK, storedMapping);
1525 checkStoredMapping(m_apMac, m_staMacs[0], WifiDirection::UPLINK, storedMapping);
1526 checkStoredMapping(m_staMacs[0], m_apMac, WifiDirection::DOWNLINK, storedMapping);
1527 checkStoredMapping(m_staMacs[0], m_apMac, WifiDirection::UPLINK, storedMapping);
1528}
1529
1530void
1532{
1533 if (m_apMac->GetNLinks() > 1)
1534 {
1537 m_staMacs[0]->GetAddress(),
1538 0);
1539
1540 for (uint8_t linkId = 0; linkId < m_apMac->GetNLinks(); ++linkId)
1541 {
1542 auto it = std::find(m_setupLinks.cbegin(), m_setupLinks.cend(), linkId);
1543
1544 // the queue on the AP should have a mask if and only if the link has been setup
1545 auto mask = m_apMac->GetMacQueueScheduler()->GetQueueLinkMask(AC_BE, queueId, linkId);
1546 NS_TEST_EXPECT_MSG_EQ(mask.has_value(),
1547 (it != m_setupLinks.cend()),
1548 "Unexpected presence/absence of mask on link " << +linkId);
1549 }
1550 }
1551
1552 if (m_staMacs[0]->GetNLinks() == 1)
1553 {
1554 // no link is disabled on a single link device
1555 return;
1556 }
1557
1558 for (const auto& linkId : m_staMacs[0]->GetLinkIds())
1559 {
1560 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), linkId);
1561 if (it == m_setupLinks.end())
1562 {
1563 // the link has not been setup
1564 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId)->GetState()->IsStateOff(),
1565 true,
1566 "Link " << +linkId << " has not been setup but is not disabled");
1567 continue;
1568 }
1569
1570 // the link has been setup and must be active
1571 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId)->GetState()->IsStateOff(),
1572 false,
1573 "Expecting link " << +linkId << " to be active");
1574 }
1575}
1576
1577void
1579 const WifiTxVector& txvector,
1580 uint8_t linkId,
1581 std::size_t index)
1582{
1584 const auto& hdr = mpdu->GetHeader();
1585
1586 NS_TEST_ASSERT_MSG_EQ(hdr.IsQosData(), true, "Expected a QoS data frame");
1587
1588 // check TX width
1589 // STA of non-AP MLD operate on the same channel as the AP (or on its primary80 if the AP
1590 // operates on a 160 MHz channel and non-AP MLD does not support 160 MHz operations)
1591 MHz_u width;
1592
1593 if (!hdr.IsToDs() && hdr.IsFromDs())
1594 {
1595 dir = WifiDirection::DOWNLINK;
1596 width = m_apMac->GetWifiPhy(linkId)->GetOperatingChannel().GetTotalWidth();
1597 }
1598 else if (hdr.IsToDs() && !hdr.IsFromDs())
1599 {
1600 dir = WifiDirection::UPLINK;
1601 width = m_staMacs[0]->GetWifiPhy(linkId)->GetOperatingChannel().GetTotalWidth();
1602 }
1603 else
1604 {
1605 NS_ABORT_MSG("Invalid combination for QoS data frame: ToDS(" << hdr.IsToDs() << ") FromDS("
1606 << hdr.IsFromDs() << ")");
1607 }
1608
1609 if (width > MHz_u{80} && !m_support160MHzOp)
1610 {
1611 width = MHz_u{80};
1612 }
1613 NS_TEST_EXPECT_MSG_EQ(txvector.GetChannelWidth(), width, "Unexpected TX width");
1614
1615 const auto& tid1 = (dir == WifiDirection::DOWNLINK) ? m_dlTid1 : m_ulTid1;
1616 const auto& tid2 = (dir == WifiDirection::DOWNLINK) ? m_dlTid2 : m_ulTid2;
1617 uint8_t tid = hdr.GetQosTid();
1618
1619 NS_TEST_ASSERT_MSG_NE((tid == tid1),
1620 (tid2.has_value() && tid == *tid2),
1621 "QoS frame with unexpected TID " << +tid);
1622
1623 // lambda to find the link set the given TID is mapped to
1624 auto findLinkSet = [this, dir](uint8_t tid) -> std::set<uint8_t> {
1625 std::set<uint8_t> linkSet(m_setupLinks.cbegin(), m_setupLinks.cend());
1626 if (auto mappingOptRef = m_apMac->GetTidToLinkMapping(m_staMacs[0]->GetAddress(), dir))
1627 {
1628 // if the TID is not present in the mapping, it is mapped to all setup links
1629 if (auto it = mappingOptRef->get().find(tid); it != mappingOptRef->get().cend())
1630 {
1631 linkSet = it->second;
1632 NS_ASSERT_MSG(!linkSet.empty(), "TID " << +tid << " mapped to no link");
1633 }
1634 }
1635 return linkSet;
1636 };
1637
1638 auto linkSet = findLinkSet(tid);
1639 auto& qosFrames = (tid == tid1) ? m_qosFrames1 : m_qosFrames2;
1640
1641 // Let N the size of the link set, the first N QoS data frames are sent simultaneously
1642 // on the links of the set, the others (if any) will be sent afterwards on such links
1643
1644 // number of concurrent frames of the same TID transmitted so far (excluding current frame)
1645 std::size_t nConcurFrames = std::min(qosFrames.size(), linkSet.size());
1646
1647 // iterate over the concurrent frames of the same TID transmitted so far
1648 for (std::size_t i = 0; i < nConcurFrames; i++)
1649 {
1650 auto prev = qosFrames[i];
1651
1652 // TX duration of i-th frame
1653 auto band = m_apMac->GetWifiPhy(m_txPsdus[prev].linkId)->GetPhyBand();
1654 Time txDuration =
1655 WifiPhy::CalculateTxDuration(m_txPsdus[prev].psduMap, m_txPsdus[prev].txVector, band);
1656
1657 // the current frame is transmitted concurrently with this previous frame if it is
1658 // within the first N (size of the link set) frames, otherwise it is transmitted after
1659 // this previous frame if they have been transmitted on the same link
1660 if (qosFrames.size() < linkSet.size())
1661 {
1662 // the current frame can be sent concurrently with this previous frame
1663 NS_TEST_EXPECT_MSG_LT(m_txPsdus[index].startTx,
1664 m_txPsdus[prev].startTx + txDuration,
1665 "The " << dir << " QoS frame number " << qosFrames.size()
1666 << " was not sent concurrently with others on link "
1667 << +linkId << " which TID " << +tid << " is mapped to");
1668 }
1669 else if (m_txPsdus[prev].linkId == linkId)
1670 {
1671 // the current frame is sent afterwards
1672 NS_TEST_EXPECT_MSG_GT(m_txPsdus[index].startTx,
1673 m_txPsdus[prev].startTx + txDuration,
1674 "The " << dir << " QoS frame number " << qosFrames.size()
1675 << " was sent concurrently with others on a link "
1676 << +linkId << " which TID " << +tid << " is mapped to");
1677 }
1678 }
1679
1680 if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1681 {
1682 NS_TEST_EXPECT_MSG_EQ(std::count(linkSet.cbegin(), linkSet.cend(), linkId),
1683 1,
1684 "QoS frame sent on Link ID "
1685 << +linkId << " that does not belong to the link set of TID "
1686 << +tid);
1687 }
1688
1689 if (tid2)
1690 {
1691 // QoS frames of two distinct TIDs are sent.
1692 auto otherTid = (tid == tid1) ? *tid2 : tid1;
1693 const auto& otherQosFrames = (tid == tid1) ? m_qosFrames2 : m_qosFrames1;
1694 auto otherLinkSet = findLinkSet(otherTid);
1695
1696 // number of concurrent frames of the other TID transmitted so far
1697 std::size_t nOtherConcurFrames = std::min(otherQosFrames.size(), otherLinkSet.size());
1698
1699 // iterate over the concurrent frames of the other TID
1700 for (std::size_t i = 0; i < nOtherConcurFrames; i++)
1701 {
1702 auto prev = otherQosFrames[i];
1703
1704 // TX duration of i-th frame
1705 auto band = m_apMac->GetWifiPhy(m_txPsdus[prev].linkId)->GetPhyBand();
1706 Time txDuration = WifiPhy::CalculateTxDuration(m_txPsdus[prev].psduMap,
1707 m_txPsdus[prev].txVector,
1708 band);
1709
1710 // the current frame is transmitted concurrently with this previous frame of the
1711 // other TID if it is within the first N (size of the link set) frames of its TID
1712 if (qosFrames.size() < linkSet.size())
1713 {
1714 // the current frame can be sent concurrently with this previous frame
1715 NS_TEST_EXPECT_MSG_LT(m_txPsdus[index].startTx,
1716 m_txPsdus[prev].startTx + txDuration,
1717 "The " << dir << " QoS frame number " << qosFrames.size()
1718 << " was not sent concurrently with others with TID "
1719 << +otherTid);
1720 }
1721 }
1722 }
1723
1724 // insert the frame
1725 qosFrames.emplace_back(index);
1726
1727 if (qosFrames.size() == m_setupLinks.size())
1728 {
1729 qosFrames.clear();
1730 }
1731}
1732
1734 WifiTrafficPattern trafficPattern,
1735 WifiBaEnabled baEnabled,
1736 WifiUseBarAfterMissedBa useBarAfterMissedBa,
1737 uint8_t nMaxInflight)
1739 std::string("Check data transmission between MLDs ") +
1740 (baEnabled == WifiBaEnabled::YES
1741 ? (useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES
1742 ? "with BA agreement, send BAR after BlockAck timeout"
1743 : "with BA agreement, send Data frames after BlockAck timeout")
1744 : "without BA agreement") +
1745 " (Traffic pattern: " + std::to_string(static_cast<uint8_t>(trafficPattern)) +
1746 (baEnabled == WifiBaEnabled::YES ? ", nMaxInflight=" + std::to_string(nMaxInflight)
1747 : "") +
1748 ")",
1749 2,
1750 baseParams),
1751 m_trafficPattern(trafficPattern),
1752 m_baEnabled(baEnabled == WifiBaEnabled::YES),
1753 m_useBarAfterMissedBa(useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES),
1754 m_nMaxInflight(nMaxInflight),
1755 m_nPackets(trafficPattern == WifiTrafficPattern::STA_TO_BCAST ||
1756 trafficPattern == WifiTrafficPattern::STA_TO_STA
1757 ? 4
1758 : 8)
1759{
1760}
1761
1762void
1764 uint8_t phyId,
1765 WifiConstPsduMap psduMap,
1766 WifiTxVector txVector,
1767 double txPowerW)
1768{
1769 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
1770 auto linkId = m_txPsdus.back().linkId;
1771
1772 auto psdu = psduMap.begin()->second;
1773
1774 switch (psdu->GetHeader(0).GetType())
1775 {
1777 // a management frame is a DL frame if TA equals BSSID
1778 CheckAddresses(psdu,
1779 psdu->GetHeader(0).GetAddr2() == psdu->GetHeader(0).GetAddr3() ? DL : UL);
1780 if (!m_baEnabled)
1781 {
1782 // corrupt all management action frames (ADDBA Request frames) to prevent
1783 // the establishment of a BA agreement
1784 m_uidList.push_front(psdu->GetPacket()->GetUid());
1785 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1786 NS_LOG_INFO("CORRUPTED");
1787 }
1788 break;
1789 case WIFI_MAC_QOSDATA:
1790 CheckAddresses(psdu);
1791
1792 for (const auto& mpdu : *psdu)
1793 {
1794 // determine the max number of simultaneous transmissions for this MPDU
1795 // (only if sent by the traffic source and this is not a broadcast frame)
1796 if (m_baEnabled && m_sourceMac->GetLinkIds().count(linkId) == 1 &&
1797 m_sourceMac->GetFrameExchangeManager(linkId)->GetAddress() ==
1798 mpdu->GetHeader().GetAddr2() &&
1799 !mpdu->GetHeader().GetAddr1().IsGroup())
1800 {
1801 auto seqNo = mpdu->GetHeader().GetSequenceNumber();
1802 auto [it, success] =
1803 m_inflightCount.insert({seqNo, mpdu->GetInFlightLinkIds().size()});
1804 if (!success)
1805 {
1806 it->second = std::max(it->second, mpdu->GetInFlightLinkIds().size());
1807 }
1808 }
1809 }
1810 for (std::size_t i = 0; i < psdu->GetNMpdus(); i++)
1811 {
1812 // corrupt QoS data frame with sequence number equal to 1 (only once) if we are
1813 // not in the AP to broadcast traffic pattern (broadcast frames are not retransmitted)
1814 // nor in the STA to broadcast or STA to STA traffic patterns (retransmissions from
1815 // STA 1 could collide with frames forwarded by the AP)
1816 if (psdu->GetHeader(i).GetSequenceNumber() != 1 ||
1820 {
1821 continue;
1822 }
1823 auto uid = psdu->GetPayload(i)->GetUid();
1824 if (!m_dataCorrupted)
1825 {
1826 m_uidList.push_front(uid);
1827 m_dataCorrupted = true;
1828 NS_LOG_INFO("CORRUPTED");
1829 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1830 }
1831 else
1832 {
1833 // do not corrupt the QoS data frame anymore
1834 if (auto it = std::find(m_uidList.cbegin(), m_uidList.cend(), uid);
1835 it != m_uidList.cend())
1836 {
1837 m_uidList.erase(it);
1838 }
1839 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1840 }
1841 break;
1842 }
1843 break;
1844 case WIFI_MAC_CTL_BACKRESP: {
1845 // ignore BlockAck frames not addressed to the source of the application packets
1846 if (!m_sourceMac->GetLinkIdByAddress(psdu->GetHeader(0).GetAddr1()))
1847 {
1848 break;
1849 }
1850 if (m_nMaxInflight > 1)
1851 {
1852 // we do not check the content of BlockAck when m_nMaxInflight is greater than 1
1853 break;
1854 }
1855 CheckBlockAck(psdu, txVector, linkId);
1857 if (m_blockAckCount == 2)
1858 {
1859 // corrupt the second BlockAck frame to simulate a missed BlockAck
1860 m_uidList.push_front(psdu->GetPacket()->GetUid());
1861 NS_LOG_INFO("CORRUPTED");
1862 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1863 }
1864 break;
1866 // ignore BlockAckReq frames not transmitted by the source of the application packets
1867 if (m_sourceMac->GetLinkIdByAddress(psdu->GetHeader(0).GetAddr2()))
1868 {
1870 }
1871 break;
1872 }
1873 default:;
1874 }
1875}
1876
1877void
1879 const WifiTxVector& txVector,
1880 uint8_t linkId)
1881{
1882 NS_TEST_ASSERT_MSG_EQ(m_baEnabled, true, "No BlockAck expected without BA agreement");
1884 true,
1885 "No BlockAck expected in AP to broadcast traffic pattern");
1886
1887 /*
1888 * ┌───────┬───────X ┌───────┐
1889 * link 0 │ 0 │ 1 │ │ 1 │
1890 * ───────┴───────┴───────┴┬──┬────┴───────┴┬───┬────────────────────────
1891 * │BA│ │ACK│
1892 * └──┘ └───┘
1893 * ┌───────┬───────┐ ┌───────┬───────┐
1894 * link 1 │ 2 │ 3 │ │ 2 │ 3 │
1895 * ────────────────────┴───────┴───────┴┬──X───┴───────┴───────┴┬──┬─────
1896 * │BA│ │BA│
1897 * └──┘ └──┘
1898 */
1899 auto mpdu = *psdu->begin();
1900 CtrlBAckResponseHeader blockAck;
1901 mpdu->GetPacket()->PeekHeader(blockAck);
1902 bool isMpdu1corrupted = (m_trafficPattern == WifiTrafficPattern::STA_TO_AP ||
1904
1905 switch (m_blockAckCount)
1906 {
1907 case 0: // first BlockAck frame (all traffic patterns)
1909 true,
1910 "MPDU 0 expected to be successfully received");
1912 blockAck.IsPacketReceived(1),
1913 !isMpdu1corrupted,
1914 "MPDU 1 expected to be received only in STA_TO_STA/STA_TO_BCAST scenarios");
1915 // if there are at least two links setup, we expect all MPDUs to be inflight
1916 // (on distinct links)
1917 if (m_staMacs[0]->GetSetupLinkIds().size() > 1)
1918 {
1919 auto queue = m_sourceMac->GetTxopQueue(AC_BE);
1920 auto rcvMac = m_sourceMac == m_staMacs[0] ? StaticCast<WifiMac>(m_apMac)
1922 auto item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress());
1923 std::size_t nQueuedPkt = 0;
1924 auto delay = WifiPhy::CalculateTxDuration(psdu,
1925 txVector,
1926 rcvMac->GetWifiPhy(linkId)->GetPhyBand()) +
1927 MicroSeconds(1); // to account for propagation delay
1928
1929 while (item)
1930 {
1931 auto seqNo = item->GetHeader().GetSequenceNumber();
1932 NS_TEST_EXPECT_MSG_EQ(item->IsInFlight(),
1933 true,
1934 "MPDU with seqNo=" << seqNo << " is not in flight");
1935 auto linkIds = item->GetInFlightLinkIds();
1936 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
1937 1,
1938 "MPDU with seqNo=" << seqNo
1939 << " is in flight on multiple links");
1940 // The first two MPDUs are in flight on the same link on which the BlockAck
1941 // is sent. The other two MPDUs (only for AP to STA/STA to AP scenarios) are
1942 // in flight on a different link.
1943 auto srcLinkId = m_sourceMac->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1());
1944 NS_TEST_ASSERT_MSG_EQ(srcLinkId.has_value(),
1945 true,
1946 "Addr1 of BlockAck is not an originator's link address");
1947 NS_TEST_EXPECT_MSG_EQ((*linkIds.begin() == *srcLinkId),
1948 (seqNo <= 1),
1949 "MPDU with seqNo=" << seqNo
1950 << " in flight on unexpected link");
1951 // check the Retry subfield and whether this MPDU is still queued
1952 // after the originator has processed this BlockAck
1953
1954 // MPDUs acknowledged via this BlockAck are no longer queued
1955 bool isQueued = (seqNo > (isMpdu1corrupted ? 0 : 1));
1956 // The Retry subfield is set if the MPDU has not been acknowledged (i.e., it
1957 // is still queued) and has been transmitted on the same link as the BlockAck
1958 // (i.e., its sequence number is less than or equal to 1)
1959 bool isRetry = isQueued && seqNo <= 1;
1960
1961 Simulator::Schedule(delay, [this, item, isQueued, isRetry]() {
1962 NS_TEST_EXPECT_MSG_EQ(item->IsQueued(),
1963 isQueued,
1964 "MPDU with seqNo="
1965 << item->GetHeader().GetSequenceNumber() << " should "
1966 << (isQueued ? "" : "not") << " be queued");
1968 item->GetHeader().IsRetry(),
1969 isRetry,
1970 "Unexpected value for the Retry subfield of the MPDU with seqNo="
1971 << item->GetHeader().GetSequenceNumber());
1972 });
1973
1974 nQueuedPkt++;
1975 item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress(), item);
1976 }
1977 // Each MPDU contains an A-MSDU consisting of two MSDUs
1978 NS_TEST_EXPECT_MSG_EQ(nQueuedPkt, m_nPackets / 2, "Unexpected number of queued MPDUs");
1979 }
1980 break;
1981 case 1: // second BlockAck frame (STA to AP and AP to STA traffic patterns only)
1982 case 2: // third BlockAck frame (STA to AP and AP to STA traffic patterns only)
1985 true,
1986 "Did not expect to receive a second BlockAck");
1987 // the second BlockAck is corrupted, but the data frames have been received successfully
1988 std::pair<uint16_t, uint16_t> seqNos;
1989 // if multiple links were setup, the transmission of the second A-MPDU started before
1990 // the end of the first one, so the second A-MPDU includes MPDUs with sequence numbers
1991 // 2 and 3. Otherwise, MPDU with sequence number 1 is retransmitted along with the MPDU
1992 // with sequence number 2.
1993 if (m_staMacs[0]->GetSetupLinkIds().size() > 1)
1994 {
1995 seqNos = {2, 3};
1996 }
1997 else
1998 {
1999 seqNos = {1, 2};
2000 }
2001 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(seqNos.first),
2002 true,
2003 "MPDU " << seqNos.first << " expected to be successfully received");
2004 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(seqNos.second),
2005 true,
2006 "MPDU " << seqNos.second << " expected to be successfully received");
2007 break;
2008 }
2009}
2010
2011void
2013{
2015
2016 if (m_baEnabled)
2017 {
2018 // Enable A-MSDU aggregation. Max A-MSDU size is set such that two MSDUs can be aggregated
2019 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
2020 {
2021 mac->SetAttribute("BE_MaxAmsduSize", UintegerValue(2100));
2022 mac->GetQosTxop(AC_BE)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2024 mac->GetQosTxop(AC_BE)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2025 }
2026 }
2027
2028 // install post reception error model on all devices
2029 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
2030 {
2031 auto errorModel = CreateObject<ListErrorModel>();
2032 m_errorModels[m_apMac->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2033 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2034 }
2035 for (std::size_t i : {0, 1})
2036 {
2037 for (const auto linkId : m_staMacs[i]->GetLinkIds())
2038 {
2039 auto errorModel = CreateObject<ListErrorModel>();
2040 m_errorModels[m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2041 m_staMacs[i]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2042 }
2043 }
2044}
2045
2046void
2048{
2049 Address destAddr;
2050
2051 switch (m_trafficPattern)
2052 {
2055 destAddr = m_staMacs[1]->GetDevice()->GetAddress();
2056 break;
2059 destAddr = m_apMac->GetDevice()->GetAddress();
2060 break;
2063 destAddr = m_staMacs[1]->GetDevice()->GetAddress();
2064 break;
2067 destAddr = Mac48Address::GetBroadcast();
2068 break;
2071 destAddr = Mac48Address::GetBroadcast();
2072 break;
2073 }
2074
2075 PacketSocketAddress sockAddr;
2076 sockAddr.SetSingleDevice(m_sourceMac->GetDevice()->GetIfIndex());
2077 sockAddr.SetPhysicalAddress(destAddr);
2078 sockAddr.SetProtocol(1);
2079
2080 // install first client application generating at most 4 packets
2081 m_sourceMac->GetDevice()->GetNode()->AddApplication(
2082 GetApplication(sockAddr, std::min<std::size_t>(m_nPackets, 4), 1000));
2083
2084 if (m_nPackets > 4)
2085 {
2086 // install a second client application generating the remaining packets and
2087 // starting during transmission of first A-MPDU, if multiple links are setup
2088 m_sourceMac->GetDevice()->GetNode()->AddApplication(
2089 GetApplication(sockAddr, m_nPackets - 4, 1000, MilliSeconds(4)));
2090 }
2091
2093}
2094
2095void
2097{
2099
2100 // Expected number of packets received by each node (AP, STA 0, STA 1) at application layer
2101 std::array<std::size_t, 3> expectedRxPkts{};
2102
2103 switch (m_trafficPattern)
2104 {
2107 // only STA 1 receives the m_nPackets packets that have been transmitted
2108 expectedRxPkts[2] = m_nPackets;
2109 break;
2111 // only the AP receives the m_nPackets packets that have been transmitted
2112 expectedRxPkts[0] = m_nPackets;
2113 break;
2115 // the AP replicates the broadcast frames on all the links, hence each station
2116 // receives the m_nPackets packets N times, where N is the number of setup link
2117 expectedRxPkts[1] = m_nPackets * m_staMacs[0]->GetSetupLinkIds().size();
2118 expectedRxPkts[2] = m_nPackets * m_staMacs[1]->GetSetupLinkIds().size();
2119 break;
2121 // the AP receives the m_nPackets packets and then replicates them on all the links,
2122 // hence STA 1 receives m_nPackets packets N times, where N is the number of setup link
2123 expectedRxPkts[0] = m_nPackets;
2124 expectedRxPkts[2] = m_nPackets * m_staMacs[1]->GetSetupLinkIds().size();
2125 break;
2126 }
2127
2129 +expectedRxPkts[0],
2130 "Unexpected number of packets received by the AP");
2132 +expectedRxPkts[1],
2133 "Unexpected number of packets received by STA 0");
2135 +expectedRxPkts[2],
2136 "Unexpected number of packets received by STA 1");
2137
2138 // check that the expected number of BlockAck frames are transmitted
2139 if (m_baEnabled && m_nMaxInflight == 1)
2140 {
2141 std::size_t expectedBaCount = 0;
2142 std::size_t expectedBarCount = 0;
2143
2144 switch (m_trafficPattern)
2145 {
2148 // two A-MPDUs are transmitted and one BlockAck is corrupted
2149 expectedBaCount = 3;
2150 // one BlockAckReq is sent if m_useBarAfterMissedBa is true
2151 expectedBarCount = m_useBarAfterMissedBa ? 1 : 0;
2152 break;
2155 // only one A-MPDU is transmitted and the BlockAck is not corrupted
2156 expectedBaCount = 1;
2157 break;
2158 default:;
2159 }
2161 expectedBaCount,
2162 "Unexpected number of BlockAck frames");
2164 expectedBarCount,
2165 "Unexpected number of BlockAckReq frames");
2166 }
2167
2168 // check that setting the QosTxop::NMaxInflights attribute has the expected effect.
2169 // We do not support sending an MPDU multiple times concurrently without Block Ack
2170 // agreement. Also, broadcast frames are already duplicated and sent on all links.
2172 {
2174 m_inflightCount.size(),
2175 m_nPackets / 2,
2176 "Did not collect number of simultaneous transmissions for all data frames");
2177
2178 auto nMaxInflight = std::min(m_nMaxInflight, m_staMacs[0]->GetSetupLinkIds().size());
2179 std::size_t maxCount = 0;
2180 for (const auto& [seqNo, count] : m_inflightCount)
2181 {
2183 count,
2184 nMaxInflight,
2185 "MPDU with seqNo=" << seqNo
2186 << " transmitted simultaneously more times than allowed");
2187 maxCount = std::max(maxCount, count);
2188 }
2189
2191 maxCount,
2192 nMaxInflight,
2193 "Expected that at least one data frame was transmitted simultaneously a number of "
2194 "times equal to the NMaxInflights attribute");
2195 }
2196
2198}
2199
2201 WifiMuTrafficPattern muTrafficPattern,
2202 WifiUseBarAfterMissedBa useBarAfterMissedBa,
2203 uint8_t nMaxInflight)
2205 std::string("Check MU data transmission between MLDs ") +
2206 (useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES
2207 ? "(send BAR after BlockAck timeout,"
2208 : "(send Data frames after BlockAck timeout,") +
2209 " MU Traffic pattern: " + std::to_string(static_cast<uint8_t>(muTrafficPattern)) +
2210 ", nMaxInflight=" + std::to_string(nMaxInflight) + ")",
2211 2,
2212 baseParams),
2213 m_muTrafficPattern(muTrafficPattern),
2214 m_useBarAfterMissedBa(useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES),
2215 m_nMaxInflight(nMaxInflight),
2216 m_sockets(m_nStations),
2217 m_nPackets(muTrafficPattern == WifiMuTrafficPattern::UL_MU ? 4 : 8)
2218{
2219}
2220
2221void
2223 uint8_t phyId,
2224 WifiConstPsduMap psduMap,
2225 WifiTxVector txVector,
2226 double txPowerW)
2227{
2228 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2229 auto linkId = m_txPsdus.back().linkId;
2230
2231 CtrlTriggerHeader trigger;
2232
2233 for (const auto& [staId, psdu] : psduMap)
2234 {
2235 switch (psdu->GetHeader(0).GetType())
2236 {
2237 case WIFI_MAC_QOSDATA:
2238 CheckAddresses(psdu);
2239 if (psdu->GetHeader(0).HasData())
2240 {
2241 bool isDl = psdu->GetHeader(0).IsFromDs();
2242 auto linkAddress =
2243 isDl ? psdu->GetHeader(0).GetAddr1() : psdu->GetHeader(0).GetAddr2();
2244 auto address = m_apMac->GetMldAddress(linkAddress).value_or(linkAddress);
2245
2246 for (const auto& mpdu : *psdu)
2247 {
2248 // determine the max number of simultaneous transmissions for this MPDU
2249 auto seqNo = mpdu->GetHeader().GetSequenceNumber();
2250 auto [it, success] = m_inflightCount.insert(
2251 {{address, seqNo}, mpdu->GetInFlightLinkIds().size()});
2252 if (!success)
2253 {
2254 it->second = std::max(it->second, mpdu->GetInFlightLinkIds().size());
2255 }
2256 }
2257 for (std::size_t i = 0; i < psdu->GetNMpdus(); i++)
2258 {
2259 // MPDUs with seqNo=2 are always transmitted in an MU PPDU
2260 if (psdu->GetHeader(i).GetSequenceNumber() == 2)
2261 {
2263 {
2264 NS_TEST_EXPECT_MSG_EQ(txVector.IsUlMu(),
2265 true,
2266 "MPDU " << **std::next(psdu->begin(), i)
2267 << " not transmitted in a TB PPDU");
2268 }
2269 else
2270 {
2271 NS_TEST_EXPECT_MSG_EQ(txVector.GetHeMuUserInfoMap().size(),
2272 2,
2273 "MPDU " << **std::next(psdu->begin(), i)
2274 << " not transmitted in a DL MU PPDU");
2275 }
2276 }
2277 // corrupt QoS data frame with sequence number equal to 3 (only once)
2278 if (psdu->GetHeader(i).GetSequenceNumber() != 3)
2279 {
2280 continue;
2281 }
2282 auto uid = psdu->GetPayload(i)->GetUid();
2283 if (!m_dataCorruptedSta)
2284 {
2285 m_uidList.push_front(uid);
2286 m_dataCorruptedSta = isDl ? psdu->GetAddr1() : psdu->GetAddr2();
2287 NS_LOG_INFO("CORRUPTED");
2288 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2289 }
2290 else if ((isDl && m_dataCorruptedSta == psdu->GetAddr1()) ||
2291 (!isDl && m_dataCorruptedSta == psdu->GetAddr2()))
2292 {
2293 // do not corrupt the QoS data frame anymore
2294 if (auto it = std::find(m_uidList.cbegin(), m_uidList.cend(), uid);
2295 it != m_uidList.cend())
2296 {
2297 m_uidList.erase(it);
2298 }
2299 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2300 }
2301 break;
2302 }
2303 }
2304 break;
2306 if (m_nMaxInflight > 1)
2307 {
2308 // we do not check the content of BlockAck when m_nMaxInflight is greater than 1
2309 break;
2310 }
2311 CheckBlockAck(psdu, txVector, linkId);
2313 // to simulate a missed BlockAck, corrupt the fifth BlockAck frame (the first
2314 // two BlockAck frames are sent to acknowledge the QoS data frames that triggered
2315 // the establishment of Block Ack agreements)
2316 if (m_blockAckCount == 5)
2317 {
2318 // corrupt the third BlockAck frame to simulate a missed BlockAck
2319 m_uidList.push_front(psdu->GetPacket()->GetUid());
2320 NS_LOG_INFO("CORRUPTED");
2321 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2322 }
2323 break;
2325 psdu->GetPayload(0)->PeekHeader(trigger);
2326 // the MU scheduler requests channel access on all links but we have to perform the
2327 // following actions only once (hence why we only consider TF transmitted on link 0)
2328 if (trigger.IsBasic() && m_waitFirstTf)
2329 {
2330 m_waitFirstTf = false;
2331 // the AP is starting the transmission of the Basic Trigger frame, so generate
2332 // the configured number of packets at STAs, which are sent in TB PPDUs, when
2333 // transmission of the Trigger Frame ends
2334 auto band = mac->GetWifiPhy(linkId)->GetPhyBand();
2335 Time txDuration = WifiPhy::CalculateTxDuration(psduMap, txVector, band);
2336 for (uint8_t i = 0; i < m_nStations; i++)
2337 {
2338 m_staMacs[i]->GetDevice()->GetNode()->AddApplication(
2339 GetApplication(m_sockets[i], m_nPackets, 450, txDuration, i * 4));
2340 }
2341 }
2342 if (++m_tfCount == m_staMacs[0]->GetSetupLinkIds().size())
2343 {
2344 // a TF has been sent on all the setup links, we can now disable UL OFDMA
2345 auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
2346 NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "Expected an aggregated MU scheduler");
2347 muScheduler->SetAttribute("EnableUlOfdma", BooleanValue(false));
2348 }
2349 break;
2350 default:;
2351 }
2352 }
2353}
2354
2355void
2357 const WifiTxVector& txVector,
2358 uint8_t linkId)
2359{
2360 /*
2361 * Example sequence with DL_MU_BAR_BA_SEQUENCE
2362 * ┌───────┬───────X
2363 * (To:1) │ 2 │ 3 │
2364 * ├───────┼───────┤ ┌───┐ ┌───────┐
2365 * [link 0] (To:0) │ 2 │ 3 │ │BAR│ (To:1) │ 3 │
2366 * ────────────────┴───────┴───────┴┬──┼───┼──┬──────────┴───────┴┬───┬────────
2367 * │BA│ │BA│ │ACK│
2368 * └──┘ └──┘ └───┘
2369 * ┌───────┬───────┐
2370 * (To:1) │ 4 │ 5 │
2371 * ├───────┼───────┤ ┌───┐ ┌───┐
2372 * [link 1] (To:0) │ 4 │ 5 │ │BAR│ │BAR│
2373 * ────────────────────────────┴───────┴───────┴┬──X────┴───┴┬──┼───┼──┬───────
2374 * │BA│ │BA│ │BA│
2375 * └──┘ └──┘ └──┘
2376 *
2377 * Example sequence with UL_MU
2378 *
2379 * ┌──┐ ┌────┐ ┌───┐
2380 * [link 0] │TF│ │M-BA│ │ACK│
2381 * ─────────┴──┴──┬───────┬───────┬──┴────┴────────────┬───────┬─┴───┴─────────
2382 * (From:0) │ 2 │ 3 │ (From:1) │ 3 │
2383 * ├───────┼───────┤ └───────┘
2384 * (From:1) │ 2 │ 3 │
2385 * └───────┴───────X
2386 * ┌──┐
2387 * [link 1] │TF│
2388 * ─────────┴──┴──┬───────────────┬────────────────────────────────────────────
2389 * (From:0) │ QoS Null │
2390 * ├───────────────┤
2391 * (From:1) │ QoS Null │
2392 * └───────────────┘
2393 */
2394 auto mpdu = *psdu->begin();
2395 CtrlBAckResponseHeader blockAck;
2396 mpdu->GetPacket()->PeekHeader(blockAck);
2397 bool isMpdu3corrupted;
2398
2399 switch (m_blockAckCount)
2400 {
2401 case 0:
2402 case 1: // Ignore the first two BlockAck frames that acknowledged frames sent to establish BA
2403 break;
2404 case 2:
2406 {
2407 NS_TEST_EXPECT_MSG_EQ(blockAck.IsMultiSta(), true, "Expected a Multi-STA BlockAck");
2408 for (uint8_t i = 0; i < m_nStations; i++)
2409 {
2410 auto indices = blockAck.FindPerAidTidInfoWithAid(m_staMacs[i]->GetAssociationId());
2411 NS_TEST_ASSERT_MSG_EQ(indices.size(), 1, "Expected one Per AID TID Info per STA");
2412 auto index = indices.front();
2414 true,
2415 "Expected that a QoS data frame was corrupted");
2416 isMpdu3corrupted =
2417 m_staMacs[i]->GetLinkIdByAddress(*m_dataCorruptedSta).has_value();
2418 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(2, index),
2419 true,
2420 "MPDU 2 expected to be successfully received");
2421 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(3, index),
2422 !isMpdu3corrupted,
2423 "Unexpected reception status for MPDU 3");
2424 }
2425
2426 break;
2427 }
2428 case 3:
2429 // BlockAck frames in response to the first DL MU PPDU
2430 isMpdu3corrupted = (mpdu->GetHeader().GetAddr2() == m_dataCorruptedSta);
2432 true,
2433 "MPDU 2 expected to be successfully received");
2435 !isMpdu3corrupted,
2436 "Unexpected reception status for MPDU 3");
2437 // in case of DL MU, if there are at least two links setup, we expect all MPDUs to
2438 // be inflight (on distinct links)
2440 m_staMacs[0]->GetSetupLinkIds().size() > 1)
2441 {
2442 auto queue = m_apMac->GetTxopQueue(AC_BE);
2443 Ptr<StaWifiMac> rcvMac;
2444 if (m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress() ==
2445 mpdu->GetHeader().GetAddr2())
2446 {
2447 rcvMac = m_staMacs[0];
2448 }
2449 else if (m_staMacs[1]->GetFrameExchangeManager(linkId)->GetAddress() ==
2450 mpdu->GetHeader().GetAddr2())
2451 {
2452 rcvMac = m_staMacs[1];
2453 }
2454 else
2455 {
2456 NS_ABORT_MSG("BlockAck frame not sent by a station in DL scenario");
2457 }
2458 auto item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress());
2459 std::size_t nQueuedPkt = 0;
2460 auto delay = WifiPhy::CalculateTxDuration(psdu,
2461 txVector,
2462 rcvMac->GetWifiPhy(linkId)->GetPhyBand()) +
2463 MicroSeconds(1); // to account for propagation delay
2464
2465 while (item)
2466 {
2467 auto seqNo = item->GetHeader().GetSequenceNumber();
2468 NS_TEST_EXPECT_MSG_EQ(item->IsInFlight(),
2469 true,
2470 "MPDU with seqNo=" << seqNo << " is not in flight");
2471 auto linkIds = item->GetInFlightLinkIds();
2472 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
2473 1,
2474 "MPDU with seqNo=" << seqNo
2475 << " is in flight on multiple links");
2476 // The first two MPDUs are in flight on the same link on which the BlockAck
2477 // is sent. The other two MPDUs (only for AP to STA/STA to AP scenarios) are
2478 // in flight on a different link.
2479 auto srcLinkId = m_apMac->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1());
2480 NS_TEST_ASSERT_MSG_EQ(srcLinkId.has_value(),
2481 true,
2482 "Addr1 of BlockAck is not an originator's link address");
2483 NS_TEST_EXPECT_MSG_EQ((*linkIds.begin() == *srcLinkId),
2484 (seqNo <= 3),
2485 "MPDU with seqNo=" << seqNo
2486 << " in flight on unexpected link");
2487 // check the Retry subfield and whether this MPDU is still queued
2488 // after the originator has processed this BlockAck
2489
2490 // MPDUs acknowledged via this BlockAck are no longer queued
2491 bool isQueued = (seqNo > (isMpdu3corrupted ? 2 : 3));
2492 // The Retry subfield is set if the MPDU has not been acknowledged (i.e., it
2493 // is still queued) and has been transmitted on the same link as the BlockAck
2494 // (i.e., its sequence number is less than or equal to 2)
2495 bool isRetry = isQueued && seqNo <= 3;
2496
2497 Simulator::Schedule(delay, [this, item, isQueued, isRetry]() {
2498 NS_TEST_EXPECT_MSG_EQ(item->IsQueued(),
2499 isQueued,
2500 "MPDU with seqNo="
2501 << item->GetHeader().GetSequenceNumber() << " should "
2502 << (isQueued ? "" : "not") << " be queued");
2504 item->GetHeader().IsRetry(),
2505 isRetry,
2506 "Unexpected value for the Retry subfield of the MPDU with seqNo="
2507 << item->GetHeader().GetSequenceNumber());
2508 });
2509
2510 nQueuedPkt++;
2511 item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress(), item);
2512 }
2513 // Each MPDU contains an A-MSDU consisting of two MSDUs
2514 NS_TEST_EXPECT_MSG_EQ(nQueuedPkt, m_nPackets / 2, "Unexpected number of queued MPDUs");
2515 }
2516 break;
2517 }
2518}
2519
2520void
2522{
2523 switch (m_muTrafficPattern)
2524 {
2526 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2528 break;
2530 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2532 break;
2534 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2536 break;
2537 default:;
2538 }
2539
2541
2542 // Enable A-MSDU aggregation. Max A-MSDU size is set such that two MSDUs can be aggregated
2543 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
2544 {
2545 mac->SetAttribute("BE_MaxAmsduSize", UintegerValue(1050));
2546 mac->GetQosTxop(AC_BE)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2548 mac->GetQosTxop(AC_BE)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2549
2550 mac->SetAttribute("VI_MaxAmsduSize", UintegerValue(1050));
2551 mac->GetQosTxop(AC_VI)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2553 mac->GetQosTxop(AC_VI)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2554 }
2555
2556 // aggregate MU scheduler
2557 auto muScheduler = CreateObjectWithAttributes<RrMultiUserScheduler>("EnableUlOfdma",
2558 BooleanValue(false),
2559 "EnableBsrp",
2560 BooleanValue(false),
2561 "UlPsduSize",
2562 UintegerValue(2000));
2563 m_apMac->AggregateObject(muScheduler);
2564
2565 // install post reception error model on all devices
2566 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
2567 {
2568 auto errorModel = CreateObject<ListErrorModel>();
2569 m_errorModels[m_apMac->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2570 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2571 }
2572 for (std::size_t i : {0, 1})
2573 {
2574 for (const auto linkId : m_staMacs[i]->GetLinkIds())
2575 {
2576 auto errorModel = CreateObject<ListErrorModel>();
2577 m_errorModels[m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2578 m_staMacs[i]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2579 }
2580 }
2581}
2582
2583void
2585{
2587 {
2588 // DL Traffic
2589 for (uint8_t i = 0; i < m_nStations; i++)
2590 {
2591 PacketSocketAddress sockAddr;
2592 sockAddr.SetSingleDevice(m_apMac->GetDevice()->GetIfIndex());
2593 sockAddr.SetPhysicalAddress(m_staMacs[i]->GetDevice()->GetAddress());
2594 sockAddr.SetProtocol(1);
2595
2596 // the first client application generates three packets in order
2597 // to trigger the establishment of a Block Ack agreement
2598 m_apMac->GetDevice()->GetNode()->AddApplication(
2599 GetApplication(sockAddr, 3, 450, i * MilliSeconds(50)));
2600
2601 // the second client application generates the first half of the selected number
2602 // of packets, which are sent in DL MU PPDUs, and starts after all BA agreements
2603 // are established
2604 m_apMac->GetDevice()->GetNode()->AddApplication(
2605 GetApplication(sockAddr, m_nPackets / 2, 450, m_nStations * MilliSeconds(50)));
2606
2607 // the third client application generates the second half of the selected number
2608 // of packets, which are sent in DL MU PPDUs, and starts during transmission of
2609 // first A-MPDU, if multiple links are setup
2610 m_apMac->GetDevice()->GetNode()->AddApplication(
2611 GetApplication(sockAddr,
2612 m_nPackets / 2,
2613 450,
2615 }
2616 }
2617 else
2618 {
2619 // UL Traffic
2620 for (uint8_t i = 0; i < m_nStations; i++)
2621 {
2622 m_sockets[i].SetSingleDevice(m_staMacs[i]->GetDevice()->GetIfIndex());
2623 m_sockets[i].SetPhysicalAddress(m_apMac->GetDevice()->GetAddress());
2624 m_sockets[i].SetProtocol(1);
2625
2626 // the first client application generates three packets in order
2627 // to trigger the establishment of a Block Ack agreement
2628 m_staMacs[i]->GetDevice()->GetNode()->AddApplication(
2629 GetApplication(m_sockets[i], 3, 450, i * MilliSeconds(50), i * 4));
2630
2631 // packets to be included in TB PPDUs are generated (by Transmit()) when
2632 // the first Basic Trigger Frame is sent by the AP
2633 }
2634
2635 // MU scheduler starts requesting channel access when we are done with BA agreements
2637 auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
2638 NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "Expected an aggregated MU scheduler");
2639 muScheduler->SetAttribute("EnableUlOfdma", BooleanValue(true));
2640 muScheduler->SetAccessReqInterval(MilliSeconds(3));
2641 // channel access is requested only once
2642 muScheduler->SetAccessReqInterval(Seconds(0));
2643 });
2644 }
2645
2647}
2648
2649void
2651{
2653
2654 // Expected number of packets received by each node (AP, STA 0, STA 1) at application layer
2655 std::array<std::size_t, 3> expectedRxPkts{};
2656
2657 switch (m_muTrafficPattern)
2658 {
2662 // both STA 0 and STA 1 receive m_nPackets + 3 (sent to trigger BA establishment) packets
2663 expectedRxPkts[1] = m_nPackets + 3;
2664 expectedRxPkts[2] = m_nPackets + 3;
2665 break;
2667 // AP receives m_nPackets + 3 (sent to trigger BA establishment) packets from each station
2668 expectedRxPkts[0] = 2 * (m_nPackets + 3);
2669 break;
2670 }
2671
2673 +expectedRxPkts[0],
2674 "Unexpected number of packets received by the AP");
2676 +expectedRxPkts[1],
2677 "Unexpected number of packets received by STA 0");
2679 +expectedRxPkts[2],
2680 "Unexpected number of packets received by STA 1");
2681
2682 // check that setting the QosTxop::NMaxInflights attribute has the expected effect.
2683 // For DL, for each station we send 2 MPDUs to trigger BA agreement and m_nPackets / 2 MPDUs
2684 // For UL, each station sends 2 MPDUs to trigger BA agreement and m_nPackets / 2 MPDUs
2686 m_inflightCount.size(),
2687 2 * (2 + m_nPackets / 2),
2688 "Did not collect number of simultaneous transmissions for all data frames");
2689
2690 auto nMaxInflight = std::min(m_nMaxInflight, m_staMacs[0]->GetSetupLinkIds().size());
2691 std::size_t maxCount = 0;
2692 for (const auto& [txSeqNoPair, count] : m_inflightCount)
2693 {
2695 nMaxInflight,
2696 "MPDU with seqNo="
2697 << txSeqNoPair.second
2698 << " transmitted simultaneously more times than allowed");
2699 maxCount = std::max(maxCount, count);
2700 }
2701
2703 maxCount,
2704 nMaxInflight,
2705 "Expected that at least one data frame was transmitted simultaneously a number of "
2706 "times equal to the NMaxInflights attribute");
2707
2709}
2710
2713 "Check sequence numbers after CTS timeout",
2714 1,
2715 BaseParams{{"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2716 {"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2717 {}}),
2718 m_nQosDataFrames(0),
2719 m_errorModel(CreateObject<ListErrorModel>()),
2720 m_rtsCorrupted(false)
2721{
2722}
2723
2724void
2726{
2727 // Enable RTS/CTS
2728 Config::SetDefault("ns3::WifiRemoteStationManager::RtsCtsThreshold", StringValue("1000"));
2729
2731
2732 // install post reception error model on all STAs affiliated with non-AP MLD
2733 for (const auto linkId : m_staMacs[0]->GetLinkIds())
2734 {
2735 m_staMacs[0]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel);
2736 }
2737}
2738
2739void
2741{
2742 m_sockAddr.SetSingleDevice(m_apMac->GetDevice()->GetIfIndex());
2743 m_sockAddr.SetPhysicalAddress(m_staMacs[0]->GetAddress());
2745
2746 // install client application generating 4 packets
2747 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(m_sockAddr, 4, 1000));
2748}
2749
2750void
2752 uint8_t phyId,
2753 WifiConstPsduMap psduMap,
2754 WifiTxVector txVector,
2755 double txPowerW)
2756{
2757 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2758
2759 auto psdu = psduMap.begin()->second;
2760
2761 if (psdu->GetHeader(0).IsRts() && !m_rtsCorrupted)
2762 {
2763 m_errorModel->SetList({psdu->GetPacket()->GetUid()});
2764 m_rtsCorrupted = true;
2765 // generate other packets when the first RTS is transmitted
2766 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(m_sockAddr, 4, 1000));
2767 }
2768 else if (psdu->GetHeader(0).IsQosData())
2769 {
2771
2772 if (m_nQosDataFrames == 2)
2773 {
2774 // generate other packets when the second QoS data frame is transmitted
2775 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(m_sockAddr, 4, 1000));
2776 }
2777 }
2778}
2779
2780void
2782{
2785
2786 NS_TEST_EXPECT_MSG_EQ(m_nQosDataFrames, 3, "Unexpected number of transmitted QoS data frames");
2787
2788 std::size_t count{};
2789
2790 for (const auto& txPsdu : m_txPsdus)
2791 {
2792 auto psdu = txPsdu.psduMap.begin()->second;
2793
2794 if (!psdu->GetHeader(0).IsQosData())
2795 {
2796 continue;
2797 }
2798
2799 NS_TEST_EXPECT_MSG_EQ(psdu->GetNMpdus(), 4, "Unexpected number of MPDUs in A-MPDU");
2800
2801 count++;
2802 uint16_t expectedSeqNo{};
2803
2804 switch (count)
2805 {
2806 case 1:
2807 expectedSeqNo = 4;
2808 break;
2809 case 2:
2810 expectedSeqNo = 0;
2811 break;
2812 case 3:
2813 expectedSeqNo = 8;
2814 break;
2815 }
2816
2817 for (const auto& mpdu : *PeekPointer(psdu))
2818 {
2819 NS_TEST_EXPECT_MSG_EQ(mpdu->GetHeader().GetSequenceNumber(),
2820 expectedSeqNo++,
2821 "Unexpected sequence number");
2822 }
2823 }
2824
2826}
2827
2830 "Check starting sequence number update after ADDBA Response timeout",
2831 1,
2832 BaseParams{{"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2833 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2834 {}}),
2835 m_nQosDataCount(0),
2836 m_staErrorModel(CreateObject<ListErrorModel>())
2837{
2838}
2839
2840void
2842{
2843 // Enable RTS/CTS by setting a threshold lower than packet size (1000)
2844 Config::SetDefault("ns3::WifiRemoteStationManager::RtsCtsThreshold", UintegerValue(900));
2845
2847
2848 // install post reception error model on all STAs affiliated with non-AP MLD
2849 for (const auto linkId : m_staMacs[0]->GetLinkIds())
2850 {
2851 m_staMacs[0]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_staErrorModel);
2852 }
2853}
2854
2855void
2857{
2858 m_sockAddr.SetSingleDevice(m_apMac->GetDevice()->GetIfIndex());
2859 m_sockAddr.SetPhysicalAddress(m_staMacs[0]->GetAddress());
2861
2862 // install client application generating 1 packet of 1000 bytes on the AP MLD
2863 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(m_sockAddr, 1, 1000));
2864}
2865
2866void
2868 uint8_t phyId,
2869 WifiConstPsduMap psduMap,
2870 WifiTxVector txVector,
2871 double txPowerW)
2872{
2873 auto psdu = psduMap.begin()->second;
2874 const auto& hdr = psdu->GetHeader(0);
2875
2876 if (hdr.IsAck())
2877 {
2878 NS_TEST_ASSERT_MSG_EQ(m_txPsdus.empty(), false, "No frame preceding transmitted Ack");
2879
2880 auto prevPsdu = m_txPsdus.back().psduMap.begin()->second;
2881
2882 if (prevPsdu->GetHeader(0).IsAction())
2883 {
2884 WifiActionHeader actionHdr;
2885 (*prevPsdu->begin())->GetPacket()->PeekHeader(actionHdr);
2886 if (actionHdr.GetCategory() == WifiActionHeader::BLOCK_ACK &&
2888 {
2889 // non-AP MLD is acknowledging the ADDBA Request sent by the AP MLD. When the
2890 // AP MLD receives the Ack, it starts an AddBaResponse timer; when the timer
2891 // expires, the AP MLD starts sending data frames with normal acknowledgment.
2892 // Block transmissions of the non-AP MLD on the link that has to be used to send
2893 // the ADDBA Response from now until the end of the timer.
2894
2895 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2896 m_apMac->GetAddress(),
2897 {phyId});
2898
2899 auto band = m_apMac->GetWifiPhy(m_txPsdus.back().linkId)->GetPhyBand();
2900 auto ackDuration = WifiPhy::CalculateTxDuration(psduMap, txVector, band);
2901
2902 // After the AddBaResponse timeout, unblock transmissions of the non-AP MLD on the
2903 // link on which the ADDBA Response has to be sent and block transmissions of the
2904 // AP MLD on the same link, so that we recreate the situation where the AP MLD sends
2905 // the QoS data frame on a link while the non-AP MLD is sending the ADDBA Response
2906 // frame on another link.
2908 ackDuration + m_apMac->GetQosTxop(AC_BE)->GetAddBaResponseTimeout(),
2909 [=, this]() {
2910 m_apMac->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2911 m_staMacs[0]->GetAddress(),
2912 {phyId});
2913 m_staMacs[0]->UnblockUnicastTxOnLinks(
2914 WifiQueueBlockedReason::TID_NOT_MAPPED,
2915 m_apMac->GetAddress(),
2916 {phyId});
2917 });
2918 }
2919 }
2920 }
2921
2922 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2923
2924 if (hdr.IsAction())
2925 {
2926 WifiActionHeader actionHdr;
2927 (*psdu->begin())->GetPacket()->PeekHeader(actionHdr);
2928 if (actionHdr.GetCategory() == WifiActionHeader::BLOCK_ACK &&
2930 {
2931 auto band = m_staMacs[0]->GetDevice()->GetPhy(phyId)->GetPhyBand();
2932 auto addBaRespDuration = WifiPhy::CalculateTxDuration(psduMap, txVector, band);
2933
2934 Simulator::Schedule(addBaRespDuration + TimeStep(1), [=, this]() {
2935 // After the AP MLD has received the ADDBA Response frame:
2936 // - check that the AP has one queued QoS data frame that is in flight
2937 auto mpdu = m_apMac->GetTxopQueue(AC_BE)->Peek();
2938 NS_TEST_ASSERT_MSG_NE(mpdu, nullptr, "Expected an MPDU in the AP MLD queue");
2939 NS_TEST_EXPECT_MSG_EQ(mpdu->GetHeader().IsQosData(),
2940 true,
2941 "Expected a QoS data frame");
2943 mpdu->IsInFlight(),
2944 true,
2945 "Expected the data frame to be inflight when ADDBA RESP is received");
2946
2947 // - check that the starting sequence number at the originator (AP MLD) equals
2948 // the sequence number of the inflight MPDU
2950 m_apMac->GetQosTxop(AC_BE)->GetBaStartingSequence(m_staMacs[0]->GetAddress(),
2951 0),
2952 mpdu->GetHeader().GetSequenceNumber(),
2953 "Unexpected BA Starting Sequence Number");
2954 });
2955 }
2956 }
2957 else if (hdr.IsQosData())
2958 {
2959 // corrupt the reception of the data frame the first time it is sent
2960 if (m_nQosDataCount++ == 0)
2961 {
2962 m_staErrorModel->SetList({psdu->GetPacket()->GetUid()});
2963 }
2964 else
2965 {
2966 m_staErrorModel->SetList({});
2967 }
2968 }
2969}
2970
2971void
2973{
2976
2977 NS_TEST_EXPECT_MSG_EQ(+m_rxPkts[1], 1, "Unexpected number of packets received by STA 0");
2978 NS_TEST_EXPECT_MSG_EQ(m_nQosDataCount, 2, "QoS data frame should be transmitted twice");
2979
2981}
2982
2984 : TestSuite("wifi-mlo", Type::UNIT)
2985{
2986 using ParamsTuple = std::tuple<MultiLinkOperationsTestBase::BaseParams, // base config params
2987 std::vector<uint8_t>, // link ID of setup links
2988 WifiTidToLinkMappingNegSupport, // AP negotiation support
2989 std::string, // DL TID-to-Link Mapping
2990 std::string>; // UL TID-to-Link Mapping
2991
2992 AddTestCase(new GetRnrLinkInfoTest(), TestCase::Duration::QUICK);
2993 AddTestCase(new MldSwapLinksTest(), TestCase::Duration::QUICK);
2996 std::vector<std::set<uint8_t>>{{0, 1, 2}, {1, 2}, {0, 1}, {0, 2}, {0}, {1}, {2}}),
2997 TestCase::Duration::QUICK);
2998
2999 // check that the selection of channels in ML setup accounts for the inability of a
3000 // non-AP MLD to operate on a 160 MHz channel
3004 {"{42, 80, BAND_5GHZ, 2}", "{5, 40, BAND_2_4GHZ, 0}", "{7, 80, BAND_6GHZ, 0}"},
3005 {"{3, 40, BAND_2_4GHZ, 0}", "{15, 160, BAND_6GHZ, 7}", "{50, 160, BAND_5GHZ, 2}"},
3006 {}},
3007 WifiScanType::PASSIVE,
3008 {0, 1, 2},
3009 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3010 "",
3011 "",
3012 false),
3013 TestCase::Duration::QUICK);
3014
3015 for (const auto& [baseParams,
3016 setupLinks,
3017 apNegSupport,
3018 dlTidLinkMapping,
3019 ulTidLinkMapping] :
3020 {// matching channels: setup all links
3021 ParamsTuple(
3022 {{"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3023 {"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3024 {}},
3025 {0, 1, 2},
3026 WifiTidToLinkMappingNegSupport::NOT_SUPPORTED, // AP MLD does not support TID-to-Link
3027 // Mapping negotiation
3028 "0,1,2,3 0,1,2; 4,5 0,1", // default mapping used instead
3029 "0,1,2,3 1,2; 6,7 0,1" // default mapping used instead
3030 ),
3031 // non-matching channels, matching PHY bands: setup all links
3032 ParamsTuple({{"{108, 0, BAND_5GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3033 {"{36, 0, BAND_5GHZ, 0}", "{120, 0, BAND_5GHZ, 0}", "{5, 0, BAND_6GHZ, 0}"},
3034 {}},
3035 {0, 1, 2},
3036 WifiTidToLinkMappingNegSupport::SAME_LINK_SET, // AP MLD does not support
3037 // distinct link sets for TIDs
3038 "0,1,2,3 0,1,2; 4,5 0,1", // default mapping used instead
3039 ""),
3040 // non-AP MLD switches band on some links to setup 3 links
3041 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3042 {"{36, 0, BAND_5GHZ, 0}", "{9, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3043 {}},
3044 {0, 1, 2},
3045 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3046 "0,1,2,3 0; 4,5,6,7 1,2", // frames of two TIDs are generated
3047 "0,2,3 1,2; 1,4,5,6,7 0" // frames of two TIDs are generated
3048 ),
3049 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3050 // that band, hence only 2 links are setup
3051 ParamsTuple(
3052 {{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{8, 20, BAND_2_4GHZ, 0}"},
3053 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3054 {0}},
3055 {0, 1},
3056 WifiTidToLinkMappingNegSupport::SAME_LINK_SET, // AP MLD does not support distinct
3057 // link sets for TIDs
3058 "0,1,2,3,4,5,6,7 0",
3059 "0,1,2,3,4,5,6,7 0"),
3060 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3061 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3062 // an AP operating on the same channel; hence 2 links are setup
3063 ParamsTuple(
3064 {{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{8, 20, BAND_2_4GHZ, 0}"},
3065 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3066 {0, 1}},
3067 {0, 1},
3068 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3069 "0,1,2,3 1",
3070 "0,1,2,3 1"),
3071 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3072 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3073 // an AP operating on the same channel; the third link of the non-AP MLD cannot
3074 // change PHY band and there is an AP operating on the same band (different channel);
3075 // hence 2 links are setup by switching channel (not band) on the third link
3076 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{60, 0, BAND_5GHZ, 0}"},
3077 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3078 {0, 1, 2}},
3079 {0, 2},
3080 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3081 "",
3082 ""),
3083 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3084 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3085 // an AP operating on the same channel; hence one link only is setup
3086 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3087 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3088 {0, 1}},
3089 {2},
3090 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3091 "",
3092 ""),
3093 // non-AP MLD has only two STAs and setups two links
3094 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3095 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3096 {}},
3097 {1, 0},
3098 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3099 "0,1,2,3 1",
3100 ""),
3101 // single link non-AP STA associates with an AP affiliated with an AP MLD
3102 ParamsTuple({{"{120, 0, BAND_5GHZ, 0}"},
3103 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3104 {}},
3105 {2}, // link ID of AP MLD only (non-AP STA is single link)
3106 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3107 "",
3108 ""),
3109 // a STA affiliated with a non-AP MLD associates with a single link AP
3110 ParamsTuple({{"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3111 {"{120, 0, BAND_5GHZ, 0}"},
3112 {}},
3113 {2}, // link ID of non-AP MLD only (AP is single link)
3114 WifiTidToLinkMappingNegSupport::NOT_SUPPORTED,
3115 "0,1,2,3 0,1; 4,5,6,7 0,1", // ignored by single link AP
3116 "")})
3117 {
3118 AddTestCase(new MultiLinkSetupTest(baseParams,
3119 WifiScanType::PASSIVE,
3120 setupLinks,
3121 apNegSupport,
3122 dlTidLinkMapping,
3123 ulTidLinkMapping),
3124 TestCase::Duration::QUICK);
3125 AddTestCase(new MultiLinkSetupTest(baseParams,
3126 WifiScanType::ACTIVE,
3127 setupLinks,
3128 apNegSupport,
3129 dlTidLinkMapping,
3130 ulTidLinkMapping),
3131 TestCase::Duration::QUICK);
3132
3133 for (const auto& trafficPattern : {WifiTrafficPattern::STA_TO_STA,
3138 {
3139 // No Block Ack agreement
3140 AddTestCase(new MultiLinkTxTest(baseParams,
3141 trafficPattern,
3144 1),
3145 TestCase::Duration::QUICK);
3146 for (const auto& useBarAfterMissedBa :
3148 {
3149 // Block Ack agreement with nMaxInflight=1
3150 AddTestCase(new MultiLinkTxTest(baseParams,
3151 trafficPattern,
3153 useBarAfterMissedBa,
3154 1),
3155 TestCase::Duration::QUICK);
3156 // Block Ack agreement with nMaxInflight=2
3157 AddTestCase(new MultiLinkTxTest(baseParams,
3158 trafficPattern,
3160 useBarAfterMissedBa,
3161 2),
3162 TestCase::Duration::QUICK);
3163 }
3164 }
3165
3166 for (const auto& muTrafficPattern : {WifiMuTrafficPattern::DL_MU_BAR_BA_SEQUENCE,
3170 {
3171 for (const auto& useBarAfterMissedBa :
3173 {
3174 // Block Ack agreement with nMaxInflight=1
3176 new MultiLinkMuTxTest(baseParams, muTrafficPattern, useBarAfterMissedBa, 1),
3177 TestCase::Duration::QUICK);
3178 // Block Ack agreement with nMaxInflight=2
3180 new MultiLinkMuTxTest(baseParams, muTrafficPattern, useBarAfterMissedBa, 2),
3181 TestCase::Duration::QUICK);
3182 }
3183 }
3184 }
3185
3186 AddTestCase(new ReleaseSeqNoAfterCtsTimeoutTest(), TestCase::Duration::QUICK);
3187 AddTestCase(new StartSeqNoUpdateAfterAddBaTimeoutTest(), TestCase::Duration::QUICK);
3188}
3189
Test that the AIDs that an AP MLD assigns to SLDs and MLDs are all unique.
AidAssignmentTest(const std::vector< std::set< uint8_t > > &linkIds)
Constructor.
const std::vector< std::set< uint8_t > > m_linkIds
link IDs for all non-AP STAs/MLDs
void DoSetup() override
Implementation to do any local setup required for this TestCase.
void SetSsid(Ptr< StaWifiMac > staMac, Mac48Address)
Set the SSID on the next station that needs to start the association procedure.
void DoRun() override
Implementation to actually run this TestCase.
uint16_t m_expectedAid
expected AID for current non-AP STA/MLD
const std::vector< std::string > m_linkChannels
channels for all AP links
NetDeviceContainer m_staDevices
non-AP STAs/MLDs devices
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.
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.
Test update of BA starting sequence number after ADDBA Response timeout in multi-link operations.
void DoSetup() override
Implementation to do any local setup required for this TestCase.
void DoRun() override
Implementation to actually run this TestCase.
PacketSocketAddress m_sockAddr
packet socket address
std::size_t m_nQosDataCount
counter for transmitted QoS data frames
void StartTraffic() override
Start the generation of traffic (needs to be overridden)
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.
Ptr< ListErrorModel > m_staErrorModel
error rate model to corrupt frames at the non-AP MLD
a polymophic address class
Definition address.h:90
AttributeValue implementation for Boolean.
Definition boolean.h:26
Headers for BlockAck response.
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...
Headers for Trigger frames.
bool IsBasic() const
Check if this is a Basic Trigger frame.
Hold variables of type enum.
Definition enum.h:52
void SetList(const std::list< uint64_t > &packetlist)
an EUI-48 address
static Mac48Address GetBroadcast()
Implement the header for management frames of type association request.
Implement the header for management frames of type association and reassociation response.
Implement the header for management frames of type beacon.
Implement the header for management frames of type probe request.
Implement the header for management frames of type probe response.
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
uint32_t GetN() const
Get the number of Ptr<NetDevice> stored in this container.
void Add(NetDeviceContainer other)
Append the contents of another NetDeviceContainer to the end of this container.
Ptr< NetDevice > Get(uint32_t i) const
Get the Ptr<NetDevice> stored in this container at a given index.
keep track of a set of node pointers.
static Iterator Begin()
Definition node-list.cc:226
static uint32_t GetNNodes()
Definition node-list.cc:247
static Iterator End()
Definition node-list.cc:233
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.
AttributeValue implementation for Pointer.
Smart pointer class similar to boost::intrusive_ptr.
The Reduced Neighbor Report element.
std::size_t GetNNbrApInfoFields() const
Get the number of Neighbor AP Information fields.
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 SetMldParameters(std::size_t nbrApInfoId, std::size_t index, const MldParameters &mldParams)
Set the MLD Parameters subfield of the i-th TBTT Information field of the given Neighbor AP Informati...
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:560
static void Destroy()
Execute the events scheduled with ScheduleDestroy().
Definition simulator.cc:131
static Time Now()
Return the current simulation virtual time.
Definition simulator.cc:197
static void Run()
Run the simulation.
Definition simulator.cc:167
static void Stop()
Tell the Simulator the calling event should be the last one executed.
Definition simulator.cc:175
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)
void SetChannel(const Ptr< SpectrumChannel > channel)
The IEEE 802.11 SSID Information Element.
Definition ssid.h:25
AttributeValue implementation for Ssid.
Definition ssid.h:85
Hold variables of type string.
Definition string.h:45
encapsulates test code
Definition test.h:1050
void AddTestCase(TestCase *testCase, Duration duration=Duration::QUICK)
Add an individual child TestCase to this test suite.
Definition test.cc:292
A suite of tests to run.
Definition test.h:1267
Type
Type of test.
Definition test.h:1274
Simulation virtual time values and global simulation resolution.
Definition nstime.h:94
AttributeValue implementation for Time.
Definition nstime.h:1431
a unique identifier for an interface.
Definition type-id.h:49
TypeId SetParent(TypeId tid)
Set the parent TypeId.
Definition type-id.cc:1001
Hold an unsigned integer type.
Definition uinteger.h:34
See IEEE 802.11 chapter 7.3.1.11 Header format: | category: 1 | action value: 1 |.
CategoryValue GetCategory() const
Return the category value.
ActionValue GetAction() const
Return the action value.
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
static int64_t AssignStreams(NetDeviceContainer c, int64_t stream)
Assign a fixed random variable stream number to the random variables used by the PHY and MAC aspects ...
create MAC layers for a ns3::WifiNetDevice.
base class for all MAC-level wifi objects.
Definition wifi-mac.h:90
void SetPcapCaptureType(PcapCaptureType type)
Set the PCAP capture type to be used.
void SetPcapDataLinkType(SupportedPcapDataLinkTypes dlt)
Set the data link type of PCAP traces to be used.
void Set(std::string name, const AttributeValue &v)
@ DLT_IEEE802_11_RADIO
Include Radiotap link layer information.
static Time CalculateTxDuration(uint32_t size, const WifiTxVector &txVector, WifiPhyBand band, uint16_t staId=SU_STA_ID)
Definition wifi-phy.cc:1587
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.
MHz_u GetChannelWidth() 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:55
#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:75
void SetDefault(std::string name, const AttributeValue &value)
Definition config.cc:883
void ConnectWithoutContext(std::string path, const CallbackBase &cb)
Definition config.cc:943
#define NS_FATAL_ERROR(msg)
Report a fatal error with a message and terminate.
#define NS_ABORT_MSG(msg)
Unconditional abnormal program termination with a message.
Definition abort.h:38
#define NS_ABORT_IF(cond)
Abnormal program termination if a condition is true.
Definition abort.h:65
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition log.h:191
#define NS_LOG_INFO(msg)
Use NS_LOG to output a message of level LOG_INFO.
Definition log.h:264
Ptr< T > CreateObject(Args &&... args)
Create an object by type, with varying number of constructor parameters.
Definition object.h:619
Ptr< T > CreateObjectWithAttributes(Args... args)
Allocate an Object on the heap and initialize with a set of attributes.
Ptr< T > Create(Ts &&... args)
Create class instances by constructors with varying numbers of arguments and return them by Ptr.
Definition ptr.h:436
#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:699
#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:134
#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:820
#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:780
#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:946
#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:656
#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:241
#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:554
Time MicroSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1368
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition nstime.h:1344
Time MilliSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1356
WifiScanType
Scan type (active or passive)
AcIndex QosUtilsMapTidToAc(uint8_t tid)
Maps TID (Traffic ID) to Access classes.
Definition qos-utils.cc:123
@ AP
Definition wifi-mac.h:60
@ WIFI_STANDARD_80211be
@ WIFI_PHY_BAND_6GHZ
The 6 GHz band.
@ WIFI_PHY_BAND_5GHZ
The 5 GHz band.
@ AC_BE
Best Effort.
Definition qos-utils.h:64
@ AC_VO
Voice.
Definition qos-utils.h:70
@ AC_VI
Video.
Definition qos-utils.h:68
@ AC_BK
Background.
Definition qos-utils.h:66
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:443
std:: tuple< WifiContainerQueueType, WifiReceiverAddressType, Mac48Address, std::optional< uint8_t > > WifiContainerQueueId
Tuple (queue type, receiver address type, Address, TID) identifying a container queue.
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:684
WifiTidToLinkMappingNegSupport
TID-to-Link Mapping Negotiation Support.
Ptr< T1 > DynamicCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:580
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:272
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_MGT_PROBE_REQUEST
@ 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:36
bool TidToLinkMappingValidForNegType1(const WifiTidLinkMapping &dlLinkMapping, const WifiTidLinkMapping &ulLinkMapping)
Check if the given TID-to-Link Mappings are valid for a negotiation type of 1.
Ptr< T1 > StaticCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:587
std::unordered_map< uint16_t, Ptr< const WifiPsdu > > WifiConstPsduMap
Map of const PSDUs indexed by STA-ID.
Definition wifi-ppdu.h:38
constexpr FrequencyRange WIFI_SPECTRUM_2_4_GHZ
Identifier for the frequency range covering the wifi spectrum in the 2.4 GHz band.
STL namespace.
uint32_t prev
std::string dir
BlockAckActionValue blockAck
block ack
uint32_t pktSize
packet size used for the simulation (in bytes)
static WifiMultiLinkOperationsTestSuite g_wifiMultiLinkOperationsTestSuite
the test suite
WifiMuTrafficPattern
Tested MU traffic patterns.
WifiTrafficPattern
Tested traffic patterns.
WifiUseBarAfterMissedBa
Whether to send a BlockAckReq after a missed BlockAck.
WifiBaEnabled
Block Ack agreement enabled/disabled.