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 WifiAssocType assocType)
269 : TestCase("Test the assignment of AIDs"),
270 m_linkChannels({"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}"}),
271 m_linkIds(linkIds),
272 m_assocType(assocType),
273 m_expectedAid(1) // AID for first station (to be updated in case of AP MLD)
274{
275}
276
277void
279{
282 int64_t streamNumber{1};
283
284 NodeContainer wifiApNode;
285 wifiApNode.Create(1);
286 NodeContainer wifiStaNodes;
287
288 WifiHelper wifi;
289 wifi.SetStandard(WIFI_STANDARD_80211be);
290 wifi.SetRemoteStationManager("ns3::ConstantRateWifiManager",
291 "DataMode",
292 StringValue("EhtMcs0"),
293 "ControlMode",
294 StringValue("HtMcs0"));
295
297
298 // AP MLD
299 SpectrumWifiPhyHelper phyHelper(3);
302 uint8_t linkId = 0;
303 for (const auto& str : m_linkChannels)
304 {
305 phyHelper.Set(linkId++, "ChannelSettings", StringValue(str));
306 }
307 phyHelper.SetChannel(channel);
308
309 WifiMacHelper mac;
310 mac.SetType("ns3::ApWifiMac",
311 "Ssid",
312 SsidValue(Ssid("ns-3-ssid")),
313 "BeaconGeneration",
314 BooleanValue(true));
315
316 auto apDevice = wifi.Install(phyHelper, mac, wifiApNode);
317
318 // non-AP STAs/MLDs
319 for (const auto& links : m_linkIds)
320 {
321 phyHelper = SpectrumWifiPhyHelper(links.size());
324 linkId = 0;
325 for (const auto& id : links)
326 {
327 phyHelper.Set(linkId++, "ChannelSettings", StringValue(m_linkChannels.at(id)));
328 }
329 phyHelper.SetChannel(channel);
330 phyHelper.Set("FixedPhyBand", BooleanValue(true));
331
332 WifiMacHelper mac;
333 mac.SetType("ns3::StaWifiMac",
334 "Ssid", // first non-AP STA/MLD only starts associating
335 SsidValue(Ssid(m_staDevices.GetN() == 0 ? "ns-3-ssid" : "default")),
336 "ActiveProbing",
337 BooleanValue(false),
338 "AssocType",
340
341 auto staNode = CreateObject<Node>();
342 auto staDevice = wifi.Install(phyHelper, mac, staNode);
343 wifiStaNodes.Add(staNode);
344 m_staDevices.Add(staDevice);
345 }
346
347 // Assign fixed streams to random variables in use
348 streamNumber += WifiHelper::AssignStreams(apDevice, streamNumber);
349 streamNumber += WifiHelper::AssignStreams(m_staDevices, streamNumber);
350
351 auto positionAlloc = CreateObject<ListPositionAllocator>();
352 positionAlloc->Add(Vector(0.0, 0.0, 0.0));
353 MobilityHelper mobility;
354 mobility.SetPositionAllocator(positionAlloc);
355 mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
356 mobility.Install(wifiApNode);
357 mobility.Install(wifiStaNodes);
358
359 for (uint32_t i = 0; i < m_staDevices.GetN(); ++i)
360 {
361 auto mac = StaticCast<WifiNetDevice>(m_staDevices.Get(i))->GetMac();
362 mac->TraceConnectWithoutContext(
363 "Assoc",
365 }
366
367 auto apMac = StaticCast<ApWifiMac>(StaticCast<WifiNetDevice>(apDevice.Get(0))->GetMac());
368 m_expectedAid = m_startAid = apMac->GetNextAssociationId();
369}
370
371void
373{
374 const auto aid = staMac->GetAssociationId();
375 std::size_t index = aid - m_startAid;
376
377 std::stringstream linksStr;
378 const auto setupLinks = staMac->GetSetupLinkIds();
379 std::copy(setupLinks.cbegin(), setupLinks.cend(), std::ostream_iterator<int>(linksStr, " "));
380
381 NS_LOG_INFO("STA " << staMac->GetAddress() << " associated with AID " << aid << " links "
382 << linksStr.str());
383
384 NS_TEST_EXPECT_MSG_EQ(aid, m_expectedAid, "Unexpected AID for STA " << staMac->GetAddress());
385
386 // if ML setup is performed, check that the requested links have been setup; otherwise, link 0
387 // only is setup
388 const auto expectedLinks =
390 : std::set{SINGLE_LINK_OP_ID});
391
392 NS_TEST_EXPECT_MSG_EQ((staMac->GetSetupLinkIds() == expectedLinks),
393 true,
394 "Unexpected set of setup links " << linksStr.str());
395
396 if (++index < m_staDevices.GetN())
397 {
398 // let the next STA associate with the AP
399 StaticCast<WifiNetDevice>(m_staDevices.Get(index))->GetMac()->SetSsid(Ssid("ns-3-ssid"));
401 }
402 else
403 {
404 Simulator::Stop(MilliSeconds(5)); // allow sending Ack response to Association Response
405 }
406}
407
408void
410{
411 Simulator::Stop(Seconds(5)); // simulation will stop earlier if all STAs complete association
413
415 m_startAid + m_staDevices.GetN() - 1,
416 "Not all STAs completed association");
417
418 for (uint32_t i = 0; i < m_staDevices.GetN(); ++i)
419 {
420 auto mac = StaticCast<WifiNetDevice>(m_staDevices.Get(i))->GetMac();
421 mac->TraceDisconnectWithoutContext(
422 "Assoc",
424 }
425
427}
428
430 uint8_t nStations,
431 const BaseParams& baseParams)
432 : TestCase(name),
433 m_staChannels(baseParams.staChannels),
434 m_apChannels(baseParams.apChannels),
435 m_fixedPhyBands(baseParams.fixedPhyBands),
436 m_assocType(baseParams.assocType),
437 m_staMacs(nStations),
438 m_nStations(nStations),
439 m_startAid(1),
440 m_lastAid(0),
441 m_rxPkts(nStations + 1)
442{
443}
444
445void
447 std::optional<Direction> direction)
448{
449 std::optional<Mac48Address> apAddr;
450 std::optional<Mac48Address> staAddr;
451
452 // direction for Data frames is derived from ToDS/FromDS flags
453 if (psdu->GetHeader(0).IsQosData())
454 {
455 direction = (!psdu->GetHeader(0).IsToDs() && psdu->GetHeader(0).IsFromDs()) ? DL : UL;
456 }
457 NS_ASSERT(direction);
458
459 if (direction == DL)
460 {
461 if (!psdu->GetAddr1().IsGroup())
462 {
463 staAddr = psdu->GetAddr1();
464 }
465 apAddr = psdu->GetAddr2();
466 }
467 else
468 {
469 if (!psdu->GetAddr1().IsGroup())
470 {
471 apAddr = psdu->GetAddr1();
472 }
473 staAddr = psdu->GetAddr2();
474 }
475
476 if (apAddr)
477 {
478 bool found = false;
479 for (uint8_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
480 {
481 if (m_apMac->GetFrameExchangeManager(linkId)->GetAddress() == *apAddr)
482 {
483 found = true;
484 break;
485 }
486 }
488 true,
489 "Address " << *apAddr << " is not an AP device address. "
490 << "PSDU: " << *psdu);
491 }
492
493 if (staAddr)
494 {
495 bool found = false;
496 for (uint8_t i = 0; i < m_nStations; i++)
497 {
498 for (const auto& linkId : m_staMacs[i]->GetLinkIds())
499 {
500 if (m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress() == *staAddr)
501 {
502 found = true;
503 break;
504 }
505 }
506 if (found)
507 {
508 break;
509 }
510 }
512 true,
513 "Address " << *staAddr << " is not a STA device address. "
514 << "PSDU: " << *psdu);
515 }
516}
517
518void
520 uint8_t phyId,
521 WifiConstPsduMap psduMap,
522 WifiTxVector txVector,
523 double txPowerW)
524{
525 auto linkId = mac->GetLinkForPhy(phyId);
526 NS_TEST_ASSERT_MSG_EQ(linkId.has_value(), true, "No link found for PHY ID " << +phyId);
527 m_txPsdus.push_back({Simulator::Now(), psduMap, txVector, *linkId, phyId});
528
529 for (const auto& [aid, psdu] : psduMap)
530 {
531 std::stringstream ss;
532 ss << std::setprecision(10) << "PSDU #" << m_txPsdus.size() << " Link ID "
533 << +linkId.value() << " Phy ID " << +phyId << " #MPDUs " << psdu->GetNMpdus();
534 for (auto it = psdu->begin(); it != psdu->end(); ++it)
535 {
536 ss << "\n" << **it;
537 }
538 NS_LOG_INFO(ss.str());
539
540 CheckCapabilities(*psdu->begin(), mac, phyId);
541 }
542 NS_LOG_INFO("TXVECTOR = " << txVector << "\n");
543}
544
545void
547{
548 auto band = mac->GetDevice()->GetPhy(phyId)->GetPhyBand();
549 bool hasHtCapabilities;
550 bool hasVhtCapabilities;
551 bool hasHeCapabilities;
552 bool hasHe6GhzCapabilities;
553 bool hasEhtCapabilities;
554
555 auto findCapabilities = [&](auto&& frame) {
556 hasHtCapabilities = frame.template Get<HtCapabilities>().has_value();
557 hasVhtCapabilities = frame.template Get<VhtCapabilities>().has_value();
558 hasHeCapabilities = frame.template Get<HeCapabilities>().has_value();
559 hasHe6GhzCapabilities = frame.template Get<He6GhzBandCapabilities>().has_value();
560 hasEhtCapabilities = frame.template Get<EhtCapabilities>().has_value();
561 };
562
563 switch (mpdu->GetHeader().GetType())
564 {
565 case WIFI_MAC_MGT_BEACON: {
566 MgtBeaconHeader beacon;
567 mpdu->GetPacket()->PeekHeader(beacon);
568 findCapabilities(beacon);
569 }
570 break;
571
573 MgtProbeRequestHeader probeReq;
574 mpdu->GetPacket()->PeekHeader(probeReq);
575 findCapabilities(probeReq);
576 }
577 break;
578
580 MgtProbeResponseHeader probeResp;
581 mpdu->GetPacket()->PeekHeader(probeResp);
582 findCapabilities(probeResp);
583 }
584 break;
585
587 MgtAssocRequestHeader assocReq;
588 mpdu->GetPacket()->PeekHeader(assocReq);
589 findCapabilities(assocReq);
590 }
591 break;
592
594 MgtAssocResponseHeader assocResp;
595 mpdu->GetPacket()->PeekHeader(assocResp);
596 findCapabilities(assocResp);
597 }
598 break;
599
600 default:
601 return;
602 }
603
605 hasHtCapabilities,
606 (band != WIFI_PHY_BAND_6GHZ),
607 "HT Capabilities should not be present in a mgt frame sent in 6 GHz band");
609 hasVhtCapabilities,
610 (band == WIFI_PHY_BAND_5GHZ),
611 "VHT Capabilities should only be present in a mgt frame sent in 5 GHz band");
612 NS_TEST_EXPECT_MSG_EQ(hasHeCapabilities,
613 true,
614 "HE Capabilities should always be present in a mgt frame");
616 hasHe6GhzCapabilities,
617 (band == WIFI_PHY_BAND_6GHZ),
618 "HE 6GHz Band Capabilities should only be present in a mgt frame sent in 6 GHz band");
619 NS_TEST_EXPECT_MSG_EQ(hasEhtCapabilities,
620 true,
621 "EHT Capabilities should always be present in a mgt frame");
622}
623
624void
626{
627 NS_LOG_INFO("Packet received by NODE " << +nodeId << "\n");
628 m_rxPkts[nodeId]++;
629}
630
631void
633 const std::vector<std::string>& channels,
634 const ChannelMap& channelMap)
635{
636 helper = SpectrumWifiPhyHelper(channels.size());
639
640 uint8_t linkId = 0;
641 for (const auto& str : channels)
642 {
643 helper.Set(linkId++, "ChannelSettings", StringValue(str));
644 }
645
646 // NOTE replace this for loop with the line below to use a single spectrum channel
647 // helper.SetChannel(channelMap.begin()->second);
648 for (const auto& [band, channel] : channelMap)
649 {
650 helper.AddChannel(channel, band);
651 }
652}
653
654void
656{
659 int64_t streamNumber = 30;
660
661 NodeContainer wifiApNode;
662 wifiApNode.Create(1);
663
664 NodeContainer wifiStaNodes;
665 wifiStaNodes.Create(m_nStations);
666
667 WifiHelper wifi;
668 // wifi.EnableLogComponents ();
669 wifi.SetStandard(WIFI_STANDARD_80211be);
670 wifi.SetRemoteStationManager("ns3::ConstantRateWifiManager",
671 "DataMode",
672 StringValue("EhtMcs0"),
673 "ControlMode",
674 StringValue("HtMcs0"));
675
679
680 SpectrumWifiPhyHelper staPhyHelper;
681 SpectrumWifiPhyHelper apPhyHelper;
682 SetChannels(staPhyHelper, m_staChannels, channelMap);
683 SetChannels(apPhyHelper, m_apChannels, channelMap);
684
685 for (const auto& linkId : m_fixedPhyBands)
686 {
687 staPhyHelper.Set(linkId, "FixedPhyBand", BooleanValue(true));
688 }
689
690 WifiMacHelper mac;
691 mac.SetType("ns3::StaWifiMac", // default SSID
692 "MaxMissedBeacons",
693 UintegerValue(1e6), // do not deassociate
694 "ActiveProbing",
695 BooleanValue(false),
696 "AssocType",
698
699 NetDeviceContainer staDevices = wifi.Install(staPhyHelper, mac, wifiStaNodes);
700
701 mac.SetType("ns3::ApWifiMac",
702 "Ssid",
703 SsidValue(Ssid("ns-3-ssid")),
704 "BeaconGeneration",
705 BooleanValue(true));
706
707 NetDeviceContainer apDevices = wifi.Install(apPhyHelper, mac, wifiApNode);
708
709 // Uncomment the lines below to write PCAP files
710 // apPhyHelper.EnablePcap("wifi-mlo_AP", apDevices);
711 // staPhyHelper.EnablePcap("wifi-mlo_STA", staDevices);
712
713 // Assign fixed streams to random variables in use
714 streamNumber += WifiHelper::AssignStreams(apDevices, streamNumber);
715 streamNumber += WifiHelper::AssignStreams(staDevices, streamNumber);
716
717 MobilityHelper mobility;
719
720 positionAlloc->Add(Vector(0.0, 0.0, 0.0));
721 positionAlloc->Add(Vector(1.0, 0.0, 0.0));
722 mobility.SetPositionAllocator(positionAlloc);
723
724 mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
725 mobility.Install(wifiApNode);
726 mobility.Install(wifiStaNodes);
727
728 m_apMac = DynamicCast<ApWifiMac>(DynamicCast<WifiNetDevice>(apDevices.Get(0))->GetMac());
729 for (uint8_t i = 0; i < m_nStations; i++)
730 {
731 m_staMacs[i] =
732 DynamicCast<StaWifiMac>(DynamicCast<WifiNetDevice>(staDevices.Get(i))->GetMac());
733 }
734
735 // Trace PSDUs passed to the PHY on all devices
736 for (uint8_t phyId = 0; phyId < m_apMac->GetDevice()->GetNPhys(); phyId++)
737 {
739 "/NodeList/0/DeviceList/*/$ns3::WifiNetDevice/Phys/" + std::to_string(phyId) +
740 "/PhyTxPsduBegin",
742 }
743 for (uint8_t i = 0; i < m_nStations; i++)
744 {
745 for (uint8_t phyId = 0; phyId < m_staMacs[i]->GetDevice()->GetNPhys(); phyId++)
746 {
747 Config::ConnectWithoutContext("/NodeList/" + std::to_string(i + 1) +
748 "/DeviceList/*/$ns3::WifiNetDevice/Phys/" +
749 std::to_string(phyId) + "/PhyTxPsduBegin",
751 .Bind(m_staMacs[i], phyId));
752 }
753 }
754
755 // install packet socket on all nodes
756 PacketSocketHelper packetSocket;
757 packetSocket.Install(wifiApNode);
758 packetSocket.Install(wifiStaNodes);
759
760 // install a packet socket server on all nodes
761 for (auto nodeIt = NodeList::Begin(); nodeIt != NodeList::End(); ++nodeIt)
762 {
763 PacketSocketAddress srvAddr;
764 auto device = DynamicCast<WifiNetDevice>((*nodeIt)->GetDevice(0));
765 NS_TEST_ASSERT_MSG_NE(device, nullptr, "Expected a WifiNetDevice");
766 srvAddr.SetSingleDevice(device->GetIfIndex());
767 srvAddr.SetProtocol(1);
768
769 auto server = CreateObject<PacketSocketServer>();
770 server->SetLocal(srvAddr);
771 (*nodeIt)->AddApplication(server);
772 server->SetStartTime(Seconds(0)); // now
773 server->SetStopTime(m_duration);
774 }
775
776 for (std::size_t nodeId = 0; nodeId < NodeList::GetNNodes(); nodeId++)
777 {
779 "/NodeList/" + std::to_string(nodeId) +
780 "/ApplicationList/*/$ns3::PacketSocketServer/Rx",
782 }
783
784 // schedule ML setup for one station at a time
785 m_apMac->TraceConnectWithoutContext("AssociatedSta",
787 m_staMacs[0]->SetSsid(Ssid("ns-3-ssid"));
788
789 m_startAid = m_apMac->GetNextAssociationId();
790}
791
794 std::size_t count,
795 std::size_t pktSize,
796 Time delay,
797 uint8_t priority) const
798{
799 auto client = CreateObject<PacketSocketClient>();
800 client->SetAttribute("PacketSize", UintegerValue(pktSize));
801 client->SetAttribute("MaxPackets", UintegerValue(count));
802 client->SetAttribute("Interval", TimeValue(MicroSeconds(0)));
803 client->SetAttribute("Priority", UintegerValue(priority));
804 client->SetRemote(sockAddr);
805 client->SetStartTime(delay);
806 client->SetStopTime(m_duration - Simulator::Now());
807
808 return client;
809}
810
811void
813{
814 if (m_lastAid == aid)
815 {
816 // another STA of this non-AP MLD has already fired this callback
817 return;
818 }
819 m_lastAid = aid;
820
821 // make the next STA to start ML discovery & setup
822 if (const std::size_t count = aid - m_startAid + 1; count < m_nStations)
823 {
824 m_staMacs[count]->SetSsid(Ssid("ns-3-ssid"));
825 return;
826 }
827 // stop generation of beacon frames in order to avoid interference
828 m_apMac->SetAttribute("BeaconGeneration", BooleanValue(false));
829
830 // wait some time (5ms) to allow the completion of association before generating traffic
832}
833
835 WifiScanType scanType,
836 const std::vector<uint8_t>& setupLinks,
837 const std::vector<uint8_t>& staSetupLinks,
839 const std::string& dlTidToLinkMapping,
840 const std::string& ulTidToLinkMapping,
841 bool support160MHzOp)
842 : MultiLinkOperationsTestBase("Check correctness of Multi-Link Setup", 1, baseParams),
843 m_setupLinks(setupLinks),
844 m_staSetupLinks(staSetupLinks.empty() ? setupLinks : staSetupLinks),
845 m_scanType(scanType),
846 m_nProbeResp(0),
847 m_apNegSupport(apNegSupport),
848 m_dlTidLinkMappingStr(dlTidToLinkMapping),
849 m_ulTidLinkMappingStr(ulTidToLinkMapping),
850 m_support160MHzOp(support160MHzOp),
851 m_assocCompleted(false)
852{
854 m_staSetupLinks.size(),
855 "Number of setup links must be the same for AP and non-AP device");
856}
857
858void
860{
862
863 m_staMacs[0]->SetAttribute("ActiveProbing", BooleanValue(m_scanType == WifiScanType::ACTIVE));
864 m_apMac->GetEhtConfiguration()->m_tidLinkMappingSupport = m_apNegSupport;
865 // For non-AP MLD, it does not make sense to set the negotiation type to 0 (unless the AP MLD
866 // also advertises 0) or 1 (the AP MLD is discarded if it advertises a support of 3)
867 auto staEhtConfig = m_staMacs[0]->GetEhtConfiguration();
868 staEhtConfig->m_tidLinkMappingSupport = WifiTidToLinkMappingNegSupport::ANY_LINK_SET;
869 staEhtConfig->SetAttribute("TidToLinkMappingDl", StringValue(m_dlTidLinkMappingStr));
870 staEhtConfig->SetAttribute("TidToLinkMappingUl", StringValue(m_ulTidLinkMappingStr));
871
872 // the negotiated link mapping matches the one configured in EHT configuration, unless
873 // the AP MLD does not support TID-to-link mapping negotiation or the AP MLD supports
874 // the negotiation type 1 and the non-AP MLD is configured with a link mapping that
875 // maps distinct link sets to the TIDs, in which case the default link mapping is used
876 m_dlTidLinkMapping = staEhtConfig->GetTidLinkMapping(WifiDirection::DOWNLINK);
877 m_ulTidLinkMapping = staEhtConfig->GetTidLinkMapping(WifiDirection::UPLINK);
878
882 {
883 m_dlTidLinkMapping.clear(); // default link mapping
884 m_ulTidLinkMapping.clear(); // default link mapping
885 }
886
887 // find (if any) a TID that is not mapped to all setup links
888 using TupleRefs = std::tuple<std::reference_wrapper<const WifiTidLinkMapping>,
889 std::reference_wrapper<uint8_t>,
890 std::reference_wrapper<std::optional<uint8_t>>,
892 for (auto& [mappingRef, tid1Ref, tid2Ref, mac] :
895 {
896 tid1Ref.get() = 0;
897 for (uint8_t tid1 = 0; tid1 < 8; tid1++)
898 {
899 if (auto it1 = mappingRef.get().find(tid1);
900 it1 != mappingRef.get().cend() && it1->second.size() != m_setupLinks.size())
901 {
902 // found. Now search for another TID with a disjoint mapped link set
903 for (uint8_t tid2 = tid1 + 1; tid2 < 8; tid2++)
904 {
905 if (auto it2 = mappingRef.get().find(tid2);
906 it2 != mappingRef.get().cend() && it2->second.size() != m_setupLinks.size())
907 {
908 std::list<uint8_t> intersection;
909 std::set_intersection(it1->second.cbegin(),
910 it1->second.cend(),
911 it2->second.cbegin(),
912 it2->second.cend(),
913 std::back_inserter(intersection));
914 if (intersection.empty())
915 {
916 // found a second TID
917 tid2Ref.get() = tid2;
918 break;
919 }
920 }
921 }
922 tid1Ref.get() = tid1;
923 break;
924 }
925 }
926
927 std::list<uint8_t> tids = {tid1Ref.get()};
928 if (tid2Ref.get())
929 {
930 tids.emplace_back(*tid2Ref.get());
931 }
932
933 // prevent aggregation of MPDUs
934 for (auto tid : tids)
935 {
936 std::string attrName;
937 switch (QosUtilsMapTidToAc(tid))
938 {
939 case AC_VI:
940 attrName = "VI_MaxAmpduSize";
941 break;
942 case AC_VO:
943 attrName = "VO_MaxAmpduSize";
944 break;
945 case AC_BE:
946 attrName = "BE_MaxAmpduSize";
947 break;
948 case AC_BK:
949 attrName = "BK_MaxAmpduSize";
950 break;
951 default:
952 NS_FATAL_ERROR("Invalid TID " << +tid);
953 }
954
955 mac->SetAttribute(attrName, UintegerValue(100));
956 }
957 }
958
959 // configure support for 160 MHz operations and set the channels again to check that they
960 // are compatible
961 for (auto staMac : m_staMacs)
962 {
963 uint8_t linkId = 0;
964 for (const auto& str : m_staChannels)
965 {
966 staMac->GetWifiPhy(linkId++)->SetAttribute("ChannelSettings", StringValue(str));
967 }
968 }
969
970 m_apMac->TraceConnectWithoutContext("AssociatedSta",
972}
973
974void
976{
977 // DL traffic
978 {
979 PacketSocketAddress sockAddr;
980 sockAddr.SetSingleDevice(m_apMac->GetDevice()->GetIfIndex());
981 sockAddr.SetPhysicalAddress(m_staMacs[0]->GetDevice()->GetAddress());
982 sockAddr.SetProtocol(1);
983
984 m_apMac->GetDevice()->GetNode()->AddApplication(
985 GetApplication(sockAddr, m_setupLinks.size(), 500, Seconds(0), m_dlTid1));
986 if (m_dlTid2)
987 {
988 m_apMac->GetDevice()->GetNode()->AddApplication(
989 GetApplication(sockAddr, m_setupLinks.size(), 500, Seconds(0), *m_dlTid2));
990 }
991 }
992
993 // UL Traffic
994 {
995 PacketSocketAddress sockAddr;
996 sockAddr.SetSingleDevice(m_staMacs[0]->GetDevice()->GetIfIndex());
997 sockAddr.SetPhysicalAddress(m_apMac->GetDevice()->GetAddress());
998 sockAddr.SetProtocol(1);
999
1000 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
1001 GetApplication(sockAddr, m_setupLinks.size(), 500, MilliSeconds(500), m_ulTid1));
1002 if (m_ulTid2)
1003 {
1004 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
1005 GetApplication(sockAddr, m_setupLinks.size(), 500, MilliSeconds(500), *m_ulTid2));
1006 }
1007 }
1008}
1009
1010void
1012{
1014
1017
1018 /**
1019 * Check content of management frames
1020 */
1021 std::size_t index = 0;
1022
1023 for (const auto& frameInfo : m_txPsdus)
1024 {
1025 const auto& mpdu = *frameInfo.psduMap.begin()->second->begin();
1026 const auto& linkId = frameInfo.linkId;
1027
1028 switch (mpdu->GetHeader().GetType())
1029 {
1031 CheckBeacon(mpdu, linkId);
1032 break;
1033
1035 CheckProbeResponse(mpdu, linkId);
1036 m_nProbeResp++;
1037 break;
1038
1040 CheckAssocRequest(mpdu, linkId);
1041 break;
1042
1044 CheckAssocResponse(mpdu, linkId);
1045 break;
1046
1047 case WIFI_MAC_QOSDATA:
1048 CheckQosData(mpdu, frameInfo.txVector, linkId, index);
1049 break;
1050
1051 default:
1052 break;
1053 }
1054
1055 index++;
1056 }
1057
1059
1060 std::size_t expectedProbeResp = 0;
1062 {
1063 // the number of Probe Response frames that we expect to receive in active mode equals
1064 // the number of channels in common between AP MLD and non-AP MLD at initialization
1065 for (const auto& staChannel : m_staChannels)
1066 {
1067 for (const auto& apChannel : m_apChannels)
1068 {
1069 if (staChannel == apChannel)
1070 {
1071 expectedProbeResp++;
1072 break;
1073 }
1074 }
1075 }
1076 }
1077
1078 NS_TEST_EXPECT_MSG_EQ(m_nProbeResp, expectedProbeResp, "Unexpected number of Probe Responses");
1079
1080 std::size_t expectedRxDlPkts = m_setupLinks.size();
1081 if (m_dlTid2)
1082 {
1083 expectedRxDlPkts *= 2;
1084 }
1085 NS_TEST_EXPECT_MSG_EQ(m_rxPkts[m_staMacs[0]->GetDevice()->GetNode()->GetId()],
1086 expectedRxDlPkts,
1087 "Unexpected number of DL packets received");
1088
1089 std::size_t expectedRxUlPkts = m_setupLinks.size();
1090 if (m_ulTid2)
1091 {
1092 expectedRxUlPkts *= 2;
1093 }
1094 NS_TEST_EXPECT_MSG_EQ(m_rxPkts[m_apMac->GetDevice()->GetNode()->GetId()],
1095 expectedRxUlPkts,
1096 "Unexpected number of UL packets received");
1097
1099}
1100
1101void
1103{
1104 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_BEACON);
1105
1107
1108 NS_TEST_EXPECT_MSG_EQ(m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1109 mpdu->GetHeader().GetAddr2(),
1110 "TA of Beacon frame is not the address of the link it is transmitted on");
1111 MgtBeaconHeader beacon;
1112 mpdu->GetPacket()->PeekHeader(beacon);
1113 const auto& rnr = beacon.Get<ReducedNeighborReport>();
1114 const auto& mle = beacon.Get<MultiLinkElement>();
1115
1116 if (m_apMac->GetNLinks() == 1)
1117 {
1118 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(),
1119 false,
1120 "RNR Element in Beacon frame from single link AP");
1121 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1122 false,
1123 "Multi-Link Element in Beacon frame from single link AP");
1124 return;
1125 }
1126
1127 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(), true, "No RNR Element in Beacon frame");
1128 // All the other APs affiliated with the same AP MLD as the AP sending
1129 // the Beacon frame must be reported in a separate Neighbor AP Info field
1130 NS_TEST_EXPECT_MSG_EQ(rnr->GetNNbrApInfoFields(),
1131 static_cast<std::size_t>(m_apMac->GetNLinks() - 1),
1132 "Unexpected number of Neighbor AP Info fields in RNR");
1133 for (std::size_t nbrApInfoId = 0; nbrApInfoId < rnr->GetNNbrApInfoFields(); nbrApInfoId++)
1134 {
1135 NS_TEST_EXPECT_MSG_EQ(rnr->HasMldParameters(nbrApInfoId),
1136 true,
1137 "MLD Parameters not present");
1138 NS_TEST_EXPECT_MSG_EQ(rnr->GetNTbttInformationFields(nbrApInfoId),
1139 1,
1140 "Expected only one TBTT Info subfield per Neighbor AP Info");
1141 uint8_t nbrLinkId = rnr->GetMldParameters(nbrApInfoId, 0).linkId;
1142 NS_TEST_EXPECT_MSG_EQ(rnr->GetBssid(nbrApInfoId, 0),
1143 m_apMac->GetFrameExchangeManager(nbrLinkId)->GetAddress(),
1144 "BSSID advertised in Neighbor AP Info field "
1145 << nbrApInfoId
1146 << " does not match the address configured on the link "
1147 "advertised in the same field");
1148 }
1149
1150 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Beacon frame");
1151 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1152 m_apMac->GetAddress(),
1153 "Incorrect MLD address advertised in Multi-Link Element");
1154 NS_TEST_EXPECT_MSG_EQ(mle->GetLinkIdInfo(),
1155 +linkId,
1156 "Incorrect Link ID advertised in Multi-Link Element");
1157}
1158
1159void
1161{
1162 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_PROBE_RESPONSE);
1163
1165
1167 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1168 mpdu->GetHeader().GetAddr2(),
1169 "TA of Probe Response is not the address of the link it is transmitted on");
1170 MgtProbeResponseHeader probeResp;
1171 mpdu->GetPacket()->PeekHeader(probeResp);
1172 const auto& rnr = probeResp.Get<ReducedNeighborReport>();
1173 const auto& mle = probeResp.Get<MultiLinkElement>();
1174
1175 if (m_apMac->GetNLinks() == 1)
1176 {
1177 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(),
1178 false,
1179 "RNR Element in Probe Response frame from single link AP");
1180 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1181 false,
1182 "Multi-Link Element in Probe Response frame from single link AP");
1183 return;
1184 }
1185
1186 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(), true, "No RNR Element in Probe Response frame");
1187 // All the other APs affiliated with the same AP MLD as the AP sending
1188 // the Probe Response frame must be reported in a separate Neighbor AP Info field
1189 NS_TEST_EXPECT_MSG_EQ(rnr->GetNNbrApInfoFields(),
1190 static_cast<std::size_t>(m_apMac->GetNLinks() - 1),
1191 "Unexpected number of Neighbor AP Info fields in RNR");
1192 for (std::size_t nbrApInfoId = 0; nbrApInfoId < rnr->GetNNbrApInfoFields(); nbrApInfoId++)
1193 {
1194 NS_TEST_EXPECT_MSG_EQ(rnr->HasMldParameters(nbrApInfoId),
1195 true,
1196 "MLD Parameters not present");
1197 NS_TEST_EXPECT_MSG_EQ(rnr->GetNTbttInformationFields(nbrApInfoId),
1198 1,
1199 "Expected only one TBTT Info subfield per Neighbor AP Info");
1200 uint8_t nbrLinkId = rnr->GetMldParameters(nbrApInfoId, 0).linkId;
1201 NS_TEST_EXPECT_MSG_EQ(rnr->GetBssid(nbrApInfoId, 0),
1202 m_apMac->GetFrameExchangeManager(nbrLinkId)->GetAddress(),
1203 "BSSID advertised in Neighbor AP Info field "
1204 << nbrApInfoId
1205 << " does not match the address configured on the link "
1206 "advertised in the same field");
1207 }
1208
1209 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Probe Response frame");
1210 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1211 m_apMac->GetAddress(),
1212 "Incorrect MLD address advertised in Multi-Link Element");
1213 NS_TEST_EXPECT_MSG_EQ(mle->GetLinkIdInfo(),
1214 +linkId,
1215 "Incorrect Link ID advertised in Multi-Link Element");
1216}
1217
1218void
1220{
1221 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_ASSOCIATION_REQUEST);
1222
1224
1226 m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress(),
1227 mpdu->GetHeader().GetAddr2(),
1228 "TA of Assoc Request frame is not the address of the link it is transmitted on");
1230 mpdu->GetPacket()->PeekHeader(assoc);
1231 const auto& mle = assoc.Get<MultiLinkElement>();
1232
1233 if (m_apMac->GetNLinks() == 1)
1234 {
1235 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1236 false,
1237 "Multi-Link Element in Assoc Request frame to single link AP");
1238 }
1239 else if (m_staMacs[0]->GetAssocType() == WifiAssocType::LEGACY)
1240 {
1242 mle.has_value(),
1243 false,
1244 "Multi-Link Element in Assoc Request frame from non-AP using legacy association");
1245 }
1246 else
1247 {
1248 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1249 true,
1250 "No Multi-Link Element in Assoc Request frame");
1251 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1252 m_staMacs[0]->GetAddress(),
1253 "Incorrect MLD Address advertised in Multi-Link Element");
1255 mle->GetNPerStaProfileSubelements(),
1256 m_setupLinks.size() - 1,
1257 "Incorrect number of Per-STA Profile subelements in Multi-Link Element");
1258 for (std::size_t i = 0; i < mle->GetNPerStaProfileSubelements(); i++)
1259 {
1260 auto& perStaProfile = mle->GetPerStaProfile(i);
1261 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasStaMacAddress(),
1262 true,
1263 "Per-STA Profile must contain STA MAC address");
1264 // find ID of the local link corresponding to this subelement
1265 auto staLinkId = m_staMacs[0]->GetLinkIdByAddress(perStaProfile.GetStaMacAddress());
1267 staLinkId.has_value(),
1268 true,
1269 "No link found with the STA MAC address advertised in Per-STA Profile");
1271 +staLinkId.value(),
1272 +linkId,
1273 "The STA that sent the Assoc Request should not be included in a Per-STA Profile");
1274 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), staLinkId.value());
1275 NS_TEST_EXPECT_MSG_EQ((it != m_setupLinks.end()),
1276 true,
1277 "Not expecting to setup STA link ID " << +staLinkId.value());
1279 +staLinkId.value(),
1280 +perStaProfile.GetLinkId(),
1281 "Not expecting to request association to AP Link ID in Per-STA Profile");
1282 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasAssocRequest(),
1283 true,
1284 "Missing Association Request in Per-STA Profile");
1285 }
1286 }
1287
1288 const auto& tlm = assoc.Get<TidToLinkMapping>();
1289
1290 // A TID-to-Link Mapping IE is included in the Association Request if and only if the AP MLD
1291 // and the non-AP MLD are performing ML setup and the AP MLD advertises a non-null negotiation
1292 // support type
1293 if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetAssocType() == WifiAssocType::LEGACY ||
1295 {
1296 NS_TEST_EXPECT_MSG_EQ(tlm.empty(),
1297 true,
1298 "Didn't expect a TID-to-Link Mapping IE in Assoc Request frame");
1299 }
1300 else
1301 {
1302 std::size_t expectedNTlm = (m_dlTidLinkMapping == m_ulTidLinkMapping ? 1 : 2);
1303
1304 NS_TEST_ASSERT_MSG_EQ(tlm.size(),
1305 expectedNTlm,
1306 "Unexpected number of TID-to-Link Mapping IE in Assoc Request");
1307
1308 // lambda to check content of TID-to-Link Mapping IE(s)
1309 auto checkTlm = [&](std::size_t tlmId, WifiDirection dir) {
1310 NS_TEST_EXPECT_MSG_EQ(+static_cast<uint8_t>(tlm[tlmId].m_control.direction),
1311 +static_cast<uint8_t>(dir),
1312 "Unexpected direction in TID-to-Link Mapping IE " << tlmId);
1313 auto& expectedMapping =
1315
1316 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].m_control.defaultMapping,
1317 expectedMapping.empty(),
1318 "Default Link Mapping bit not set correctly");
1319 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].m_linkMapping.size(),
1320 expectedMapping.size(),
1321 "Unexpected number of Link Mapping Of TID n fields");
1322 for (uint8_t tid = 0; tid < 8; tid++)
1323 {
1324 if (auto it = expectedMapping.find(tid); it != expectedMapping.cend())
1325 {
1326 NS_TEST_EXPECT_MSG_EQ((tlm[tlmId].GetLinkMappingOfTid(tid) == it->second),
1327 true,
1328 "Unexpected link mapping for TID "
1329 << +tid << " direction " << dir);
1330 }
1331 else
1332 {
1333 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].GetLinkMappingOfTid(tid).empty(),
1334 true,
1335 "Expecting no Link Mapping Of TID n field for TID "
1336 << +tid << " direction " << dir);
1337 }
1338 }
1339 };
1340
1341 if (tlm.size() == 1)
1342 {
1343 checkTlm(0, WifiDirection::BOTH_DIRECTIONS);
1344 }
1345 else
1346 {
1347 std::size_t dlId = (tlm[0].m_control.direction == WifiDirection::DOWNLINK ? 0 : 1);
1348 std::size_t ulId = (dlId == 0 ? 1 : 0);
1349
1350 checkTlm(dlId, WifiDirection::DOWNLINK);
1351 checkTlm(ulId, WifiDirection::UPLINK);
1352 }
1353 }
1354}
1355
1356void
1358{
1359 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_ASSOCIATION_RESPONSE);
1360
1362
1364 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1365 mpdu->GetHeader().GetAddr2(),
1366 "TA of Assoc Response frame is not the address of the link it is transmitted on");
1368 mpdu->GetPacket()->PeekHeader(assoc);
1369 const auto& mle = assoc.Get<MultiLinkElement>();
1370
1371 if (m_apMac->GetNLinks() == 1)
1372 {
1373 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1374 false,
1375 "Multi-Link Element in Assoc Response frame with single link AP");
1376 return;
1377 }
1378
1379 if (m_staMacs[0]->GetAssocType() == WifiAssocType::LEGACY)
1380 {
1382 mle.has_value(),
1383 false,
1384 "Multi-Link Element in Assoc Response frame with non-AP using legacy association");
1385 return;
1386 }
1387
1388 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Assoc Request frame");
1389 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1390 m_apMac->GetAddress(),
1391 "Incorrect MLD Address advertised in Multi-Link Element");
1392 NS_TEST_EXPECT_MSG_EQ(mle->GetNPerStaProfileSubelements(),
1393 m_setupLinks.size() - 1,
1394 "Incorrect number of Per-STA Profile subelements in Multi-Link Element");
1395 for (std::size_t i = 0; i < mle->GetNPerStaProfileSubelements(); i++)
1396 {
1397 auto& perStaProfile = mle->GetPerStaProfile(i);
1398 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasStaMacAddress(),
1399 true,
1400 "Per-STA Profile must contain STA MAC address");
1401 // find ID of the local link corresponding to this subelement
1402 auto apLinkId = m_apMac->GetLinkIdByAddress(perStaProfile.GetStaMacAddress());
1404 apLinkId.has_value(),
1405 true,
1406 "No link found with the STA MAC address advertised in Per-STA Profile");
1407 NS_TEST_EXPECT_MSG_EQ(+apLinkId.value(),
1408 +perStaProfile.GetLinkId(),
1409 "Link ID and MAC address advertised in Per-STA Profile do not match");
1411 +apLinkId.value(),
1412 +linkId,
1413 "The AP that sent the Assoc Response should not be included in a Per-STA Profile");
1414 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), apLinkId.value());
1415 NS_TEST_EXPECT_MSG_EQ((it != m_setupLinks.end()),
1416 true,
1417 "Not expecting to setup AP link ID " << +apLinkId.value());
1418 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasAssocResponse(),
1419 true,
1420 "Missing Association Response in Per-STA Profile");
1421 }
1422
1423 // For the moment, the AP MLD always accepts a valid TID-to-Link Mapping request, hence
1424 // in every case there is no TID-to-Link Mapping IE in the Association Response
1425 NS_TEST_EXPECT_MSG_EQ(assoc.Get<TidToLinkMapping>().empty(),
1426 true,
1427 "Didn't expect to find a TID-to-Link Mapping IE in Association Response");
1428}
1429
1430void
1432{
1433 /**
1434 * Check outcome of Multi-Link Setup
1435 */
1436 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->IsAssociated(), true, "Expected the STA to be associated");
1437
1438 for (auto linkIdIter = m_setupLinks.cbegin(), staLinkIdIter = m_staSetupLinks.cbegin();
1439 linkIdIter != m_setupLinks.cend();
1440 ++linkIdIter, ++staLinkIdIter)
1441 {
1442 auto staLinkId = *staLinkIdIter;
1443 auto apLinkId = *linkIdIter;
1444
1445 auto staAddr = m_staMacs[0]->GetFrameExchangeManager(staLinkId)->GetAddress();
1446 auto apAddr = m_apMac->GetFrameExchangeManager(apLinkId)->GetAddress();
1447
1448 auto staRemoteMgr = m_staMacs[0]->GetWifiRemoteStationManager(staLinkId);
1449 auto apRemoteMgr = m_apMac->GetWifiRemoteStationManager(apLinkId);
1450
1451 // STA side
1452 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetFrameExchangeManager(staLinkId)->GetBssid(),
1453 apAddr,
1454 "Unexpected BSSID for STA link ID " << +staLinkId);
1455 if (m_apMac->GetNLinks() > 1 && m_assocType == WifiAssocType::ML_SETUP)
1456 {
1457 NS_TEST_EXPECT_MSG_EQ((staRemoteMgr->GetMldAddress(apAddr) == m_apMac->GetAddress()),
1458 true,
1459 "Incorrect MLD address stored by STA on link ID " << +staLinkId);
1461 (staRemoteMgr->GetAffiliatedStaAddress(m_apMac->GetAddress()) == apAddr),
1462 true,
1463 "Incorrect affiliated address stored by STA on link ID " << +staLinkId);
1464 }
1465
1466 // AP side
1467 NS_TEST_EXPECT_MSG_EQ(apRemoteMgr->IsAssociated(staAddr),
1468 true,
1469 "Expecting STA " << staAddr << " to be associated on link "
1470 << +apLinkId);
1471 if (m_apMac->GetNLinks() > 1 && m_assocType == WifiAssocType::ML_SETUP)
1472 {
1474 (apRemoteMgr->GetMldAddress(staAddr) == m_staMacs[0]->GetAddress()),
1475 true,
1476 "Incorrect MLD address stored by AP on link ID " << +apLinkId);
1478 (apRemoteMgr->GetAffiliatedStaAddress(m_staMacs[0]->GetAddress()) == staAddr),
1479 true,
1480 "Incorrect affiliated address stored by AP on link ID " << +apLinkId);
1481 }
1482 auto aid = m_apMac->GetAssociationId(staAddr, apLinkId);
1483 const auto& staList = m_apMac->GetStaList(apLinkId);
1484 NS_TEST_EXPECT_MSG_EQ((staList.find(aid) != staList.end()),
1485 true,
1486 "STA " << staAddr << " not found in list of associated STAs");
1487
1488 // STA of non-AP MLD operate on the same channel as the AP (or on its primary80 if the AP
1489 // operates on a 160 MHz channel and non-AP MLD does not support 160 MHz operations)
1490 const auto& staChannel = m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel();
1491 const auto& apChannel = m_apMac->GetWifiPhy(apLinkId)->GetOperatingChannel();
1492
1493 auto width = apChannel.GetTotalWidth();
1494 auto primary20 = apChannel.GetPrimaryChannelIndex(MHz_u{20});
1495
1496 if (width > MHz_u{80} && !m_support160MHzOp)
1497 {
1498 width = MHz_u{80};
1499 primary20 -= apChannel.GetPrimaryChannelIndex(MHz_u{80}) * 4;
1500 }
1501
1502 NS_TEST_EXPECT_MSG_EQ(+staChannel.GetNumber(),
1503 +apChannel.GetPrimaryChannelNumber(width, WIFI_STANDARD_80211be),
1504 "Incorrect operating channel number for STA on link " << +staLinkId);
1505 NS_TEST_EXPECT_MSG_EQ(staChannel.GetFrequency(),
1506 apChannel.GetPrimaryChannelCenterFrequency(width),
1507 "Incorrect operating channel frequency for STA on link "
1508 << +staLinkId);
1509 NS_TEST_EXPECT_MSG_EQ(staChannel.GetWidth(),
1510 width,
1511 "Incorrect operating channel width for STA on link " << +staLinkId);
1512 NS_TEST_EXPECT_MSG_EQ(+staChannel.GetPhyBand(),
1513 +apChannel.GetPhyBand(),
1514 "Incorrect operating PHY band for STA on link " << +staLinkId);
1515 NS_TEST_EXPECT_MSG_EQ(+staChannel.GetPrimaryChannelIndex(MHz_u{20}),
1516 +primary20,
1517 "Incorrect operating primary channel index for STA on link "
1518 << +staLinkId);
1519 }
1520
1521 // lambda to check the link mapping stored at wifi MAC
1522 auto checkStoredMapping =
1523 [this](Ptr<WifiMac> mac, Ptr<WifiMac> dest, WifiDirection dir, bool present) {
1524 NS_TEST_ASSERT_MSG_EQ(mac->GetTidToLinkMapping(dest->GetAddress(), dir).has_value(),
1525 present,
1526 "Link mapping stored by "
1527 << (mac->GetTypeOfStation() == AP ? "AP" : "non-AP")
1528 << " MLD for " << dir << " direction "
1529 << (present ? "expected" : "not expected"));
1530 if (present)
1531 {
1532 const auto& mapping =
1535 (mac->GetTidToLinkMapping(dest->GetAddress(), dir)->get() == mapping),
1536 true,
1537 "Incorrect link mapping stored by "
1538 << (mac->GetTypeOfStation() == AP ? "AP" : "non-AP") << " MLD for " << dir
1539 << " direction");
1540
1541 // check correctness of WifiMac::TidMappedOnLink function
1542 std::set<uint8_t> setupLinks(m_setupLinks.cbegin(), m_setupLinks.cend());
1543 for (uint8_t tid = 0; tid < 8; ++tid)
1544 {
1545 const auto& linkSet = mapping.contains(tid) ? mapping.at(tid) : setupLinks;
1546
1547 for (const auto linkId : setupLinks)
1548 {
1550 mac->TidMappedOnLink(dest->GetAddress(), dir, tid, linkId),
1551 linkSet.contains(linkId),
1552 "Incorrect return value on " << (mac == m_apMac ? "AP" : "STA")
1553 << " direction " << dir << " TID " << +tid
1554 << " linkID " << +linkId);
1555 }
1556 }
1557 }
1558 };
1559
1560 auto storedMapping = (m_apMac->GetNLinks() > 1) &&
1561 (m_staMacs[0]->GetAssocType() == WifiAssocType::ML_SETUP) &&
1563 checkStoredMapping(m_apMac, m_staMacs[0], WifiDirection::DOWNLINK, storedMapping);
1564 checkStoredMapping(m_apMac, m_staMacs[0], WifiDirection::UPLINK, storedMapping);
1565 checkStoredMapping(m_staMacs[0], m_apMac, WifiDirection::DOWNLINK, storedMapping);
1566 checkStoredMapping(m_staMacs[0], m_apMac, WifiDirection::UPLINK, storedMapping);
1567}
1568
1569void
1571{
1572 // this callback is fired for every STA affiliated with the non-AP MLD that has completed
1573 // association; the first STA is the one operating on the link used for association.
1574 // Checks are scheduled now because this callback is fired by the AP before setting the PM mode.
1575 Simulator::ScheduleNow([=, this, isAssocLink = !m_assocCompleted] {
1576 // AP MLD side
1577 const auto apLinkId = m_apMac->IsAssociated(staAddr);
1578 NS_TEST_ASSERT_MSG_EQ(apLinkId.has_value(), true, staAddr << " is not associated");
1579 NS_TEST_EXPECT_MSG_EQ(m_apMac->GetWifiRemoteStationManager(*apLinkId)->IsInPsMode(staAddr),
1580 !isAssocLink,
1581 "Unexpected PM mode for " << staAddr << " (AP MLD side)");
1582 // non-AP MLD side
1583 const auto staLinkId = m_staMacs[0]->GetLinkIdByAddress(staAddr);
1584 NS_TEST_ASSERT_MSG_EQ(staLinkId.has_value(),
1585 true,
1586 staAddr << " is not a link address of the non-AP MLD");
1587 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetPmMode(*staLinkId),
1589 "Unexpected PM mode for " << staAddr << " (non-AP MLD side)");
1590 });
1591 m_assocCompleted = true;
1592}
1593
1594void
1596{
1597 const auto legacyAssoc = (m_assocType == WifiAssocType::LEGACY || m_apMac->GetNLinks() == 1);
1598
1599 if (legacyAssoc)
1600 {
1602 1,
1603 "One link is expected to be setup with legacy association");
1604 }
1605
1606 for (uint8_t linkId = 0; linkId < m_apMac->GetNLinks(); ++linkId)
1607 {
1608 auto it = std::find(m_setupLinks.cbegin(), m_setupLinks.cend(), linkId);
1609 auto addr =
1610 (legacyAssoc
1611 ? m_staMacs[0]->GetFrameExchangeManager(m_staSetupLinks.front())->GetAddress()
1612 : m_staMacs[0]->GetAddress());
1614
1615 // the queue on the AP should have a mask if and only if the link has been setup
1616 auto mask = m_apMac->GetMacQueueScheduler()->GetQueueLinkMask(AC_BE, queueId, linkId);
1617 NS_TEST_EXPECT_MSG_EQ(mask.has_value(),
1618 (it != m_setupLinks.cend()),
1619 "Unexpected presence/absence of mask on link " << +linkId);
1620 }
1621
1622 for (const auto& linkId : m_staMacs[0]->GetLinkIds())
1623 {
1624 auto it = std::find(m_staSetupLinks.begin(), m_staSetupLinks.end(), linkId);
1625 if (it == m_staSetupLinks.end())
1626 {
1627 // the link has not been setup
1628 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId)->GetState()->IsStateOff(),
1629 true,
1630 "Link " << +linkId << " has not been setup but is not disabled");
1631 continue;
1632 }
1633
1634 // the link has been setup and must be active
1635 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId)->GetState()->IsStateOff(),
1636 false,
1637 "Expecting link " << +linkId << " to be active");
1638 }
1639}
1640
1641void
1643 const WifiTxVector& txvector,
1644 uint8_t linkId,
1645 std::size_t index)
1646{
1648 const auto& hdr = mpdu->GetHeader();
1649
1650 NS_TEST_ASSERT_MSG_EQ(hdr.IsQosData(), true, "Expected a QoS data frame");
1651
1652 // check TX width
1653 // STA of non-AP MLD operate on the same channel as the AP (or on its primary80 if the AP
1654 // operates on a 160 MHz channel and non-AP MLD does not support 160 MHz operations)
1655 MHz_u width;
1656
1657 if (!hdr.IsToDs() && hdr.IsFromDs())
1658 {
1660 width = m_apMac->GetWifiPhy(linkId)->GetOperatingChannel().GetTotalWidth();
1661 }
1662 else if (hdr.IsToDs() && !hdr.IsFromDs())
1663 {
1665 width = m_staMacs[0]->GetWifiPhy(linkId)->GetOperatingChannel().GetTotalWidth();
1666 }
1667 else
1668 {
1669 NS_ABORT_MSG("Invalid combination for QoS data frame: ToDS(" << hdr.IsToDs() << ") FromDS("
1670 << hdr.IsFromDs() << ")");
1671 }
1672
1673 if (width > MHz_u{80} && !m_support160MHzOp)
1674 {
1675 width = MHz_u{80};
1676 }
1677 NS_TEST_EXPECT_MSG_EQ(txvector.GetChannelWidth(), width, "Unexpected TX width");
1678
1679 const auto& tid1 = (dir == WifiDirection::DOWNLINK) ? m_dlTid1 : m_ulTid1;
1680 const auto& tid2 = (dir == WifiDirection::DOWNLINK) ? m_dlTid2 : m_ulTid2;
1681 uint8_t tid = hdr.GetQosTid();
1682
1683 NS_TEST_ASSERT_MSG_NE((tid == tid1),
1684 (tid2.has_value() && tid == *tid2),
1685 "QoS frame with unexpected TID " << +tid);
1686
1687 // lambda to find the link set the given TID is mapped to
1688 auto findLinkSet = [this, dir](uint8_t tid) -> std::set<uint8_t> {
1689 std::set<uint8_t> linkSet(m_setupLinks.cbegin(), m_setupLinks.cend());
1690 if (auto mappingOptRef = m_apMac->GetTidToLinkMapping(m_staMacs[0]->GetAddress(), dir))
1691 {
1692 // if the TID is not present in the mapping, it is mapped to all setup links
1693 if (auto it = mappingOptRef->get().find(tid); it != mappingOptRef->get().cend())
1694 {
1695 linkSet = it->second;
1696 NS_ASSERT_MSG(!linkSet.empty(), "TID " << +tid << " mapped to no link");
1697 }
1698 }
1699 return linkSet;
1700 };
1701
1702 auto linkSet = findLinkSet(tid);
1703 auto& qosFrames = (tid == tid1) ? m_qosFrames1 : m_qosFrames2;
1704
1705 // Let N the size of the link set, the first N QoS data frames are sent simultaneously
1706 // on the links of the set, the others (if any) will be sent afterwards on such links
1707
1708 // number of concurrent frames of the same TID transmitted so far (excluding current frame)
1709 std::size_t nConcurFrames = std::min(qosFrames.size(), linkSet.size());
1710
1711 // iterate over the concurrent frames of the same TID transmitted so far
1712 for (std::size_t i = 0; i < nConcurFrames; i++)
1713 {
1714 auto prev = qosFrames[i];
1715
1716 // TX duration of i-th frame
1717 auto band = m_apMac->GetWifiPhy(m_txPsdus[prev].linkId)->GetPhyBand();
1718 Time txDuration =
1719 WifiPhy::CalculateTxDuration(m_txPsdus[prev].psduMap, m_txPsdus[prev].txVector, band);
1720
1721 // the current frame is transmitted concurrently with this previous frame if it is
1722 // within the first N (size of the link set) frames, otherwise it is transmitted after
1723 // this previous frame if they have been transmitted on the same link
1724 if (qosFrames.size() < linkSet.size())
1725 {
1726 // the current frame can be sent concurrently with this previous frame
1727 NS_TEST_EXPECT_MSG_LT(m_txPsdus[index].startTx,
1728 m_txPsdus[prev].startTx + txDuration,
1729 "The " << dir << " QoS frame number " << qosFrames.size()
1730 << " was not sent concurrently with others on link "
1731 << +linkId << " which TID " << +tid << " is mapped to");
1732 }
1733 else if (m_txPsdus[prev].linkId == linkId)
1734 {
1735 // the current frame is sent afterwards
1736 NS_TEST_EXPECT_MSG_GT(m_txPsdus[index].startTx,
1737 m_txPsdus[prev].startTx + txDuration,
1738 "The " << dir << " QoS frame number " << qosFrames.size()
1739 << " was sent concurrently with others on a link "
1740 << +linkId << " which TID " << +tid << " is mapped to");
1741 }
1742 }
1743
1744 if (m_apMac->GetNLinks() > 1 && m_assocType == WifiAssocType::ML_SETUP)
1745 {
1746 NS_TEST_EXPECT_MSG_EQ(std::count(linkSet.cbegin(), linkSet.cend(), linkId),
1747 1,
1748 "QoS frame sent on Link ID "
1749 << +linkId << " that does not belong to the link set of TID "
1750 << +tid);
1751 }
1752
1753 if (tid2)
1754 {
1755 // QoS frames of two distinct TIDs are sent.
1756 auto otherTid = (tid == tid1) ? *tid2 : tid1;
1757 const auto& otherQosFrames = (tid == tid1) ? m_qosFrames2 : m_qosFrames1;
1758 auto otherLinkSet = findLinkSet(otherTid);
1759
1760 // number of concurrent frames of the other TID transmitted so far
1761 std::size_t nOtherConcurFrames = std::min(otherQosFrames.size(), otherLinkSet.size());
1762
1763 // iterate over the concurrent frames of the other TID
1764 for (std::size_t i = 0; i < nOtherConcurFrames; i++)
1765 {
1766 auto prev = otherQosFrames[i];
1767
1768 // TX duration of i-th frame
1769 auto band = m_apMac->GetWifiPhy(m_txPsdus[prev].linkId)->GetPhyBand();
1770 Time txDuration = WifiPhy::CalculateTxDuration(m_txPsdus[prev].psduMap,
1771 m_txPsdus[prev].txVector,
1772 band);
1773
1774 // the current frame is transmitted concurrently with this previous frame of the
1775 // other TID if it is within the first N (size of the link set) frames of its TID
1776 if (qosFrames.size() < linkSet.size())
1777 {
1778 // the current frame can be sent concurrently with this previous frame
1779 NS_TEST_EXPECT_MSG_LT(m_txPsdus[index].startTx,
1780 m_txPsdus[prev].startTx + txDuration,
1781 "The " << dir << " QoS frame number " << qosFrames.size()
1782 << " was not sent concurrently with others with TID "
1783 << +otherTid);
1784 }
1785 }
1786 }
1787
1788 // insert the frame
1789 qosFrames.emplace_back(index);
1790
1791 if (qosFrames.size() == m_setupLinks.size())
1792 {
1793 qosFrames.clear();
1794 }
1795}
1796
1798 WifiTrafficPattern trafficPattern,
1799 WifiBaEnabled baEnabled,
1800 WifiUseBarAfterMissedBa useBarAfterMissedBa,
1801 uint8_t nMaxInflight)
1803 std::string("Check data transmission between MLDs ") +
1804 (baEnabled == WifiBaEnabled::YES
1805 ? (useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES
1806 ? "with BA agreement, send BAR after BlockAck timeout"
1807 : "with BA agreement, send Data frames after BlockAck timeout")
1808 : "without BA agreement") +
1809 " (Traffic pattern: " + std::to_string(static_cast<uint8_t>(trafficPattern)) +
1810 (baEnabled == WifiBaEnabled::YES ? ", nMaxInflight=" + std::to_string(nMaxInflight)
1811 : "") +
1812 ")",
1813 2,
1814 baseParams),
1815 m_trafficPattern(trafficPattern),
1816 m_baEnabled(baEnabled == WifiBaEnabled::YES),
1817 m_useBarAfterMissedBa(useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES),
1818 m_nMaxInflight(nMaxInflight),
1819 m_nPackets(trafficPattern == WifiTrafficPattern::STA_TO_BCAST ||
1820 trafficPattern == WifiTrafficPattern::STA_TO_STA
1821 ? 4
1822 : 8)
1823{
1824}
1825
1826void
1828 uint8_t phyId,
1829 WifiConstPsduMap psduMap,
1830 WifiTxVector txVector,
1831 double txPowerW)
1832{
1833 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
1834 auto linkId = m_txPsdus.back().linkId;
1835
1836 auto psdu = psduMap.begin()->second;
1837
1838 switch (psdu->GetHeader(0).GetType())
1839 {
1841 // a management frame is a DL frame if TA equals BSSID
1842 CheckAddresses(psdu,
1843 psdu->GetHeader(0).GetAddr2() == psdu->GetHeader(0).GetAddr3() ? DL : UL);
1844 if (!m_baEnabled)
1845 {
1846 // corrupt all management action frames (ADDBA Request frames) to prevent
1847 // the establishment of a BA agreement
1848 m_uidList.push_front(psdu->GetPacket()->GetUid());
1849 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1850 NS_LOG_INFO("CORRUPTED");
1851 }
1852 break;
1853 case WIFI_MAC_QOSDATA:
1854 CheckAddresses(psdu);
1855
1856 for (const auto& mpdu : *psdu)
1857 {
1858 // determine the max number of simultaneous transmissions for this MPDU
1859 // (only if sent by the traffic source and this is not a broadcast frame)
1860 if (m_baEnabled && m_sourceMac->GetLinkIds().count(linkId) == 1 &&
1861 m_sourceMac->GetFrameExchangeManager(linkId)->GetAddress() ==
1862 mpdu->GetHeader().GetAddr2() &&
1863 !mpdu->GetHeader().GetAddr1().IsGroup())
1864 {
1865 auto seqNo = mpdu->GetHeader().GetSequenceNumber();
1866 auto [it, success] =
1867 m_inflightCount.insert({seqNo, mpdu->GetInFlightLinkIds().size()});
1868 if (!success)
1869 {
1870 it->second = std::max(it->second, mpdu->GetInFlightLinkIds().size());
1871 }
1872 }
1873 }
1874 for (std::size_t i = 0; i < psdu->GetNMpdus(); i++)
1875 {
1876 // corrupt QoS data frame with sequence number equal to 1 (only once) if we are
1877 // not in the AP to broadcast traffic pattern (broadcast frames are not retransmitted)
1878 // nor in the STA to broadcast or STA to STA traffic patterns (retransmissions from
1879 // STA 1 could collide with frames forwarded by the AP)
1880 if (psdu->GetHeader(i).GetSequenceNumber() != 1 ||
1884 {
1885 continue;
1886 }
1887 auto uid = psdu->GetPayload(i)->GetUid();
1888 if (!m_dataCorrupted)
1889 {
1890 m_uidList.push_front(uid);
1891 m_dataCorrupted = true;
1892 NS_LOG_INFO("CORRUPTED");
1893 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1894 }
1895 else
1896 {
1897 // do not corrupt the QoS data frame anymore
1898 if (auto it = std::find(m_uidList.cbegin(), m_uidList.cend(), uid);
1899 it != m_uidList.cend())
1900 {
1901 m_uidList.erase(it);
1902 }
1903 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1904 }
1905 break;
1906 }
1907 break;
1908 case WIFI_MAC_CTL_BACKRESP: {
1909 // ignore BlockAck frames not addressed to the source of the application packets
1910 if (!m_sourceMac->GetLinkIdByAddress(psdu->GetHeader(0).GetAddr1()))
1911 {
1912 break;
1913 }
1914 if (m_nMaxInflight > 1)
1915 {
1916 // we do not check the content of BlockAck when m_nMaxInflight is greater than 1
1917 break;
1918 }
1919 CheckBlockAck(psdu, txVector, linkId);
1921 if (m_blockAckCount == 2)
1922 {
1923 // corrupt the second BlockAck frame to simulate a missed BlockAck
1924 m_uidList.push_front(psdu->GetPacket()->GetUid());
1925 NS_LOG_INFO("CORRUPTED");
1926 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1927 }
1928 break;
1930 // ignore BlockAckReq frames not transmitted by the source of the application packets
1931 if (m_sourceMac->GetLinkIdByAddress(psdu->GetHeader(0).GetAddr2()))
1932 {
1934 }
1935 break;
1936 }
1937 default:;
1938 }
1939}
1940
1941void
1943 const WifiTxVector& txVector,
1944 uint8_t linkId)
1945{
1946 NS_TEST_ASSERT_MSG_EQ(m_baEnabled, true, "No BlockAck expected without BA agreement");
1948 true,
1949 "No BlockAck expected in AP to broadcast traffic pattern");
1950
1951 /*
1952 * ┌───────┬───────X ┌───────┐
1953 * link 0 │ 0 │ 1 │ │ 1 │
1954 * ───────┴───────┴───────┴┬──┬────┴───────┴┬───┬────────────────────────
1955 * │BA│ │ACK│
1956 * └──┘ └───┘
1957 * ┌───────┬───────┐ ┌───────┬───────┐
1958 * link 1 │ 2 │ 3 │ │ 2 │ 3 │
1959 * ────────────────────┴───────┴───────┴┬──X───┴───────┴───────┴┬──┬─────
1960 * │BA│ │BA│
1961 * └──┘ └──┘
1962 */
1963 auto mpdu = *psdu->begin();
1964 CtrlBAckResponseHeader blockAck;
1965 mpdu->GetPacket()->PeekHeader(blockAck);
1966 bool isMpdu1corrupted = (m_trafficPattern == WifiTrafficPattern::STA_TO_AP ||
1968
1969 switch (m_blockAckCount)
1970 {
1971 case 0: // first BlockAck frame (all traffic patterns)
1973 true,
1974 "MPDU 0 expected to be successfully received");
1976 blockAck.IsPacketReceived(1),
1977 !isMpdu1corrupted,
1978 "MPDU 1 expected to be received only in STA_TO_STA/STA_TO_BCAST scenarios");
1979 // if there are at least two links setup, we expect all MPDUs to be inflight
1980 // (on distinct links)
1981 if (m_staMacs[0]->GetSetupLinkIds().size() > 1)
1982 {
1983 auto queue = m_sourceMac->GetTxopQueue(AC_BE);
1984 auto rcvMac = m_sourceMac == m_staMacs[0] ? StaticCast<WifiMac>(m_apMac)
1986 auto item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress());
1987 std::size_t nQueuedPkt = 0;
1988 auto delay = WifiPhy::CalculateTxDuration(psdu,
1989 txVector,
1990 rcvMac->GetWifiPhy(linkId)->GetPhyBand()) +
1991 MicroSeconds(1); // to account for propagation delay
1992
1993 while (item)
1994 {
1995 auto seqNo = item->GetHeader().GetSequenceNumber();
1996 NS_TEST_EXPECT_MSG_EQ(item->IsInFlight(),
1997 true,
1998 "MPDU with seqNo=" << seqNo << " is not in flight");
1999 auto linkIds = item->GetInFlightLinkIds();
2000 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
2001 1,
2002 "MPDU with seqNo=" << seqNo
2003 << " is in flight on multiple links");
2004 // The first two MPDUs are in flight on the same link on which the BlockAck
2005 // is sent. The other two MPDUs (only for AP to STA/STA to AP scenarios) are
2006 // in flight on a different link.
2007 auto srcLinkId = m_sourceMac->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1());
2008 NS_TEST_ASSERT_MSG_EQ(srcLinkId.has_value(),
2009 true,
2010 "Addr1 of BlockAck is not an originator's link address");
2011 NS_TEST_EXPECT_MSG_EQ((*linkIds.begin() == *srcLinkId),
2012 (seqNo <= 1),
2013 "MPDU with seqNo=" << seqNo
2014 << " in flight on unexpected link");
2015 // check the Retry subfield and whether this MPDU is still queued
2016 // after the originator has processed this BlockAck
2017
2018 // MPDUs acknowledged via this BlockAck are no longer queued
2019 bool isQueued = (seqNo > (isMpdu1corrupted ? 0 : 1));
2020 // The Retry subfield is set if the MPDU has not been acknowledged (i.e., it
2021 // is still queued) and has been transmitted on the same link as the BlockAck
2022 // (i.e., its sequence number is less than or equal to 1)
2023 bool isRetry = isQueued && seqNo <= 1;
2024
2025 Simulator::Schedule(delay, [this, item, isQueued, isRetry]() {
2026 NS_TEST_EXPECT_MSG_EQ(item->IsQueued(),
2027 isQueued,
2028 "MPDU with seqNo="
2029 << item->GetHeader().GetSequenceNumber() << " should "
2030 << (isQueued ? "" : "not") << " be queued");
2032 item->GetHeader().IsRetry(),
2033 isRetry,
2034 "Unexpected value for the Retry subfield of the MPDU with seqNo="
2035 << item->GetHeader().GetSequenceNumber());
2036 });
2037
2038 nQueuedPkt++;
2039 item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress(), item);
2040 }
2041 // Each MPDU contains an A-MSDU consisting of two MSDUs
2042 NS_TEST_EXPECT_MSG_EQ(nQueuedPkt, m_nPackets / 2, "Unexpected number of queued MPDUs");
2043 }
2044 break;
2045 case 1: // second BlockAck frame (STA to AP and AP to STA traffic patterns only)
2046 case 2: // third BlockAck frame (STA to AP and AP to STA traffic patterns only)
2049 true,
2050 "Did not expect to receive a second BlockAck");
2051 // the second BlockAck is corrupted, but the data frames have been received successfully
2052 std::pair<uint16_t, uint16_t> seqNos;
2053 // if multiple links were setup, the transmission of the second A-MPDU started before
2054 // the end of the first one, so the second A-MPDU includes MPDUs with sequence numbers
2055 // 2 and 3. Otherwise, MPDU with sequence number 1 is retransmitted along with the MPDU
2056 // with sequence number 2.
2057 if (m_staMacs[0]->GetSetupLinkIds().size() > 1)
2058 {
2059 seqNos = {2, 3};
2060 }
2061 else
2062 {
2063 seqNos = {1, 2};
2064 }
2065 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(seqNos.first),
2066 true,
2067 "MPDU " << seqNos.first << " expected to be successfully received");
2068 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(seqNos.second),
2069 true,
2070 "MPDU " << seqNos.second << " expected to be successfully received");
2071 break;
2072 }
2073}
2074
2075void
2077{
2079
2080 if (m_baEnabled)
2081 {
2082 // Enable A-MSDU aggregation. Max A-MSDU size is set such that two MSDUs can be aggregated
2083 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
2084 {
2085 mac->SetAttribute("BE_MaxAmsduSize", UintegerValue(2100));
2086 mac->GetQosTxop(AC_BE)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2088 mac->GetQosTxop(AC_BE)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2089 }
2090 }
2091
2092 // install post reception error model on all devices
2093 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
2094 {
2095 auto errorModel = CreateObject<ListErrorModel>();
2096 m_errorModels[m_apMac->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2097 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2098 }
2099 for (std::size_t i : {0, 1})
2100 {
2101 for (const auto linkId : m_staMacs[i]->GetLinkIds())
2102 {
2103 auto errorModel = CreateObject<ListErrorModel>();
2104 m_errorModels[m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2105 m_staMacs[i]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2106 }
2107 }
2108}
2109
2110void
2112{
2113 Address destAddr;
2114
2115 switch (m_trafficPattern)
2116 {
2119 destAddr = m_staMacs[1]->GetDevice()->GetAddress();
2120 break;
2123 destAddr = m_apMac->GetDevice()->GetAddress();
2124 break;
2127 destAddr = m_staMacs[1]->GetDevice()->GetAddress();
2128 break;
2131 destAddr = Mac48Address::GetBroadcast();
2132 break;
2135 destAddr = Mac48Address::GetBroadcast();
2136 break;
2137 }
2138
2139 PacketSocketAddress sockAddr;
2140 sockAddr.SetSingleDevice(m_sourceMac->GetDevice()->GetIfIndex());
2141 sockAddr.SetPhysicalAddress(destAddr);
2142 sockAddr.SetProtocol(1);
2143
2144 // install first client application generating at most 4 packets
2145 m_sourceMac->GetDevice()->GetNode()->AddApplication(
2146 GetApplication(sockAddr, std::min<std::size_t>(m_nPackets, 4), 1000));
2147
2148 if (m_nPackets > 4)
2149 {
2150 // install a second client application generating the remaining packets and
2151 // starting during transmission of first A-MPDU, if multiple links are setup
2152 m_sourceMac->GetDevice()->GetNode()->AddApplication(
2153 GetApplication(sockAddr, m_nPackets - 4, 1000, MilliSeconds(4)));
2154 }
2155
2157}
2158
2159void
2161{
2163
2164 // Expected number of packets received by each node (AP, STA 0, STA 1) at application layer
2165 std::array<std::size_t, 3> expectedRxPkts{};
2166
2167 switch (m_trafficPattern)
2168 {
2171 // only STA 1 receives the m_nPackets packets that have been transmitted
2172 expectedRxPkts[2] = m_nPackets;
2173 break;
2175 // only the AP receives the m_nPackets packets that have been transmitted
2176 expectedRxPkts[0] = m_nPackets;
2177 break;
2179 // the AP replicates the broadcast frames on all the links, hence each station
2180 // receives the m_nPackets packets N times, where N is the number of setup link
2181 expectedRxPkts[1] = m_nPackets * m_staMacs[0]->GetSetupLinkIds().size();
2182 expectedRxPkts[2] = m_nPackets * m_staMacs[1]->GetSetupLinkIds().size();
2183 break;
2185 // the AP receives the m_nPackets packets and then replicates them on all the links,
2186 // hence STA 1 receives m_nPackets packets N times, where N is the number of setup link
2187 expectedRxPkts[0] = m_nPackets;
2188 expectedRxPkts[2] = m_nPackets * m_staMacs[1]->GetSetupLinkIds().size();
2189 break;
2190 }
2191
2193 +expectedRxPkts[0],
2194 "Unexpected number of packets received by the AP");
2196 +expectedRxPkts[1],
2197 "Unexpected number of packets received by STA 0");
2199 +expectedRxPkts[2],
2200 "Unexpected number of packets received by STA 1");
2201
2202 // check that the expected number of BlockAck frames are transmitted
2203 if (m_baEnabled && m_nMaxInflight == 1)
2204 {
2205 std::size_t expectedBaCount = 0;
2206 std::size_t expectedBarCount = 0;
2207
2208 switch (m_trafficPattern)
2209 {
2212 // two A-MPDUs are transmitted and one BlockAck is corrupted
2213 expectedBaCount = 3;
2214 // one BlockAckReq is sent if m_useBarAfterMissedBa is true
2215 expectedBarCount = m_useBarAfterMissedBa ? 1 : 0;
2216 break;
2219 // only one A-MPDU is transmitted and the BlockAck is not corrupted
2220 expectedBaCount = 1;
2221 break;
2222 default:;
2223 }
2225 expectedBaCount,
2226 "Unexpected number of BlockAck frames");
2228 expectedBarCount,
2229 "Unexpected number of BlockAckReq frames");
2230 }
2231
2232 // check that setting the QosTxop::NMaxInflights attribute has the expected effect.
2233 // We do not support sending an MPDU multiple times concurrently without Block Ack
2234 // agreement. Also, broadcast frames are already duplicated and sent on all links.
2236 {
2238 m_inflightCount.size(),
2239 m_nPackets / 2,
2240 "Did not collect number of simultaneous transmissions for all data frames");
2241
2242 auto nMaxInflight = std::min(m_nMaxInflight, m_staMacs[0]->GetSetupLinkIds().size());
2243 std::size_t maxCount = 0;
2244 for (const auto& [seqNo, count] : m_inflightCount)
2245 {
2247 count,
2248 nMaxInflight,
2249 "MPDU with seqNo=" << seqNo
2250 << " transmitted simultaneously more times than allowed");
2251 maxCount = std::max(maxCount, count);
2252 }
2253
2255 maxCount,
2256 nMaxInflight,
2257 "Expected that at least one data frame was transmitted simultaneously a number of "
2258 "times equal to the NMaxInflights attribute");
2259 }
2260
2262}
2263
2265 WifiMuTrafficPattern muTrafficPattern,
2266 WifiUseBarAfterMissedBa useBarAfterMissedBa,
2267 uint8_t nMaxInflight)
2269 std::string("Check MU data transmission between MLDs ") +
2270 (useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES
2271 ? "(send BAR after BlockAck timeout,"
2272 : "(send Data frames after BlockAck timeout,") +
2273 " MU Traffic pattern: " + std::to_string(static_cast<uint8_t>(muTrafficPattern)) +
2274 ", nMaxInflight=" + std::to_string(nMaxInflight) + ")",
2275 2,
2276 baseParams),
2277 m_muTrafficPattern(muTrafficPattern),
2278 m_useBarAfterMissedBa(useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES),
2279 m_nMaxInflight(nMaxInflight),
2281 m_nPackets(muTrafficPattern == WifiMuTrafficPattern::UL_MU ? 4 : 8)
2282{
2283}
2284
2285void
2287 uint8_t phyId,
2288 WifiConstPsduMap psduMap,
2289 WifiTxVector txVector,
2290 double txPowerW)
2291{
2292 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2293 auto linkId = m_txPsdus.back().linkId;
2294
2295 CtrlTriggerHeader trigger;
2296
2297 for (const auto& [staId, psdu] : psduMap)
2298 {
2299 switch (psdu->GetHeader(0).GetType())
2300 {
2301 case WIFI_MAC_QOSDATA:
2302 CheckAddresses(psdu);
2303 if (psdu->GetHeader(0).HasData())
2304 {
2305 bool isDl = psdu->GetHeader(0).IsFromDs();
2306 auto linkAddress =
2307 isDl ? psdu->GetHeader(0).GetAddr1() : psdu->GetHeader(0).GetAddr2();
2308 auto address = m_apMac->GetMldAddress(linkAddress).value_or(linkAddress);
2309
2310 for (const auto& mpdu : *psdu)
2311 {
2312 // determine the max number of simultaneous transmissions for this MPDU
2313 auto seqNo = mpdu->GetHeader().GetSequenceNumber();
2314 auto [it, success] = m_inflightCount.insert(
2315 {{address, seqNo}, mpdu->GetInFlightLinkIds().size()});
2316 if (!success)
2317 {
2318 it->second = std::max(it->second, mpdu->GetInFlightLinkIds().size());
2319 }
2320 }
2321 for (std::size_t i = 0; i < psdu->GetNMpdus(); i++)
2322 {
2323 // MPDUs with seqNo=2 are always transmitted in an MU PPDU
2324 if (psdu->GetHeader(i).GetSequenceNumber() == 2)
2325 {
2327 {
2328 NS_TEST_EXPECT_MSG_EQ(txVector.IsUlMu(),
2329 true,
2330 "MPDU " << **std::next(psdu->begin(), i)
2331 << " not transmitted in a TB PPDU");
2332 }
2333 else
2334 {
2335 NS_TEST_EXPECT_MSG_EQ(txVector.GetHeMuUserInfoMap().size(),
2336 2,
2337 "MPDU " << **std::next(psdu->begin(), i)
2338 << " not transmitted in a DL MU PPDU");
2339 }
2340 }
2341 // corrupt QoS data frame with sequence number equal to 3 (only once)
2342 if (psdu->GetHeader(i).GetSequenceNumber() != 3)
2343 {
2344 continue;
2345 }
2346 auto uid = psdu->GetPayload(i)->GetUid();
2347 if (!m_dataCorruptedSta)
2348 {
2349 m_uidList.push_front(uid);
2350 m_dataCorruptedSta = isDl ? psdu->GetAddr1() : psdu->GetAddr2();
2351 NS_LOG_INFO("CORRUPTED");
2352 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2353 }
2354 else if ((isDl && m_dataCorruptedSta == psdu->GetAddr1()) ||
2355 (!isDl && m_dataCorruptedSta == psdu->GetAddr2()))
2356 {
2357 // do not corrupt the QoS data frame anymore
2358 if (auto it = std::find(m_uidList.cbegin(), m_uidList.cend(), uid);
2359 it != m_uidList.cend())
2360 {
2361 m_uidList.erase(it);
2362 }
2363 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2364 }
2365 break;
2366 }
2367 }
2368 break;
2370 if (m_nMaxInflight > 1)
2371 {
2372 // we do not check the content of BlockAck when m_nMaxInflight is greater than 1
2373 break;
2374 }
2375 CheckBlockAck(psdu, txVector, linkId);
2377 // to simulate a missed BlockAck, corrupt the fifth BlockAck frame (the first
2378 // two BlockAck frames are sent to acknowledge the QoS data frames that triggered
2379 // the establishment of Block Ack agreements)
2380 if (m_blockAckCount == 5)
2381 {
2382 // corrupt the third BlockAck frame to simulate a missed BlockAck
2383 m_uidList.push_front(psdu->GetPacket()->GetUid());
2384 NS_LOG_INFO("CORRUPTED");
2385 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2386 }
2387 break;
2389 psdu->GetPayload(0)->PeekHeader(trigger);
2390 // the MU scheduler requests channel access on all links but we have to perform the
2391 // following actions only once (hence why we only consider TF transmitted on link 0)
2392 if (trigger.IsBasic() && m_waitFirstTf)
2393 {
2394 m_waitFirstTf = false;
2395 // the AP is starting the transmission of the Basic Trigger frame, so generate
2396 // the configured number of packets at STAs, which are sent in TB PPDUs, when
2397 // transmission of the Trigger Frame ends
2398 auto band = mac->GetWifiPhy(linkId)->GetPhyBand();
2399 Time txDuration = WifiPhy::CalculateTxDuration(psduMap, txVector, band);
2400 for (uint8_t i = 0; i < m_nStations; i++)
2401 {
2402 m_staMacs[i]->GetDevice()->GetNode()->AddApplication(
2403 GetApplication(m_sockets[i], m_nPackets, 450, txDuration, i * 4));
2404 }
2405 }
2406 if (++m_tfCount == m_staMacs[0]->GetSetupLinkIds().size())
2407 {
2408 // a TF has been sent on all the setup links, we can now disable UL OFDMA
2409 auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
2410 NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "Expected an aggregated MU scheduler");
2411 muScheduler->SetAttribute("EnableUlOfdma", BooleanValue(false));
2412 }
2413 break;
2414 default:;
2415 }
2416 }
2417}
2418
2419void
2421 const WifiTxVector& txVector,
2422 uint8_t linkId)
2423{
2424 /*
2425 * Example sequence with DL_MU_BAR_BA_SEQUENCE
2426 * ┌───────┬───────X
2427 * (To:1) │ 2 │ 3 │
2428 * ├───────┼───────┤ ┌───┐ ┌───────┐
2429 * [link 0] (To:0) │ 2 │ 3 │ │BAR│ (To:1) │ 3 │
2430 * ────────────────┴───────┴───────┴┬──┼───┼──┬──────────┴───────┴┬───┬────────
2431 * │BA│ │BA│ │ACK│
2432 * └──┘ └──┘ └───┘
2433 * ┌───────┬───────┐
2434 * (To:1) │ 4 │ 5 │
2435 * ├───────┼───────┤ ┌───┐ ┌───┐
2436 * [link 1] (To:0) │ 4 │ 5 │ │BAR│ │BAR│
2437 * ────────────────────────────┴───────┴───────┴┬──X────┴───┴┬──┼───┼──┬───────
2438 * │BA│ │BA│ │BA│
2439 * └──┘ └──┘ └──┘
2440 *
2441 * Example sequence with UL_MU
2442 *
2443 * ┌──┐ ┌────┐ ┌───┐
2444 * [link 0] │TF│ │M-BA│ │ACK│
2445 * ─────────┴──┴──┬───────┬───────┬──┴────┴────────────┬───────┬─┴───┴─────────
2446 * (From:0) │ 2 │ 3 │ (From:1) │ 3 │
2447 * ├───────┼───────┤ └───────┘
2448 * (From:1) │ 2 │ 3 │
2449 * └───────┴───────X
2450 * ┌──┐
2451 * [link 1] │TF│
2452 * ─────────┴──┴──┬───────────────┬────────────────────────────────────────────
2453 * (From:0) │ QoS Null │
2454 * ├───────────────┤
2455 * (From:1) │ QoS Null │
2456 * └───────────────┘
2457 */
2458 auto mpdu = *psdu->begin();
2459 CtrlBAckResponseHeader blockAck;
2460 mpdu->GetPacket()->PeekHeader(blockAck);
2461 bool isMpdu3corrupted;
2462
2463 switch (m_blockAckCount)
2464 {
2465 case 0:
2466 case 1: // Ignore the first two BlockAck frames that acknowledged frames sent to establish BA
2467 break;
2468 case 2:
2470 {
2471 NS_TEST_EXPECT_MSG_EQ(blockAck.IsMultiSta(), true, "Expected a Multi-STA BlockAck");
2472 for (uint8_t i = 0; i < m_nStations; i++)
2473 {
2474 auto indices = blockAck.FindPerAidTidInfoWithAid(m_staMacs[i]->GetAssociationId());
2475 NS_TEST_ASSERT_MSG_EQ(indices.size(), 1, "Expected one Per AID TID Info per STA");
2476 auto index = indices.front();
2478 true,
2479 "Expected that a QoS data frame was corrupted");
2480 isMpdu3corrupted =
2481 m_staMacs[i]->GetLinkIdByAddress(*m_dataCorruptedSta).has_value();
2482 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(2, index),
2483 true,
2484 "MPDU 2 expected to be successfully received");
2485 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(3, index),
2486 !isMpdu3corrupted,
2487 "Unexpected reception status for MPDU 3");
2488 }
2489
2490 break;
2491 }
2492 case 3:
2493 // BlockAck frames in response to the first DL MU PPDU
2494 isMpdu3corrupted = (mpdu->GetHeader().GetAddr2() == m_dataCorruptedSta);
2496 true,
2497 "MPDU 2 expected to be successfully received");
2499 !isMpdu3corrupted,
2500 "Unexpected reception status for MPDU 3");
2501 // in case of DL MU, if there are at least two links setup, we expect all MPDUs to
2502 // be inflight (on distinct links)
2504 m_staMacs[0]->GetSetupLinkIds().size() > 1)
2505 {
2506 auto queue = m_apMac->GetTxopQueue(AC_BE);
2507 Ptr<StaWifiMac> rcvMac;
2508 if (m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress() ==
2509 mpdu->GetHeader().GetAddr2())
2510 {
2511 rcvMac = m_staMacs[0];
2512 }
2513 else if (m_staMacs[1]->GetFrameExchangeManager(linkId)->GetAddress() ==
2514 mpdu->GetHeader().GetAddr2())
2515 {
2516 rcvMac = m_staMacs[1];
2517 }
2518 else
2519 {
2520 NS_ABORT_MSG("BlockAck frame not sent by a station in DL scenario");
2521 }
2522 auto item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress());
2523 std::size_t nQueuedPkt = 0;
2524 auto delay = WifiPhy::CalculateTxDuration(psdu,
2525 txVector,
2526 rcvMac->GetWifiPhy(linkId)->GetPhyBand()) +
2527 MicroSeconds(1); // to account for propagation delay
2528
2529 while (item)
2530 {
2531 auto seqNo = item->GetHeader().GetSequenceNumber();
2532 NS_TEST_EXPECT_MSG_EQ(item->IsInFlight(),
2533 true,
2534 "MPDU with seqNo=" << seqNo << " is not in flight");
2535 auto linkIds = item->GetInFlightLinkIds();
2536 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
2537 1,
2538 "MPDU with seqNo=" << seqNo
2539 << " is in flight on multiple links");
2540 // The first two MPDUs are in flight on the same link on which the BlockAck
2541 // is sent. The other two MPDUs (only for AP to STA/STA to AP scenarios) are
2542 // in flight on a different link.
2543 auto srcLinkId = m_apMac->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1());
2544 NS_TEST_ASSERT_MSG_EQ(srcLinkId.has_value(),
2545 true,
2546 "Addr1 of BlockAck is not an originator's link address");
2547 NS_TEST_EXPECT_MSG_EQ((*linkIds.begin() == *srcLinkId),
2548 (seqNo <= 3),
2549 "MPDU with seqNo=" << seqNo
2550 << " in flight on unexpected link");
2551 // check the Retry subfield and whether this MPDU is still queued
2552 // after the originator has processed this BlockAck
2553
2554 // MPDUs acknowledged via this BlockAck are no longer queued
2555 bool isQueued = (seqNo > (isMpdu3corrupted ? 2 : 3));
2556 // The Retry subfield is set if the MPDU has not been acknowledged (i.e., it
2557 // is still queued) and has been transmitted on the same link as the BlockAck
2558 // (i.e., its sequence number is less than or equal to 2)
2559 bool isRetry = isQueued && seqNo <= 3;
2560
2561 Simulator::Schedule(delay, [this, item, isQueued, isRetry]() {
2562 NS_TEST_EXPECT_MSG_EQ(item->IsQueued(),
2563 isQueued,
2564 "MPDU with seqNo="
2565 << item->GetHeader().GetSequenceNumber() << " should "
2566 << (isQueued ? "" : "not") << " be queued");
2568 item->GetHeader().IsRetry(),
2569 isRetry,
2570 "Unexpected value for the Retry subfield of the MPDU with seqNo="
2571 << item->GetHeader().GetSequenceNumber());
2572 });
2573
2574 nQueuedPkt++;
2575 item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress(), item);
2576 }
2577 // Each MPDU contains an A-MSDU consisting of two MSDUs
2578 NS_TEST_EXPECT_MSG_EQ(nQueuedPkt, m_nPackets / 2, "Unexpected number of queued MPDUs");
2579 }
2580 break;
2581 }
2582}
2583
2584void
2586{
2587 switch (m_muTrafficPattern)
2588 {
2590 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2592 break;
2594 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2596 break;
2598 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2600 break;
2601 default:;
2602 }
2603
2605
2606 // Enable A-MSDU aggregation. Max A-MSDU size is set such that two MSDUs can be aggregated
2607 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
2608 {
2609 mac->SetAttribute("BE_MaxAmsduSize", UintegerValue(1050));
2610 mac->GetQosTxop(AC_BE)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2612 mac->GetQosTxop(AC_BE)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2613
2614 mac->SetAttribute("VI_MaxAmsduSize", UintegerValue(1050));
2615 mac->GetQosTxop(AC_VI)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2617 mac->GetQosTxop(AC_VI)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2618 }
2619
2620 // aggregate MU scheduler
2621 auto muScheduler = CreateObjectWithAttributes<RrMultiUserScheduler>("EnableUlOfdma",
2622 BooleanValue(false),
2623 "EnableBsrp",
2624 BooleanValue(false),
2625 "UlPsduSize",
2626 UintegerValue(2000));
2627 m_apMac->AggregateObject(muScheduler);
2628
2629 // install post reception error model on all devices
2630 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
2631 {
2632 auto errorModel = CreateObject<ListErrorModel>();
2633 m_errorModels[m_apMac->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2634 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2635 }
2636 for (std::size_t i : {0, 1})
2637 {
2638 for (const auto linkId : m_staMacs[i]->GetLinkIds())
2639 {
2640 auto errorModel = CreateObject<ListErrorModel>();
2641 m_errorModels[m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2642 m_staMacs[i]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2643 }
2644 }
2645}
2646
2647void
2649{
2651 {
2652 // DL Traffic
2653 for (uint8_t i = 0; i < m_nStations; i++)
2654 {
2655 PacketSocketAddress sockAddr;
2656 sockAddr.SetSingleDevice(m_apMac->GetDevice()->GetIfIndex());
2657 sockAddr.SetPhysicalAddress(m_staMacs[i]->GetDevice()->GetAddress());
2658 sockAddr.SetProtocol(1);
2659
2660 // the first client application generates three packets in order
2661 // to trigger the establishment of a Block Ack agreement
2662 m_apMac->GetDevice()->GetNode()->AddApplication(
2663 GetApplication(sockAddr, 3, 450, i * MilliSeconds(50)));
2664
2665 // the second client application generates the first half of the selected number
2666 // of packets, which are sent in DL MU PPDUs, and starts after all BA agreements
2667 // are established
2668 m_apMac->GetDevice()->GetNode()->AddApplication(
2669 GetApplication(sockAddr, m_nPackets / 2, 450, m_nStations * MilliSeconds(50)));
2670
2671 // the third client application generates the second half of the selected number
2672 // of packets, which are sent in DL MU PPDUs, and starts during transmission of
2673 // first A-MPDU, if multiple links are setup
2674 m_apMac->GetDevice()->GetNode()->AddApplication(
2675 GetApplication(sockAddr,
2676 m_nPackets / 2,
2677 450,
2679 }
2680 }
2681 else
2682 {
2683 // UL Traffic
2684 for (uint8_t i = 0; i < m_nStations; i++)
2685 {
2686 m_sockets[i].SetSingleDevice(m_staMacs[i]->GetDevice()->GetIfIndex());
2687 m_sockets[i].SetPhysicalAddress(m_apMac->GetDevice()->GetAddress());
2688 m_sockets[i].SetProtocol(1);
2689
2690 // the first client application generates three packets in order
2691 // to trigger the establishment of a Block Ack agreement
2692 m_staMacs[i]->GetDevice()->GetNode()->AddApplication(
2693 GetApplication(m_sockets[i], 3, 450, i * MilliSeconds(50), i * 4));
2694
2695 // packets to be included in TB PPDUs are generated (by Transmit()) when
2696 // the first Basic Trigger Frame is sent by the AP
2697 }
2698
2699 // MU scheduler starts requesting channel access when we are done with BA agreements
2701 auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
2702 NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "Expected an aggregated MU scheduler");
2703 muScheduler->SetAttribute("EnableUlOfdma", BooleanValue(true));
2704 muScheduler->SetAccessReqInterval(MilliSeconds(3));
2705 // channel access is requested only once
2706 muScheduler->SetAccessReqInterval(Seconds(0));
2707 });
2708 }
2709
2711}
2712
2713void
2715{
2717
2718 // Expected number of packets received by each node (AP, STA 0, STA 1) at application layer
2719 std::array<std::size_t, 3> expectedRxPkts{};
2720
2721 switch (m_muTrafficPattern)
2722 {
2726 // both STA 0 and STA 1 receive m_nPackets + 3 (sent to trigger BA establishment) packets
2727 expectedRxPkts[1] = m_nPackets + 3;
2728 expectedRxPkts[2] = m_nPackets + 3;
2729 break;
2731 // AP receives m_nPackets + 3 (sent to trigger BA establishment) packets from each station
2732 expectedRxPkts[0] = 2 * (m_nPackets + 3);
2733 break;
2734 }
2735
2737 +expectedRxPkts[0],
2738 "Unexpected number of packets received by the AP");
2740 +expectedRxPkts[1],
2741 "Unexpected number of packets received by STA 0");
2743 +expectedRxPkts[2],
2744 "Unexpected number of packets received by STA 1");
2745
2746 // check that setting the QosTxop::NMaxInflights attribute has the expected effect.
2747 // For DL, for each station we send 2 MPDUs to trigger BA agreement and m_nPackets / 2 MPDUs
2748 // For UL, each station sends 2 MPDUs to trigger BA agreement and m_nPackets / 2 MPDUs
2750 m_inflightCount.size(),
2751 2 * (2 + m_nPackets / 2),
2752 "Did not collect number of simultaneous transmissions for all data frames");
2753
2754 auto nMaxInflight = std::min(m_nMaxInflight, m_staMacs[0]->GetSetupLinkIds().size());
2755 std::size_t maxCount = 0;
2756 for (const auto& [txSeqNoPair, count] : m_inflightCount)
2757 {
2759 nMaxInflight,
2760 "MPDU with seqNo="
2761 << txSeqNoPair.second
2762 << " transmitted simultaneously more times than allowed");
2763 maxCount = std::max(maxCount, count);
2764 }
2765
2767 maxCount,
2768 nMaxInflight,
2769 "Expected that at least one data frame was transmitted simultaneously a number of "
2770 "times equal to the NMaxInflights attribute");
2771
2773}
2774
2777 "Check sequence numbers after CTS timeout",
2778 1,
2779 BaseParams{{"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2780 {"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2781 {},
2783 m_nQosDataFrames(0),
2784 m_errorModel(CreateObject<ListErrorModel>()),
2785 m_rtsCorrupted(false)
2786{
2787}
2788
2789void
2791{
2792 // Enable RTS/CTS
2793 Config::SetDefault("ns3::WifiRemoteStationManager::RtsCtsThreshold", StringValue("1000"));
2794
2796
2797 // install post reception error model on all STAs affiliated with non-AP MLD
2798 for (const auto linkId : m_staMacs[0]->GetLinkIds())
2799 {
2800 m_staMacs[0]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel);
2801 }
2802}
2803
2804void
2806{
2807 m_sockAddr.SetSingleDevice(m_apMac->GetDevice()->GetIfIndex());
2808 m_sockAddr.SetPhysicalAddress(m_staMacs[0]->GetAddress());
2809 m_sockAddr.SetProtocol(1);
2810
2811 // install client application generating 4 packets
2812 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(m_sockAddr, 4, 1000));
2813}
2814
2815void
2817 uint8_t phyId,
2818 WifiConstPsduMap psduMap,
2819 WifiTxVector txVector,
2820 double txPowerW)
2821{
2822 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2823
2824 auto psdu = psduMap.begin()->second;
2825
2826 if (psdu->GetHeader(0).IsRts() && !m_rtsCorrupted)
2827 {
2828 m_errorModel->SetList({psdu->GetPacket()->GetUid()});
2829 m_rtsCorrupted = true;
2830 // generate other packets when the first RTS is transmitted
2831 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(m_sockAddr, 4, 1000));
2832 }
2833 else if (psdu->GetHeader(0).IsQosData())
2834 {
2836
2837 if (m_nQosDataFrames == 2)
2838 {
2839 // generate other packets when the second QoS data frame is transmitted
2840 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(m_sockAddr, 4, 1000));
2841 }
2842 }
2843}
2844
2845void
2847{
2850
2851 NS_TEST_EXPECT_MSG_EQ(m_nQosDataFrames, 3, "Unexpected number of transmitted QoS data frames");
2852
2853 std::size_t count{};
2854
2855 for (const auto& txPsdu : m_txPsdus)
2856 {
2857 auto psdu = txPsdu.psduMap.begin()->second;
2858
2859 if (!psdu->GetHeader(0).IsQosData())
2860 {
2861 continue;
2862 }
2863
2864 NS_TEST_EXPECT_MSG_EQ(psdu->GetNMpdus(), 4, "Unexpected number of MPDUs in A-MPDU");
2865
2866 count++;
2867 uint16_t expectedSeqNo{};
2868
2869 switch (count)
2870 {
2871 case 1:
2872 expectedSeqNo = 4;
2873 break;
2874 case 2:
2875 expectedSeqNo = 0;
2876 break;
2877 case 3:
2878 expectedSeqNo = 8;
2879 break;
2880 }
2881
2882 for (const auto& mpdu : *PeekPointer(psdu))
2883 {
2884 NS_TEST_EXPECT_MSG_EQ(mpdu->GetHeader().GetSequenceNumber(),
2885 expectedSeqNo++,
2886 "Unexpected sequence number");
2887 }
2888 }
2889
2891}
2892
2895 "Check starting sequence number update after ADDBA Response timeout",
2896 1,
2897 BaseParams{{"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2898 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2899 {},
2901 m_nQosDataCount(0),
2902 m_staErrorModel(CreateObject<ListErrorModel>())
2903{
2904}
2905
2906void
2908{
2909 // Enable RTS/CTS by setting a threshold lower than packet size (1000)
2910 Config::SetDefault("ns3::WifiRemoteStationManager::RtsCtsThreshold", UintegerValue(900));
2911
2913
2914 // install post reception error model on all STAs affiliated with non-AP MLD
2915 for (const auto linkId : m_staMacs[0]->GetLinkIds())
2916 {
2917 m_staMacs[0]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_staErrorModel);
2918 }
2919}
2920
2921void
2923{
2924 m_sockAddr.SetSingleDevice(m_apMac->GetDevice()->GetIfIndex());
2925 m_sockAddr.SetPhysicalAddress(m_staMacs[0]->GetAddress());
2926 m_sockAddr.SetProtocol(1);
2927
2928 // install client application generating 1 packet of 1000 bytes on the AP MLD
2929 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(m_sockAddr, 1, 1000));
2930}
2931
2932void
2934 uint8_t phyId,
2935 WifiConstPsduMap psduMap,
2936 WifiTxVector txVector,
2937 double txPowerW)
2938{
2939 auto psdu = psduMap.begin()->second;
2940 const auto& hdr = psdu->GetHeader(0);
2941
2942 if (hdr.IsAck())
2943 {
2944 NS_TEST_ASSERT_MSG_EQ(m_txPsdus.empty(), false, "No frame preceding transmitted Ack");
2945
2946 auto prevPsdu = m_txPsdus.back().psduMap.begin()->second;
2947
2948 if (prevPsdu->GetHeader(0).IsAction())
2949 {
2950 WifiActionHeader actionHdr;
2951 (*prevPsdu->begin())->GetPacket()->PeekHeader(actionHdr);
2952 if (actionHdr.GetCategory() == WifiActionHeader::BLOCK_ACK &&
2954 {
2955 // non-AP MLD is acknowledging the ADDBA Request sent by the AP MLD. When the
2956 // AP MLD receives the Ack, it starts an AddBaResponse timer; when the timer
2957 // expires, the AP MLD starts sending data frames with normal acknowledgment.
2958 // Block transmissions of the non-AP MLD on the link that has to be used to send
2959 // the ADDBA Response from now until the end of the timer.
2960
2961 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2962 m_apMac->GetAddress(),
2963 {phyId});
2964
2965 auto band = m_apMac->GetWifiPhy(m_txPsdus.back().linkId)->GetPhyBand();
2966 auto ackDuration = WifiPhy::CalculateTxDuration(psduMap, txVector, band);
2967
2968 // After the AddBaResponse timeout, unblock transmissions of the non-AP MLD on the
2969 // link on which the ADDBA Response has to be sent and block transmissions of the
2970 // AP MLD on the same link, so that we recreate the situation where the AP MLD sends
2971 // the QoS data frame on a link while the non-AP MLD is sending the ADDBA Response
2972 // frame on another link.
2974 ackDuration + m_apMac->GetQosTxop(AC_BE)->GetAddBaResponseTimeout(),
2975 [=, this]() {
2976 m_apMac->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2977 m_staMacs[0]->GetAddress(),
2978 {phyId});
2979 m_staMacs[0]->UnblockUnicastTxOnLinks(
2981 m_apMac->GetAddress(),
2982 {phyId});
2983 });
2984 }
2985 }
2986 }
2987
2988 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2989
2990 if (hdr.IsAction())
2991 {
2992 WifiActionHeader actionHdr;
2993 (*psdu->begin())->GetPacket()->PeekHeader(actionHdr);
2994 if (actionHdr.GetCategory() == WifiActionHeader::BLOCK_ACK &&
2996 {
2997 auto band = m_staMacs[0]->GetDevice()->GetPhy(phyId)->GetPhyBand();
2998 auto addBaRespDuration = WifiPhy::CalculateTxDuration(psduMap, txVector, band);
2999
3000 Simulator::Schedule(addBaRespDuration + TimeStep(1), [=, this]() {
3001 // After the AP MLD has received the ADDBA Response frame:
3002 // - check that the AP has one queued QoS data frame that is in flight
3003 auto mpdu = m_apMac->GetTxopQueue(AC_BE)->Peek();
3004 NS_TEST_ASSERT_MSG_NE(mpdu, nullptr, "Expected an MPDU in the AP MLD queue");
3005 NS_TEST_EXPECT_MSG_EQ(mpdu->GetHeader().IsQosData(),
3006 true,
3007 "Expected a QoS data frame");
3009 mpdu->IsInFlight(),
3010 true,
3011 "Expected the data frame to be inflight when ADDBA RESP is received");
3012
3013 // - check that the starting sequence number at the originator (AP MLD) equals
3014 // the sequence number of the inflight MPDU
3016 m_apMac->GetQosTxop(AC_BE)->GetBaStartingSequence(m_staMacs[0]->GetAddress(),
3017 0),
3018 mpdu->GetHeader().GetSequenceNumber(),
3019 "Unexpected BA Starting Sequence Number");
3020 });
3021 }
3022 }
3023 else if (hdr.IsQosData())
3024 {
3025 // corrupt the reception of the data frame the first time it is sent
3026 if (m_nQosDataCount++ == 0)
3027 {
3028 m_staErrorModel->SetList({psdu->GetPacket()->GetUid()});
3029 }
3030 else
3031 {
3032 m_staErrorModel->SetList({});
3033 }
3034 }
3035}
3036
3037void
3039{
3042
3043 NS_TEST_EXPECT_MSG_EQ(+m_rxPkts[1], 1, "Unexpected number of packets received by STA 0");
3044 NS_TEST_EXPECT_MSG_EQ(m_nQosDataCount, 2, "QoS data frame should be transmitted twice");
3045
3047}
3048
3050 : MultiLinkOperationsTestBase("Check correct reception of the BAR sent after dropping MPDUs",
3051 1,
3052 BaseParams{{"{1, 0, BAND_6GHZ, 0}"},
3053 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3054 {},
3055 assocType}),
3056 m_staErrorModel(CreateObject<ListErrorModel>()),
3057 m_apErrorModel(CreateObject<ListErrorModel>())
3058{
3059}
3060
3061void
3063{
3064 // Set the frame retry limit to 1 so that QoS data frames are dropped after the first TX failure
3065 Config::SetDefault("ns3::WifiMac::FrameRetryLimit", UintegerValue(1));
3066 Config::SetDefault("ns3::WifiRemoteStationManager::IncrementRetryCountUnderBa",
3067 BooleanValue(true));
3068
3070
3071 // install post reception error model on all STAs affiliated with non-AP MLD
3072 for (const auto linkId : m_staMacs[0]->GetLinkIds())
3073 {
3074 m_staMacs[0]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_staErrorModel);
3075 }
3076 // install post reception error model on all APs affiliated with the AP MLD
3077 for (const auto linkId : m_apMac->GetLinkIds())
3078 {
3079 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_apErrorModel);
3080 }
3081}
3082
3083void
3085 uint8_t phyId,
3086 WifiConstPsduMap psduMap,
3087 WifiTxVector txVector,
3088 double txPowerW)
3089{
3090 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
3091
3092 const auto psdu = psduMap.cbegin()->second;
3093 const auto& hdr = psdu->GetHeader(0);
3094
3095 // nothing to do before setup is completed or if this is a Beacon frame
3096 if (!m_setupDone || hdr.IsBeacon())
3097 {
3098 return;
3099 }
3100
3101 auto linkId = mac->GetLinkForPhy(phyId);
3102 NS_TEST_ASSERT_MSG_EQ(linkId.has_value(),
3103 true,
3104 "PHY " << +phyId << " is not operating on any link");
3105
3106 if (!m_events.empty())
3107 {
3108 // check that the expected frame is being transmitted
3110 std::string(hdr.GetTypeString()),
3111 "Unexpected MAC header type for frame #" << ++m_processedEvents);
3112 // perform actions/checks, if any
3113 if (m_events.front().func)
3114 {
3115 m_events.front().func(psdu, txVector, linkId.value());
3116 }
3117
3118 m_events.pop_front();
3119 }
3120}
3121
3122void
3124{
3125 m_setupDone = true;
3126 InsertEvents();
3127
3128 PacketSocketAddress sockAddr;
3129 sockAddr.SetSingleDevice(m_apMac->GetDevice()->GetIfIndex());
3130 sockAddr.SetPhysicalAddress(m_staMacs[0]->GetAddress());
3131 sockAddr.SetProtocol(1);
3132
3133 // install client application generating 2 packets of 1000 bytes on the AP MLD
3134 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(sockAddr, 2, 1000));
3135}
3136
3137void
3139{
3140 // lambda returning the UIDs of all MPDUs in the given PSDU
3141 auto getUids = [](Ptr<const WifiPsdu> psdu) {
3142 std::list<uint64_t> uids;
3143 for (const auto& mpdu : *PeekPointer(psdu))
3144 {
3145 uids.push_back(mpdu->GetPacket()->GetUid());
3146 }
3147 return uids;
3148 };
3149
3150 // BlockAck agreement establishment (AP MLD -> non-AP STA)
3151 m_events.emplace_back(WIFI_MAC_MGT_ACTION);
3152 m_events.emplace_back(WIFI_MAC_CTL_ACK);
3153 m_events.emplace_back(WIFI_MAC_MGT_ACTION);
3154 m_events.emplace_back(WIFI_MAC_CTL_ACK);
3155
3156 m_events.emplace_back(
3158 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
3159 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
3160 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
3161 "Unexpected TA for the data frame sent by the AP");
3162 NS_TEST_EXPECT_MSG_EQ(psdu->GetNMpdus(), 2, "Expected to transmit an A-MPDU");
3163
3164 // corrupt all the MPDUs in the A-MPDU
3165 m_staErrorModel->SetList(getUids(psdu));
3166 });
3167
3168 // QoS data frames are dropped, thus expect a BAR to advance recipient window
3169 m_events.emplace_back(
3171 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
3172 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
3173 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
3174 "Unexpected TA for the BlockAckReq sent by the AP");
3175 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr1(),
3176 m_staMacs[0]->GetAddress(),
3177 "Unexpected RA for the BlockAckReq sent by the AP");
3178 });
3179
3180 m_events.emplace_back(
3182 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
3183 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
3184 m_staMacs[0]->GetAddress(),
3185 "Unexpected TA for the BlockAck sent by the non-AP STA");
3186 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr1(),
3187 m_staMacs[0]->GetBssid(linkId),
3188 "Unexpected RA for the BlockAck sent by the non-AP STA");
3189
3190 // generate uplink frames
3191 Simulator::Schedule(MilliSeconds(5), [=, this]() {
3192 PacketSocketAddress sockAddr;
3193 sockAddr.SetSingleDevice(m_staMacs[0]->GetDevice()->GetIfIndex());
3194 sockAddr.SetPhysicalAddress(m_apMac->GetAddress());
3195 sockAddr.SetProtocol(1);
3196
3197 // install client application generating 2 packets of 1000 bytes on the non-AP STA
3198 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
3199 GetApplication(sockAddr, 2, 1000));
3200 });
3201 });
3202
3203 // BlockAck agreement establishment (non-AP STA -> AP MLD)
3204 m_events.emplace_back(WIFI_MAC_MGT_ACTION);
3205 m_events.emplace_back(WIFI_MAC_CTL_ACK);
3206 m_events.emplace_back(WIFI_MAC_MGT_ACTION);
3207 m_events.emplace_back(WIFI_MAC_CTL_ACK);
3208
3209 m_events.emplace_back(
3211 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
3212 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
3213 m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress(),
3214 "Unexpected TA for the data frame sent by the non-AP STA");
3215 NS_TEST_EXPECT_MSG_EQ(psdu->GetNMpdus(), 2, "Expected to transmit an A-MPDU");
3216
3217 // corrupt all the MPDUs in the A-MPDU
3218 m_apErrorModel->SetList(getUids(psdu));
3219 });
3220
3221 // QoS data frames are dropped, thus expect a BAR to advance recipient window
3222 m_events.emplace_back(
3224 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
3225 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
3226 m_staMacs[0]->GetAddress(),
3227 "Unexpected TA for the BlockAckReq sent by the non-AP STA");
3228 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr1(),
3229 m_staMacs[0]->GetBssid(linkId),
3230 "Unexpected RA for the BlockAckReq sent by the non-AP STA");
3231 });
3232
3233 m_events.emplace_back(
3235 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
3236 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
3237 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
3238 "Unexpected TA for the BlockAck sent by the AP");
3239 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr1(),
3240 m_staMacs[0]->GetAddress(),
3241 "Unexpected RA for the BlockAck sent by the AP");
3242 });
3243}
3244
3245void
3247{
3250
3251 NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
3252
3254}
3255
3257 : TestSuite("wifi-mlo", Type::UNIT)
3258{
3259 using ParamsTuple =
3260 std::tuple<MultiLinkOperationsTestBase::BaseParams, // base config params
3261 std::vector<uint8_t>, // link ID (as seen by AP device) of setup links
3262 std::vector<uint8_t>, // link ID (as seen by non-AP device) of setup links
3263 WifiTidToLinkMappingNegSupport, // AP negotiation support
3264 std::string, // DL TID-to-Link Mapping
3265 std::string>; // UL TID-to-Link Mapping
3266
3271 std::vector<std::set<uint8_t>>{{0, 1, 2}, {1, 2}, {0, 1}, {0, 2}, {0}, {1}, {2}},
3276 std::vector<std::set<uint8_t>>{{0, 1, 2}, {1, 2}, {0, 1}, {0, 2}, {0}, {1}, {2}},
3279
3280 // check that the selection of channels in ML setup accounts for the inability of a
3281 // non-AP MLD to operate on a 160 MHz channel
3285 {"{42, 80, BAND_5GHZ, 2}", "{5, 40, BAND_2_4GHZ, 0}", "{7, 80, BAND_6GHZ, 0}"},
3286 {"{3, 40, BAND_2_4GHZ, 0}", "{15, 160, BAND_6GHZ, 7}", "{50, 160, BAND_5GHZ, 2}"},
3287 {},
3290 {0, 1, 2},
3291 {}, // IDs of setup links are the same for AP and non-AP devices
3293 "",
3294 "",
3295 false),
3297
3298 for (const auto& [baseParams,
3299 setupLinks,
3300 staSetupLinks,
3301 apNegSupport,
3302 dlTidLinkMapping,
3303 ulTidLinkMapping] :
3304 {// matching channels: setup all links
3305 ParamsTuple(
3306 {{"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3307 {"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3308 {},
3310 {0, 1, 2},
3311 {}, // IDs of setup links are the same for AP and non-AP devices
3312 WifiTidToLinkMappingNegSupport::NOT_SUPPORTED, // AP MLD does not support TID-to-Link
3313 // Mapping negotiation
3314 "0,1,2,3 0,1,2; 4,5 0,1", // default mapping used instead
3315 "0,1,2,3 1,2; 6,7 0,1" // default mapping used instead
3316 ),
3317 // non-matching channels, matching PHY bands: setup all links
3318 ParamsTuple({{"{108, 0, BAND_5GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3319 {"{36, 0, BAND_5GHZ, 0}", "{120, 0, BAND_5GHZ, 0}", "{5, 0, BAND_6GHZ, 0}"},
3320 {},
3322 {0, 1, 2},
3323 {}, // IDs of setup links are the same for AP and non-AP devices
3324 WifiTidToLinkMappingNegSupport::SAME_LINK_SET, // AP MLD does not support
3325 // distinct link sets for TIDs
3326 "0,1,2,3 0,1,2; 4,5 0,1", // default mapping used instead
3327 ""),
3328 // non-AP MLD switches band on some links to setup 3 links
3329 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3330 {"{36, 0, BAND_5GHZ, 0}", "{9, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3331 {},
3333 {0, 1, 2},
3334 {}, // IDs of setup links are the same for AP and non-AP devices
3336 "0,1,2,3 0; 4,5,6,7 1,2", // frames of two TIDs are generated
3337 "0,2,3 1,2; 1,4,5,6,7 0" // frames of two TIDs are generated
3338 ),
3339 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3340 // that band, hence only 2 links are setup
3341 ParamsTuple(
3342 {{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{8, 20, BAND_2_4GHZ, 0}"},
3343 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3344 {0},
3346 {0, 1},
3347 {}, // IDs of setup links are the same for AP and non-AP devices
3348 WifiTidToLinkMappingNegSupport::SAME_LINK_SET, // AP MLD does not support distinct
3349 // link sets for TIDs
3350 "0,1,2,3,4,5,6,7 0",
3351 "0,1,2,3,4,5,6,7 0"),
3352 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3353 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3354 // an AP operating on the same channel; hence 2 links are setup
3355 ParamsTuple(
3356 {{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{8, 20, BAND_2_4GHZ, 0}"},
3357 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3358 {0, 1},
3360 {0, 1},
3361 {}, // IDs of setup links are the same for AP and non-AP devices
3363 "0,1,2,3 1",
3364 "0,1,2,3 1"),
3365 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3366 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3367 // an AP operating on the same channel; the third link of the non-AP MLD cannot
3368 // change PHY band and there is an AP operating on the same band (different channel);
3369 // hence 2 links are setup by switching channel (not band) on the third link
3370 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{60, 0, BAND_5GHZ, 0}"},
3371 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3372 {0, 1, 2},
3374 {0, 2},
3375 {}, // IDs of setup links are the same for AP and non-AP devices
3377 "",
3378 ""),
3379 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3380 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3381 // an AP operating on the same channel; hence one link only is setup
3382 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3383 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3384 {0, 1},
3386 {2},
3387 {}, // IDs of setup links are the same for AP and non-AP devices
3389 "",
3390 ""),
3391 // non-AP MLD has only two STAs and setups two links
3392 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3393 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3394 {},
3396 {1, 0},
3397 {}, // IDs of setup links are the same for AP and non-AP devices
3399 "0,1,2,3 1",
3400 ""),
3401 // AP MLD and non-AP MLD setup only one link using legacy association
3402 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{60, 0, BAND_5GHZ, 0}"},
3403 {"{120, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3404 {},
3406 {2},
3407 {1},
3409 "",
3410 ""),
3411 // single link non-AP STA performs legacy association with an AP affiliated with an AP MLD
3412 ParamsTuple({{"{120, 0, BAND_5GHZ, 0}"},
3413 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3414 {},
3416 {2},
3417 {0}, // non-AP STA performs legacy association
3419 "",
3420 ""),
3421 // single link non-AP STA performs ML setup with an AP affiliated with an AP MLD
3422 ParamsTuple({{"{120, 0, BAND_5GHZ, 0}"},
3423 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3424 {},
3426 {2},
3427 {2}, // IDs of setup links are the same for AP and non-AP devices
3429 "",
3430 ""),
3431 // a STA affiliated with a non-AP MLD performs legacy association with a single link AP
3432 ParamsTuple({{"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3433 {"{120, 0, BAND_5GHZ, 0}"},
3434 {},
3436 {0}, // AP is single link
3437 {2},
3439 "0,1,2,3 0,1; 4,5,6,7 0,1", // ignored by single link AP
3440 ""),
3441 // a STA affiliated with a non-AP MLD performs ML setup with a single link AP
3442 ParamsTuple({{"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3443 {"{120, 0, BAND_5GHZ, 0}"},
3444 {},
3446 {0}, // AP is single link
3447 {2}, // legacy association is performed anyway because AP is single link
3449 "0,1,2,3 0,1; 4,5,6,7 0,1", // ignored by single link AP
3450 "")})
3451 {
3452 AddTestCase(new MultiLinkSetupTest(baseParams,
3454 setupLinks,
3455 staSetupLinks,
3456 apNegSupport,
3457 dlTidLinkMapping,
3458 ulTidLinkMapping),
3460 AddTestCase(new MultiLinkSetupTest(baseParams,
3462 setupLinks,
3463 staSetupLinks,
3464 apNegSupport,
3465 dlTidLinkMapping,
3466 ulTidLinkMapping),
3468
3469 for (const auto& trafficPattern : {WifiTrafficPattern::STA_TO_STA,
3474 {
3475 // No Block Ack agreement
3476 AddTestCase(new MultiLinkTxTest(baseParams,
3477 trafficPattern,
3480 1),
3482 for (const auto& useBarAfterMissedBa :
3484 {
3485 // Block Ack agreement with nMaxInflight=1
3486 AddTestCase(new MultiLinkTxTest(baseParams,
3487 trafficPattern,
3489 useBarAfterMissedBa,
3490 1),
3492 // Block Ack agreement with nMaxInflight=2
3493 AddTestCase(new MultiLinkTxTest(baseParams,
3494 trafficPattern,
3496 useBarAfterMissedBa,
3497 2),
3499 }
3500 }
3501
3502 for (const auto& muTrafficPattern : {WifiMuTrafficPattern::DL_MU_BAR_BA_SEQUENCE,
3506 {
3507 for (const auto& useBarAfterMissedBa :
3509 {
3510 // Block Ack agreement with nMaxInflight=1
3512 new MultiLinkMuTxTest(baseParams, muTrafficPattern, useBarAfterMissedBa, 1),
3514 // Block Ack agreement with nMaxInflight=2
3516 new MultiLinkMuTxTest(baseParams, muTrafficPattern, useBarAfterMissedBa, 2),
3518 }
3519 }
3520 }
3521
3526}
3527
Test that the AIDs that an AP MLD assigns to SLDs and MLDs are all unique.
WifiAssocType m_assocType
association type
AidAssignmentTest(const std::vector< std::set< uint8_t > > &linkIds, WifiAssocType assocType)
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.
uint16_t m_startAid
first AID to allocate to stations
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 BlockAckReq frame sent by a Block Ack originator after dropping QoS data frames.
Ptr< ListErrorModel > m_apErrorModel
error rate model to corrupt frames at the AP MLD
void InsertEvents()
Insert elements in the list of expected events (transmitted frames).
void DoSetup() override
Implementation to do any local setup required for this TestCase.
void DoRun() override
Implementation to actually run 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.
std::list< Events > m_events
list of events for a test run
bool m_setupDone
whether association has been completed
void StartTraffic() override
Start the generation of traffic (needs to be overridden).
std::size_t m_processedEvents
number of processed events
Ptr< ListErrorModel > m_staErrorModel
error rate model to corrupt frames at the non-AP STA
BarAfterDroppedMpduTest(WifiAssocType assocType)
Constructor.
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:114
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
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
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.
Definition pointer.h:37
Smart pointer class similar to boost::intrusive_ptr.
Definition ptr.h:70
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:580
static void Destroy()
Execute the events scheduled with ScheduleDestroy().
Definition simulator.cc:125
static Time Now()
Return the current simulation virtual time.
Definition simulator.cc:191
static void Run()
Run the simulation.
Definition simulator.cc:161
static EventId ScheduleNow(FUNC f, Ts &&... args)
Schedule an event to expire Now.
Definition simulator.h:614
static void Stop()
Tell the Simulator the calling event should be the last one executed.
Definition simulator.cc:169
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
void AddTestCase(TestCase *testCase, Duration duration=Duration::QUICK)
Add an individual child TestCase to this test suite.
Definition test.cc:296
@ QUICK
Fast test.
Definition test.h:1057
TestCase(const TestCase &)=delete
Caller graph was not generated because of its size.
Type
Type of test.
Definition test.h:1271
TestSuite(std::string name, Type type=Type::UNIT)
Construct a new test suite.
Definition test.cc:494
Simulation virtual time values and global simulation resolution.
Definition nstime.h:95
Time TimeStep(uint64_t ts)
Scheduler interface.
Definition nstime.h:1370
AttributeValue implementation for Time.
Definition nstime.h:1375
a unique identifier for an interface.
Definition type-id.h:50
TypeId SetParent(TypeId tid)
Set the parent TypeId.
Definition type-id.cc:999
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 ...
Implements the IEEE 802.11 MAC header.
virtual const char * GetTypeString() const
Return a string corresponds to the header type.
create MAC layers for a ns3::WifiNetDevice.
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.
@ PCAP_PER_LINK
Single capture file per link.
static Time CalculateTxDuration(uint32_t size, const WifiTxVector &txVector, WifiPhyBand band, uint16_t staId=SU_STA_ID)
Definition wifi-phy.cc:1574
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
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:690
void SetDefault(std::string name, const AttributeValue &value)
Definition config.cc:886
void ConnectWithoutContext(std::string path, const CallbackBase &cb)
Definition config.cc:946
#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:194
#define NS_LOG_INFO(msg)
Use NS_LOG to output a message of level LOG_INFO.
Definition log.h:267
Ptr< T > CreateObject(Args &&... args)
Create an object by type, with varying number of constructor parameters.
Definition object.h:627
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:454
#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:698
#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:133
#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:819
#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:779
#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:945
#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:655
#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:240
#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:553
Time MicroSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1307
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition nstime.h:1273
Time MilliSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1290
WifiScanType
Scan type (active or passive).
AcIndex QosUtilsMapTidToAc(uint8_t tid)
Maps TID (Traffic ID) to Access classes.
Definition qos-utils.cc:123
WifiAssocType
Type of association performed by this device (provided that it is supported by the standard configure...
@ AP
Definition wifi-mac.h:60
@ WIFI_STANDARD_80211be
@ WIFI_PM_SWITCHING_TO_ACTIVE
@ WIFI_PM_ACTIVE
@ WIFI_PHY_BAND_6GHZ
The 6 GHz band.
@ WIFI_PHY_BAND_5GHZ
The 5 GHz band.
@ AC_BE
Best Effort.
Definition qos-utils.h:66
@ AC_VO
Voice.
Definition qos-utils.h:72
@ AC_VI
Video.
Definition qos-utils.h:70
@ AC_BK
Background.
Definition qos-utils.h:68
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:463
WifiTidToLinkMappingNegSupport
TID-to-Link Mapping Negotiation Support.
double MHz_u
MHz weak type.
Definition wifi-units.h:31
Ptr< T1 > DynamicCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:605
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:295
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_CTL_ACK
@ WIFI_MAC_MGT_ASSOCIATION_REQUEST
@ WIFI_MAC_CTL_BACKRESP
@ WIFI_MAC_MGT_PROBE_RESPONSE
@ WIFI_MAC_QOSDATA
WifiDirection
Wifi direction.
Definition wifi-utils.h:40
bool TidToLinkMappingValidForNegType1(const WifiTidLinkMapping &dlLinkMapping, const WifiTidLinkMapping &ulLinkMapping)
Check if the given TID-to-Link Mappings are valid for a negotiation type of 1.
std::tuple< WifiContainerQueueType, WifiRcvAddr, Mac48Address, std::optional< uint8_t > > WifiContainerQueueId
Tuple (queue type, receiver address type, Address, TID) identifying a container queue.
Ptr< T1 > StaticCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:612
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
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.