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