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);
301 phyHelper.SetPcapCaptureType(WifiPhyHelper::PcapCaptureType::PCAP_PER_LINK);
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());
323 phyHelper.SetPcapCaptureType(WifiPhyHelper::PcapCaptureType::PCAP_PER_LINK);
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 =
389 (m_assocType == WifiAssocType::ML_SETUP ? m_linkIds.at(index)
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
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());
638 helper.SetPcapCaptureType(WifiPhyHelper::PcapCaptureType::PCAP_PER_LINK);
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{
853 m_staSetupLinks.size(),
854 "Number of setup links must be the same for AP and non-AP device");
855}
856
857void
859{
861
862 m_staMacs[0]->SetAttribute("ActiveProbing", BooleanValue(m_scanType == WifiScanType::ACTIVE));
863 m_apMac->GetEhtConfiguration()->m_tidLinkMappingSupport = m_apNegSupport;
864 // For non-AP MLD, it does not make sense to set the negotiation type to 0 (unless the AP MLD
865 // also advertises 0) or 1 (the AP MLD is discarded if it advertises a support of 3)
866 auto staEhtConfig = m_staMacs[0]->GetEhtConfiguration();
867 staEhtConfig->m_tidLinkMappingSupport = WifiTidToLinkMappingNegSupport::ANY_LINK_SET;
868 staEhtConfig->SetAttribute("TidToLinkMappingDl", StringValue(m_dlTidLinkMappingStr));
869 staEhtConfig->SetAttribute("TidToLinkMappingUl", StringValue(m_ulTidLinkMappingStr));
870
871 // the negotiated link mapping matches the one configured in EHT configuration, unless
872 // the AP MLD does not support TID-to-link mapping negotiation or the AP MLD supports
873 // the negotiation type 1 and the non-AP MLD is configured with a link mapping that
874 // maps distinct link sets to the TIDs, in which case the default link mapping is used
875 m_dlTidLinkMapping = staEhtConfig->GetTidLinkMapping(WifiDirection::DOWNLINK);
876 m_ulTidLinkMapping = staEhtConfig->GetTidLinkMapping(WifiDirection::UPLINK);
877
878 if (m_apNegSupport == WifiTidToLinkMappingNegSupport::NOT_SUPPORTED ||
879 (m_apNegSupport == WifiTidToLinkMappingNegSupport::SAME_LINK_SET &&
881 {
882 m_dlTidLinkMapping.clear(); // default link mapping
883 m_ulTidLinkMapping.clear(); // default link mapping
884 }
885
886 // find (if any) a TID that is not mapped to all setup links
887 using TupleRefs = std::tuple<std::reference_wrapper<const WifiTidLinkMapping>,
888 std::reference_wrapper<uint8_t>,
889 std::reference_wrapper<std::optional<uint8_t>>,
891 for (auto& [mappingRef, tid1Ref, tid2Ref, mac] :
894 {
895 tid1Ref.get() = 0;
896 for (uint8_t tid1 = 0; tid1 < 8; tid1++)
897 {
898 if (auto it1 = mappingRef.get().find(tid1);
899 it1 != mappingRef.get().cend() && it1->second.size() != m_setupLinks.size())
900 {
901 // found. Now search for another TID with a disjoint mapped link set
902 for (uint8_t tid2 = tid1 + 1; tid2 < 8; tid2++)
903 {
904 if (auto it2 = mappingRef.get().find(tid2);
905 it2 != mappingRef.get().cend() && it2->second.size() != m_setupLinks.size())
906 {
907 std::list<uint8_t> intersection;
908 std::set_intersection(it1->second.cbegin(),
909 it1->second.cend(),
910 it2->second.cbegin(),
911 it2->second.cend(),
912 std::back_inserter(intersection));
913 if (intersection.empty())
914 {
915 // found a second TID
916 tid2Ref.get() = tid2;
917 break;
918 }
919 }
920 }
921 tid1Ref.get() = tid1;
922 break;
923 }
924 }
925
926 std::list<uint8_t> tids = {tid1Ref.get()};
927 if (tid2Ref.get())
928 {
929 tids.emplace_back(*tid2Ref.get());
930 }
931
932 // prevent aggregation of MPDUs
933 for (auto tid : tids)
934 {
935 std::string attrName;
936 switch (QosUtilsMapTidToAc(tid))
937 {
938 case AC_VI:
939 attrName = "VI_MaxAmpduSize";
940 break;
941 case AC_VO:
942 attrName = "VO_MaxAmpduSize";
943 break;
944 case AC_BE:
945 attrName = "BE_MaxAmpduSize";
946 break;
947 case AC_BK:
948 attrName = "BK_MaxAmpduSize";
949 break;
950 default:
951 NS_FATAL_ERROR("Invalid TID " << +tid);
952 }
953
954 mac->SetAttribute(attrName, UintegerValue(100));
955 }
956 }
957
958 // configure support for 160 MHz operations and set the channels again to check that they
959 // are compatible
960 for (auto staMac : m_staMacs)
961 {
962 uint8_t linkId = 0;
963 for (const auto& str : m_staChannels)
964 {
965 staMac->GetWifiPhy(linkId++)->SetAttribute("ChannelSettings", StringValue(str));
966 }
967 }
968}
969
970void
972{
973 // DL traffic
974 {
975 PacketSocketAddress sockAddr;
976 sockAddr.SetSingleDevice(m_apMac->GetDevice()->GetIfIndex());
977 sockAddr.SetPhysicalAddress(m_staMacs[0]->GetDevice()->GetAddress());
978 sockAddr.SetProtocol(1);
979
980 m_apMac->GetDevice()->GetNode()->AddApplication(
981 GetApplication(sockAddr, m_setupLinks.size(), 500, Seconds(0), m_dlTid1));
982 if (m_dlTid2)
983 {
984 m_apMac->GetDevice()->GetNode()->AddApplication(
985 GetApplication(sockAddr, m_setupLinks.size(), 500, Seconds(0), *m_dlTid2));
986 }
987 }
988
989 // UL Traffic
990 {
991 PacketSocketAddress sockAddr;
992 sockAddr.SetSingleDevice(m_staMacs[0]->GetDevice()->GetIfIndex());
993 sockAddr.SetPhysicalAddress(m_apMac->GetDevice()->GetAddress());
994 sockAddr.SetProtocol(1);
995
996 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
997 GetApplication(sockAddr, m_setupLinks.size(), 500, MilliSeconds(500), m_ulTid1));
998 if (m_ulTid2)
999 {
1000 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
1001 GetApplication(sockAddr, m_setupLinks.size(), 500, MilliSeconds(500), *m_ulTid2));
1002 }
1003 }
1004}
1005
1006void
1008{
1010
1013
1014 /**
1015 * Check content of management frames
1016 */
1017 std::size_t index = 0;
1018
1019 for (const auto& frameInfo : m_txPsdus)
1020 {
1021 const auto& mpdu = *frameInfo.psduMap.begin()->second->begin();
1022 const auto& linkId = frameInfo.linkId;
1023
1024 switch (mpdu->GetHeader().GetType())
1025 {
1027 CheckBeacon(mpdu, linkId);
1028 break;
1029
1031 CheckProbeResponse(mpdu, linkId);
1032 m_nProbeResp++;
1033 break;
1034
1036 CheckAssocRequest(mpdu, linkId);
1037 break;
1038
1040 CheckAssocResponse(mpdu, linkId);
1041 break;
1042
1043 case WIFI_MAC_QOSDATA:
1044 CheckQosData(mpdu, frameInfo.txVector, linkId, index);
1045 break;
1046
1047 default:
1048 break;
1049 }
1050
1051 index++;
1052 }
1053
1055
1056 std::size_t expectedProbeResp = 0;
1057 if (m_scanType == WifiScanType::ACTIVE)
1058 {
1059 // the number of Probe Response frames that we expect to receive in active mode equals
1060 // the number of channels in common between AP MLD and non-AP MLD at initialization
1061 for (const auto& staChannel : m_staChannels)
1062 {
1063 for (const auto& apChannel : m_apChannels)
1064 {
1065 if (staChannel == apChannel)
1066 {
1067 expectedProbeResp++;
1068 break;
1069 }
1070 }
1071 }
1072 }
1073
1074 NS_TEST_EXPECT_MSG_EQ(m_nProbeResp, expectedProbeResp, "Unexpected number of Probe Responses");
1075
1076 std::size_t expectedRxDlPkts = m_setupLinks.size();
1077 if (m_dlTid2)
1078 {
1079 expectedRxDlPkts *= 2;
1080 }
1081 NS_TEST_EXPECT_MSG_EQ(m_rxPkts[m_staMacs[0]->GetDevice()->GetNode()->GetId()],
1082 expectedRxDlPkts,
1083 "Unexpected number of DL packets received");
1084
1085 std::size_t expectedRxUlPkts = m_setupLinks.size();
1086 if (m_ulTid2)
1087 {
1088 expectedRxUlPkts *= 2;
1089 }
1090 NS_TEST_EXPECT_MSG_EQ(m_rxPkts[m_apMac->GetDevice()->GetNode()->GetId()],
1091 expectedRxUlPkts,
1092 "Unexpected number of UL packets received");
1093
1095}
1096
1097void
1099{
1100 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_BEACON);
1101
1103
1104 NS_TEST_EXPECT_MSG_EQ(m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1105 mpdu->GetHeader().GetAddr2(),
1106 "TA of Beacon frame is not the address of the link it is transmitted on");
1107 MgtBeaconHeader beacon;
1108 mpdu->GetPacket()->PeekHeader(beacon);
1109 const auto& rnr = beacon.Get<ReducedNeighborReport>();
1110 const auto& mle = beacon.Get<MultiLinkElement>();
1111
1112 if (m_apMac->GetNLinks() == 1)
1113 {
1114 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(),
1115 false,
1116 "RNR Element in Beacon frame from single link AP");
1117 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1118 false,
1119 "Multi-Link Element in Beacon frame from single link AP");
1120 return;
1121 }
1122
1123 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(), true, "No RNR Element in Beacon frame");
1124 // All the other APs affiliated with the same AP MLD as the AP sending
1125 // the Beacon frame must be reported in a separate Neighbor AP Info field
1126 NS_TEST_EXPECT_MSG_EQ(rnr->GetNNbrApInfoFields(),
1127 static_cast<std::size_t>(m_apMac->GetNLinks() - 1),
1128 "Unexpected number of Neighbor AP Info fields in RNR");
1129 for (std::size_t nbrApInfoId = 0; nbrApInfoId < rnr->GetNNbrApInfoFields(); nbrApInfoId++)
1130 {
1131 NS_TEST_EXPECT_MSG_EQ(rnr->HasMldParameters(nbrApInfoId),
1132 true,
1133 "MLD Parameters not present");
1134 NS_TEST_EXPECT_MSG_EQ(rnr->GetNTbttInformationFields(nbrApInfoId),
1135 1,
1136 "Expected only one TBTT Info subfield per Neighbor AP Info");
1137 uint8_t nbrLinkId = rnr->GetMldParameters(nbrApInfoId, 0).linkId;
1138 NS_TEST_EXPECT_MSG_EQ(rnr->GetBssid(nbrApInfoId, 0),
1139 m_apMac->GetFrameExchangeManager(nbrLinkId)->GetAddress(),
1140 "BSSID advertised in Neighbor AP Info field "
1141 << nbrApInfoId
1142 << " does not match the address configured on the link "
1143 "advertised in the same field");
1144 }
1145
1146 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Beacon frame");
1147 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1148 m_apMac->GetAddress(),
1149 "Incorrect MLD address advertised in Multi-Link Element");
1150 NS_TEST_EXPECT_MSG_EQ(mle->GetLinkIdInfo(),
1151 +linkId,
1152 "Incorrect Link ID advertised in Multi-Link Element");
1153}
1154
1155void
1157{
1158 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_PROBE_RESPONSE);
1159
1161
1163 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1164 mpdu->GetHeader().GetAddr2(),
1165 "TA of Probe Response is not the address of the link it is transmitted on");
1166 MgtProbeResponseHeader probeResp;
1167 mpdu->GetPacket()->PeekHeader(probeResp);
1168 const auto& rnr = probeResp.Get<ReducedNeighborReport>();
1169 const auto& mle = probeResp.Get<MultiLinkElement>();
1170
1171 if (m_apMac->GetNLinks() == 1)
1172 {
1173 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(),
1174 false,
1175 "RNR Element in Probe Response frame from single link AP");
1176 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1177 false,
1178 "Multi-Link Element in Probe Response frame from single link AP");
1179 return;
1180 }
1181
1182 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(), true, "No RNR Element in Probe Response frame");
1183 // All the other APs affiliated with the same AP MLD as the AP sending
1184 // the Probe Response frame must be reported in a separate Neighbor AP Info field
1185 NS_TEST_EXPECT_MSG_EQ(rnr->GetNNbrApInfoFields(),
1186 static_cast<std::size_t>(m_apMac->GetNLinks() - 1),
1187 "Unexpected number of Neighbor AP Info fields in RNR");
1188 for (std::size_t nbrApInfoId = 0; nbrApInfoId < rnr->GetNNbrApInfoFields(); nbrApInfoId++)
1189 {
1190 NS_TEST_EXPECT_MSG_EQ(rnr->HasMldParameters(nbrApInfoId),
1191 true,
1192 "MLD Parameters not present");
1193 NS_TEST_EXPECT_MSG_EQ(rnr->GetNTbttInformationFields(nbrApInfoId),
1194 1,
1195 "Expected only one TBTT Info subfield per Neighbor AP Info");
1196 uint8_t nbrLinkId = rnr->GetMldParameters(nbrApInfoId, 0).linkId;
1197 NS_TEST_EXPECT_MSG_EQ(rnr->GetBssid(nbrApInfoId, 0),
1198 m_apMac->GetFrameExchangeManager(nbrLinkId)->GetAddress(),
1199 "BSSID advertised in Neighbor AP Info field "
1200 << nbrApInfoId
1201 << " does not match the address configured on the link "
1202 "advertised in the same field");
1203 }
1204
1205 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Probe Response frame");
1206 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1207 m_apMac->GetAddress(),
1208 "Incorrect MLD address advertised in Multi-Link Element");
1209 NS_TEST_EXPECT_MSG_EQ(mle->GetLinkIdInfo(),
1210 +linkId,
1211 "Incorrect Link ID advertised in Multi-Link Element");
1212}
1213
1214void
1216{
1217 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_ASSOCIATION_REQUEST);
1218
1220
1222 m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress(),
1223 mpdu->GetHeader().GetAddr2(),
1224 "TA of Assoc Request frame is not the address of the link it is transmitted on");
1226 mpdu->GetPacket()->PeekHeader(assoc);
1227 const auto& mle = assoc.Get<MultiLinkElement>();
1228
1229 if (m_apMac->GetNLinks() == 1)
1230 {
1231 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1232 false,
1233 "Multi-Link Element in Assoc Request frame to single link AP");
1234 }
1235 else if (m_staMacs[0]->GetAssocType() == WifiAssocType::LEGACY)
1236 {
1238 mle.has_value(),
1239 false,
1240 "Multi-Link Element in Assoc Request frame from non-AP using legacy association");
1241 }
1242 else
1243 {
1244 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1245 true,
1246 "No Multi-Link Element in Assoc Request frame");
1247 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1248 m_staMacs[0]->GetAddress(),
1249 "Incorrect MLD Address advertised in Multi-Link Element");
1251 mle->GetNPerStaProfileSubelements(),
1252 m_setupLinks.size() - 1,
1253 "Incorrect number of Per-STA Profile subelements in Multi-Link Element");
1254 for (std::size_t i = 0; i < mle->GetNPerStaProfileSubelements(); i++)
1255 {
1256 auto& perStaProfile = mle->GetPerStaProfile(i);
1257 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasStaMacAddress(),
1258 true,
1259 "Per-STA Profile must contain STA MAC address");
1260 // find ID of the local link corresponding to this subelement
1261 auto staLinkId = m_staMacs[0]->GetLinkIdByAddress(perStaProfile.GetStaMacAddress());
1263 staLinkId.has_value(),
1264 true,
1265 "No link found with the STA MAC address advertised in Per-STA Profile");
1267 +staLinkId.value(),
1268 +linkId,
1269 "The STA that sent the Assoc Request should not be included in a Per-STA Profile");
1270 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), staLinkId.value());
1271 NS_TEST_EXPECT_MSG_EQ((it != m_setupLinks.end()),
1272 true,
1273 "Not expecting to setup STA link ID " << +staLinkId.value());
1275 +staLinkId.value(),
1276 +perStaProfile.GetLinkId(),
1277 "Not expecting to request association to AP Link ID in Per-STA Profile");
1278 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasAssocRequest(),
1279 true,
1280 "Missing Association Request in Per-STA Profile");
1281 }
1282 }
1283
1284 const auto& tlm = assoc.Get<TidToLinkMapping>();
1285
1286 // A TID-to-Link Mapping IE is included in the Association Request if and only if the AP MLD
1287 // and the non-AP MLD are performing ML setup and the AP MLD advertises a non-null negotiation
1288 // support type
1289 if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetAssocType() == WifiAssocType::LEGACY ||
1290 m_apNegSupport == WifiTidToLinkMappingNegSupport::NOT_SUPPORTED)
1291 {
1292 NS_TEST_EXPECT_MSG_EQ(tlm.empty(),
1293 true,
1294 "Didn't expect a TID-to-Link Mapping IE in Assoc Request frame");
1295 }
1296 else
1297 {
1298 std::size_t expectedNTlm = (m_dlTidLinkMapping == m_ulTidLinkMapping ? 1 : 2);
1299
1300 NS_TEST_ASSERT_MSG_EQ(tlm.size(),
1301 expectedNTlm,
1302 "Unexpected number of TID-to-Link Mapping IE in Assoc Request");
1303
1304 // lambda to check content of TID-to-Link Mapping IE(s)
1305 auto checkTlm = [&](std::size_t tlmId, WifiDirection dir) {
1306 NS_TEST_EXPECT_MSG_EQ(+static_cast<uint8_t>(tlm[tlmId].m_control.direction),
1307 +static_cast<uint8_t>(dir),
1308 "Unexpected direction in TID-to-Link Mapping IE " << tlmId);
1309 auto& expectedMapping =
1310 (dir == WifiDirection::UPLINK ? m_ulTidLinkMapping : m_dlTidLinkMapping);
1311
1312 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].m_control.defaultMapping,
1313 expectedMapping.empty(),
1314 "Default Link Mapping bit not set correctly");
1315 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].m_linkMapping.size(),
1316 expectedMapping.size(),
1317 "Unexpected number of Link Mapping Of TID n fields");
1318 for (uint8_t tid = 0; tid < 8; tid++)
1319 {
1320 if (auto it = expectedMapping.find(tid); it != expectedMapping.cend())
1321 {
1322 NS_TEST_EXPECT_MSG_EQ((tlm[tlmId].GetLinkMappingOfTid(tid) == it->second),
1323 true,
1324 "Unexpected link mapping for TID "
1325 << +tid << " direction " << dir);
1326 }
1327 else
1328 {
1329 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].GetLinkMappingOfTid(tid).empty(),
1330 true,
1331 "Expecting no Link Mapping Of TID n field for TID "
1332 << +tid << " direction " << dir);
1333 }
1334 }
1335 };
1336
1337 if (tlm.size() == 1)
1338 {
1339 checkTlm(0, WifiDirection::BOTH_DIRECTIONS);
1340 }
1341 else
1342 {
1343 std::size_t dlId = (tlm[0].m_control.direction == WifiDirection::DOWNLINK ? 0 : 1);
1344 std::size_t ulId = (dlId == 0 ? 1 : 0);
1345
1346 checkTlm(dlId, WifiDirection::DOWNLINK);
1347 checkTlm(ulId, WifiDirection::UPLINK);
1348 }
1349 }
1350}
1351
1352void
1354{
1355 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_ASSOCIATION_RESPONSE);
1356
1358
1360 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1361 mpdu->GetHeader().GetAddr2(),
1362 "TA of Assoc Response frame is not the address of the link it is transmitted on");
1364 mpdu->GetPacket()->PeekHeader(assoc);
1365 const auto& mle = assoc.Get<MultiLinkElement>();
1366
1367 if (m_apMac->GetNLinks() == 1)
1368 {
1369 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1370 false,
1371 "Multi-Link Element in Assoc Response frame with single link AP");
1372 return;
1373 }
1374
1375 if (m_staMacs[0]->GetAssocType() == WifiAssocType::LEGACY)
1376 {
1378 mle.has_value(),
1379 false,
1380 "Multi-Link Element in Assoc Response frame with non-AP using legacy association");
1381 return;
1382 }
1383
1384 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Assoc Request frame");
1385 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1386 m_apMac->GetAddress(),
1387 "Incorrect MLD Address advertised in Multi-Link Element");
1388 NS_TEST_EXPECT_MSG_EQ(mle->GetNPerStaProfileSubelements(),
1389 m_setupLinks.size() - 1,
1390 "Incorrect number of Per-STA Profile subelements in Multi-Link Element");
1391 for (std::size_t i = 0; i < mle->GetNPerStaProfileSubelements(); i++)
1392 {
1393 auto& perStaProfile = mle->GetPerStaProfile(i);
1394 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasStaMacAddress(),
1395 true,
1396 "Per-STA Profile must contain STA MAC address");
1397 // find ID of the local link corresponding to this subelement
1398 auto apLinkId = m_apMac->GetLinkIdByAddress(perStaProfile.GetStaMacAddress());
1400 apLinkId.has_value(),
1401 true,
1402 "No link found with the STA MAC address advertised in Per-STA Profile");
1403 NS_TEST_EXPECT_MSG_EQ(+apLinkId.value(),
1404 +perStaProfile.GetLinkId(),
1405 "Link ID and MAC address advertised in Per-STA Profile do not match");
1407 +apLinkId.value(),
1408 +linkId,
1409 "The AP that sent the Assoc Response should not be included in a Per-STA Profile");
1410 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), apLinkId.value());
1411 NS_TEST_EXPECT_MSG_EQ((it != m_setupLinks.end()),
1412 true,
1413 "Not expecting to setup AP link ID " << +apLinkId.value());
1414 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasAssocResponse(),
1415 true,
1416 "Missing Association Response in Per-STA Profile");
1417 }
1418
1419 // For the moment, the AP MLD always accepts a valid TID-to-Link Mapping request, hence
1420 // in every case there is no TID-to-Link Mapping IE in the Association Response
1421 NS_TEST_EXPECT_MSG_EQ(assoc.Get<TidToLinkMapping>().empty(),
1422 true,
1423 "Didn't expect to find a TID-to-Link Mapping IE in Association Response");
1424}
1425
1426void
1428{
1429 /**
1430 * Check outcome of Multi-Link Setup
1431 */
1432 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->IsAssociated(), true, "Expected the STA to be associated");
1433
1434 for (auto linkIdIter = m_setupLinks.cbegin(), staLinkIdIter = m_staSetupLinks.cbegin();
1435 linkIdIter != m_setupLinks.cend();
1436 ++linkIdIter, ++staLinkIdIter)
1437 {
1438 auto staLinkId = *staLinkIdIter;
1439 auto apLinkId = *linkIdIter;
1440
1441 auto staAddr = m_staMacs[0]->GetFrameExchangeManager(staLinkId)->GetAddress();
1442 auto apAddr = m_apMac->GetFrameExchangeManager(apLinkId)->GetAddress();
1443
1444 auto staRemoteMgr = m_staMacs[0]->GetWifiRemoteStationManager(staLinkId);
1445 auto apRemoteMgr = m_apMac->GetWifiRemoteStationManager(apLinkId);
1446
1447 // STA side
1448 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetFrameExchangeManager(staLinkId)->GetBssid(),
1449 apAddr,
1450 "Unexpected BSSID for STA link ID " << +staLinkId);
1451 if (m_apMac->GetNLinks() > 1 && m_assocType == WifiAssocType::ML_SETUP)
1452 {
1453 NS_TEST_EXPECT_MSG_EQ((staRemoteMgr->GetMldAddress(apAddr) == m_apMac->GetAddress()),
1454 true,
1455 "Incorrect MLD address stored by STA on link ID " << +staLinkId);
1457 (staRemoteMgr->GetAffiliatedStaAddress(m_apMac->GetAddress()) == apAddr),
1458 true,
1459 "Incorrect affiliated address stored by STA on link ID " << +staLinkId);
1460 }
1461
1462 // AP side
1463 NS_TEST_EXPECT_MSG_EQ(apRemoteMgr->IsAssociated(staAddr),
1464 true,
1465 "Expecting STA " << staAddr << " to be associated on link "
1466 << +apLinkId);
1467 if (m_apMac->GetNLinks() > 1 && m_assocType == WifiAssocType::ML_SETUP)
1468 {
1470 (apRemoteMgr->GetMldAddress(staAddr) == m_staMacs[0]->GetAddress()),
1471 true,
1472 "Incorrect MLD address stored by AP on link ID " << +apLinkId);
1474 (apRemoteMgr->GetAffiliatedStaAddress(m_staMacs[0]->GetAddress()) == staAddr),
1475 true,
1476 "Incorrect affiliated address stored by AP on link ID " << +apLinkId);
1477 }
1478 auto aid = m_apMac->GetAssociationId(staAddr, apLinkId);
1479 const auto& staList = m_apMac->GetStaList(apLinkId);
1480 NS_TEST_EXPECT_MSG_EQ((staList.find(aid) != staList.end()),
1481 true,
1482 "STA " << staAddr << " not found in list of associated STAs");
1483
1484 // STA of non-AP MLD operate on the same channel as the AP (or on its primary80 if the AP
1485 // operates on a 160 MHz channel and non-AP MLD does not support 160 MHz operations)
1486 const auto& staChannel = m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel();
1487 const auto& apChannel = m_apMac->GetWifiPhy(apLinkId)->GetOperatingChannel();
1488
1489 auto width = apChannel.GetTotalWidth();
1490 auto primary20 = apChannel.GetPrimaryChannelIndex(MHz_u{20});
1491
1492 if (width > MHz_u{80} && !m_support160MHzOp)
1493 {
1494 width = MHz_u{80};
1495 primary20 -= apChannel.GetPrimaryChannelIndex(MHz_u{80}) * 4;
1496 }
1497
1498 NS_TEST_EXPECT_MSG_EQ(+staChannel.GetNumber(),
1499 +apChannel.GetPrimaryChannelNumber(width, WIFI_STANDARD_80211be),
1500 "Incorrect operating channel number for STA on link " << +staLinkId);
1501 NS_TEST_EXPECT_MSG_EQ(staChannel.GetFrequency(),
1502 apChannel.GetPrimaryChannelCenterFrequency(width),
1503 "Incorrect operating channel frequency for STA on link "
1504 << +staLinkId);
1505 NS_TEST_EXPECT_MSG_EQ(staChannel.GetWidth(),
1506 width,
1507 "Incorrect operating channel width for STA on link " << +staLinkId);
1508 NS_TEST_EXPECT_MSG_EQ(+staChannel.GetPhyBand(),
1509 +apChannel.GetPhyBand(),
1510 "Incorrect operating PHY band for STA on link " << +staLinkId);
1511 NS_TEST_EXPECT_MSG_EQ(+staChannel.GetPrimaryChannelIndex(MHz_u{20}),
1512 +primary20,
1513 "Incorrect operating primary channel index for STA on link "
1514 << +staLinkId);
1515 }
1516
1517 // lambda to check the link mapping stored at wifi MAC
1518 auto checkStoredMapping =
1519 [this](Ptr<WifiMac> mac, Ptr<WifiMac> dest, WifiDirection dir, bool present) {
1520 NS_TEST_ASSERT_MSG_EQ(mac->GetTidToLinkMapping(dest->GetAddress(), dir).has_value(),
1521 present,
1522 "Link mapping stored by "
1523 << (mac->GetTypeOfStation() == AP ? "AP" : "non-AP")
1524 << " MLD for " << dir << " direction "
1525 << (present ? "expected" : "not expected"));
1526 if (present)
1527 {
1528 const auto& mapping =
1529 (dir == WifiDirection::DOWNLINK ? m_dlTidLinkMapping : m_ulTidLinkMapping);
1531 (mac->GetTidToLinkMapping(dest->GetAddress(), dir)->get() == mapping),
1532 true,
1533 "Incorrect link mapping stored by "
1534 << (mac->GetTypeOfStation() == AP ? "AP" : "non-AP") << " MLD for " << dir
1535 << " direction");
1536
1537 // check correctness of WifiMac::TidMappedOnLink function
1538 std::set<uint8_t> setupLinks(m_setupLinks.cbegin(), m_setupLinks.cend());
1539 for (uint8_t tid = 0; tid < 8; ++tid)
1540 {
1541 const auto& linkSet = mapping.contains(tid) ? mapping.at(tid) : setupLinks;
1542
1543 for (const auto linkId : setupLinks)
1544 {
1546 mac->TidMappedOnLink(dest->GetAddress(), dir, tid, linkId),
1547 linkSet.contains(linkId),
1548 "Incorrect return value on " << (mac == m_apMac ? "AP" : "STA")
1549 << " direction " << dir << " TID " << +tid
1550 << " linkID " << +linkId);
1551 }
1552 }
1553 }
1554 };
1555
1556 auto storedMapping = (m_apMac->GetNLinks() > 1) &&
1557 (m_staMacs[0]->GetAssocType() == WifiAssocType::ML_SETUP) &&
1558 (m_apNegSupport > WifiTidToLinkMappingNegSupport::NOT_SUPPORTED);
1559 checkStoredMapping(m_apMac, m_staMacs[0], WifiDirection::DOWNLINK, storedMapping);
1560 checkStoredMapping(m_apMac, m_staMacs[0], WifiDirection::UPLINK, storedMapping);
1561 checkStoredMapping(m_staMacs[0], m_apMac, WifiDirection::DOWNLINK, storedMapping);
1562 checkStoredMapping(m_staMacs[0], m_apMac, WifiDirection::UPLINK, storedMapping);
1563}
1564
1565void
1567{
1568 const auto legacyAssoc = (m_assocType == WifiAssocType::LEGACY || m_apMac->GetNLinks() == 1);
1569
1570 if (legacyAssoc)
1571 {
1573 1,
1574 "One link is expected to be setup with legacy association");
1575 }
1576
1577 for (uint8_t linkId = 0; linkId < m_apMac->GetNLinks(); ++linkId)
1578 {
1579 auto it = std::find(m_setupLinks.cbegin(), m_setupLinks.cend(), linkId);
1580 auto addr =
1581 (legacyAssoc
1582 ? m_staMacs[0]->GetFrameExchangeManager(m_staSetupLinks.front())->GetAddress()
1583 : m_staMacs[0]->GetAddress());
1585
1586 // the queue on the AP should have a mask if and only if the link has been setup
1587 auto mask = m_apMac->GetMacQueueScheduler()->GetQueueLinkMask(AC_BE, queueId, linkId);
1588 NS_TEST_EXPECT_MSG_EQ(mask.has_value(),
1589 (it != m_setupLinks.cend()),
1590 "Unexpected presence/absence of mask on link " << +linkId);
1591 }
1592
1593 for (const auto& linkId : m_staMacs[0]->GetLinkIds())
1594 {
1595 auto it = std::find(m_staSetupLinks.begin(), m_staSetupLinks.end(), linkId);
1596 if (it == m_staSetupLinks.end())
1597 {
1598 // the link has not been setup
1599 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId)->GetState()->IsStateOff(),
1600 true,
1601 "Link " << +linkId << " has not been setup but is not disabled");
1602 continue;
1603 }
1604
1605 // the link has been setup and must be active
1606 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId)->GetState()->IsStateOff(),
1607 false,
1608 "Expecting link " << +linkId << " to be active");
1609 }
1610}
1611
1612void
1614 const WifiTxVector& txvector,
1615 uint8_t linkId,
1616 std::size_t index)
1617{
1619 const auto& hdr = mpdu->GetHeader();
1620
1621 NS_TEST_ASSERT_MSG_EQ(hdr.IsQosData(), true, "Expected a QoS data frame");
1622
1623 // check TX width
1624 // STA of non-AP MLD operate on the same channel as the AP (or on its primary80 if the AP
1625 // operates on a 160 MHz channel and non-AP MLD does not support 160 MHz operations)
1626 MHz_u width;
1627
1628 if (!hdr.IsToDs() && hdr.IsFromDs())
1629 {
1630 dir = WifiDirection::DOWNLINK;
1631 width = m_apMac->GetWifiPhy(linkId)->GetOperatingChannel().GetTotalWidth();
1632 }
1633 else if (hdr.IsToDs() && !hdr.IsFromDs())
1634 {
1635 dir = WifiDirection::UPLINK;
1636 width = m_staMacs[0]->GetWifiPhy(linkId)->GetOperatingChannel().GetTotalWidth();
1637 }
1638 else
1639 {
1640 NS_ABORT_MSG("Invalid combination for QoS data frame: ToDS(" << hdr.IsToDs() << ") FromDS("
1641 << hdr.IsFromDs() << ")");
1642 }
1643
1644 if (width > MHz_u{80} && !m_support160MHzOp)
1645 {
1646 width = MHz_u{80};
1647 }
1648 NS_TEST_EXPECT_MSG_EQ(txvector.GetChannelWidth(), width, "Unexpected TX width");
1649
1650 const auto& tid1 = (dir == WifiDirection::DOWNLINK) ? m_dlTid1 : m_ulTid1;
1651 const auto& tid2 = (dir == WifiDirection::DOWNLINK) ? m_dlTid2 : m_ulTid2;
1652 uint8_t tid = hdr.GetQosTid();
1653
1654 NS_TEST_ASSERT_MSG_NE((tid == tid1),
1655 (tid2.has_value() && tid == *tid2),
1656 "QoS frame with unexpected TID " << +tid);
1657
1658 // lambda to find the link set the given TID is mapped to
1659 auto findLinkSet = [this, dir](uint8_t tid) -> std::set<uint8_t> {
1660 std::set<uint8_t> linkSet(m_setupLinks.cbegin(), m_setupLinks.cend());
1661 if (auto mappingOptRef = m_apMac->GetTidToLinkMapping(m_staMacs[0]->GetAddress(), dir))
1662 {
1663 // if the TID is not present in the mapping, it is mapped to all setup links
1664 if (auto it = mappingOptRef->get().find(tid); it != mappingOptRef->get().cend())
1665 {
1666 linkSet = it->second;
1667 NS_ASSERT_MSG(!linkSet.empty(), "TID " << +tid << " mapped to no link");
1668 }
1669 }
1670 return linkSet;
1671 };
1672
1673 auto linkSet = findLinkSet(tid);
1674 auto& qosFrames = (tid == tid1) ? m_qosFrames1 : m_qosFrames2;
1675
1676 // Let N the size of the link set, the first N QoS data frames are sent simultaneously
1677 // on the links of the set, the others (if any) will be sent afterwards on such links
1678
1679 // number of concurrent frames of the same TID transmitted so far (excluding current frame)
1680 std::size_t nConcurFrames = std::min(qosFrames.size(), linkSet.size());
1681
1682 // iterate over the concurrent frames of the same TID transmitted so far
1683 for (std::size_t i = 0; i < nConcurFrames; i++)
1684 {
1685 auto prev = qosFrames[i];
1686
1687 // TX duration of i-th frame
1688 auto band = m_apMac->GetWifiPhy(m_txPsdus[prev].linkId)->GetPhyBand();
1689 Time txDuration =
1690 WifiPhy::CalculateTxDuration(m_txPsdus[prev].psduMap, m_txPsdus[prev].txVector, band);
1691
1692 // the current frame is transmitted concurrently with this previous frame if it is
1693 // within the first N (size of the link set) frames, otherwise it is transmitted after
1694 // this previous frame if they have been transmitted on the same link
1695 if (qosFrames.size() < linkSet.size())
1696 {
1697 // the current frame can be sent concurrently with this previous frame
1698 NS_TEST_EXPECT_MSG_LT(m_txPsdus[index].startTx,
1699 m_txPsdus[prev].startTx + txDuration,
1700 "The " << dir << " QoS frame number " << qosFrames.size()
1701 << " was not sent concurrently with others on link "
1702 << +linkId << " which TID " << +tid << " is mapped to");
1703 }
1704 else if (m_txPsdus[prev].linkId == linkId)
1705 {
1706 // the current frame is sent afterwards
1707 NS_TEST_EXPECT_MSG_GT(m_txPsdus[index].startTx,
1708 m_txPsdus[prev].startTx + txDuration,
1709 "The " << dir << " QoS frame number " << qosFrames.size()
1710 << " was sent concurrently with others on a link "
1711 << +linkId << " which TID " << +tid << " is mapped to");
1712 }
1713 }
1714
1715 if (m_apMac->GetNLinks() > 1 && m_assocType == WifiAssocType::ML_SETUP)
1716 {
1717 NS_TEST_EXPECT_MSG_EQ(std::count(linkSet.cbegin(), linkSet.cend(), linkId),
1718 1,
1719 "QoS frame sent on Link ID "
1720 << +linkId << " that does not belong to the link set of TID "
1721 << +tid);
1722 }
1723
1724 if (tid2)
1725 {
1726 // QoS frames of two distinct TIDs are sent.
1727 auto otherTid = (tid == tid1) ? *tid2 : tid1;
1728 const auto& otherQosFrames = (tid == tid1) ? m_qosFrames2 : m_qosFrames1;
1729 auto otherLinkSet = findLinkSet(otherTid);
1730
1731 // number of concurrent frames of the other TID transmitted so far
1732 std::size_t nOtherConcurFrames = std::min(otherQosFrames.size(), otherLinkSet.size());
1733
1734 // iterate over the concurrent frames of the other TID
1735 for (std::size_t i = 0; i < nOtherConcurFrames; i++)
1736 {
1737 auto prev = otherQosFrames[i];
1738
1739 // TX duration of i-th frame
1740 auto band = m_apMac->GetWifiPhy(m_txPsdus[prev].linkId)->GetPhyBand();
1741 Time txDuration = WifiPhy::CalculateTxDuration(m_txPsdus[prev].psduMap,
1742 m_txPsdus[prev].txVector,
1743 band);
1744
1745 // the current frame is transmitted concurrently with this previous frame of the
1746 // other TID if it is within the first N (size of the link set) frames of its TID
1747 if (qosFrames.size() < linkSet.size())
1748 {
1749 // the current frame can be sent concurrently with this previous frame
1750 NS_TEST_EXPECT_MSG_LT(m_txPsdus[index].startTx,
1751 m_txPsdus[prev].startTx + txDuration,
1752 "The " << dir << " QoS frame number " << qosFrames.size()
1753 << " was not sent concurrently with others with TID "
1754 << +otherTid);
1755 }
1756 }
1757 }
1758
1759 // insert the frame
1760 qosFrames.emplace_back(index);
1761
1762 if (qosFrames.size() == m_setupLinks.size())
1763 {
1764 qosFrames.clear();
1765 }
1766}
1767
1769 WifiTrafficPattern trafficPattern,
1770 WifiBaEnabled baEnabled,
1771 WifiUseBarAfterMissedBa useBarAfterMissedBa,
1772 uint8_t nMaxInflight)
1774 std::string("Check data transmission between MLDs ") +
1775 (baEnabled == WifiBaEnabled::YES
1776 ? (useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES
1777 ? "with BA agreement, send BAR after BlockAck timeout"
1778 : "with BA agreement, send Data frames after BlockAck timeout")
1779 : "without BA agreement") +
1780 " (Traffic pattern: " + std::to_string(static_cast<uint8_t>(trafficPattern)) +
1781 (baEnabled == WifiBaEnabled::YES ? ", nMaxInflight=" + std::to_string(nMaxInflight)
1782 : "") +
1783 ")",
1784 2,
1785 baseParams),
1786 m_trafficPattern(trafficPattern),
1787 m_baEnabled(baEnabled == WifiBaEnabled::YES),
1788 m_useBarAfterMissedBa(useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES),
1789 m_nMaxInflight(nMaxInflight),
1790 m_nPackets(trafficPattern == WifiTrafficPattern::STA_TO_BCAST ||
1791 trafficPattern == WifiTrafficPattern::STA_TO_STA
1792 ? 4
1793 : 8)
1794{
1795}
1796
1797void
1799 uint8_t phyId,
1800 WifiConstPsduMap psduMap,
1801 WifiTxVector txVector,
1802 double txPowerW)
1803{
1804 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
1805 auto linkId = m_txPsdus.back().linkId;
1806
1807 auto psdu = psduMap.begin()->second;
1808
1809 switch (psdu->GetHeader(0).GetType())
1810 {
1812 // a management frame is a DL frame if TA equals BSSID
1813 CheckAddresses(psdu,
1814 psdu->GetHeader(0).GetAddr2() == psdu->GetHeader(0).GetAddr3() ? DL : UL);
1815 if (!m_baEnabled)
1816 {
1817 // corrupt all management action frames (ADDBA Request frames) to prevent
1818 // the establishment of a BA agreement
1819 m_uidList.push_front(psdu->GetPacket()->GetUid());
1820 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1821 NS_LOG_INFO("CORRUPTED");
1822 }
1823 break;
1824 case WIFI_MAC_QOSDATA:
1825 CheckAddresses(psdu);
1826
1827 for (const auto& mpdu : *psdu)
1828 {
1829 // determine the max number of simultaneous transmissions for this MPDU
1830 // (only if sent by the traffic source and this is not a broadcast frame)
1831 if (m_baEnabled && m_sourceMac->GetLinkIds().count(linkId) == 1 &&
1832 m_sourceMac->GetFrameExchangeManager(linkId)->GetAddress() ==
1833 mpdu->GetHeader().GetAddr2() &&
1834 !mpdu->GetHeader().GetAddr1().IsGroup())
1835 {
1836 auto seqNo = mpdu->GetHeader().GetSequenceNumber();
1837 auto [it, success] =
1838 m_inflightCount.insert({seqNo, mpdu->GetInFlightLinkIds().size()});
1839 if (!success)
1840 {
1841 it->second = std::max(it->second, mpdu->GetInFlightLinkIds().size());
1842 }
1843 }
1844 }
1845 for (std::size_t i = 0; i < psdu->GetNMpdus(); i++)
1846 {
1847 // corrupt QoS data frame with sequence number equal to 1 (only once) if we are
1848 // not in the AP to broadcast traffic pattern (broadcast frames are not retransmitted)
1849 // nor in the STA to broadcast or STA to STA traffic patterns (retransmissions from
1850 // STA 1 could collide with frames forwarded by the AP)
1851 if (psdu->GetHeader(i).GetSequenceNumber() != 1 ||
1855 {
1856 continue;
1857 }
1858 auto uid = psdu->GetPayload(i)->GetUid();
1859 if (!m_dataCorrupted)
1860 {
1861 m_uidList.push_front(uid);
1862 m_dataCorrupted = true;
1863 NS_LOG_INFO("CORRUPTED");
1864 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1865 }
1866 else
1867 {
1868 // do not corrupt the QoS data frame anymore
1869 if (auto it = std::find(m_uidList.cbegin(), m_uidList.cend(), uid);
1870 it != m_uidList.cend())
1871 {
1872 m_uidList.erase(it);
1873 }
1874 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1875 }
1876 break;
1877 }
1878 break;
1879 case WIFI_MAC_CTL_BACKRESP: {
1880 // ignore BlockAck frames not addressed to the source of the application packets
1881 if (!m_sourceMac->GetLinkIdByAddress(psdu->GetHeader(0).GetAddr1()))
1882 {
1883 break;
1884 }
1885 if (m_nMaxInflight > 1)
1886 {
1887 // we do not check the content of BlockAck when m_nMaxInflight is greater than 1
1888 break;
1889 }
1890 CheckBlockAck(psdu, txVector, linkId);
1892 if (m_blockAckCount == 2)
1893 {
1894 // corrupt the second BlockAck frame to simulate a missed BlockAck
1895 m_uidList.push_front(psdu->GetPacket()->GetUid());
1896 NS_LOG_INFO("CORRUPTED");
1897 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
1898 }
1899 break;
1901 // ignore BlockAckReq frames not transmitted by the source of the application packets
1902 if (m_sourceMac->GetLinkIdByAddress(psdu->GetHeader(0).GetAddr2()))
1903 {
1905 }
1906 break;
1907 }
1908 default:;
1909 }
1910}
1911
1912void
1914 const WifiTxVector& txVector,
1915 uint8_t linkId)
1916{
1917 NS_TEST_ASSERT_MSG_EQ(m_baEnabled, true, "No BlockAck expected without BA agreement");
1919 true,
1920 "No BlockAck expected in AP to broadcast traffic pattern");
1921
1922 /*
1923 * ┌───────┬───────X ┌───────┐
1924 * link 0 │ 0 │ 1 │ │ 1 │
1925 * ───────┴───────┴───────┴┬──┬────┴───────┴┬───┬────────────────────────
1926 * │BA│ │ACK│
1927 * └──┘ └───┘
1928 * ┌───────┬───────┐ ┌───────┬───────┐
1929 * link 1 │ 2 │ 3 │ │ 2 │ 3 │
1930 * ────────────────────┴───────┴───────┴┬──X───┴───────┴───────┴┬──┬─────
1931 * │BA│ │BA│
1932 * └──┘ └──┘
1933 */
1934 auto mpdu = *psdu->begin();
1935 CtrlBAckResponseHeader blockAck;
1936 mpdu->GetPacket()->PeekHeader(blockAck);
1937 bool isMpdu1corrupted = (m_trafficPattern == WifiTrafficPattern::STA_TO_AP ||
1939
1940 switch (m_blockAckCount)
1941 {
1942 case 0: // first BlockAck frame (all traffic patterns)
1944 true,
1945 "MPDU 0 expected to be successfully received");
1947 blockAck.IsPacketReceived(1),
1948 !isMpdu1corrupted,
1949 "MPDU 1 expected to be received only in STA_TO_STA/STA_TO_BCAST scenarios");
1950 // if there are at least two links setup, we expect all MPDUs to be inflight
1951 // (on distinct links)
1952 if (m_staMacs[0]->GetSetupLinkIds().size() > 1)
1953 {
1954 auto queue = m_sourceMac->GetTxopQueue(AC_BE);
1955 auto rcvMac = m_sourceMac == m_staMacs[0] ? StaticCast<WifiMac>(m_apMac)
1957 auto item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress());
1958 std::size_t nQueuedPkt = 0;
1959 auto delay = WifiPhy::CalculateTxDuration(psdu,
1960 txVector,
1961 rcvMac->GetWifiPhy(linkId)->GetPhyBand()) +
1962 MicroSeconds(1); // to account for propagation delay
1963
1964 while (item)
1965 {
1966 auto seqNo = item->GetHeader().GetSequenceNumber();
1967 NS_TEST_EXPECT_MSG_EQ(item->IsInFlight(),
1968 true,
1969 "MPDU with seqNo=" << seqNo << " is not in flight");
1970 auto linkIds = item->GetInFlightLinkIds();
1971 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
1972 1,
1973 "MPDU with seqNo=" << seqNo
1974 << " is in flight on multiple links");
1975 // The first two MPDUs are in flight on the same link on which the BlockAck
1976 // is sent. The other two MPDUs (only for AP to STA/STA to AP scenarios) are
1977 // in flight on a different link.
1978 auto srcLinkId = m_sourceMac->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1());
1979 NS_TEST_ASSERT_MSG_EQ(srcLinkId.has_value(),
1980 true,
1981 "Addr1 of BlockAck is not an originator's link address");
1982 NS_TEST_EXPECT_MSG_EQ((*linkIds.begin() == *srcLinkId),
1983 (seqNo <= 1),
1984 "MPDU with seqNo=" << seqNo
1985 << " in flight on unexpected link");
1986 // check the Retry subfield and whether this MPDU is still queued
1987 // after the originator has processed this BlockAck
1988
1989 // MPDUs acknowledged via this BlockAck are no longer queued
1990 bool isQueued = (seqNo > (isMpdu1corrupted ? 0 : 1));
1991 // The Retry subfield is set if the MPDU has not been acknowledged (i.e., it
1992 // is still queued) and has been transmitted on the same link as the BlockAck
1993 // (i.e., its sequence number is less than or equal to 1)
1994 bool isRetry = isQueued && seqNo <= 1;
1995
1996 Simulator::Schedule(delay, [this, item, isQueued, isRetry]() {
1997 NS_TEST_EXPECT_MSG_EQ(item->IsQueued(),
1998 isQueued,
1999 "MPDU with seqNo="
2000 << item->GetHeader().GetSequenceNumber() << " should "
2001 << (isQueued ? "" : "not") << " be queued");
2003 item->GetHeader().IsRetry(),
2004 isRetry,
2005 "Unexpected value for the Retry subfield of the MPDU with seqNo="
2006 << item->GetHeader().GetSequenceNumber());
2007 });
2008
2009 nQueuedPkt++;
2010 item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress(), item);
2011 }
2012 // Each MPDU contains an A-MSDU consisting of two MSDUs
2013 NS_TEST_EXPECT_MSG_EQ(nQueuedPkt, m_nPackets / 2, "Unexpected number of queued MPDUs");
2014 }
2015 break;
2016 case 1: // second BlockAck frame (STA to AP and AP to STA traffic patterns only)
2017 case 2: // third BlockAck frame (STA to AP and AP to STA traffic patterns only)
2020 true,
2021 "Did not expect to receive a second BlockAck");
2022 // the second BlockAck is corrupted, but the data frames have been received successfully
2023 std::pair<uint16_t, uint16_t> seqNos;
2024 // if multiple links were setup, the transmission of the second A-MPDU started before
2025 // the end of the first one, so the second A-MPDU includes MPDUs with sequence numbers
2026 // 2 and 3. Otherwise, MPDU with sequence number 1 is retransmitted along with the MPDU
2027 // with sequence number 2.
2028 if (m_staMacs[0]->GetSetupLinkIds().size() > 1)
2029 {
2030 seqNos = {2, 3};
2031 }
2032 else
2033 {
2034 seqNos = {1, 2};
2035 }
2036 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(seqNos.first),
2037 true,
2038 "MPDU " << seqNos.first << " expected to be successfully received");
2039 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(seqNos.second),
2040 true,
2041 "MPDU " << seqNos.second << " expected to be successfully received");
2042 break;
2043 }
2044}
2045
2046void
2048{
2050
2051 if (m_baEnabled)
2052 {
2053 // Enable A-MSDU aggregation. Max A-MSDU size is set such that two MSDUs can be aggregated
2054 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
2055 {
2056 mac->SetAttribute("BE_MaxAmsduSize", UintegerValue(2100));
2057 mac->GetQosTxop(AC_BE)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2059 mac->GetQosTxop(AC_BE)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2060 }
2061 }
2062
2063 // install post reception error model on all devices
2064 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
2065 {
2066 auto errorModel = CreateObject<ListErrorModel>();
2067 m_errorModels[m_apMac->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2068 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2069 }
2070 for (std::size_t i : {0, 1})
2071 {
2072 for (const auto linkId : m_staMacs[i]->GetLinkIds())
2073 {
2074 auto errorModel = CreateObject<ListErrorModel>();
2075 m_errorModels[m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2076 m_staMacs[i]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2077 }
2078 }
2079}
2080
2081void
2083{
2084 Address destAddr;
2085
2086 switch (m_trafficPattern)
2087 {
2090 destAddr = m_staMacs[1]->GetDevice()->GetAddress();
2091 break;
2094 destAddr = m_apMac->GetDevice()->GetAddress();
2095 break;
2098 destAddr = m_staMacs[1]->GetDevice()->GetAddress();
2099 break;
2102 destAddr = Mac48Address::GetBroadcast();
2103 break;
2106 destAddr = Mac48Address::GetBroadcast();
2107 break;
2108 }
2109
2110 PacketSocketAddress sockAddr;
2111 sockAddr.SetSingleDevice(m_sourceMac->GetDevice()->GetIfIndex());
2112 sockAddr.SetPhysicalAddress(destAddr);
2113 sockAddr.SetProtocol(1);
2114
2115 // install first client application generating at most 4 packets
2116 m_sourceMac->GetDevice()->GetNode()->AddApplication(
2117 GetApplication(sockAddr, std::min<std::size_t>(m_nPackets, 4), 1000));
2118
2119 if (m_nPackets > 4)
2120 {
2121 // install a second client application generating the remaining packets and
2122 // starting during transmission of first A-MPDU, if multiple links are setup
2123 m_sourceMac->GetDevice()->GetNode()->AddApplication(
2124 GetApplication(sockAddr, m_nPackets - 4, 1000, MilliSeconds(4)));
2125 }
2126
2128}
2129
2130void
2132{
2134
2135 // Expected number of packets received by each node (AP, STA 0, STA 1) at application layer
2136 std::array<std::size_t, 3> expectedRxPkts{};
2137
2138 switch (m_trafficPattern)
2139 {
2142 // only STA 1 receives the m_nPackets packets that have been transmitted
2143 expectedRxPkts[2] = m_nPackets;
2144 break;
2146 // only the AP receives the m_nPackets packets that have been transmitted
2147 expectedRxPkts[0] = m_nPackets;
2148 break;
2150 // the AP replicates the broadcast frames on all the links, hence each station
2151 // receives the m_nPackets packets N times, where N is the number of setup link
2152 expectedRxPkts[1] = m_nPackets * m_staMacs[0]->GetSetupLinkIds().size();
2153 expectedRxPkts[2] = m_nPackets * m_staMacs[1]->GetSetupLinkIds().size();
2154 break;
2156 // the AP receives the m_nPackets packets and then replicates them on all the links,
2157 // hence STA 1 receives m_nPackets packets N times, where N is the number of setup link
2158 expectedRxPkts[0] = m_nPackets;
2159 expectedRxPkts[2] = m_nPackets * m_staMacs[1]->GetSetupLinkIds().size();
2160 break;
2161 }
2162
2164 +expectedRxPkts[0],
2165 "Unexpected number of packets received by the AP");
2167 +expectedRxPkts[1],
2168 "Unexpected number of packets received by STA 0");
2170 +expectedRxPkts[2],
2171 "Unexpected number of packets received by STA 1");
2172
2173 // check that the expected number of BlockAck frames are transmitted
2174 if (m_baEnabled && m_nMaxInflight == 1)
2175 {
2176 std::size_t expectedBaCount = 0;
2177 std::size_t expectedBarCount = 0;
2178
2179 switch (m_trafficPattern)
2180 {
2183 // two A-MPDUs are transmitted and one BlockAck is corrupted
2184 expectedBaCount = 3;
2185 // one BlockAckReq is sent if m_useBarAfterMissedBa is true
2186 expectedBarCount = m_useBarAfterMissedBa ? 1 : 0;
2187 break;
2190 // only one A-MPDU is transmitted and the BlockAck is not corrupted
2191 expectedBaCount = 1;
2192 break;
2193 default:;
2194 }
2196 expectedBaCount,
2197 "Unexpected number of BlockAck frames");
2199 expectedBarCount,
2200 "Unexpected number of BlockAckReq frames");
2201 }
2202
2203 // check that setting the QosTxop::NMaxInflights attribute has the expected effect.
2204 // We do not support sending an MPDU multiple times concurrently without Block Ack
2205 // agreement. Also, broadcast frames are already duplicated and sent on all links.
2207 {
2209 m_inflightCount.size(),
2210 m_nPackets / 2,
2211 "Did not collect number of simultaneous transmissions for all data frames");
2212
2213 auto nMaxInflight = std::min(m_nMaxInflight, m_staMacs[0]->GetSetupLinkIds().size());
2214 std::size_t maxCount = 0;
2215 for (const auto& [seqNo, count] : m_inflightCount)
2216 {
2218 count,
2219 nMaxInflight,
2220 "MPDU with seqNo=" << seqNo
2221 << " transmitted simultaneously more times than allowed");
2222 maxCount = std::max(maxCount, count);
2223 }
2224
2226 maxCount,
2227 nMaxInflight,
2228 "Expected that at least one data frame was transmitted simultaneously a number of "
2229 "times equal to the NMaxInflights attribute");
2230 }
2231
2233}
2234
2236 WifiMuTrafficPattern muTrafficPattern,
2237 WifiUseBarAfterMissedBa useBarAfterMissedBa,
2238 uint8_t nMaxInflight)
2240 std::string("Check MU data transmission between MLDs ") +
2241 (useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES
2242 ? "(send BAR after BlockAck timeout,"
2243 : "(send Data frames after BlockAck timeout,") +
2244 " MU Traffic pattern: " + std::to_string(static_cast<uint8_t>(muTrafficPattern)) +
2245 ", nMaxInflight=" + std::to_string(nMaxInflight) + ")",
2246 2,
2247 baseParams),
2248 m_muTrafficPattern(muTrafficPattern),
2249 m_useBarAfterMissedBa(useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES),
2250 m_nMaxInflight(nMaxInflight),
2251 m_sockets(m_nStations),
2252 m_nPackets(muTrafficPattern == WifiMuTrafficPattern::UL_MU ? 4 : 8)
2253{
2254}
2255
2256void
2258 uint8_t phyId,
2259 WifiConstPsduMap psduMap,
2260 WifiTxVector txVector,
2261 double txPowerW)
2262{
2263 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2264 auto linkId = m_txPsdus.back().linkId;
2265
2266 CtrlTriggerHeader trigger;
2267
2268 for (const auto& [staId, psdu] : psduMap)
2269 {
2270 switch (psdu->GetHeader(0).GetType())
2271 {
2272 case WIFI_MAC_QOSDATA:
2273 CheckAddresses(psdu);
2274 if (psdu->GetHeader(0).HasData())
2275 {
2276 bool isDl = psdu->GetHeader(0).IsFromDs();
2277 auto linkAddress =
2278 isDl ? psdu->GetHeader(0).GetAddr1() : psdu->GetHeader(0).GetAddr2();
2279 auto address = m_apMac->GetMldAddress(linkAddress).value_or(linkAddress);
2280
2281 for (const auto& mpdu : *psdu)
2282 {
2283 // determine the max number of simultaneous transmissions for this MPDU
2284 auto seqNo = mpdu->GetHeader().GetSequenceNumber();
2285 auto [it, success] = m_inflightCount.insert(
2286 {{address, seqNo}, mpdu->GetInFlightLinkIds().size()});
2287 if (!success)
2288 {
2289 it->second = std::max(it->second, mpdu->GetInFlightLinkIds().size());
2290 }
2291 }
2292 for (std::size_t i = 0; i < psdu->GetNMpdus(); i++)
2293 {
2294 // MPDUs with seqNo=2 are always transmitted in an MU PPDU
2295 if (psdu->GetHeader(i).GetSequenceNumber() == 2)
2296 {
2298 {
2299 NS_TEST_EXPECT_MSG_EQ(txVector.IsUlMu(),
2300 true,
2301 "MPDU " << **std::next(psdu->begin(), i)
2302 << " not transmitted in a TB PPDU");
2303 }
2304 else
2305 {
2306 NS_TEST_EXPECT_MSG_EQ(txVector.GetHeMuUserInfoMap().size(),
2307 2,
2308 "MPDU " << **std::next(psdu->begin(), i)
2309 << " not transmitted in a DL MU PPDU");
2310 }
2311 }
2312 // corrupt QoS data frame with sequence number equal to 3 (only once)
2313 if (psdu->GetHeader(i).GetSequenceNumber() != 3)
2314 {
2315 continue;
2316 }
2317 auto uid = psdu->GetPayload(i)->GetUid();
2318 if (!m_dataCorruptedSta)
2319 {
2320 m_uidList.push_front(uid);
2321 m_dataCorruptedSta = isDl ? psdu->GetAddr1() : psdu->GetAddr2();
2322 NS_LOG_INFO("CORRUPTED");
2323 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2324 }
2325 else if ((isDl && m_dataCorruptedSta == psdu->GetAddr1()) ||
2326 (!isDl && m_dataCorruptedSta == psdu->GetAddr2()))
2327 {
2328 // do not corrupt the QoS data frame anymore
2329 if (auto it = std::find(m_uidList.cbegin(), m_uidList.cend(), uid);
2330 it != m_uidList.cend())
2331 {
2332 m_uidList.erase(it);
2333 }
2334 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2335 }
2336 break;
2337 }
2338 }
2339 break;
2341 if (m_nMaxInflight > 1)
2342 {
2343 // we do not check the content of BlockAck when m_nMaxInflight is greater than 1
2344 break;
2345 }
2346 CheckBlockAck(psdu, txVector, linkId);
2348 // to simulate a missed BlockAck, corrupt the fifth BlockAck frame (the first
2349 // two BlockAck frames are sent to acknowledge the QoS data frames that triggered
2350 // the establishment of Block Ack agreements)
2351 if (m_blockAckCount == 5)
2352 {
2353 // corrupt the third BlockAck frame to simulate a missed BlockAck
2354 m_uidList.push_front(psdu->GetPacket()->GetUid());
2355 NS_LOG_INFO("CORRUPTED");
2356 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2357 }
2358 break;
2360 psdu->GetPayload(0)->PeekHeader(trigger);
2361 // the MU scheduler requests channel access on all links but we have to perform the
2362 // following actions only once (hence why we only consider TF transmitted on link 0)
2363 if (trigger.IsBasic() && m_waitFirstTf)
2364 {
2365 m_waitFirstTf = false;
2366 // the AP is starting the transmission of the Basic Trigger frame, so generate
2367 // the configured number of packets at STAs, which are sent in TB PPDUs, when
2368 // transmission of the Trigger Frame ends
2369 auto band = mac->GetWifiPhy(linkId)->GetPhyBand();
2370 Time txDuration = WifiPhy::CalculateTxDuration(psduMap, txVector, band);
2371 for (uint8_t i = 0; i < m_nStations; i++)
2372 {
2373 m_staMacs[i]->GetDevice()->GetNode()->AddApplication(
2374 GetApplication(m_sockets[i], m_nPackets, 450, txDuration, i * 4));
2375 }
2376 }
2377 if (++m_tfCount == m_staMacs[0]->GetSetupLinkIds().size())
2378 {
2379 // a TF has been sent on all the setup links, we can now disable UL OFDMA
2380 auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
2381 NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "Expected an aggregated MU scheduler");
2382 muScheduler->SetAttribute("EnableUlOfdma", BooleanValue(false));
2383 }
2384 break;
2385 default:;
2386 }
2387 }
2388}
2389
2390void
2392 const WifiTxVector& txVector,
2393 uint8_t linkId)
2394{
2395 /*
2396 * Example sequence with DL_MU_BAR_BA_SEQUENCE
2397 * ┌───────┬───────X
2398 * (To:1) │ 2 │ 3 │
2399 * ├───────┼───────┤ ┌───┐ ┌───────┐
2400 * [link 0] (To:0) │ 2 │ 3 │ │BAR│ (To:1) │ 3 │
2401 * ────────────────┴───────┴───────┴┬──┼───┼──┬──────────┴───────┴┬───┬────────
2402 * │BA│ │BA│ │ACK│
2403 * └──┘ └──┘ └───┘
2404 * ┌───────┬───────┐
2405 * (To:1) │ 4 │ 5 │
2406 * ├───────┼───────┤ ┌───┐ ┌───┐
2407 * [link 1] (To:0) │ 4 │ 5 │ │BAR│ │BAR│
2408 * ────────────────────────────┴───────┴───────┴┬──X────┴───┴┬──┼───┼──┬───────
2409 * │BA│ │BA│ │BA│
2410 * └──┘ └──┘ └──┘
2411 *
2412 * Example sequence with UL_MU
2413 *
2414 * ┌──┐ ┌────┐ ┌───┐
2415 * [link 0] │TF│ │M-BA│ │ACK│
2416 * ─────────┴──┴──┬───────┬───────┬──┴────┴────────────┬───────┬─┴───┴─────────
2417 * (From:0) │ 2 │ 3 │ (From:1) │ 3 │
2418 * ├───────┼───────┤ └───────┘
2419 * (From:1) │ 2 │ 3 │
2420 * └───────┴───────X
2421 * ┌──┐
2422 * [link 1] │TF│
2423 * ─────────┴──┴──┬───────────────┬────────────────────────────────────────────
2424 * (From:0) │ QoS Null │
2425 * ├───────────────┤
2426 * (From:1) │ QoS Null │
2427 * └───────────────┘
2428 */
2429 auto mpdu = *psdu->begin();
2430 CtrlBAckResponseHeader blockAck;
2431 mpdu->GetPacket()->PeekHeader(blockAck);
2432 bool isMpdu3corrupted;
2433
2434 switch (m_blockAckCount)
2435 {
2436 case 0:
2437 case 1: // Ignore the first two BlockAck frames that acknowledged frames sent to establish BA
2438 break;
2439 case 2:
2441 {
2442 NS_TEST_EXPECT_MSG_EQ(blockAck.IsMultiSta(), true, "Expected a Multi-STA BlockAck");
2443 for (uint8_t i = 0; i < m_nStations; i++)
2444 {
2445 auto indices = blockAck.FindPerAidTidInfoWithAid(m_staMacs[i]->GetAssociationId());
2446 NS_TEST_ASSERT_MSG_EQ(indices.size(), 1, "Expected one Per AID TID Info per STA");
2447 auto index = indices.front();
2449 true,
2450 "Expected that a QoS data frame was corrupted");
2451 isMpdu3corrupted =
2452 m_staMacs[i]->GetLinkIdByAddress(*m_dataCorruptedSta).has_value();
2453 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(2, index),
2454 true,
2455 "MPDU 2 expected to be successfully received");
2456 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(3, index),
2457 !isMpdu3corrupted,
2458 "Unexpected reception status for MPDU 3");
2459 }
2460
2461 break;
2462 }
2463 case 3:
2464 // BlockAck frames in response to the first DL MU PPDU
2465 isMpdu3corrupted = (mpdu->GetHeader().GetAddr2() == m_dataCorruptedSta);
2467 true,
2468 "MPDU 2 expected to be successfully received");
2470 !isMpdu3corrupted,
2471 "Unexpected reception status for MPDU 3");
2472 // in case of DL MU, if there are at least two links setup, we expect all MPDUs to
2473 // be inflight (on distinct links)
2475 m_staMacs[0]->GetSetupLinkIds().size() > 1)
2476 {
2477 auto queue = m_apMac->GetTxopQueue(AC_BE);
2478 Ptr<StaWifiMac> rcvMac;
2479 if (m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress() ==
2480 mpdu->GetHeader().GetAddr2())
2481 {
2482 rcvMac = m_staMacs[0];
2483 }
2484 else if (m_staMacs[1]->GetFrameExchangeManager(linkId)->GetAddress() ==
2485 mpdu->GetHeader().GetAddr2())
2486 {
2487 rcvMac = m_staMacs[1];
2488 }
2489 else
2490 {
2491 NS_ABORT_MSG("BlockAck frame not sent by a station in DL scenario");
2492 }
2493 auto item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress());
2494 std::size_t nQueuedPkt = 0;
2495 auto delay = WifiPhy::CalculateTxDuration(psdu,
2496 txVector,
2497 rcvMac->GetWifiPhy(linkId)->GetPhyBand()) +
2498 MicroSeconds(1); // to account for propagation delay
2499
2500 while (item)
2501 {
2502 auto seqNo = item->GetHeader().GetSequenceNumber();
2503 NS_TEST_EXPECT_MSG_EQ(item->IsInFlight(),
2504 true,
2505 "MPDU with seqNo=" << seqNo << " is not in flight");
2506 auto linkIds = item->GetInFlightLinkIds();
2507 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
2508 1,
2509 "MPDU with seqNo=" << seqNo
2510 << " is in flight on multiple links");
2511 // The first two MPDUs are in flight on the same link on which the BlockAck
2512 // is sent. The other two MPDUs (only for AP to STA/STA to AP scenarios) are
2513 // in flight on a different link.
2514 auto srcLinkId = m_apMac->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1());
2515 NS_TEST_ASSERT_MSG_EQ(srcLinkId.has_value(),
2516 true,
2517 "Addr1 of BlockAck is not an originator's link address");
2518 NS_TEST_EXPECT_MSG_EQ((*linkIds.begin() == *srcLinkId),
2519 (seqNo <= 3),
2520 "MPDU with seqNo=" << seqNo
2521 << " in flight on unexpected link");
2522 // check the Retry subfield and whether this MPDU is still queued
2523 // after the originator has processed this BlockAck
2524
2525 // MPDUs acknowledged via this BlockAck are no longer queued
2526 bool isQueued = (seqNo > (isMpdu3corrupted ? 2 : 3));
2527 // The Retry subfield is set if the MPDU has not been acknowledged (i.e., it
2528 // is still queued) and has been transmitted on the same link as the BlockAck
2529 // (i.e., its sequence number is less than or equal to 2)
2530 bool isRetry = isQueued && seqNo <= 3;
2531
2532 Simulator::Schedule(delay, [this, item, isQueued, isRetry]() {
2533 NS_TEST_EXPECT_MSG_EQ(item->IsQueued(),
2534 isQueued,
2535 "MPDU with seqNo="
2536 << item->GetHeader().GetSequenceNumber() << " should "
2537 << (isQueued ? "" : "not") << " be queued");
2539 item->GetHeader().IsRetry(),
2540 isRetry,
2541 "Unexpected value for the Retry subfield of the MPDU with seqNo="
2542 << item->GetHeader().GetSequenceNumber());
2543 });
2544
2545 nQueuedPkt++;
2546 item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress(), item);
2547 }
2548 // Each MPDU contains an A-MSDU consisting of two MSDUs
2549 NS_TEST_EXPECT_MSG_EQ(nQueuedPkt, m_nPackets / 2, "Unexpected number of queued MPDUs");
2550 }
2551 break;
2552 }
2553}
2554
2555void
2557{
2558 switch (m_muTrafficPattern)
2559 {
2561 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2563 break;
2565 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2567 break;
2569 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2571 break;
2572 default:;
2573 }
2574
2576
2577 // Enable A-MSDU aggregation. Max A-MSDU size is set such that two MSDUs can be aggregated
2578 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
2579 {
2580 mac->SetAttribute("BE_MaxAmsduSize", UintegerValue(1050));
2581 mac->GetQosTxop(AC_BE)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2583 mac->GetQosTxop(AC_BE)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2584
2585 mac->SetAttribute("VI_MaxAmsduSize", UintegerValue(1050));
2586 mac->GetQosTxop(AC_VI)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2588 mac->GetQosTxop(AC_VI)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2589 }
2590
2591 // aggregate MU scheduler
2592 auto muScheduler = CreateObjectWithAttributes<RrMultiUserScheduler>("EnableUlOfdma",
2593 BooleanValue(false),
2594 "EnableBsrp",
2595 BooleanValue(false),
2596 "UlPsduSize",
2597 UintegerValue(2000));
2598 m_apMac->AggregateObject(muScheduler);
2599
2600 // install post reception error model on all devices
2601 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
2602 {
2603 auto errorModel = CreateObject<ListErrorModel>();
2604 m_errorModels[m_apMac->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2605 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2606 }
2607 for (std::size_t i : {0, 1})
2608 {
2609 for (const auto linkId : m_staMacs[i]->GetLinkIds())
2610 {
2611 auto errorModel = CreateObject<ListErrorModel>();
2612 m_errorModels[m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2613 m_staMacs[i]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2614 }
2615 }
2616}
2617
2618void
2620{
2622 {
2623 // DL Traffic
2624 for (uint8_t i = 0; i < m_nStations; i++)
2625 {
2626 PacketSocketAddress sockAddr;
2627 sockAddr.SetSingleDevice(m_apMac->GetDevice()->GetIfIndex());
2628 sockAddr.SetPhysicalAddress(m_staMacs[i]->GetDevice()->GetAddress());
2629 sockAddr.SetProtocol(1);
2630
2631 // the first client application generates three packets in order
2632 // to trigger the establishment of a Block Ack agreement
2633 m_apMac->GetDevice()->GetNode()->AddApplication(
2634 GetApplication(sockAddr, 3, 450, i * MilliSeconds(50)));
2635
2636 // the second client application generates the first half of the selected number
2637 // of packets, which are sent in DL MU PPDUs, and starts after all BA agreements
2638 // are established
2639 m_apMac->GetDevice()->GetNode()->AddApplication(
2640 GetApplication(sockAddr, m_nPackets / 2, 450, m_nStations * MilliSeconds(50)));
2641
2642 // the third client application generates the second half of the selected number
2643 // of packets, which are sent in DL MU PPDUs, and starts during transmission of
2644 // first A-MPDU, if multiple links are setup
2645 m_apMac->GetDevice()->GetNode()->AddApplication(
2646 GetApplication(sockAddr,
2647 m_nPackets / 2,
2648 450,
2650 }
2651 }
2652 else
2653 {
2654 // UL Traffic
2655 for (uint8_t i = 0; i < m_nStations; i++)
2656 {
2657 m_sockets[i].SetSingleDevice(m_staMacs[i]->GetDevice()->GetIfIndex());
2658 m_sockets[i].SetPhysicalAddress(m_apMac->GetDevice()->GetAddress());
2659 m_sockets[i].SetProtocol(1);
2660
2661 // the first client application generates three packets in order
2662 // to trigger the establishment of a Block Ack agreement
2663 m_staMacs[i]->GetDevice()->GetNode()->AddApplication(
2664 GetApplication(m_sockets[i], 3, 450, i * MilliSeconds(50), i * 4));
2665
2666 // packets to be included in TB PPDUs are generated (by Transmit()) when
2667 // the first Basic Trigger Frame is sent by the AP
2668 }
2669
2670 // MU scheduler starts requesting channel access when we are done with BA agreements
2672 auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
2673 NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "Expected an aggregated MU scheduler");
2674 muScheduler->SetAttribute("EnableUlOfdma", BooleanValue(true));
2675 muScheduler->SetAccessReqInterval(MilliSeconds(3));
2676 // channel access is requested only once
2677 muScheduler->SetAccessReqInterval(Seconds(0));
2678 });
2679 }
2680
2682}
2683
2684void
2686{
2688
2689 // Expected number of packets received by each node (AP, STA 0, STA 1) at application layer
2690 std::array<std::size_t, 3> expectedRxPkts{};
2691
2692 switch (m_muTrafficPattern)
2693 {
2697 // both STA 0 and STA 1 receive m_nPackets + 3 (sent to trigger BA establishment) packets
2698 expectedRxPkts[1] = m_nPackets + 3;
2699 expectedRxPkts[2] = m_nPackets + 3;
2700 break;
2702 // AP receives m_nPackets + 3 (sent to trigger BA establishment) packets from each station
2703 expectedRxPkts[0] = 2 * (m_nPackets + 3);
2704 break;
2705 }
2706
2708 +expectedRxPkts[0],
2709 "Unexpected number of packets received by the AP");
2711 +expectedRxPkts[1],
2712 "Unexpected number of packets received by STA 0");
2714 +expectedRxPkts[2],
2715 "Unexpected number of packets received by STA 1");
2716
2717 // check that setting the QosTxop::NMaxInflights attribute has the expected effect.
2718 // For DL, for each station we send 2 MPDUs to trigger BA agreement and m_nPackets / 2 MPDUs
2719 // For UL, each station sends 2 MPDUs to trigger BA agreement and m_nPackets / 2 MPDUs
2721 m_inflightCount.size(),
2722 2 * (2 + m_nPackets / 2),
2723 "Did not collect number of simultaneous transmissions for all data frames");
2724
2725 auto nMaxInflight = std::min(m_nMaxInflight, m_staMacs[0]->GetSetupLinkIds().size());
2726 std::size_t maxCount = 0;
2727 for (const auto& [txSeqNoPair, count] : m_inflightCount)
2728 {
2730 nMaxInflight,
2731 "MPDU with seqNo="
2732 << txSeqNoPair.second
2733 << " transmitted simultaneously more times than allowed");
2734 maxCount = std::max(maxCount, count);
2735 }
2736
2738 maxCount,
2739 nMaxInflight,
2740 "Expected that at least one data frame was transmitted simultaneously a number of "
2741 "times equal to the NMaxInflights attribute");
2742
2744}
2745
2748 "Check sequence numbers after CTS timeout",
2749 1,
2750 BaseParams{{"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2751 {"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2752 {},
2753 WifiAssocType::ML_SETUP}),
2754 m_nQosDataFrames(0),
2755 m_errorModel(CreateObject<ListErrorModel>()),
2756 m_rtsCorrupted(false)
2757{
2758}
2759
2760void
2762{
2763 // Enable RTS/CTS
2764 Config::SetDefault("ns3::WifiRemoteStationManager::RtsCtsThreshold", StringValue("1000"));
2765
2767
2768 // install post reception error model on all STAs affiliated with non-AP MLD
2769 for (const auto linkId : m_staMacs[0]->GetLinkIds())
2770 {
2771 m_staMacs[0]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel);
2772 }
2773}
2774
2775void
2777{
2778 m_sockAddr.SetSingleDevice(m_apMac->GetDevice()->GetIfIndex());
2779 m_sockAddr.SetPhysicalAddress(m_staMacs[0]->GetAddress());
2781
2782 // install client application generating 4 packets
2783 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(m_sockAddr, 4, 1000));
2784}
2785
2786void
2788 uint8_t phyId,
2789 WifiConstPsduMap psduMap,
2790 WifiTxVector txVector,
2791 double txPowerW)
2792{
2793 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2794
2795 auto psdu = psduMap.begin()->second;
2796
2797 if (psdu->GetHeader(0).IsRts() && !m_rtsCorrupted)
2798 {
2799 m_errorModel->SetList({psdu->GetPacket()->GetUid()});
2800 m_rtsCorrupted = true;
2801 // generate other packets when the first RTS is transmitted
2802 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(m_sockAddr, 4, 1000));
2803 }
2804 else if (psdu->GetHeader(0).IsQosData())
2805 {
2807
2808 if (m_nQosDataFrames == 2)
2809 {
2810 // generate other packets when the second QoS data frame is transmitted
2811 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(m_sockAddr, 4, 1000));
2812 }
2813 }
2814}
2815
2816void
2818{
2821
2822 NS_TEST_EXPECT_MSG_EQ(m_nQosDataFrames, 3, "Unexpected number of transmitted QoS data frames");
2823
2824 std::size_t count{};
2825
2826 for (const auto& txPsdu : m_txPsdus)
2827 {
2828 auto psdu = txPsdu.psduMap.begin()->second;
2829
2830 if (!psdu->GetHeader(0).IsQosData())
2831 {
2832 continue;
2833 }
2834
2835 NS_TEST_EXPECT_MSG_EQ(psdu->GetNMpdus(), 4, "Unexpected number of MPDUs in A-MPDU");
2836
2837 count++;
2838 uint16_t expectedSeqNo{};
2839
2840 switch (count)
2841 {
2842 case 1:
2843 expectedSeqNo = 4;
2844 break;
2845 case 2:
2846 expectedSeqNo = 0;
2847 break;
2848 case 3:
2849 expectedSeqNo = 8;
2850 break;
2851 }
2852
2853 for (const auto& mpdu : *PeekPointer(psdu))
2854 {
2855 NS_TEST_EXPECT_MSG_EQ(mpdu->GetHeader().GetSequenceNumber(),
2856 expectedSeqNo++,
2857 "Unexpected sequence number");
2858 }
2859 }
2860
2862}
2863
2866 "Check starting sequence number update after ADDBA Response timeout",
2867 1,
2868 BaseParams{{"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2869 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
2870 {},
2871 WifiAssocType::ML_SETUP}),
2872 m_nQosDataCount(0),
2873 m_staErrorModel(CreateObject<ListErrorModel>())
2874{
2875}
2876
2877void
2879{
2880 // Enable RTS/CTS by setting a threshold lower than packet size (1000)
2881 Config::SetDefault("ns3::WifiRemoteStationManager::RtsCtsThreshold", UintegerValue(900));
2882
2884
2885 // install post reception error model on all STAs affiliated with non-AP MLD
2886 for (const auto linkId : m_staMacs[0]->GetLinkIds())
2887 {
2888 m_staMacs[0]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_staErrorModel);
2889 }
2890}
2891
2892void
2894{
2895 m_sockAddr.SetSingleDevice(m_apMac->GetDevice()->GetIfIndex());
2896 m_sockAddr.SetPhysicalAddress(m_staMacs[0]->GetAddress());
2898
2899 // install client application generating 1 packet of 1000 bytes on the AP MLD
2900 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(m_sockAddr, 1, 1000));
2901}
2902
2903void
2905 uint8_t phyId,
2906 WifiConstPsduMap psduMap,
2907 WifiTxVector txVector,
2908 double txPowerW)
2909{
2910 auto psdu = psduMap.begin()->second;
2911 const auto& hdr = psdu->GetHeader(0);
2912
2913 if (hdr.IsAck())
2914 {
2915 NS_TEST_ASSERT_MSG_EQ(m_txPsdus.empty(), false, "No frame preceding transmitted Ack");
2916
2917 auto prevPsdu = m_txPsdus.back().psduMap.begin()->second;
2918
2919 if (prevPsdu->GetHeader(0).IsAction())
2920 {
2921 WifiActionHeader actionHdr;
2922 (*prevPsdu->begin())->GetPacket()->PeekHeader(actionHdr);
2923 if (actionHdr.GetCategory() == WifiActionHeader::BLOCK_ACK &&
2925 {
2926 // non-AP MLD is acknowledging the ADDBA Request sent by the AP MLD. When the
2927 // AP MLD receives the Ack, it starts an AddBaResponse timer; when the timer
2928 // expires, the AP MLD starts sending data frames with normal acknowledgment.
2929 // Block transmissions of the non-AP MLD on the link that has to be used to send
2930 // the ADDBA Response from now until the end of the timer.
2931
2932 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2933 m_apMac->GetAddress(),
2934 {phyId});
2935
2936 auto band = m_apMac->GetWifiPhy(m_txPsdus.back().linkId)->GetPhyBand();
2937 auto ackDuration = WifiPhy::CalculateTxDuration(psduMap, txVector, band);
2938
2939 // After the AddBaResponse timeout, unblock transmissions of the non-AP MLD on the
2940 // link on which the ADDBA Response has to be sent and block transmissions of the
2941 // AP MLD on the same link, so that we recreate the situation where the AP MLD sends
2942 // the QoS data frame on a link while the non-AP MLD is sending the ADDBA Response
2943 // frame on another link.
2945 ackDuration + m_apMac->GetQosTxop(AC_BE)->GetAddBaResponseTimeout(),
2946 [=, this]() {
2947 m_apMac->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2948 m_staMacs[0]->GetAddress(),
2949 {phyId});
2950 m_staMacs[0]->UnblockUnicastTxOnLinks(
2951 WifiQueueBlockedReason::TID_NOT_MAPPED,
2952 m_apMac->GetAddress(),
2953 {phyId});
2954 });
2955 }
2956 }
2957 }
2958
2959 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2960
2961 if (hdr.IsAction())
2962 {
2963 WifiActionHeader actionHdr;
2964 (*psdu->begin())->GetPacket()->PeekHeader(actionHdr);
2965 if (actionHdr.GetCategory() == WifiActionHeader::BLOCK_ACK &&
2967 {
2968 auto band = m_staMacs[0]->GetDevice()->GetPhy(phyId)->GetPhyBand();
2969 auto addBaRespDuration = WifiPhy::CalculateTxDuration(psduMap, txVector, band);
2970
2971 Simulator::Schedule(addBaRespDuration + TimeStep(1), [=, this]() {
2972 // After the AP MLD has received the ADDBA Response frame:
2973 // - check that the AP has one queued QoS data frame that is in flight
2974 auto mpdu = m_apMac->GetTxopQueue(AC_BE)->Peek();
2975 NS_TEST_ASSERT_MSG_NE(mpdu, nullptr, "Expected an MPDU in the AP MLD queue");
2976 NS_TEST_EXPECT_MSG_EQ(mpdu->GetHeader().IsQosData(),
2977 true,
2978 "Expected a QoS data frame");
2980 mpdu->IsInFlight(),
2981 true,
2982 "Expected the data frame to be inflight when ADDBA RESP is received");
2983
2984 // - check that the starting sequence number at the originator (AP MLD) equals
2985 // the sequence number of the inflight MPDU
2987 m_apMac->GetQosTxop(AC_BE)->GetBaStartingSequence(m_staMacs[0]->GetAddress(),
2988 0),
2989 mpdu->GetHeader().GetSequenceNumber(),
2990 "Unexpected BA Starting Sequence Number");
2991 });
2992 }
2993 }
2994 else if (hdr.IsQosData())
2995 {
2996 // corrupt the reception of the data frame the first time it is sent
2997 if (m_nQosDataCount++ == 0)
2998 {
2999 m_staErrorModel->SetList({psdu->GetPacket()->GetUid()});
3000 }
3001 else
3002 {
3003 m_staErrorModel->SetList({});
3004 }
3005 }
3006}
3007
3008void
3010{
3013
3014 NS_TEST_EXPECT_MSG_EQ(+m_rxPkts[1], 1, "Unexpected number of packets received by STA 0");
3015 NS_TEST_EXPECT_MSG_EQ(m_nQosDataCount, 2, "QoS data frame should be transmitted twice");
3016
3018}
3019
3021 : MultiLinkOperationsTestBase("Check correct reception of the BAR sent after dropping MPDUs",
3022 1,
3023 BaseParams{{"{1, 0, BAND_6GHZ, 0}"},
3024 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3025 {},
3026 assocType}),
3027 m_staErrorModel(CreateObject<ListErrorModel>()),
3028 m_apErrorModel(CreateObject<ListErrorModel>())
3029{
3030}
3031
3032void
3034{
3035 // Set the frame retry limit to 1 so that QoS data frames are dropped after the first TX failure
3036 Config::SetDefault("ns3::WifiMac::FrameRetryLimit", UintegerValue(1));
3037 Config::SetDefault("ns3::WifiRemoteStationManager::IncrementRetryCountUnderBa",
3038 BooleanValue(true));
3039
3041
3042 // install post reception error model on all STAs affiliated with non-AP MLD
3043 for (const auto linkId : m_staMacs[0]->GetLinkIds())
3044 {
3045 m_staMacs[0]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_staErrorModel);
3046 }
3047 // install post reception error model on all APs affiliated with the AP MLD
3048 for (const auto linkId : m_apMac->GetLinkIds())
3049 {
3050 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_apErrorModel);
3051 }
3052}
3053
3054void
3056 uint8_t phyId,
3057 WifiConstPsduMap psduMap,
3058 WifiTxVector txVector,
3059 double txPowerW)
3060{
3061 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
3062
3063 const auto psdu = psduMap.cbegin()->second;
3064 const auto& hdr = psdu->GetHeader(0);
3065
3066 // nothing to do before setup is completed or if this is a Beacon frame
3067 if (!m_setupDone || hdr.IsBeacon())
3068 {
3069 return;
3070 }
3071
3072 auto linkId = mac->GetLinkForPhy(phyId);
3073 NS_TEST_ASSERT_MSG_EQ(linkId.has_value(),
3074 true,
3075 "PHY " << +phyId << " is not operating on any link");
3076
3077 if (!m_events.empty())
3078 {
3079 // check that the expected frame is being transmitted
3081 std::string(hdr.GetTypeString()),
3082 "Unexpected MAC header type for frame #" << ++m_processedEvents);
3083 // perform actions/checks, if any
3084 if (m_events.front().func)
3085 {
3086 m_events.front().func(psdu, txVector, linkId.value());
3087 }
3088
3089 m_events.pop_front();
3090 }
3091}
3092
3093void
3095{
3096 m_setupDone = true;
3097 InsertEvents();
3098
3099 PacketSocketAddress sockAddr;
3100 sockAddr.SetSingleDevice(m_apMac->GetDevice()->GetIfIndex());
3101 sockAddr.SetPhysicalAddress(m_staMacs[0]->GetAddress());
3102 sockAddr.SetProtocol(1);
3103
3104 // install client application generating 2 packets of 1000 bytes on the AP MLD
3105 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(sockAddr, 2, 1000));
3106}
3107
3108void
3110{
3111 // lambda returning the UIDs of all MPDUs in the given PSDU
3112 auto getUids = [](Ptr<const WifiPsdu> psdu) {
3113 std::list<uint64_t> uids;
3114 for (const auto& mpdu : *PeekPointer(psdu))
3115 {
3116 uids.push_back(mpdu->GetPacket()->GetUid());
3117 }
3118 return uids;
3119 };
3120
3121 // BlockAck agreement establishment (AP MLD -> non-AP STA)
3122 m_events.emplace_back(WIFI_MAC_MGT_ACTION);
3123 m_events.emplace_back(WIFI_MAC_CTL_ACK);
3124 m_events.emplace_back(WIFI_MAC_MGT_ACTION);
3125 m_events.emplace_back(WIFI_MAC_CTL_ACK);
3126
3127 m_events.emplace_back(
3129 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
3130 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
3131 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
3132 "Unexpected TA for the data frame sent by the AP");
3133 NS_TEST_EXPECT_MSG_EQ(psdu->GetNMpdus(), 2, "Expected to transmit an A-MPDU");
3134
3135 // corrupt all the MPDUs in the A-MPDU
3136 m_staErrorModel->SetList(getUids(psdu));
3137 });
3138
3139 // QoS data frames are dropped, thus expect a BAR to advance recipient window
3140 m_events.emplace_back(
3142 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
3143 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
3144 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
3145 "Unexpected TA for the BlockAckReq sent by the AP");
3146 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr1(),
3147 m_staMacs[0]->GetAddress(),
3148 "Unexpected RA for the BlockAckReq sent by the AP");
3149 });
3150
3151 m_events.emplace_back(
3153 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
3154 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
3155 m_staMacs[0]->GetAddress(),
3156 "Unexpected TA for the BlockAck sent by the non-AP STA");
3157 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr1(),
3158 m_staMacs[0]->GetBssid(linkId),
3159 "Unexpected RA for the BlockAck sent by the non-AP STA");
3160
3161 // generate uplink frames
3162 Simulator::Schedule(MilliSeconds(5), [=, this]() {
3163 PacketSocketAddress sockAddr;
3164 sockAddr.SetSingleDevice(m_staMacs[0]->GetDevice()->GetIfIndex());
3165 sockAddr.SetPhysicalAddress(m_apMac->GetAddress());
3166 sockAddr.SetProtocol(1);
3167
3168 // install client application generating 2 packets of 1000 bytes on the non-AP STA
3169 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
3170 GetApplication(sockAddr, 2, 1000));
3171 });
3172 });
3173
3174 // BlockAck agreement establishment (non-AP STA -> AP MLD)
3175 m_events.emplace_back(WIFI_MAC_MGT_ACTION);
3176 m_events.emplace_back(WIFI_MAC_CTL_ACK);
3177 m_events.emplace_back(WIFI_MAC_MGT_ACTION);
3178 m_events.emplace_back(WIFI_MAC_CTL_ACK);
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]->GetFrameExchangeManager(linkId)->GetAddress(),
3185 "Unexpected TA for the data frame sent by the non-AP STA");
3186 NS_TEST_EXPECT_MSG_EQ(psdu->GetNMpdus(), 2, "Expected to transmit an A-MPDU");
3187
3188 // corrupt all the MPDUs in the A-MPDU
3189 m_apErrorModel->SetList(getUids(psdu));
3190 });
3191
3192 // QoS data frames are dropped, thus expect a BAR to advance recipient window
3193 m_events.emplace_back(
3195 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
3196 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
3197 m_staMacs[0]->GetAddress(),
3198 "Unexpected TA for the BlockAckReq sent by the non-AP STA");
3199 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr1(),
3200 m_staMacs[0]->GetBssid(linkId),
3201 "Unexpected RA for the BlockAckReq sent by the non-AP STA");
3202 });
3203
3204 m_events.emplace_back(
3206 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
3207 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
3208 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
3209 "Unexpected TA for the BlockAck sent by the AP");
3210 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr1(),
3211 m_staMacs[0]->GetAddress(),
3212 "Unexpected RA for the BlockAck sent by the AP");
3213 });
3214}
3215
3216void
3218{
3221
3222 NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
3223
3225}
3226
3228 : TestSuite("wifi-mlo", Type::UNIT)
3229{
3230 using ParamsTuple =
3231 std::tuple<MultiLinkOperationsTestBase::BaseParams, // base config params
3232 std::vector<uint8_t>, // link ID (as seen by AP device) of setup links
3233 std::vector<uint8_t>, // link ID (as seen by non-AP device) of setup links
3234 WifiTidToLinkMappingNegSupport, // AP negotiation support
3235 std::string, // DL TID-to-Link Mapping
3236 std::string>; // UL TID-to-Link Mapping
3237
3238 AddTestCase(new GetRnrLinkInfoTest(), TestCase::Duration::QUICK);
3239 AddTestCase(new MldSwapLinksTest(), TestCase::Duration::QUICK);
3242 std::vector<std::set<uint8_t>>{{0, 1, 2}, {1, 2}, {0, 1}, {0, 2}, {0}, {1}, {2}},
3243 WifiAssocType::ML_SETUP),
3244 TestCase::Duration::QUICK);
3247 std::vector<std::set<uint8_t>>{{0, 1, 2}, {1, 2}, {0, 1}, {0, 2}, {0}, {1}, {2}},
3248 WifiAssocType::LEGACY),
3249 TestCase::Duration::QUICK);
3250
3251 // check that the selection of channels in ML setup accounts for the inability of a
3252 // non-AP MLD to operate on a 160 MHz channel
3256 {"{42, 80, BAND_5GHZ, 2}", "{5, 40, BAND_2_4GHZ, 0}", "{7, 80, BAND_6GHZ, 0}"},
3257 {"{3, 40, BAND_2_4GHZ, 0}", "{15, 160, BAND_6GHZ, 7}", "{50, 160, BAND_5GHZ, 2}"},
3258 {},
3259 WifiAssocType::ML_SETUP},
3260 WifiScanType::PASSIVE,
3261 {0, 1, 2},
3262 {}, // IDs of setup links are the same for AP and non-AP devices
3263 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3264 "",
3265 "",
3266 false),
3267 TestCase::Duration::QUICK);
3268
3269 for (const auto& [baseParams,
3270 setupLinks,
3271 staSetupLinks,
3272 apNegSupport,
3273 dlTidLinkMapping,
3274 ulTidLinkMapping] :
3275 {// matching channels: setup all links
3276 ParamsTuple(
3277 {{"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3278 {"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3279 {},
3280 WifiAssocType::ML_SETUP},
3281 {0, 1, 2},
3282 {}, // IDs of setup links are the same for AP and non-AP devices
3283 WifiTidToLinkMappingNegSupport::NOT_SUPPORTED, // AP MLD does not support TID-to-Link
3284 // Mapping negotiation
3285 "0,1,2,3 0,1,2; 4,5 0,1", // default mapping used instead
3286 "0,1,2,3 1,2; 6,7 0,1" // default mapping used instead
3287 ),
3288 // non-matching channels, matching PHY bands: setup all links
3289 ParamsTuple({{"{108, 0, BAND_5GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3290 {"{36, 0, BAND_5GHZ, 0}", "{120, 0, BAND_5GHZ, 0}", "{5, 0, BAND_6GHZ, 0}"},
3291 {},
3292 WifiAssocType::ML_SETUP},
3293 {0, 1, 2},
3294 {}, // IDs of setup links are the same for AP and non-AP devices
3295 WifiTidToLinkMappingNegSupport::SAME_LINK_SET, // AP MLD does not support
3296 // distinct link sets for TIDs
3297 "0,1,2,3 0,1,2; 4,5 0,1", // default mapping used instead
3298 ""),
3299 // non-AP MLD switches band on some links to setup 3 links
3300 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3301 {"{36, 0, BAND_5GHZ, 0}", "{9, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3302 {},
3303 WifiAssocType::ML_SETUP},
3304 {0, 1, 2},
3305 {}, // IDs of setup links are the same for AP and non-AP devices
3306 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3307 "0,1,2,3 0; 4,5,6,7 1,2", // frames of two TIDs are generated
3308 "0,2,3 1,2; 1,4,5,6,7 0" // frames of two TIDs are generated
3309 ),
3310 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3311 // that band, hence only 2 links are setup
3312 ParamsTuple(
3313 {{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{8, 20, BAND_2_4GHZ, 0}"},
3314 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3315 {0},
3316 WifiAssocType::ML_SETUP},
3317 {0, 1},
3318 {}, // IDs of setup links are the same for AP and non-AP devices
3319 WifiTidToLinkMappingNegSupport::SAME_LINK_SET, // AP MLD does not support distinct
3320 // link sets for TIDs
3321 "0,1,2,3,4,5,6,7 0",
3322 "0,1,2,3,4,5,6,7 0"),
3323 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3324 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3325 // an AP operating on the same channel; hence 2 links are setup
3326 ParamsTuple(
3327 {{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{8, 20, BAND_2_4GHZ, 0}"},
3328 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3329 {0, 1},
3330 WifiAssocType::ML_SETUP},
3331 {0, 1},
3332 {}, // IDs of setup links are the same for AP and non-AP devices
3333 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3334 "0,1,2,3 1",
3335 "0,1,2,3 1"),
3336 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3337 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3338 // an AP operating on the same channel; the third link of the non-AP MLD cannot
3339 // change PHY band and there is an AP operating on the same band (different channel);
3340 // hence 2 links are setup by switching channel (not band) on the third link
3341 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{60, 0, BAND_5GHZ, 0}"},
3342 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3343 {0, 1, 2},
3344 WifiAssocType::ML_SETUP},
3345 {0, 2},
3346 {}, // IDs of setup links are the same for AP and non-AP devices
3347 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3348 "",
3349 ""),
3350 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3351 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3352 // an AP operating on the same channel; hence one link only is setup
3353 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3354 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3355 {0, 1},
3356 WifiAssocType::ML_SETUP},
3357 {2},
3358 {}, // IDs of setup links are the same for AP and non-AP devices
3359 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3360 "",
3361 ""),
3362 // non-AP MLD has only two STAs and setups two links
3363 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3364 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3365 {},
3366 WifiAssocType::ML_SETUP},
3367 {1, 0},
3368 {}, // IDs of setup links are the same for AP and non-AP devices
3369 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3370 "0,1,2,3 1",
3371 ""),
3372 // AP MLD and non-AP MLD setup only one link using legacy association
3373 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{60, 0, BAND_5GHZ, 0}"},
3374 {"{120, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3375 {},
3376 WifiAssocType::LEGACY},
3377 {2},
3378 {1},
3379 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3380 "",
3381 ""),
3382 // single link non-AP STA performs legacy association with an AP affiliated with an AP MLD
3383 ParamsTuple({{"{120, 0, BAND_5GHZ, 0}"},
3384 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3385 {},
3386 WifiAssocType::LEGACY},
3387 {2},
3388 {0}, // non-AP STA performs legacy association
3389 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3390 "",
3391 ""),
3392 // single link non-AP STA performs ML setup with an AP affiliated with an AP MLD
3393 ParamsTuple({{"{120, 0, BAND_5GHZ, 0}"},
3394 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3395 {},
3396 WifiAssocType::ML_SETUP},
3397 {2},
3398 {2}, // IDs of setup links are the same for AP and non-AP devices
3399 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3400 "",
3401 ""),
3402 // a STA affiliated with a non-AP MLD performs legacy association with a single link AP
3403 ParamsTuple({{"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3404 {"{120, 0, BAND_5GHZ, 0}"},
3405 {},
3406 WifiAssocType::LEGACY},
3407 {0}, // AP is single link
3408 {2},
3409 WifiTidToLinkMappingNegSupport::NOT_SUPPORTED,
3410 "0,1,2,3 0,1; 4,5,6,7 0,1", // ignored by single link AP
3411 ""),
3412 // a STA affiliated with a non-AP MLD performs ML setup with a single link AP
3413 ParamsTuple({{"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3414 {"{120, 0, BAND_5GHZ, 0}"},
3415 {},
3416 WifiAssocType::ML_SETUP},
3417 {0}, // AP is single link
3418 {2}, // legacy association is performed anyway because AP is single link
3419 WifiTidToLinkMappingNegSupport::NOT_SUPPORTED,
3420 "0,1,2,3 0,1; 4,5,6,7 0,1", // ignored by single link AP
3421 "")})
3422 {
3423 AddTestCase(new MultiLinkSetupTest(baseParams,
3424 WifiScanType::PASSIVE,
3425 setupLinks,
3426 staSetupLinks,
3427 apNegSupport,
3428 dlTidLinkMapping,
3429 ulTidLinkMapping),
3430 TestCase::Duration::QUICK);
3431 AddTestCase(new MultiLinkSetupTest(baseParams,
3432 WifiScanType::ACTIVE,
3433 setupLinks,
3434 staSetupLinks,
3435 apNegSupport,
3436 dlTidLinkMapping,
3437 ulTidLinkMapping),
3438 TestCase::Duration::QUICK);
3439
3440 for (const auto& trafficPattern : {WifiTrafficPattern::STA_TO_STA,
3445 {
3446 // No Block Ack agreement
3447 AddTestCase(new MultiLinkTxTest(baseParams,
3448 trafficPattern,
3451 1),
3452 TestCase::Duration::QUICK);
3453 for (const auto& useBarAfterMissedBa :
3455 {
3456 // Block Ack agreement with nMaxInflight=1
3457 AddTestCase(new MultiLinkTxTest(baseParams,
3458 trafficPattern,
3460 useBarAfterMissedBa,
3461 1),
3462 TestCase::Duration::QUICK);
3463 // Block Ack agreement with nMaxInflight=2
3464 AddTestCase(new MultiLinkTxTest(baseParams,
3465 trafficPattern,
3467 useBarAfterMissedBa,
3468 2),
3469 TestCase::Duration::QUICK);
3470 }
3471 }
3472
3473 for (const auto& muTrafficPattern : {WifiMuTrafficPattern::DL_MU_BAR_BA_SEQUENCE,
3477 {
3478 for (const auto& useBarAfterMissedBa :
3480 {
3481 // Block Ack agreement with nMaxInflight=1
3483 new MultiLinkMuTxTest(baseParams, muTrafficPattern, useBarAfterMissedBa, 1),
3484 TestCase::Duration::QUICK);
3485 // Block Ack agreement with nMaxInflight=2
3487 new MultiLinkMuTxTest(baseParams, muTrafficPattern, useBarAfterMissedBa, 2),
3488 TestCase::Duration::QUICK);
3489 }
3490 }
3491 }
3492
3493 AddTestCase(new ReleaseSeqNoAfterCtsTimeoutTest(), TestCase::Duration::QUICK);
3494 AddTestCase(new StartSeqNoUpdateAfterAddBaTimeoutTest(), TestCase::Duration::QUICK);
3495 AddTestCase(new BarAfterDroppedMpduTest(WifiAssocType::ML_SETUP), TestCase::Duration::QUICK);
3496 AddTestCase(new BarAfterDroppedMpduTest(WifiAssocType::LEGACY), TestCase::Duration::QUICK);
3497}
3498
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:90
AttributeValue implementation for Boolean.
Definition boolean.h:26
Headers for BlockAck response.
bool IsPacketReceived(uint16_t seq, std::size_t index=0) const
Check if the packet with the given sequence number was acknowledged in this BlockAck response.
std::vector< uint32_t > FindPerAidTidInfoWithAid(uint16_t aid) const
For Multi-STA Block Acks, get the indices of the Per AID TID Info subfields carrying the given AID in...
Headers for Trigger frames.
bool IsBasic() const
Check if this is a Basic Trigger frame.
Hold variables of type enum.
Definition enum.h:52
void SetList(const std::list< uint64_t > &packetlist)
an EUI-48 address
static Mac48Address GetBroadcast()
Implement the header for management frames of type association request.
Implement the header for management frames of type association and reassociation response.
Implement the header for management frames of type beacon.
Implement the header for management frames of type probe request.
Implement the header for management frames of type probe response.
Helper class used to assign positions and mobility models to nodes.
MultiUserScheduler is an abstract base class defining the API that APs supporting at least VHT can us...
holds a vector of ns3::NetDevice pointers
uint32_t GetN() const
Get the number of Ptr<NetDevice> stored in this container.
void Add(NetDeviceContainer other)
Append the contents of another NetDeviceContainer to the end of this container.
Ptr< NetDevice > Get(uint32_t i) const
Get the Ptr<NetDevice> stored in this container at a given index.
keep track of a set of node pointers.
static Iterator Begin()
Definition node-list.cc:226
static uint32_t GetNNodes()
Definition node-list.cc:247
static Iterator End()
Definition node-list.cc:233
an address for a packet socket
void SetProtocol(uint16_t protocol)
Set the protocol.
void SetPhysicalAddress(const Address address)
Set the destination address.
void SetSingleDevice(uint32_t device)
Set the address to match only a specified NetDevice.
Give ns3::PacketSocket powers to ns3::Node.
void Install(Ptr< Node > node) const
Aggregate an instance of a ns3::PacketSocketFactory onto the provided node.
AttributeValue implementation for Pointer.
Smart pointer class similar to boost::intrusive_ptr.
The Reduced Neighbor Report element.
std::size_t GetNNbrApInfoFields() const
Get the number of Neighbor AP Information fields.
std::size_t GetNTbttInformationFields(std::size_t nbrApInfoId) const
Get the number of TBTT Information fields included in the TBTT Information Set field of the given Nei...
void AddNbrApInfoField()
Add a Neighbor AP Information field.
void SetMldParameters(std::size_t nbrApInfoId, std::size_t index, const MldParameters &mldParams)
Set the MLD Parameters subfield of the i-th TBTT Information field of the given Neighbor AP Informati...
void AddTbttInformationField(std::size_t nbrApInfoId)
Add a TBTT Information fields to the TBTT Information Set field of the given Neighbor AP Information ...
static void SetRun(uint64_t run)
Set the run number of simulation.
static void SetSeed(uint32_t seed)
Set the seed.
static EventId Schedule(const Time &delay, FUNC f, Ts &&... args)
Schedule an event to expire after delay.
Definition simulator.h:561
static void Destroy()
Execute the events scheduled with ScheduleDestroy().
Definition simulator.cc:131
static Time Now()
Return the current simulation virtual time.
Definition simulator.cc:197
static void Run()
Run the simulation.
Definition simulator.cc:167
static void Stop()
Tell the Simulator the calling event should be the last one executed.
Definition simulator.cc:175
Make it easy to create and manage PHY objects for the spectrum model.
void AddChannel(const Ptr< SpectrumChannel > channel, const FrequencyRange &freqRange=WHOLE_WIFI_SPECTRUM)
void SetChannel(const Ptr< SpectrumChannel > channel)
The IEEE 802.11 SSID Information Element.
Definition ssid.h:25
AttributeValue implementation for Ssid.
Definition ssid.h:85
Hold variables of type string.
Definition string.h:45
encapsulates test code
Definition test.h:1050
void AddTestCase(TestCase *testCase, Duration duration=Duration::QUICK)
Add an individual child TestCase to this test suite.
Definition test.cc:292
A suite of tests to run.
Definition test.h:1267
Type
Type of test.
Definition test.h:1274
Simulation virtual time values and global simulation resolution.
Definition nstime.h:94
AttributeValue implementation for Time.
Definition nstime.h:1432
a unique identifier for an interface.
Definition type-id.h:49
TypeId SetParent(TypeId tid)
Set the parent TypeId.
Definition type-id.cc:1001
Hold an unsigned integer type.
Definition uinteger.h:34
See IEEE 802.11 chapter 7.3.1.11 Header format: | category: 1 | action value: 1 |.
CategoryValue GetCategory() const
Return the category value.
ActionValue GetAction() const
Return the action value.
static std::optional< WifiAssocManager::RnrLinkInfo > GetNextAffiliatedAp(const ReducedNeighborReport &rnr, std::size_t nbrApInfoId)
Search the given RNR element for APs affiliated to the same AP MLD as the reporting AP.
static std::list< WifiAssocManager::RnrLinkInfo > GetAllAffiliatedAps(const ReducedNeighborReport &rnr)
Find all the APs affiliated to the same AP MLD as the reporting AP that sent the given RNR element.
helps to create WifiNetDevice objects
static int64_t AssignStreams(NetDeviceContainer c, int64_t stream)
Assign a fixed random variable stream number to the random variables used by the PHY and MAC aspects ...
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.
base class for all MAC-level wifi objects.
Definition wifi-mac.h:90
void SetPcapCaptureType(PcapCaptureType type)
Set the PCAP capture type to be used.
void SetPcapDataLinkType(SupportedPcapDataLinkTypes dlt)
Set the data link type of PCAP traces to be used.
void Set(std::string name, const AttributeValue &v)
@ DLT_IEEE802_11_RADIO
Include Radiotap link layer information.
static Time CalculateTxDuration(uint32_t size, const WifiTxVector &txVector, WifiPhyBand band, uint16_t staId=SU_STA_ID)
Definition wifi-phy.cc:1563
This class mimics the TXVECTOR which is to be passed to the PHY in order to define the parameters whi...
const HeMuUserInfoMap & GetHeMuUserInfoMap() const
Get a const reference to the map HE MU user-specific transmission information indexed by STA-ID.
MHz_u GetChannelWidth() const
#define NS_ASSERT(condition)
At runtime, in debugging builds, if this condition is not true, the program prints the source file,...
Definition assert.h:55
#define NS_ASSERT_MSG(condition, message)
At runtime, in debugging builds, if this condition is not true, the program prints the message to out...
Definition assert.h:75
void SetDefault(std::string name, const AttributeValue &value)
Definition config.cc: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:191
#define NS_LOG_INFO(msg)
Use NS_LOG to output a message of level LOG_INFO.
Definition log.h:264
Ptr< T > CreateObject(Args &&... args)
Create an object by type, with varying number of constructor parameters.
Definition object.h:619
Ptr< T > CreateObjectWithAttributes(Args... args)
Allocate an Object on the heap and initialize with a set of attributes.
Ptr< T > Create(Ts &&... args)
Create class instances by constructors with varying numbers of arguments and return them by Ptr.
Definition ptr.h:436
#define NS_TEST_ASSERT_MSG_LT(actual, limit, msg)
Test that an actual value is less than a limit and report and abort if not.
Definition test.h:699
#define NS_TEST_ASSERT_MSG_EQ(actual, limit, msg)
Test that an actual and expected (limit) value are equal and report and abort if not.
Definition test.h:134
#define NS_TEST_EXPECT_MSG_LT_OR_EQ(actual, limit, msg)
Test that an actual value is less than or equal to a limit and report if not.
Definition test.h:820
#define NS_TEST_EXPECT_MSG_LT(actual, limit, msg)
Test that an actual value is less than a limit and report if not.
Definition test.h:780
#define NS_TEST_EXPECT_MSG_GT(actual, limit, msg)
Test that an actual value is greater than a limit and report if not.
Definition test.h:946
#define NS_TEST_EXPECT_MSG_NE(actual, limit, msg)
Test that an actual and expected (limit) value are not equal and report if not.
Definition test.h:656
#define NS_TEST_EXPECT_MSG_EQ(actual, limit, msg)
Test that an actual and expected (limit) value are equal and report if not.
Definition test.h:241
#define NS_TEST_ASSERT_MSG_NE(actual, limit, msg)
Test that an actual and expected (limit) value are not equal and report and abort if not.
Definition test.h:554
Time MicroSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1369
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition nstime.h:1345
Time MilliSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1357
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_PHY_BAND_6GHZ
The 6 GHz band.
@ WIFI_PHY_BAND_5GHZ
The 5 GHz band.
@ AC_BE
Best Effort.
Definition qos-utils.h:64
@ AC_VO
Voice.
Definition qos-utils.h:70
@ AC_VI
Video.
Definition qos-utils.h:68
@ AC_BK
Background.
Definition qos-utils.h:66
Every class exported by the ns3 library is enclosed in the ns3 namespace.
constexpr FrequencyRange WIFI_SPECTRUM_6_GHZ
Identifier for the frequency range covering the wifi spectrum in the 6 GHz band.
U * PeekPointer(const Ptr< U > &p)
Definition ptr.h:443
std:: tuple< WifiContainerQueueType, WifiReceiverAddressType, Mac48Address, std::optional< uint8_t > > WifiContainerQueueId
Tuple (queue type, receiver address type, Address, TID) identifying a container queue.
Callback< R, Args... > MakeCallback(R(T::*memPtr)(Args...), OBJ objPtr)
Build Callbacks for class method members which take varying numbers of arguments and potentially retu...
Definition callback.h:684
WifiTidToLinkMappingNegSupport
TID-to-Link Mapping Negotiation Support.
Ptr< T1 > DynamicCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:580
static constexpr uint8_t SINGLE_LINK_OP_ID
Link ID for single link operations (helps tracking places where correct link ID is to be used to supp...
Definition wifi-utils.h:280
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:37
bool TidToLinkMappingValidForNegType1(const WifiTidLinkMapping &dlLinkMapping, const WifiTidLinkMapping &ulLinkMapping)
Check if the given TID-to-Link Mappings are valid for a negotiation type of 1.
Ptr< T1 > StaticCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:587
std::unordered_map< uint16_t, Ptr< const WifiPsdu > > WifiConstPsduMap
Map of const PSDUs indexed by STA-ID.
Definition wifi-ppdu.h:38
constexpr FrequencyRange WIFI_SPECTRUM_2_4_GHZ
Identifier for the frequency range covering the wifi spectrum in the 2.4 GHz band.
STL namespace.
uint32_t prev
std::string dir
BlockAckActionValue blockAck
block ack
uint32_t pktSize
packet size used for the simulation (in bytes)
static WifiMultiLinkOperationsTestSuite g_wifiMultiLinkOperationsTestSuite
the test suite
WifiMuTrafficPattern
Tested MU traffic patterns.
WifiTrafficPattern
Tested traffic patterns.
WifiUseBarAfterMissedBa
Whether to send a BlockAckReq after a missed BlockAck.
WifiBaEnabled
Block Ack agreement enabled/disabled.