A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
wifi-mlo-test.cc
Go to the documentation of this file.
1/*
2 * Copyright (c) 2022 Universita' degli Studi di Napoli Federico II
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation;
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 *
17 * Author: Stefano Avallone <stavallo@unina.it>
18 */
19
20#include "ns3/ap-wifi-mac.h"
21#include "ns3/config.h"
22#include "ns3/eht-configuration.h"
23#include "ns3/frame-exchange-manager.h"
24#include "ns3/log.h"
25#include "ns3/mgt-headers.h"
26#include "ns3/mobility-helper.h"
27#include "ns3/multi-link-element.h"
28#include "ns3/multi-model-spectrum-channel.h"
29#include "ns3/node-list.h"
30#include "ns3/packet-socket-client.h"
31#include "ns3/packet-socket-helper.h"
32#include "ns3/packet-socket-server.h"
33#include "ns3/packet.h"
34#include "ns3/pointer.h"
35#include "ns3/qos-utils.h"
36#include "ns3/rng-seed-manager.h"
37#include "ns3/rr-multi-user-scheduler.h"
38#include "ns3/spectrum-wifi-helper.h"
39#include "ns3/spectrum-wifi-phy.h"
40#include "ns3/sta-wifi-mac.h"
41#include "ns3/string.h"
42#include "ns3/test.h"
43#include "ns3/wifi-acknowledgment.h"
44#include "ns3/wifi-assoc-manager.h"
45#include "ns3/wifi-mac-header.h"
46#include "ns3/wifi-mac-queue.h"
47#include "ns3/wifi-net-device.h"
48#include "ns3/wifi-protection.h"
49#include "ns3/wifi-psdu.h"
50
51#include <algorithm>
52#include <array>
53#include <iomanip>
54#include <optional>
55#include <sstream>
56#include <tuple>
57#include <vector>
58
59using namespace ns3;
60
61NS_LOG_COMPONENT_DEFINE("WifiMloTest");
62
63/**
64 * \ingroup wifi-test
65 * \ingroup tests
66 *
67 * \brief Test the implementation of WifiAssocManager::GetNextAffiliatedAp(), which
68 * searches a given RNR element for APs affiliated to the same AP MLD as the
69 * reporting AP that sent the frame containing the element.
70 */
72{
73 public:
74 /**
75 * Constructor
76 */
78 ~GetRnrLinkInfoTest() override = default;
79
80 private:
81 void DoRun() override;
82};
83
85 : TestCase("Check the implementation of WifiAssocManager::GetNextAffiliatedAp()")
86{
87}
88
89void
91{
93 std::size_t nbrId;
94 std::size_t tbttId;
95
96 // Add a first Neighbor AP Information field without MLD Parameters
98 nbrId = rnr.GetNNbrApInfoFields() - 1;
99
100 rnr.AddTbttInformationField(nbrId);
101 rnr.AddTbttInformationField(nbrId);
102
103 // Add a second Neighbor AP Information field with MLD Parameters; the first
104 // TBTT Information field is related to an AP affiliated to the same AP MLD
105 // as the reported AP; the second TBTT Information field is not (it does not
106 // make sense that two APs affiliated to the same AP MLD are using the same
107 // channel).
108 rnr.AddNbrApInfoField();
109 nbrId = rnr.GetNNbrApInfoFields() - 1;
110
111 rnr.AddTbttInformationField(nbrId);
112 tbttId = rnr.GetNTbttInformationFields(nbrId) - 1;
113 rnr.SetMldParameters(nbrId, tbttId, 0, 0, 0);
114
115 rnr.AddTbttInformationField(nbrId);
116 tbttId = rnr.GetNTbttInformationFields(nbrId) - 1;
117 rnr.SetMldParameters(nbrId, tbttId, 5, 0, 0);
118
119 // Add a third Neighbor AP Information field with MLD Parameters; none of the
120 // TBTT Information fields is related to an AP affiliated to the same AP MLD
121 // as the reported AP.
122 rnr.AddNbrApInfoField();
123 nbrId = rnr.GetNNbrApInfoFields() - 1;
124
125 rnr.AddTbttInformationField(nbrId);
126 tbttId = rnr.GetNTbttInformationFields(nbrId) - 1;
127 rnr.SetMldParameters(nbrId, tbttId, 3, 0, 0);
128
129 rnr.AddTbttInformationField(nbrId);
130 tbttId = rnr.GetNTbttInformationFields(nbrId) - 1;
131 rnr.SetMldParameters(nbrId, tbttId, 4, 0, 0);
132
133 // Add a fourth Neighbor AP Information field with MLD Parameters; the first
134 // TBTT Information field is not related to an AP affiliated to the same AP MLD
135 // as the reported AP; the second TBTT Information field is.
136 rnr.AddNbrApInfoField();
137 nbrId = rnr.GetNNbrApInfoFields() - 1;
138
139 rnr.AddTbttInformationField(nbrId);
140 tbttId = rnr.GetNTbttInformationFields(nbrId) - 1;
141 rnr.SetMldParameters(nbrId, tbttId, 6, 0, 0);
142
143 rnr.AddTbttInformationField(nbrId);
144 tbttId = rnr.GetNTbttInformationFields(nbrId) - 1;
145 rnr.SetMldParameters(nbrId, tbttId, 0, 0, 0);
146
147 // check implementation of WifiAssocManager::GetNextAffiliatedAp()
148 auto ret = WifiAssocManager::GetNextAffiliatedAp(rnr, 0);
149
150 NS_TEST_EXPECT_MSG_EQ(ret.has_value(), true, "Expected to find a suitable reported AP");
151 NS_TEST_EXPECT_MSG_EQ(ret->m_nbrApInfoId, 1, "Unexpected neighbor ID of the first reported AP");
152 NS_TEST_EXPECT_MSG_EQ(ret->m_tbttInfoFieldId, 0, "Unexpected tbtt ID of the first reported AP");
153
154 ret = WifiAssocManager::GetNextAffiliatedAp(rnr, ret->m_nbrApInfoId + 1);
155
156 NS_TEST_EXPECT_MSG_EQ(ret.has_value(), true, "Expected to find a second suitable reported AP");
157 NS_TEST_EXPECT_MSG_EQ(ret->m_nbrApInfoId,
158 3,
159 "Unexpected neighbor ID of the second reported AP");
160 NS_TEST_EXPECT_MSG_EQ(ret->m_tbttInfoFieldId,
161 1,
162 "Unexpected tbtt ID of the second reported AP");
163
164 ret = WifiAssocManager::GetNextAffiliatedAp(rnr, ret->m_nbrApInfoId + 1);
165
166 NS_TEST_EXPECT_MSG_EQ(ret.has_value(),
167 false,
168 "Did not expect to find a third suitable reported AP");
169
170 // check implementation of WifiAssocManager::GetAllAffiliatedAps()
171 auto allAps = WifiAssocManager::GetAllAffiliatedAps(rnr);
172
173 NS_TEST_EXPECT_MSG_EQ(allAps.size(), 2, "Expected to find two suitable reported APs");
174
175 auto apIt = allAps.begin();
176 NS_TEST_EXPECT_MSG_EQ(apIt->m_nbrApInfoId,
177 1,
178 "Unexpected neighbor ID of the first reported AP");
179 NS_TEST_EXPECT_MSG_EQ(apIt->m_tbttInfoFieldId,
180 0,
181 "Unexpected tbtt ID of the first reported AP");
182
183 apIt++;
184 NS_TEST_EXPECT_MSG_EQ(apIt->m_nbrApInfoId,
185 3,
186 "Unexpected neighbor ID of the second reported AP");
187 NS_TEST_EXPECT_MSG_EQ(apIt->m_tbttInfoFieldId,
188 1,
189 "Unexpected tbtt ID of the second reported AP");
190}
191
192/**
193 * \ingroup wifi-test
194 * \ingroup tests
195 *
196 * Test the WifiMac::SwapLinks() method.
197 */
199{
200 /**
201 * Test WifiMac subclass used to access the SwapLinks method.
202 */
203 class TestWifiMac : public WifiMac
204 {
205 public:
206 ~TestWifiMac() override = default;
207
208 using WifiMac::GetLinks;
209 using WifiMac::SwapLinks;
210
211 bool CanForwardPacketsTo(Mac48Address to) const override
212 {
213 return true;
214 }
215
216 void Enqueue(Ptr<Packet> packet, Mac48Address to) override
217 {
218 }
219
220 private:
221 void DoCompleteConfig() override
222 {
223 }
224 };
225
226 public:
228 ~MldSwapLinksTest() override = default;
229
230 protected:
231 void DoRun() override;
232
233 private:
234 /**
235 * Run a single test case.
236 *
237 * \param text string identifying the test case
238 * \param nLinks the number of links of the MLD
239 * \param links a set of pairs (from, to) each mapping a current link ID to the
240 * link ID it has to become (i.e., link 'from' becomes link 'to')
241 * \param expected maps each link ID to the id of the PHY that is expected
242 * to operate on that link after the swap
243 */
244 void RunOne(std::string text,
245 std::size_t nLinks,
246 const std::map<uint8_t, uint8_t>& links,
247 const std::map<uint8_t, uint8_t>& expected);
248};
249
251 : TestCase("Test the WifiMac::SwapLinks() method")
252{
253}
254
255void
257 std::size_t nLinks,
258 const std::map<uint8_t, uint8_t>& links,
259 const std::map<uint8_t, uint8_t>& expected)
260{
261 TestWifiMac mac;
262
263 std::vector<Ptr<WifiPhy>> phys;
264 for (std::size_t i = 0; i < nLinks; i++)
265 {
266 phys.emplace_back(CreateObject<SpectrumWifiPhy>());
267 }
268 mac.SetWifiPhys(phys); // create links containing the given PHYs
269
270 mac.SwapLinks(links);
271
272 NS_TEST_EXPECT_MSG_EQ(mac.GetNLinks(), nLinks, "Number of links changed after swapping");
273
274 for (const auto& [linkId, phyId] : expected)
275 {
276 NS_TEST_ASSERT_MSG_EQ(mac.GetLinks().count(linkId),
277 1,
278 "Link ID " << +linkId << " does not exist");
279
280 NS_TEST_ASSERT_MSG_LT(+phyId, nLinks, "Invalid PHY ID");
281
282 // the id of the PHY operating on a link is the original ID of the link
283 NS_TEST_EXPECT_MSG_EQ(mac.GetWifiPhy(linkId),
284 phys.at(phyId),
285 text << ": Link " << +phyId << " has not been moved to link "
286 << +linkId);
287 }
288}
289
290void
292{
293 RunOne("No change needed", 3, {{0, 0}, {1, 1}, {2, 2}}, {{0, 0}, {1, 1}, {2, 2}});
294 RunOne("Circular swapping", 3, {{0, 2}, {1, 0}, {2, 1}}, {{0, 1}, {1, 2}, {2, 0}});
295 RunOne("Swapping two links, one unchanged", 3, {{0, 2}, {2, 0}}, {{0, 2}, {1, 1}, {2, 0}});
296 RunOne("Non-circular swapping, autodetect how to close the loop",
297 3,
298 {{0, 2}, {2, 1}},
299 {{0, 1}, {1, 2}, {2, 0}});
300 RunOne("One move only, autodetect how to complete the swapping",
301 3,
302 {{2, 0}},
303 {{0, 2}, {1, 1}, {2, 0}});
304 RunOne("Create a new link ID (2), remove the unused one (0)",
305 2,
306 {{0, 1}, {1, 2}},
307 {{1, 0}, {2, 1}});
308 RunOne("One move only that creates a new link ID (2)", 2, {{0, 2}}, {{1, 1}, {2, 0}});
309 RunOne("Move all links to a new set of IDs", 2, {{0, 2}, {1, 3}}, {{2, 0}, {3, 1}});
310}
311
312/**
313 * \ingroup wifi-test
314 * \ingroup tests
315 *
316 * \brief Base class for Multi-Link Operations tests
317 *
318 * Three spectrum channels are created, one for each band (2.4 GHz, 5 GHz and 6 GHz).
319 * Each PHY object is attached to the spectrum channel corresponding to the PHY band
320 * in which it is operating.
321 */
323{
324 public:
325 /**
326 * Configuration parameters common to all subclasses
327 */
329 {
330 std::vector<std::string>
331 staChannels; //!< the strings specifying the operating channels for the non-AP MLD
332 std::vector<std::string>
333 apChannels; //!< the strings specifying the operating channels for the AP MLD
334 std::vector<uint8_t>
335 fixedPhyBands; //!< list of IDs of non-AP MLD PHYs that cannot switch band
336 };
337
338 /**
339 * Constructor
340 *
341 * \param name The name of the new TestCase created
342 * \param nStations the number of stations to create
343 * \param baseParams common configuration parameters
344 */
345 MultiLinkOperationsTestBase(const std::string& name,
346 uint8_t nStations,
347 const BaseParams& baseParams);
348 ~MultiLinkOperationsTestBase() override = default;
349
350 protected:
351 /**
352 * Callback invoked when a FEM passes PSDUs to the PHY.
353 *
354 * \param mac the MAC transmitting the PSDUs
355 * \param phyId the ID of the PHY transmitting the PSDUs
356 * \param psduMap the PSDU map
357 * \param txVector the TX vector
358 * \param txPowerW the tx power in Watts
359 */
360 virtual void Transmit(Ptr<WifiMac> mac,
361 uint8_t phyId,
362 WifiConstPsduMap psduMap,
363 WifiTxVector txVector,
364 double txPowerW);
365
366 /**
367 * Check that the expected Capabilities information elements are present in the given
368 * management frame based on the band in which the given link is operating.
369 *
370 * \param mpdu the given management frame
371 * \param mac the MAC transmitting the management frame
372 * \param phyId the ID of the PHY transmitting the management frame
373 */
374 void CheckCapabilities(Ptr<WifiMpdu> mpdu, Ptr<WifiMac> mac, uint8_t phyId);
375
376 /**
377 * Function to trace packets received by the server application
378 * \param nodeId the ID of the node that received the packet
379 * \param p the packet
380 * \param addr the address
381 */
382 virtual void L7Receive(uint8_t nodeId, Ptr<const Packet> p, const Address& addr);
383
384 /**
385 * \param sockAddr the packet socket address identifying local outgoing interface
386 * and remote address
387 * \param count the number of packets to generate
388 * \param pktSize the size of the packets to generate
389 * \param delay the delay with which traffic generation starts
390 * \param priority user priority for generated packets
391 * \return an application generating the given number packets of the given size destined
392 * to the given packet socket address
393 */
395 std::size_t count,
396 std::size_t pktSize,
397 Time delay = Seconds(0),
398 uint8_t priority = 0) const;
399
400 void DoSetup() override;
401
402 /// PHY band-indexed map of spectrum channels
403 using ChannelMap = std::map<FrequencyRange, Ptr<MultiModelSpectrumChannel>>;
404
405 /**
406 * Uplink or Downlink direction
407 */
409 {
410 DL = 0,
411 UL
412 };
413
414 /**
415 * Check that the Address 1 and Address 2 fields of the given PSDU contain device MAC addresses.
416 *
417 * \param psdu the given PSDU
418 * \param direction indicates direction for management frames (DL or UL)
419 */
421 std::optional<Direction> direction = std::nullopt);
422
423 /// Information about transmitted frames
425 {
426 Time startTx; ///< TX start time
427 WifiConstPsduMap psduMap; ///< transmitted PSDU map
428 WifiTxVector txVector; ///< TXVECTOR
429 uint8_t linkId; ///< link ID
430 uint8_t phyId; ///< ID of the transmitting PHY
431 };
432
433 std::vector<FrameInfo> m_txPsdus; ///< transmitted PSDUs
434 const std::vector<std::string> m_staChannels; ///< strings specifying channels for STA
435 const std::vector<std::string> m_apChannels; ///< strings specifying channels for AP
436 const std::vector<uint8_t> m_fixedPhyBands; ///< links on non-AP MLD with fixed PHY band
437 Ptr<ApWifiMac> m_apMac; ///< AP wifi MAC
438 std::vector<Ptr<StaWifiMac>> m_staMacs; ///< STA wifi MACs
439 uint8_t m_nStations; ///< number of stations to create
440 uint16_t m_lastAid; ///< AID of last associated station
441 Time m_duration{Seconds(1)}; ///< simulation duration
442 std::vector<std::size_t> m_rxPkts; ///< number of packets received at application layer
443 ///< by each node (index is node ID)
444
445 private:
446 /**
447 * Reset the given PHY helper, use the given strings to set the ChannelSettings
448 * attribute of the PHY objects to create, and attach them to the given spectrum
449 * channels appropriately.
450 *
451 * \param helper the given PHY helper
452 * \param channels the strings specifying the operating channels to configure
453 * \param channelMap the created spectrum channels
454 */
456 const std::vector<std::string>& channels,
457 const ChannelMap& channelMap);
458
459 /**
460 * Set the SSID on the next station that needs to start the association procedure.
461 * This method is connected to the ApWifiMac's AssociatedSta trace source.
462 * Start generating traffic (if needed) when all stations are associated.
463 *
464 * \param aid the AID assigned to the previous associated STA
465 */
466 void SetSsid(uint16_t aid, Mac48Address /* addr */);
467
468 /**
469 * Start the generation of traffic (needs to be overridden)
470 */
471 virtual void StartTraffic()
472 {
473 }
474};
475
477 uint8_t nStations,
478 const BaseParams& baseParams)
479 : TestCase(name),
480 m_staChannels(baseParams.staChannels),
481 m_apChannels(baseParams.apChannels),
482 m_fixedPhyBands(baseParams.fixedPhyBands),
483 m_staMacs(nStations),
484 m_nStations(nStations),
485 m_lastAid(0),
486 m_rxPkts(nStations + 1)
487{
488}
489
490void
492 std::optional<Direction> direction)
493{
494 std::optional<Mac48Address> apAddr;
495 std::optional<Mac48Address> staAddr;
496
497 // direction for Data frames is derived from ToDS/FromDS flags
498 if (psdu->GetHeader(0).IsQosData())
499 {
500 direction = (!psdu->GetHeader(0).IsToDs() && psdu->GetHeader(0).IsFromDs()) ? DL : UL;
501 }
502 NS_ASSERT(direction);
503
504 if (direction == DL)
505 {
506 if (!psdu->GetAddr1().IsGroup())
507 {
508 staAddr = psdu->GetAddr1();
509 }
510 apAddr = psdu->GetAddr2();
511 }
512 else
513 {
514 if (!psdu->GetAddr1().IsGroup())
515 {
516 apAddr = psdu->GetAddr1();
517 }
518 staAddr = psdu->GetAddr2();
519 }
520
521 if (apAddr)
522 {
523 bool found = false;
524 for (uint8_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
525 {
526 if (m_apMac->GetFrameExchangeManager(linkId)->GetAddress() == *apAddr)
527 {
528 found = true;
529 break;
530 }
531 }
533 true,
534 "Address " << *apAddr << " is not an AP device address. "
535 << "PSDU: " << *psdu);
536 }
537
538 if (staAddr)
539 {
540 bool found = false;
541 for (uint8_t i = 0; i < m_nStations; i++)
542 {
543 for (const auto& linkId : m_staMacs[i]->GetLinkIds())
544 {
545 if (m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress() == *staAddr)
546 {
547 found = true;
548 break;
549 }
550 }
551 if (found)
552 {
553 break;
554 }
555 }
557 true,
558 "Address " << *staAddr << " is not a STA device address. "
559 << "PSDU: " << *psdu);
560 }
561}
562
563void
565 uint8_t phyId,
566 WifiConstPsduMap psduMap,
567 WifiTxVector txVector,
568 double txPowerW)
569{
570 auto linkId = mac->GetLinkForPhy(phyId);
571 NS_TEST_ASSERT_MSG_EQ(linkId.has_value(), true, "No link found for PHY ID " << +phyId);
572 m_txPsdus.push_back({Simulator::Now(), psduMap, txVector, *linkId, phyId});
573
574 for (const auto& [aid, psdu] : psduMap)
575 {
576 std::stringstream ss;
577 ss << std::setprecision(10) << "PSDU #" << m_txPsdus.size() << " Link ID "
578 << +linkId.value() << " Phy ID " << +phyId << " " << psdu->GetHeader(0).GetTypeString()
579 << " #MPDUs " << psdu->GetNMpdus() << " duration/ID " << psdu->GetHeader(0).GetDuration()
580 << " RA = " << psdu->GetAddr1() << " TA = " << psdu->GetAddr2()
581 << " ADDR3 = " << psdu->GetHeader(0).GetAddr3()
582 << " ToDS = " << psdu->GetHeader(0).IsToDs()
583 << " FromDS = " << psdu->GetHeader(0).IsFromDs();
584 if (psdu->GetHeader(0).IsQosData())
585 {
586 ss << " seqNo = {";
587 for (auto& mpdu : *PeekPointer(psdu))
588 {
589 ss << mpdu->GetHeader().GetSequenceNumber() << ",";
590 }
591 ss << "} TID = " << +psdu->GetHeader(0).GetQosTid();
592 }
593 NS_LOG_INFO(ss.str());
594
595 CheckCapabilities(*psdu->begin(), mac, phyId);
596 }
597 NS_LOG_INFO("TXVECTOR = " << txVector << "\n");
598}
599
600void
602{
603 auto band = mac->GetDevice()->GetPhy(phyId)->GetPhyBand();
604 bool hasHtCapabilities;
605 bool hasVhtCapabilities;
606 bool hasHeCapabilities;
607 bool hasHe6GhzCapabilities;
608 bool hasEhtCapabilities;
609
610 auto findCapabilities = [&](auto&& frame) {
611 hasHtCapabilities = frame.template Get<HtCapabilities>().has_value();
612 hasVhtCapabilities = frame.template Get<VhtCapabilities>().has_value();
613 hasHeCapabilities = frame.template Get<HeCapabilities>().has_value();
614 hasHe6GhzCapabilities = frame.template Get<He6GhzBandCapabilities>().has_value();
615 hasEhtCapabilities = frame.template Get<EhtCapabilities>().has_value();
616 };
617
618 switch (mpdu->GetHeader().GetType())
619 {
620 case WIFI_MAC_MGT_BEACON: {
621 MgtBeaconHeader beacon;
622 mpdu->GetPacket()->PeekHeader(beacon);
623 findCapabilities(beacon);
624 }
625 break;
626
628 MgtProbeRequestHeader probeReq;
629 mpdu->GetPacket()->PeekHeader(probeReq);
630 findCapabilities(probeReq);
631 }
632 break;
633
635 MgtProbeResponseHeader probeResp;
636 mpdu->GetPacket()->PeekHeader(probeResp);
637 findCapabilities(probeResp);
638 }
639 break;
640
642 MgtAssocRequestHeader assocReq;
643 mpdu->GetPacket()->PeekHeader(assocReq);
644 findCapabilities(assocReq);
645 }
646 break;
647
649 MgtAssocResponseHeader assocResp;
650 mpdu->GetPacket()->PeekHeader(assocResp);
651 findCapabilities(assocResp);
652 }
653 break;
654
655 default:
656 return;
657 }
658
660 hasHtCapabilities,
661 (band != WIFI_PHY_BAND_6GHZ),
662 "HT Capabilities should not be present in a mgt frame sent in 6 GHz band");
664 hasVhtCapabilities,
665 (band == WIFI_PHY_BAND_5GHZ),
666 "VHT Capabilities should only be present in a mgt frame sent in 5 GHz band");
667 NS_TEST_EXPECT_MSG_EQ(hasHeCapabilities,
668 true,
669 "HE Capabilities should always be present in a mgt frame");
671 hasHe6GhzCapabilities,
672 (band == WIFI_PHY_BAND_6GHZ),
673 "HE 6GHz Band Capabilities should only be present in a mgt frame sent in 6 GHz band");
674 NS_TEST_EXPECT_MSG_EQ(hasEhtCapabilities,
675 true,
676 "EHT Capabilities should always be present in a mgt frame");
677}
678
679void
681{
682 NS_LOG_INFO("Packet received by NODE " << +nodeId << "\n");
683 m_rxPkts[nodeId]++;
684}
685
686void
688 const std::vector<std::string>& channels,
689 const ChannelMap& channelMap)
690{
691 helper = SpectrumWifiPhyHelper(channels.size());
693
694 uint8_t linkId = 0;
695 for (const auto& str : channels)
696 {
697 helper.Set(linkId++, "ChannelSettings", StringValue(str));
698 }
699
700 // NOTE replace this for loop with the line below to use a single spectrum channel
701 // helper.SetChannel(channelMap.begin()->second);
702 for (const auto& [band, channel] : channelMap)
703 {
704 helper.AddChannel(channel, band);
705 }
706}
707
708void
710{
713 int64_t streamNumber = 30;
714
715 NodeContainer wifiApNode;
716 wifiApNode.Create(1);
717
718 NodeContainer wifiStaNodes;
719 wifiStaNodes.Create(m_nStations);
720
721 WifiHelper wifi;
722 // wifi.EnableLogComponents ();
723 wifi.SetStandard(WIFI_STANDARD_80211be);
724 wifi.SetRemoteStationManager("ns3::ConstantRateWifiManager",
725 "DataMode",
726 StringValue("EhtMcs0"),
727 "ControlMode",
728 StringValue("HtMcs0"));
729
730 ChannelMap channelMap{{WIFI_SPECTRUM_2_4_GHZ, CreateObject<MultiModelSpectrumChannel>()},
731 {WIFI_SPECTRUM_5_GHZ, CreateObject<MultiModelSpectrumChannel>()},
732 {WIFI_SPECTRUM_6_GHZ, CreateObject<MultiModelSpectrumChannel>()}};
733
734 SpectrumWifiPhyHelper staPhyHelper;
735 SpectrumWifiPhyHelper apPhyHelper;
736 SetChannels(staPhyHelper, m_staChannels, channelMap);
737 SetChannels(apPhyHelper, m_apChannels, channelMap);
738
739 for (const auto& linkId : m_fixedPhyBands)
740 {
741 staPhyHelper.Set(linkId, "FixedPhyBand", BooleanValue(true));
742 }
743
744 WifiMacHelper mac;
745 mac.SetType("ns3::StaWifiMac", // default SSID
746 "ActiveProbing",
747 BooleanValue(false));
748
749 NetDeviceContainer staDevices = wifi.Install(staPhyHelper, mac, wifiStaNodes);
750
751 mac.SetType("ns3::ApWifiMac",
752 "Ssid",
753 SsidValue(Ssid("ns-3-ssid")),
754 "BeaconGeneration",
755 BooleanValue(true));
756
757 NetDeviceContainer apDevices = wifi.Install(apPhyHelper, mac, wifiApNode);
758
759 // Uncomment the lines below to write PCAP files
760 // apPhyHelper.EnablePcap("wifi-mlo_AP", apDevices);
761 // staPhyHelper.EnablePcap("wifi-mlo_STA", staDevices);
762
763 // Assign fixed streams to random variables in use
764 streamNumber += wifi.AssignStreams(apDevices, streamNumber);
765 streamNumber += wifi.AssignStreams(staDevices, streamNumber);
766
767 MobilityHelper mobility;
768 Ptr<ListPositionAllocator> positionAlloc = CreateObject<ListPositionAllocator>();
769
770 positionAlloc->Add(Vector(0.0, 0.0, 0.0));
771 positionAlloc->Add(Vector(1.0, 0.0, 0.0));
772 mobility.SetPositionAllocator(positionAlloc);
773
774 mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
775 mobility.Install(wifiApNode);
776 mobility.Install(wifiStaNodes);
777
778 m_apMac = DynamicCast<ApWifiMac>(DynamicCast<WifiNetDevice>(apDevices.Get(0))->GetMac());
779 for (uint8_t i = 0; i < m_nStations; i++)
780 {
781 m_staMacs[i] =
782 DynamicCast<StaWifiMac>(DynamicCast<WifiNetDevice>(staDevices.Get(i))->GetMac());
783 }
784
785 // Trace PSDUs passed to the PHY on all devices
786 for (uint8_t phyId = 0; phyId < m_apMac->GetDevice()->GetNPhys(); phyId++)
787 {
789 "/NodeList/0/DeviceList/*/$ns3::WifiNetDevice/Phys/" + std::to_string(phyId) +
790 "/PhyTxPsduBegin",
792 }
793 for (uint8_t i = 0; i < m_nStations; i++)
794 {
795 for (uint8_t phyId = 0; phyId < m_staMacs[i]->GetDevice()->GetNPhys(); phyId++)
796 {
797 Config::ConnectWithoutContext("/NodeList/" + std::to_string(i + 1) +
798 "/DeviceList/*/$ns3::WifiNetDevice/Phys/" +
799 std::to_string(phyId) + "/PhyTxPsduBegin",
801 .Bind(m_staMacs[i], phyId));
802 }
803 }
804
805 // install packet socket on all nodes
806 PacketSocketHelper packetSocket;
807 packetSocket.Install(wifiApNode);
808 packetSocket.Install(wifiStaNodes);
809
810 // install a packet socket server on all nodes
811 for (auto nodeIt = NodeList::Begin(); nodeIt != NodeList::End(); ++nodeIt)
812 {
813 PacketSocketAddress srvAddr;
814 auto device = DynamicCast<WifiNetDevice>((*nodeIt)->GetDevice(0));
815 NS_TEST_ASSERT_MSG_NE(device, nullptr, "Expected a WifiNetDevice");
816 srvAddr.SetSingleDevice(device->GetIfIndex());
817 srvAddr.SetProtocol(1);
818
819 auto server = CreateObject<PacketSocketServer>();
820 server->SetLocal(srvAddr);
821 (*nodeIt)->AddApplication(server);
822 server->SetStartTime(Seconds(0)); // now
823 server->SetStopTime(m_duration);
824 }
825
826 for (std::size_t nodeId = 0; nodeId < NodeList::GetNNodes(); nodeId++)
827 {
829 "/NodeList/" + std::to_string(nodeId) +
830 "/ApplicationList/*/$ns3::PacketSocketServer/Rx",
832 }
833
834 // schedule ML setup for one station at a time
835 m_apMac->TraceConnectWithoutContext("AssociatedSta",
837 m_staMacs[0]->SetSsid(Ssid("ns-3-ssid"));
838}
839
842 std::size_t count,
843 std::size_t pktSize,
844 Time delay,
845 uint8_t priority) const
846{
847 auto client = CreateObject<PacketSocketClient>();
848 client->SetAttribute("PacketSize", UintegerValue(pktSize));
849 client->SetAttribute("MaxPackets", UintegerValue(count));
850 client->SetAttribute("Interval", TimeValue(MicroSeconds(0)));
851 client->SetAttribute("Priority", UintegerValue(priority));
852 client->SetRemote(sockAddr);
853 client->SetStartTime(delay);
854 client->SetStopTime(m_duration - Simulator::Now());
855
856 return client;
857}
858
859void
861{
862 if (m_lastAid == aid)
863 {
864 // another STA of this non-AP MLD has already fired this callback
865 return;
866 }
867 m_lastAid = aid;
868
869 // make the next STA to start ML discovery & setup
870 if (aid < m_nStations)
871 {
872 m_staMacs[aid]->SetSsid(Ssid("ns-3-ssid"));
873 return;
874 }
875 // wait some time (5ms) to allow the completion of association before generating traffic
877}
878
879/**
880 * \ingroup wifi-test
881 * \ingroup tests
882 *
883 * \brief Multi-Link Discovery & Setup test.
884 *
885 * This test sets up an AP MLD and a non-AP MLD having a variable number of links.
886 * The RF channels to set each link to are provided as input parameters through the test
887 * case constructor, along with the identifiers (starting at 0) of the links that cannot
888 * switch PHY band (if any). The links that are expected to be setup are also provided as input
889 * parameters. This test verifies that the management frames exchanged during ML discovery
890 * and ML setup contain the expected values and that the two MLDs setup the expected links.
891 *
892 * The negotiated TID-to-link mapping is tested by verifying that generated QoS data frames of
893 * a given TID are transmitted on links which the TID is mapped to. Specifically, the following
894 * operations are performed separately for each direction (downlink and uplink). A first TID
895 * is searched such that it is not mapped on all the setup links. If no such TID is found, only
896 * QoS frames of TID 0 are generated. Otherwise, we also search for a second TID that is mapped
897 * to a link set that is disjoint with the link set to which the first TID is mapped. If such a
898 * TID is found, QoS frames of both the first TID and the second TID are generated; otherwise,
899 * only QoS frames of the first TID are generated. For each TID, a number of QoS frames equal
900 * to the number of setup links is generated. For each TID, we check that the first N QoS frames,
901 * where N is the size of the link set to which the TID is mapped, are transmitted concurrently,
902 * while the following QoS frames are sent after the first QoS frame sent on the same link. We
903 * also check that all the QoS frames are sent on a link belonging to the link set to which the
904 * TID is mapped. If QoS frames of two TIDs are generated, we also check that the first N QoS
905 * frames of a TID, where N is the size of the link set to which that TID is mapped, are sent
906 * concurrently with the first M QoS frames of the other TID, where M is the size of the link
907 * set to which the other TID is mapped.
908 */
910{
911 public:
912 /**
913 * Constructor
914 *
915 * \param baseParams common configuration parameters
916 * \param scanType the scan type (active or passive)
917 * \param setupLinks a list of links that are expected to be setup. In case one of the two
918 * devices has a single link, the ID of the link on the MLD is indicated
919 * \param apNegSupport TID-to-Link Mapping negotiation supported by the AP MLD (0, 1, or 3)
920 * \param dlTidToLinkMapping DL TID-to-Link Mapping for EHT configuration of non-AP MLD
921 * \param ulTidToLinkMapping UL TID-to-Link Mapping for EHT configuration of non-AP MLD
922 */
923 MultiLinkSetupTest(const BaseParams& baseParams,
924 WifiScanType scanType,
925 const std::vector<uint8_t>& setupLinks,
927 const std::string& dlTidToLinkMapping,
928 const std::string& ulTidToLinkMapping);
929 ~MultiLinkSetupTest() override = default;
930
931 protected:
932 void DoSetup() override;
933 void DoRun() override;
934
935 private:
936 void StartTraffic() override;
937
938 /**
939 * Check correctness of Multi-Link Setup procedure.
940 */
941 void CheckMlSetup();
942
943 /**
944 * Check that links that are not setup on the non-AP MLD are disabled. Also, on the AP side,
945 * check that the queue storing QoS data frames destined to the non-AP MLD has a mask for a
946 * link if and only if the link has been setup by the non-AO MLD.
947 */
948 void CheckDisabledLinks();
949
950 /**
951 * Check correctness of the given Beacon frame.
952 *
953 * \param mpdu the given Beacon frame
954 * \param linkId the ID of the link on which the Beacon frame was transmitted
955 */
956 void CheckBeacon(Ptr<WifiMpdu> mpdu, uint8_t linkId);
957
958 /**
959 * Check correctness of the given Probe Response frame.
960 *
961 * \param mpdu the given Probe Response frame
962 * \param linkId the ID of the link on which the Probe Response frame was transmitted
963 */
964 void CheckProbeResponse(Ptr<WifiMpdu> mpdu, uint8_t linkId);
965
966 /**
967 * Check correctness of the given Association Request frame.
968 *
969 * \param mpdu the given Association Request frame
970 * \param linkId the ID of the link on which the Association Request frame was transmitted
971 */
972 void CheckAssocRequest(Ptr<WifiMpdu> mpdu, uint8_t linkId);
973
974 /**
975 * Check correctness of the given Association Response frame.
976 *
977 * \param mpdu the given Association Response frame
978 * \param linkId the ID of the link on which the Association Response frame was transmitted
979 */
980 void CheckAssocResponse(Ptr<WifiMpdu> mpdu, uint8_t linkId);
981
982 /**
983 * Check that QoS data frames are sent on links their TID is mapped to.
984 *
985 * \param mpdu the given QoS data frame
986 * \param linkId the ID of the link on which the QoS data frame was transmitted
987 * \param index index of the QoS data frame in the vector of transmitted PSDUs
988 */
989 void CheckQosData(Ptr<WifiMpdu> mpdu, uint8_t linkId, std::size_t index);
990
991 const std::vector<uint8_t> m_setupLinks; //!< IDs of the expected links to setup
992 WifiScanType m_scanType; //!< the scan type (active or passive)
993 std::size_t m_nProbeResp; //!< number of Probe Responses received by the non-AP MLD
995 m_apNegSupport; //!< TID-to-Link Mapping negotiation supported by the AP MLD
996 std::string m_dlTidLinkMappingStr; //!< DL TID-to-Link Mapping for non-AP MLD EHT configuration
997 std::string m_ulTidLinkMappingStr; //!< UL TID-to-Link Mapping for non-AP MLD EHT configuration
998 WifiTidLinkMapping m_dlTidLinkMapping; //!< expected DL TID-to-Link Mapping requested by non-AP
999 //!< MLD and accepted by AP MLD
1000 WifiTidLinkMapping m_ulTidLinkMapping; //!< expected UL TID-to-Link Mapping requested by non-AP
1001 //!< MLD and accepted by AP MLD
1002 uint8_t m_dlTid1; //!< the TID of the first set of DL QoS data frames
1003 uint8_t m_ulTid1; //!< the TID of the first set of UL QoS data frames
1004 std::optional<uint8_t> m_dlTid2; //!< the TID of the optional set of DL QoS data frames
1005 std::optional<uint8_t> m_ulTid2; //!< the TID of the optional set of UL QoS data frames
1006 std::vector<std::size_t>
1007 m_qosFrames1; //!< indices of QoS frames of the first set in the vector of TX PSDUs
1008 std::vector<std::size_t>
1009 m_qosFrames2; //!< indices of QoS frames of the optional set in the vector of TX PSDUs
1010};
1011
1013 WifiScanType scanType,
1014 const std::vector<uint8_t>& setupLinks,
1015 WifiTidToLinkMappingNegSupport apNegSupport,
1016 const std::string& dlTidToLinkMapping,
1017 const std::string& ulTidToLinkMapping)
1018 : MultiLinkOperationsTestBase("Check correctness of Multi-Link Setup", 1, baseParams),
1019 m_setupLinks(setupLinks),
1020 m_scanType(scanType),
1021 m_nProbeResp(0),
1022 m_apNegSupport(apNegSupport),
1023 m_dlTidLinkMappingStr(dlTidToLinkMapping),
1024 m_ulTidLinkMappingStr(ulTidToLinkMapping)
1025{
1026}
1027
1028void
1030{
1032
1033 m_staMacs[0]->SetAttribute("ActiveProbing", BooleanValue(m_scanType == WifiScanType::ACTIVE));
1034 m_apMac->GetEhtConfiguration()->SetAttribute("TidToLinkMappingNegSupport",
1036 // For non-AP MLD, it does not make sense to set the negotiation type to 0 (unless the AP MLD
1037 // also advertises 0) or 1 (the AP MLD is discarded if it advertises a support of 3)
1038 auto staEhtConfig = m_staMacs[0]->GetEhtConfiguration();
1039 staEhtConfig->SetAttribute("TidToLinkMappingNegSupport",
1040 EnumValue(WifiTidToLinkMappingNegSupport::ANY_LINK_SET));
1041 staEhtConfig->SetAttribute("TidToLinkMappingDl", StringValue(m_dlTidLinkMappingStr));
1042 staEhtConfig->SetAttribute("TidToLinkMappingUl", StringValue(m_ulTidLinkMappingStr));
1043
1044 // the negotiated link mapping matches the one configured in EHT configuration, unless
1045 // the AP MLD does not support TID-to-link mapping negotiation or the AP MLD supports
1046 // the negotiation type 1 and the non-AP MLD is configured with a link mapping that
1047 // maps distinct link sets to the TIDs, in which case the default link mapping is used
1048 m_dlTidLinkMapping = staEhtConfig->GetTidLinkMapping(WifiDirection::DOWNLINK);
1049 m_ulTidLinkMapping = staEhtConfig->GetTidLinkMapping(WifiDirection::UPLINK);
1050
1051 if (m_apNegSupport == WifiTidToLinkMappingNegSupport::NOT_SUPPORTED ||
1052 (m_apNegSupport == WifiTidToLinkMappingNegSupport::SAME_LINK_SET &&
1054 {
1055 m_dlTidLinkMapping.clear(); // default link mapping
1056 m_ulTidLinkMapping.clear(); // default link mapping
1057 }
1058
1059 // find (if any) a TID that is not mapped to all setup links
1060 using TupleRefs = std::tuple<std::reference_wrapper<const WifiTidLinkMapping>,
1061 std::reference_wrapper<uint8_t>,
1062 std::reference_wrapper<std::optional<uint8_t>>,
1063 Ptr<WifiMac>>;
1064 for (auto& [mappingRef, tid1Ref, tid2Ref, mac] :
1067 {
1068 tid1Ref.get() = 0;
1069 for (uint8_t tid1 = 0; tid1 < 8; tid1++)
1070 {
1071 if (auto it1 = mappingRef.get().find(tid1);
1072 it1 != mappingRef.get().cend() && it1->second.size() != m_setupLinks.size())
1073 {
1074 // found. Now search for another TID with a disjoint mapped link set
1075 for (uint8_t tid2 = tid1 + 1; tid2 < 8; tid2++)
1076 {
1077 if (auto it2 = mappingRef.get().find(tid2);
1078 it2 != mappingRef.get().cend() && it2->second.size() != m_setupLinks.size())
1079 {
1080 std::list<uint8_t> intersection;
1081 std::set_intersection(it1->second.cbegin(),
1082 it1->second.cend(),
1083 it2->second.cbegin(),
1084 it2->second.cend(),
1085 std::back_inserter(intersection));
1086 if (intersection.empty())
1087 {
1088 // found a second TID
1089 tid2Ref.get() = tid2;
1090 break;
1091 }
1092 }
1093 }
1094 tid1Ref.get() = tid1;
1095 break;
1096 }
1097 }
1098
1099 std::list<uint8_t> tids = {tid1Ref.get()};
1100 if (tid2Ref.get())
1101 {
1102 tids.emplace_back(*tid2Ref.get());
1103 }
1104
1105 // prevent aggregation of MPDUs
1106 for (auto tid : tids)
1107 {
1108 std::string attrName;
1109 switch (QosUtilsMapTidToAc(tid))
1110 {
1111 case AC_VI:
1112 attrName = "VI_MaxAmpduSize";
1113 break;
1114 case AC_VO:
1115 attrName = "VO_MaxAmpduSize";
1116 break;
1117 case AC_BE:
1118 attrName = "BE_MaxAmpduSize";
1119 break;
1120 case AC_BK:
1121 attrName = "BK_MaxAmpduSize";
1122 break;
1123 default:
1124 NS_FATAL_ERROR("Invalid TID " << +tid);
1125 }
1126
1127 mac->SetAttribute(attrName, UintegerValue(100));
1128 }
1129 }
1130}
1131
1132void
1134{
1135 // DL traffic
1136 {
1137 PacketSocketAddress sockAddr;
1139 sockAddr.SetPhysicalAddress(m_staMacs[0]->GetDevice()->GetAddress());
1140 sockAddr.SetProtocol(1);
1141
1143 GetApplication(sockAddr, m_setupLinks.size(), 500, Seconds(0), m_dlTid1));
1144 if (m_dlTid2)
1145 {
1147 GetApplication(sockAddr, m_setupLinks.size(), 500, Seconds(0), *m_dlTid2));
1148 }
1149 }
1150
1151 // UL Traffic
1152 {
1153 PacketSocketAddress sockAddr;
1154 sockAddr.SetSingleDevice(m_staMacs[0]->GetDevice()->GetIfIndex());
1156 sockAddr.SetProtocol(1);
1157
1158 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
1159 GetApplication(sockAddr, m_setupLinks.size(), 500, MilliSeconds(500), m_ulTid1));
1160 if (m_ulTid2)
1161 {
1162 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
1163 GetApplication(sockAddr, m_setupLinks.size(), 500, MilliSeconds(500), *m_ulTid2));
1164 }
1165 }
1166}
1167
1168void
1170{
1172
1175
1176 /**
1177 * Check content of management frames
1178 */
1179 std::size_t index = 0;
1180
1181 for (const auto& frameInfo : m_txPsdus)
1182 {
1183 const auto& mpdu = *frameInfo.psduMap.begin()->second->begin();
1184 const auto& linkId = frameInfo.linkId;
1185
1186 switch (mpdu->GetHeader().GetType())
1187 {
1189 CheckBeacon(mpdu, linkId);
1190 break;
1191
1193 CheckProbeResponse(mpdu, linkId);
1194 m_nProbeResp++;
1195 break;
1196
1198 CheckAssocRequest(mpdu, linkId);
1199 break;
1200
1202 CheckAssocResponse(mpdu, linkId);
1203 break;
1204
1205 case WIFI_MAC_QOSDATA:
1206 CheckQosData(mpdu, linkId, index);
1207 break;
1208
1209 default:
1210 break;
1211 }
1212
1213 index++;
1214 }
1215
1217
1218 std::size_t expectedProbeResp = 0;
1219 if (m_scanType == WifiScanType::ACTIVE)
1220 {
1221 // the number of Probe Response frames that we expect to receive in active mode equals
1222 // the number of channels in common between AP MLD and non-AP MLD at initialization
1223 for (const auto& staChannel : m_staChannels)
1224 {
1225 for (const auto& apChannel : m_apChannels)
1226 {
1227 if (staChannel == apChannel)
1228 {
1229 expectedProbeResp++;
1230 break;
1231 }
1232 }
1233 }
1234 }
1235
1236 NS_TEST_EXPECT_MSG_EQ(m_nProbeResp, expectedProbeResp, "Unexpected number of Probe Responses");
1237
1238 std::size_t expectedRxDlPkts = m_setupLinks.size();
1239 if (m_dlTid2)
1240 {
1241 expectedRxDlPkts *= 2;
1242 }
1243 NS_TEST_EXPECT_MSG_EQ(m_rxPkts[m_staMacs[0]->GetDevice()->GetNode()->GetId()],
1244 expectedRxDlPkts,
1245 "Unexpected number of DL packets received");
1246
1247 std::size_t expectedRxUlPkts = m_setupLinks.size();
1248 if (m_ulTid2)
1249 {
1250 expectedRxUlPkts *= 2;
1251 }
1253 expectedRxUlPkts,
1254 "Unexpected number of UL packets received");
1255
1257}
1258
1259void
1261{
1262 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_BEACON);
1263
1264 CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::DL);
1265
1267 mpdu->GetHeader().GetAddr2(),
1268 "TA of Beacon frame is not the address of the link it is transmitted on");
1269 MgtBeaconHeader beacon;
1270 mpdu->GetPacket()->PeekHeader(beacon);
1271 const auto& rnr = beacon.Get<ReducedNeighborReport>();
1272 const auto& mle = beacon.Get<MultiLinkElement>();
1273
1274 if (m_apMac->GetNLinks() == 1)
1275 {
1276 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(),
1277 false,
1278 "RNR Element in Beacon frame from single link AP");
1279 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1280 false,
1281 "Multi-Link Element in Beacon frame from single link AP");
1282 return;
1283 }
1284
1285 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(), true, "No RNR Element in Beacon frame");
1286 // All the other APs affiliated with the same AP MLD as the AP sending
1287 // the Beacon frame must be reported in a separate Neighbor AP Info field
1288 NS_TEST_EXPECT_MSG_EQ(rnr->GetNNbrApInfoFields(),
1289 static_cast<std::size_t>(m_apMac->GetNLinks() - 1),
1290 "Unexpected number of Neighbor AP Info fields in RNR");
1291 for (std::size_t nbrApInfoId = 0; nbrApInfoId < rnr->GetNNbrApInfoFields(); nbrApInfoId++)
1292 {
1293 NS_TEST_EXPECT_MSG_EQ(rnr->HasMldParameters(nbrApInfoId),
1294 true,
1295 "MLD Parameters not present");
1296 NS_TEST_EXPECT_MSG_EQ(rnr->GetNTbttInformationFields(nbrApInfoId),
1297 1,
1298 "Expected only one TBTT Info subfield per Neighbor AP Info");
1299 uint8_t nbrLinkId = rnr->GetLinkId(nbrApInfoId, 0);
1300 NS_TEST_EXPECT_MSG_EQ(rnr->GetBssid(nbrApInfoId, 0),
1301 m_apMac->GetFrameExchangeManager(nbrLinkId)->GetAddress(),
1302 "BSSID advertised in Neighbor AP Info field "
1303 << nbrApInfoId
1304 << " does not match the address configured on the link "
1305 "advertised in the same field");
1306 }
1307
1308 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Beacon frame");
1309 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1311 "Incorrect MLD address advertised in Multi-Link Element");
1312 NS_TEST_EXPECT_MSG_EQ(mle->GetLinkIdInfo(),
1313 +linkId,
1314 "Incorrect Link ID advertised in Multi-Link Element");
1315}
1316
1317void
1319{
1320 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_PROBE_RESPONSE);
1321
1322 CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::DL);
1323
1325 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1326 mpdu->GetHeader().GetAddr2(),
1327 "TA of Probe Response is not the address of the link it is transmitted on");
1328 MgtProbeResponseHeader probeResp;
1329 mpdu->GetPacket()->PeekHeader(probeResp);
1330 const auto& rnr = probeResp.Get<ReducedNeighborReport>();
1331 const auto& mle = probeResp.Get<MultiLinkElement>();
1332
1333 if (m_apMac->GetNLinks() == 1)
1334 {
1335 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(),
1336 false,
1337 "RNR Element in Probe Response frame from single link AP");
1338 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1339 false,
1340 "Multi-Link Element in Probe Response frame from single link AP");
1341 return;
1342 }
1343
1344 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(), true, "No RNR Element in Probe Response frame");
1345 // All the other APs affiliated with the same AP MLD as the AP sending
1346 // the Probe Response frame must be reported in a separate Neighbor AP Info field
1347 NS_TEST_EXPECT_MSG_EQ(rnr->GetNNbrApInfoFields(),
1348 static_cast<std::size_t>(m_apMac->GetNLinks() - 1),
1349 "Unexpected number of Neighbor AP Info fields in RNR");
1350 for (std::size_t nbrApInfoId = 0; nbrApInfoId < rnr->GetNNbrApInfoFields(); nbrApInfoId++)
1351 {
1352 NS_TEST_EXPECT_MSG_EQ(rnr->HasMldParameters(nbrApInfoId),
1353 true,
1354 "MLD Parameters not present");
1355 NS_TEST_EXPECT_MSG_EQ(rnr->GetNTbttInformationFields(nbrApInfoId),
1356 1,
1357 "Expected only one TBTT Info subfield per Neighbor AP Info");
1358 uint8_t nbrLinkId = rnr->GetLinkId(nbrApInfoId, 0);
1359 NS_TEST_EXPECT_MSG_EQ(rnr->GetBssid(nbrApInfoId, 0),
1360 m_apMac->GetFrameExchangeManager(nbrLinkId)->GetAddress(),
1361 "BSSID advertised in Neighbor AP Info field "
1362 << nbrApInfoId
1363 << " does not match the address configured on the link "
1364 "advertised in the same field");
1365 }
1366
1367 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Probe Response frame");
1368 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1370 "Incorrect MLD address advertised in Multi-Link Element");
1371 NS_TEST_EXPECT_MSG_EQ(mle->GetLinkIdInfo(),
1372 +linkId,
1373 "Incorrect Link ID advertised in Multi-Link Element");
1374}
1375
1376void
1378{
1379 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_ASSOCIATION_REQUEST);
1380
1381 CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::UL);
1382
1384 m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress(),
1385 mpdu->GetHeader().GetAddr2(),
1386 "TA of Assoc Request frame is not the address of the link it is transmitted on");
1388 mpdu->GetPacket()->PeekHeader(assoc);
1389 const auto& mle = assoc.Get<MultiLinkElement>();
1390
1391 if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetNLinks() == 1)
1392 {
1393 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1394 false,
1395 "Multi-Link Element in Assoc Request frame from single link STA");
1396 }
1397 else
1398 {
1399 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1400 true,
1401 "No Multi-Link Element in Assoc Request frame");
1402 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1403 m_staMacs[0]->GetAddress(),
1404 "Incorrect MLD Address advertised in Multi-Link Element");
1406 mle->GetNPerStaProfileSubelements(),
1407 m_setupLinks.size() - 1,
1408 "Incorrect number of Per-STA Profile subelements in Multi-Link Element");
1409 for (std::size_t i = 0; i < mle->GetNPerStaProfileSubelements(); i++)
1410 {
1411 auto& perStaProfile = mle->GetPerStaProfile(i);
1412 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasStaMacAddress(),
1413 true,
1414 "Per-STA Profile must contain STA MAC address");
1415 // find ID of the local link corresponding to this subelement
1416 auto staLinkId = m_staMacs[0]->GetLinkIdByAddress(perStaProfile.GetStaMacAddress());
1418 staLinkId.has_value(),
1419 true,
1420 "No link found with the STA MAC address advertised in Per-STA Profile");
1422 +staLinkId.value(),
1423 +linkId,
1424 "The STA that sent the Assoc Request should not be included in a Per-STA Profile");
1425 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), staLinkId.value());
1426 NS_TEST_EXPECT_MSG_EQ((it != m_setupLinks.end()),
1427 true,
1428 "Not expecting to setup STA link ID " << +staLinkId.value());
1430 +staLinkId.value(),
1431 +perStaProfile.GetLinkId(),
1432 "Not expecting to request association to AP Link ID in Per-STA Profile");
1433 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasAssocRequest(),
1434 true,
1435 "Missing Association Request in Per-STA Profile");
1436 }
1437 }
1438
1439 const auto& tlm = assoc.Get<TidToLinkMapping>();
1440
1441 // A TID-to-Link Mapping IE is included in the Association Request if and only if the AP MLD
1442 // and the non-AP MLD are performing ML setup (i.e., they both have multiple links) and the
1443 // AP MLD advertises a non-null negotiation support type
1444 if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetNLinks() == 1 ||
1445 m_apNegSupport == WifiTidToLinkMappingNegSupport::NOT_SUPPORTED)
1446 {
1447 NS_TEST_EXPECT_MSG_EQ(tlm.empty(),
1448 true,
1449 "Didn't expect a TID-to-Link Mapping IE in Assoc Request frame");
1450 }
1451 else
1452 {
1453 std::size_t expectedNTlm = (m_dlTidLinkMapping == m_ulTidLinkMapping ? 1 : 2);
1454
1455 NS_TEST_ASSERT_MSG_EQ(tlm.size(),
1456 expectedNTlm,
1457 "Unexpected number of TID-to-Link Mapping IE in Assoc Request");
1458
1459 // lambda to check content of TID-to-Link Mapping IE(s)
1460 auto checkTlm = [&](std::size_t tlmId, WifiDirection dir) {
1461 NS_TEST_EXPECT_MSG_EQ(+static_cast<uint8_t>(tlm[tlmId].m_control.direction),
1462 +static_cast<uint8_t>(dir),
1463 "Unexpected direction in TID-to-Link Mapping IE " << tlmId);
1464 auto& expectedMapping =
1465 (dir == WifiDirection::UPLINK ? m_ulTidLinkMapping : m_dlTidLinkMapping);
1466
1467 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].m_control.defaultMapping,
1468 expectedMapping.empty(),
1469 "Default Link Mapping bit not set correctly");
1470 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].m_linkMapping.size(),
1471 expectedMapping.size(),
1472 "Unexpected number of Link Mapping Of TID n fields");
1473 for (uint8_t tid = 0; tid < 8; tid++)
1474 {
1475 if (auto it = expectedMapping.find(tid); it != expectedMapping.cend())
1476 {
1477 NS_TEST_EXPECT_MSG_EQ((tlm[tlmId].GetLinkMappingOfTid(tid) == it->second),
1478 true,
1479 "Unexpected link mapping for TID "
1480 << +tid << " direction " << dir);
1481 }
1482 else
1483 {
1484 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].GetLinkMappingOfTid(tid).empty(),
1485 true,
1486 "Expecting no Link Mapping Of TID n field for TID "
1487 << +tid << " direction " << dir);
1488 }
1489 }
1490 };
1491
1492 if (tlm.size() == 1)
1493 {
1494 checkTlm(0, WifiDirection::BOTH_DIRECTIONS);
1495 }
1496 else
1497 {
1498 std::size_t dlId = (tlm[0].m_control.direction == WifiDirection::DOWNLINK ? 0 : 1);
1499 std::size_t ulId = (dlId == 0 ? 1 : 0);
1500
1501 checkTlm(dlId, WifiDirection::DOWNLINK);
1502 checkTlm(ulId, WifiDirection::UPLINK);
1503 }
1504 }
1505}
1506
1507void
1509{
1510 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_ASSOCIATION_RESPONSE);
1511
1512 CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::DL);
1513
1515 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1516 mpdu->GetHeader().GetAddr2(),
1517 "TA of Assoc Response frame is not the address of the link it is transmitted on");
1519 mpdu->GetPacket()->PeekHeader(assoc);
1520 const auto& mle = assoc.Get<MultiLinkElement>();
1521
1522 if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetNLinks() == 1)
1523 {
1525 mle.has_value(),
1526 false,
1527 "Multi-Link Element in Assoc Response frame with single link AP or single link STA");
1528 return;
1529 }
1530
1531 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Assoc Request frame");
1532 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1534 "Incorrect MLD Address advertised in Multi-Link Element");
1535 NS_TEST_EXPECT_MSG_EQ(mle->GetNPerStaProfileSubelements(),
1536 m_setupLinks.size() - 1,
1537 "Incorrect number of Per-STA Profile subelements in Multi-Link Element");
1538 for (std::size_t i = 0; i < mle->GetNPerStaProfileSubelements(); i++)
1539 {
1540 auto& perStaProfile = mle->GetPerStaProfile(i);
1541 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasStaMacAddress(),
1542 true,
1543 "Per-STA Profile must contain STA MAC address");
1544 // find ID of the local link corresponding to this subelement
1545 auto apLinkId = m_apMac->GetLinkIdByAddress(perStaProfile.GetStaMacAddress());
1547 apLinkId.has_value(),
1548 true,
1549 "No link found with the STA MAC address advertised in Per-STA Profile");
1550 NS_TEST_EXPECT_MSG_EQ(+apLinkId.value(),
1551 +perStaProfile.GetLinkId(),
1552 "Link ID and MAC address advertised in Per-STA Profile do not match");
1554 +apLinkId.value(),
1555 +linkId,
1556 "The AP that sent the Assoc Response should not be included in a Per-STA Profile");
1557 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), apLinkId.value());
1558 NS_TEST_EXPECT_MSG_EQ((it != m_setupLinks.end()),
1559 true,
1560 "Not expecting to setup AP link ID " << +apLinkId.value());
1561 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasAssocResponse(),
1562 true,
1563 "Missing Association Response in Per-STA Profile");
1564 }
1565
1566 // For the moment, the AP MLD always accepts a valid TID-to-Link Mapping request, hence
1567 // in every case there is no TID-to-Link Mapping IE in the Association Response
1568 NS_TEST_EXPECT_MSG_EQ(assoc.Get<TidToLinkMapping>().empty(),
1569 true,
1570 "Didn't expect to find a TID-to-Link Mapping IE in Association Response");
1571}
1572
1573void
1575{
1576 /**
1577 * Check outcome of Multi-Link Setup
1578 */
1579 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->IsAssociated(), true, "Expected the STA to be associated");
1580
1581 for (const auto linkId : m_setupLinks)
1582 {
1583 auto staLinkId = (m_staMacs[0]->GetNLinks() > 1 ? linkId : SINGLE_LINK_OP_ID);
1584 auto apLinkId = (m_apMac->GetNLinks() > 1 ? linkId : SINGLE_LINK_OP_ID);
1585
1586 auto staAddr = m_staMacs[0]->GetFrameExchangeManager(staLinkId)->GetAddress();
1587 auto apAddr = m_apMac->GetFrameExchangeManager(apLinkId)->GetAddress();
1588
1589 auto staRemoteMgr = m_staMacs[0]->GetWifiRemoteStationManager(staLinkId);
1590 auto apRemoteMgr = m_apMac->GetWifiRemoteStationManager(apLinkId);
1591
1592 // STA side
1593 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetFrameExchangeManager(staLinkId)->GetBssid(),
1594 apAddr,
1595 "Unexpected BSSID for STA link ID " << +staLinkId);
1596 if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1597 {
1598 NS_TEST_EXPECT_MSG_EQ((staRemoteMgr->GetMldAddress(apAddr) == m_apMac->GetAddress()),
1599 true,
1600 "Incorrect MLD address stored by STA on link ID " << +staLinkId);
1602 (staRemoteMgr->GetAffiliatedStaAddress(m_apMac->GetAddress()) == apAddr),
1603 true,
1604 "Incorrect affiliated address stored by STA on link ID " << +staLinkId);
1605 }
1606
1607 // AP side
1608 NS_TEST_EXPECT_MSG_EQ(apRemoteMgr->IsAssociated(staAddr),
1609 true,
1610 "Expecting STA " << staAddr << " to be associated on link "
1611 << +apLinkId);
1612 if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1613 {
1615 (apRemoteMgr->GetMldAddress(staAddr) == m_staMacs[0]->GetAddress()),
1616 true,
1617 "Incorrect MLD address stored by AP on link ID " << +apLinkId);
1619 (apRemoteMgr->GetAffiliatedStaAddress(m_staMacs[0]->GetAddress()) == staAddr),
1620 true,
1621 "Incorrect affiliated address stored by AP on link ID " << +apLinkId);
1622 }
1623 auto aid = m_apMac->GetAssociationId(staAddr, apLinkId);
1624 const auto& staList = m_apMac->GetStaList(apLinkId);
1625 NS_TEST_EXPECT_MSG_EQ((staList.find(aid) != staList.end()),
1626 true,
1627 "STA " << staAddr << " not found in list of associated STAs");
1628
1629 // STA of non-AP MLD operate on the same channel as the AP
1631 +m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetNumber(),
1633 "Incorrect operating channel number for STA on link " << +staLinkId);
1635 m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetFrequency(),
1637 "Incorrect operating channel frequency for STA on link " << +staLinkId);
1638 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetWidth(),
1640 "Incorrect operating channel width for STA on link " << +staLinkId);
1642 +m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetPhyBand(),
1644 "Incorrect operating PHY band for STA on link " << +staLinkId);
1646 +m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetPrimaryChannelIndex(20),
1648 "Incorrect operating primary channel index for STA on link " << +staLinkId);
1649 }
1650
1651 // lambda to check the link mapping stored at wifi MAC
1652 auto checkStoredMapping =
1653 [this](Ptr<WifiMac> mac, Ptr<WifiMac> dest, WifiDirection dir, bool present) {
1654 NS_TEST_ASSERT_MSG_EQ(mac->GetTidToLinkMapping(dest->GetAddress(), dir).has_value(),
1655 present,
1656 "Link mapping stored by "
1657 << (mac->GetTypeOfStation() == AP ? "AP" : "non-AP")
1658 << " MLD for " << dir << " direction "
1659 << (present ? "expected" : "not expected"));
1660 if (present)
1661 {
1662 const auto& mapping =
1663 (dir == WifiDirection::DOWNLINK ? m_dlTidLinkMapping : m_ulTidLinkMapping);
1665 (mac->GetTidToLinkMapping(dest->GetAddress(), dir)->get() == mapping),
1666 true,
1667 "Incorrect link mapping stored by "
1668 << (mac->GetTypeOfStation() == AP ? "AP" : "non-AP") << " MLD for " << dir
1669 << " direction");
1670 }
1671 };
1672
1673 auto storedMapping = m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1 &&
1674 m_apNegSupport > WifiTidToLinkMappingNegSupport::NOT_SUPPORTED;
1675 checkStoredMapping(m_apMac, m_staMacs[0], WifiDirection::DOWNLINK, storedMapping);
1676 checkStoredMapping(m_apMac, m_staMacs[0], WifiDirection::UPLINK, storedMapping);
1677 checkStoredMapping(m_staMacs[0], m_apMac, WifiDirection::DOWNLINK, storedMapping);
1678 checkStoredMapping(m_staMacs[0], m_apMac, WifiDirection::UPLINK, storedMapping);
1679}
1680
1681void
1683{
1684 if (m_apMac->GetNLinks() > 1)
1685 {
1688 m_staMacs[0]->GetAddress(),
1689 0);
1690
1691 for (uint8_t linkId = 0; linkId < m_apMac->GetNLinks(); ++linkId)
1692 {
1693 auto it = std::find(m_setupLinks.cbegin(), m_setupLinks.cend(), linkId);
1694
1695 // the queue on the AP should have a mask if and only if the link has been setup
1696 auto mask = m_apMac->GetMacQueueScheduler()->GetQueueLinkMask(AC_BE, queueId, linkId);
1697 NS_TEST_EXPECT_MSG_EQ(mask.has_value(),
1698 (it != m_setupLinks.cend()),
1699 "Unexpected presence/absence of mask on link " << +linkId);
1700 }
1701 }
1702
1703 if (m_staMacs[0]->GetNLinks() == 1)
1704 {
1705 // no link is disabled on a single link device
1706 return;
1707 }
1708
1709 for (const auto& linkId : m_staMacs[0]->GetLinkIds())
1710 {
1711 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), linkId);
1712 if (it == m_setupLinks.end())
1713 {
1714 // the link has not been setup
1715 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId)->GetState()->IsStateOff(),
1716 true,
1717 "Link " << +linkId << " has not been setup but is not disabled");
1718 continue;
1719 }
1720
1721 // the link has been setup and must be active
1722 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId)->GetState()->IsStateOff(),
1723 false,
1724 "Expecting link " << +linkId << " to be active");
1725 }
1726}
1727
1728void
1729MultiLinkSetupTest::CheckQosData(Ptr<WifiMpdu> mpdu, uint8_t linkId, std::size_t index)
1730{
1732 const auto& hdr = mpdu->GetHeader();
1733
1734 NS_TEST_ASSERT_MSG_EQ(hdr.IsQosData(), true, "Expected a QoS data frame");
1735
1736 if (!hdr.IsToDs() && hdr.IsFromDs())
1737 {
1738 dir = WifiDirection::DOWNLINK;
1739 }
1740 else if (hdr.IsToDs() && !hdr.IsFromDs())
1741 {
1742 dir = WifiDirection::UPLINK;
1743 }
1744 else
1745 {
1746 NS_ABORT_MSG("Invalid combination for QoS data frame: ToDS(" << hdr.IsToDs() << ") FromDS("
1747 << hdr.IsFromDs() << ")");
1748 }
1749
1750 const auto& tid1 = (dir == WifiDirection::DOWNLINK) ? m_dlTid1 : m_ulTid1;
1751 const auto& tid2 = (dir == WifiDirection::DOWNLINK) ? m_dlTid2 : m_ulTid2;
1752 uint8_t tid = hdr.GetQosTid();
1753
1754 NS_TEST_ASSERT_MSG_NE((tid == tid1),
1755 (tid2.has_value() && tid == *tid2),
1756 "QoS frame with unexpected TID " << +tid);
1757
1758 // lambda to find the link set the given TID is mapped to
1759 auto findLinkSet = [this, dir](uint8_t tid) -> std::set<uint8_t> {
1760 std::set<uint8_t> linkSet(m_setupLinks.cbegin(), m_setupLinks.cend());
1761 if (auto mappingOptRef = m_apMac->GetTidToLinkMapping(m_staMacs[0]->GetAddress(), dir))
1762 {
1763 // if the TID is not present in the mapping, it is mapped to all setup links
1764 if (auto it = mappingOptRef->get().find(tid); it != mappingOptRef->get().cend())
1765 {
1766 linkSet = it->second;
1767 NS_ASSERT_MSG(!linkSet.empty(), "TID " << +tid << " mapped to no link");
1768 }
1769 }
1770 return linkSet;
1771 };
1772
1773 auto linkSet = findLinkSet(tid);
1774 auto& qosFrames = (tid == tid1) ? m_qosFrames1 : m_qosFrames2;
1775
1776 // Let N the size of the link set, the first N QoS data frames are sent simultaneously
1777 // on the links of the set, the others (if any) will be sent afterwards on such links
1778
1779 // number of concurrent frames of the same TID transmitted so far (excluding current frame)
1780 std::size_t nConcurFrames = std::min(qosFrames.size(), linkSet.size());
1781
1782 // iterate over the concurrent frames of the same TID transmitted so far
1783 for (std::size_t i = 0; i < nConcurFrames; i++)
1784 {
1785 auto prev = qosFrames[i];
1786
1787 // TX duration of i-th frame
1788 auto band = m_apMac->GetWifiPhy(m_txPsdus[prev].linkId)->GetPhyBand();
1789 Time txDuration =
1790 WifiPhy::CalculateTxDuration(m_txPsdus[prev].psduMap, m_txPsdus[prev].txVector, band);
1791
1792 // the current frame is transmitted concurrently with this previous frame if it is
1793 // within the first N (size of the link set) frames, otherwise it is transmitted after
1794 // this previous frame if they have been transmitted on the same link
1795 if (qosFrames.size() < linkSet.size())
1796 {
1797 // the current frame can be sent concurrently with this previous frame
1798 NS_TEST_EXPECT_MSG_LT(m_txPsdus[index].startTx,
1799 m_txPsdus[prev].startTx + txDuration,
1800 "The " << dir << " QoS frame number " << qosFrames.size()
1801 << " was not sent concurrently with others on link "
1802 << +linkId << " which TID " << +tid << " is mapped to");
1803 }
1804 else if (m_txPsdus[prev].linkId == linkId)
1805 {
1806 // the current frame is sent afterwards
1807 NS_TEST_EXPECT_MSG_GT(m_txPsdus[index].startTx,
1808 m_txPsdus[prev].startTx + txDuration,
1809 "The " << dir << " QoS frame number " << qosFrames.size()
1810 << " was sent concurrently with others on a link "
1811 << +linkId << " which TID " << +tid << " is mapped to");
1812 }
1813 }
1814
1815 if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1816 {
1817 NS_TEST_EXPECT_MSG_EQ(std::count(linkSet.cbegin(), linkSet.cend(), linkId),
1818 1,
1819 "QoS frame sent on Link ID "
1820 << +linkId << " that does not belong to the link set of TID "
1821 << +tid);
1822 }
1823
1824 if (tid2)
1825 {
1826 // QoS frames of two distinct TIDs are sent.
1827 auto otherTid = (tid == tid1) ? *tid2 : tid1;
1828 const auto& otherQosFrames = (tid == tid1) ? m_qosFrames2 : m_qosFrames1;
1829 auto otherLinkSet = findLinkSet(otherTid);
1830
1831 // number of concurrent frames of the other TID transmitted so far
1832 std::size_t nOtherConcurFrames = std::min(otherQosFrames.size(), otherLinkSet.size());
1833
1834 // iterate over the concurrent frames of the other TID
1835 for (std::size_t i = 0; i < nOtherConcurFrames; i++)
1836 {
1837 auto prev = otherQosFrames[i];
1838
1839 // TX duration of i-th frame
1840 auto band = m_apMac->GetWifiPhy(m_txPsdus[prev].linkId)->GetPhyBand();
1841 Time txDuration = WifiPhy::CalculateTxDuration(m_txPsdus[prev].psduMap,
1842 m_txPsdus[prev].txVector,
1843 band);
1844
1845 // the current frame is transmitted concurrently with this previous frame of the
1846 // other TID if it is within the first N (size of the link set) frames of its TID
1847 if (qosFrames.size() < linkSet.size())
1848 {
1849 // the current frame can be sent concurrently with this previous frame
1850 NS_TEST_EXPECT_MSG_LT(m_txPsdus[index].startTx,
1851 m_txPsdus[prev].startTx + txDuration,
1852 "The " << dir << " QoS frame number " << qosFrames.size()
1853 << " was not sent concurrently with others with TID "
1854 << +otherTid);
1855 }
1856 }
1857 }
1858
1859 // insert the frame
1860 qosFrames.emplace_back(index);
1861
1862 if (qosFrames.size() == m_setupLinks.size())
1863 {
1864 qosFrames.clear();
1865 }
1866}
1867
1868/**
1869 * Tested traffic patterns.
1870 */
1871enum class WifiTrafficPattern : uint8_t
1872{
1873 STA_TO_STA = 0,
1874 STA_TO_AP,
1875 AP_TO_STA,
1878};
1879
1880/**
1881 * Block Ack agreement enabled/disabled
1882 */
1883enum class WifiBaEnabled : uint8_t
1884{
1885 NO = 0,
1886 YES
1887};
1888
1889/**
1890 * Whether to send a BlockAckReq after a missed BlockAck
1891 */
1892enum class WifiUseBarAfterMissedBa : uint8_t
1893{
1894 NO = 0,
1895 YES
1896};
1897
1898/**
1899 * \ingroup wifi-test
1900 * \ingroup tests
1901 *
1902 * \brief Test data transmission between two MLDs.
1903 *
1904 * This test sets up an AP MLD and two non-AP MLDs having a variable number of links.
1905 * The RF channels to set each link to are provided as input parameters through the test
1906 * case constructor, along with the identifiers (starting at 0) of the links that cannot
1907 * switch PHY band (if any). This test aims at veryfing the successful transmission of both
1908 * unicast QoS data frames (from one station to another, from one station to the AP, from
1909 * the AP to the station) and broadcast QoS data frames (from the AP or from one station).
1910 * In the scenarios in which the AP forwards frames (i.e., from one station to another and
1911 * from one station to broadcast) the client application generates only 4 packets, in order
1912 * to limit the probability of collisions. In the other scenarios, 8 packets are generated.
1913 * When BlockAck agreements are enabled, the maximum A-MSDU size is set such that two
1914 * packets can be aggregated in an A-MSDU. The MPDU with sequence number equal to 1 is
1915 * corrupted (once, by using a post reception error model) to test its successful
1916 * re-transmission, unless the traffic scenario is from the AP to broadcast (broadcast frames
1917 * are not retransmitted) or is a scenario where the AP forwards frame (to limit the
1918 * probability of collisions).
1919 *
1920 * When BlockAck agreements are enabled, we also corrupt a BlockAck frame, so as to simulate
1921 * the case of BlockAck timeout. Both the case where a BlockAckReq is sent and the case where
1922 * data frame are retransmitted are tested. Finally, when BlockAck agreements are enabled, we
1923 * also enable the concurrent transmission of data frames over two links and check that at
1924 * least one MPDU is concurrently transmitted over two links.
1925 */
1927{
1928 public:
1929 /**
1930 * Constructor
1931 *
1932 * \param baseParams common configuration parameters
1933 * \param trafficPattern the pattern of traffic to generate
1934 * \param baEnabled whether BA agreement is enabled or disabled
1935 * \param useBarAfterMissedBa whether a BAR or Data frames are sent after missed BlockAck
1936 * \param nMaxInflight the max number of links on which an MPDU can be simultaneously inflight
1937 * (unused if Block Ack agreements are not established)
1938 */
1939 MultiLinkTxTest(const BaseParams& baseParams,
1940 WifiTrafficPattern trafficPattern,
1941 WifiBaEnabled baEnabled,
1942 WifiUseBarAfterMissedBa useBarAfterMissedBa,
1943 uint8_t nMaxInflight);
1944 ~MultiLinkTxTest() override = default;
1945
1946 protected:
1947 /**
1948 * Check the content of a received BlockAck frame when the max number of links on which
1949 * an MPDU can be inflight is one.
1950 *
1951 * \param psdu the PSDU containing the BlockAck
1952 * \param txVector the TXVECTOR used to transmit the BlockAck
1953 * \param linkId the ID of the link on which the BlockAck was transmitted
1954 */
1955 void CheckBlockAck(Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId);
1956
1957 void Transmit(Ptr<WifiMac> mac,
1958 uint8_t phyId,
1959 WifiConstPsduMap psduMap,
1960 WifiTxVector txVector,
1961 double txPowerW) override;
1962 void DoSetup() override;
1963 void DoRun() override;
1964
1965 private:
1966 void StartTraffic() override;
1967
1968 /// Receiver address-indexed map of list error models
1969 using RxErrorModelMap = std::unordered_map<Mac48Address, Ptr<ListErrorModel>, WifiAddressHash>;
1970
1971 RxErrorModelMap m_errorModels; ///< error rate models to corrupt packets
1972 std::list<uint64_t> m_uidList; ///< list of UIDs of packets to corrupt
1973 bool m_dataCorrupted{false}; ///< whether second data frame has been already corrupted
1974 WifiTrafficPattern m_trafficPattern; ///< the pattern of traffic to generate
1975 bool m_baEnabled; ///< whether BA agreement is enabled or disabled
1976 bool m_useBarAfterMissedBa; ///< whether to send BAR after missed BlockAck
1977 std::size_t m_nMaxInflight; ///< max number of links on which an MPDU can be inflight
1978 std::size_t m_nPackets; ///< number of application packets to generate
1979 std::size_t m_blockAckCount{0}; ///< transmitted BlockAck counter
1980 std::size_t m_blockAckReqCount{0}; ///< transmitted BlockAckReq counter
1981 std::map<uint16_t, std::size_t> m_inflightCount; ///< seqNo-indexed max number of simultaneous
1982 ///< transmissions of a data frame
1983 Ptr<WifiMac> m_sourceMac; ///< MAC of the node sending application packets
1984};
1985
1987 WifiTrafficPattern trafficPattern,
1988 WifiBaEnabled baEnabled,
1989 WifiUseBarAfterMissedBa useBarAfterMissedBa,
1990 uint8_t nMaxInflight)
1992 std::string("Check data transmission between MLDs ") +
1993 (baEnabled == WifiBaEnabled::YES
1994 ? (useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES
1995 ? "with BA agreement, send BAR after BlockAck timeout"
1996 : "with BA agreement, send Data frames after BlockAck timeout")
1997 : "without BA agreement") +
1998 " (Traffic pattern: " + std::to_string(static_cast<uint8_t>(trafficPattern)) +
1999 (baEnabled == WifiBaEnabled::YES ? ", nMaxInflight=" + std::to_string(nMaxInflight)
2000 : "") +
2001 ")",
2002 2,
2003 baseParams),
2004 m_trafficPattern(trafficPattern),
2005 m_baEnabled(baEnabled == WifiBaEnabled::YES),
2006 m_useBarAfterMissedBa(useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES),
2007 m_nMaxInflight(nMaxInflight),
2008 m_nPackets(trafficPattern == WifiTrafficPattern::STA_TO_BCAST ||
2009 trafficPattern == WifiTrafficPattern::STA_TO_STA
2010 ? 4
2011 : 8)
2012{
2013}
2014
2015void
2017 uint8_t phyId,
2018 WifiConstPsduMap psduMap,
2019 WifiTxVector txVector,
2020 double txPowerW)
2021{
2022 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2023 auto linkId = m_txPsdus.back().linkId;
2024
2025 auto psdu = psduMap.begin()->second;
2026
2027 switch (psdu->GetHeader(0).GetType())
2028 {
2030 // a management frame is a DL frame if TA equals BSSID
2031 CheckAddresses(psdu,
2032 psdu->GetHeader(0).GetAddr2() == psdu->GetHeader(0).GetAddr3() ? DL : UL);
2033 if (!m_baEnabled)
2034 {
2035 // corrupt all management action frames (ADDBA Request frames) to prevent
2036 // the establishment of a BA agreement
2037 m_uidList.push_front(psdu->GetPacket()->GetUid());
2038 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2039 NS_LOG_INFO("CORRUPTED");
2040 }
2041 break;
2042 case WIFI_MAC_QOSDATA:
2043 CheckAddresses(psdu);
2044
2045 for (const auto& mpdu : *psdu)
2046 {
2047 // determine the max number of simultaneous transmissions for this MPDU
2048 // (only if sent by the traffic source and this is not a broadcast frame)
2049 if (m_baEnabled && m_sourceMac->GetLinkIds().count(linkId) == 1 &&
2050 m_sourceMac->GetFrameExchangeManager(linkId)->GetAddress() ==
2051 mpdu->GetHeader().GetAddr2() &&
2052 !mpdu->GetHeader().GetAddr1().IsGroup())
2053 {
2054 auto seqNo = mpdu->GetHeader().GetSequenceNumber();
2055 auto [it, success] =
2056 m_inflightCount.insert({seqNo, mpdu->GetInFlightLinkIds().size()});
2057 if (!success)
2058 {
2059 it->second = std::max(it->second, mpdu->GetInFlightLinkIds().size());
2060 }
2061 }
2062 }
2063 for (std::size_t i = 0; i < psdu->GetNMpdus(); i++)
2064 {
2065 // corrupt QoS data frame with sequence number equal to 1 (only once) if we are
2066 // not in the AP to broadcast traffic pattern (broadcast frames are not retransmitted)
2067 // nor in the STA to broadcast or STA to STA traffic patterns (retransmissions from
2068 // STA 1 could collide with frames forwarded by the AP)
2069 if (psdu->GetHeader(i).GetSequenceNumber() != 1 ||
2070 m_trafficPattern == WifiTrafficPattern::AP_TO_BCAST ||
2071 m_trafficPattern == WifiTrafficPattern::STA_TO_BCAST ||
2072 m_trafficPattern == WifiTrafficPattern::STA_TO_STA)
2073 {
2074 continue;
2075 }
2076 auto uid = psdu->GetPayload(i)->GetUid();
2077 if (!m_dataCorrupted)
2078 {
2079 m_uidList.push_front(uid);
2080 m_dataCorrupted = true;
2081 NS_LOG_INFO("CORRUPTED");
2082 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2083 }
2084 else
2085 {
2086 // do not corrupt the QoS data frame anymore
2087 if (auto it = std::find(m_uidList.cbegin(), m_uidList.cend(), uid);
2088 it != m_uidList.cend())
2089 {
2090 m_uidList.erase(it);
2091 }
2092 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2093 }
2094 break;
2095 }
2096 break;
2097 case WIFI_MAC_CTL_BACKRESP: {
2098 // ignore BlockAck frames not addressed to the source of the application packets
2099 if (!m_sourceMac->GetLinkIdByAddress(psdu->GetHeader(0).GetAddr1()))
2100 {
2101 break;
2102 }
2103 if (m_nMaxInflight > 1)
2104 {
2105 // we do not check the content of BlockAck when m_nMaxInflight is greater than 1
2106 break;
2107 }
2108 CheckBlockAck(psdu, txVector, linkId);
2110 if (m_blockAckCount == 2)
2111 {
2112 // corrupt the second BlockAck frame to simulate a missed BlockAck
2113 m_uidList.push_front(psdu->GetPacket()->GetUid());
2114 NS_LOG_INFO("CORRUPTED");
2115 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2116 }
2117 break;
2119 // ignore BlockAckReq frames not transmitted by the source of the application packets
2120 if (m_sourceMac->GetLinkIdByAddress(psdu->GetHeader(0).GetAddr2()))
2121 {
2123 }
2124 break;
2125 }
2126 default:;
2127 }
2128}
2129
2130void
2132 const WifiTxVector& txVector,
2133 uint8_t linkId)
2134{
2135 NS_TEST_ASSERT_MSG_EQ(m_baEnabled, true, "No BlockAck expected without BA agreement");
2136 NS_TEST_ASSERT_MSG_EQ((m_trafficPattern != WifiTrafficPattern::AP_TO_BCAST),
2137 true,
2138 "No BlockAck expected in AP to broadcast traffic pattern");
2139
2140 /*
2141 * ┌───────┬───────X ┌───────┐
2142 * link 0 │ 0 │ 1 │ │ 1 │
2143 * ───────┴───────┴───────┴┬──┬────┴───────┴┬───┬────────────────────────
2144 * │BA│ │ACK│
2145 * └──┘ └───┘
2146 * ┌───────┬───────┐ ┌───────┬───────┐
2147 * link 1 │ 2 │ 3 │ │ 2 │ 3 │
2148 * ────────────────────┴───────┴───────┴┬──X───┴───────┴───────┴┬──┬─────
2149 * │BA│ │BA│
2150 * └──┘ └──┘
2151 */
2152 auto mpdu = *psdu->begin();
2153 CtrlBAckResponseHeader blockAck;
2154 mpdu->GetPacket()->PeekHeader(blockAck);
2155 bool isMpdu1corrupted = (m_trafficPattern == WifiTrafficPattern::STA_TO_AP ||
2156 m_trafficPattern == WifiTrafficPattern::AP_TO_STA);
2157
2158 switch (m_blockAckCount)
2159 {
2160 case 0: // first BlockAck frame (all traffic patterns)
2162 true,
2163 "MPDU 0 expected to be successfully received");
2165 blockAck.IsPacketReceived(1),
2166 !isMpdu1corrupted,
2167 "MPDU 1 expected to be received only in STA_TO_STA/STA_TO_BCAST scenarios");
2168 // if there are at least two links setup, we expect all MPDUs to be inflight
2169 // (on distinct links)
2170 if (m_staMacs[0]->GetSetupLinkIds().size() > 1)
2171 {
2172 auto queue = m_sourceMac->GetTxopQueue(AC_BE);
2173 auto rcvMac = m_sourceMac == m_staMacs[0] ? StaticCast<WifiMac>(m_apMac)
2174 : StaticCast<WifiMac>(m_staMacs[1]);
2175 auto item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress());
2176 std::size_t nQueuedPkt = 0;
2177 auto delay = WifiPhy::CalculateTxDuration(psdu,
2178 txVector,
2179 rcvMac->GetWifiPhy(linkId)->GetPhyBand()) +
2180 MicroSeconds(1); // to account for propagation delay
2181
2182 while (item)
2183 {
2184 auto seqNo = item->GetHeader().GetSequenceNumber();
2185 NS_TEST_EXPECT_MSG_EQ(item->IsInFlight(),
2186 true,
2187 "MPDU with seqNo=" << seqNo << " is not in flight");
2188 auto linkIds = item->GetInFlightLinkIds();
2189 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
2190 1,
2191 "MPDU with seqNo=" << seqNo
2192 << " is in flight on multiple links");
2193 // The first two MPDUs are in flight on the same link on which the BlockAck
2194 // is sent. The other two MPDUs (only for AP to STA/STA to AP scenarios) are
2195 // in flight on a different link.
2196 auto srcLinkId = m_sourceMac->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1());
2197 NS_TEST_ASSERT_MSG_EQ(srcLinkId.has_value(),
2198 true,
2199 "Addr1 of BlockAck is not an originator's link address");
2200 NS_TEST_EXPECT_MSG_EQ((*linkIds.begin() == *srcLinkId),
2201 (seqNo <= 1),
2202 "MPDU with seqNo=" << seqNo
2203 << " in flight on unexpected link");
2204 // check the Retry subfield and whether this MPDU is still queued
2205 // after the originator has processed this BlockAck
2206
2207 // MPDUs acknowledged via this BlockAck are no longer queued
2208 bool isQueued = (seqNo > (isMpdu1corrupted ? 0 : 1));
2209 // The Retry subfield is set if the MPDU has not been acknowledged (i.e., it
2210 // is still queued) and has been transmitted on the same link as the BlockAck
2211 // (i.e., its sequence number is less than or equal to 1)
2212 bool isRetry = isQueued && seqNo <= 1;
2213
2214 Simulator::Schedule(delay, [this, item, isQueued, isRetry]() {
2215 NS_TEST_EXPECT_MSG_EQ(item->IsQueued(),
2216 isQueued,
2217 "MPDU with seqNo="
2218 << item->GetHeader().GetSequenceNumber() << " should "
2219 << (isQueued ? "" : "not") << " be queued");
2221 item->GetHeader().IsRetry(),
2222 isRetry,
2223 "Unexpected value for the Retry subfield of the MPDU with seqNo="
2224 << item->GetHeader().GetSequenceNumber());
2225 });
2226
2227 nQueuedPkt++;
2228 item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress(), item);
2229 }
2230 // Each MPDU contains an A-MSDU consisting of two MSDUs
2231 NS_TEST_EXPECT_MSG_EQ(nQueuedPkt, m_nPackets / 2, "Unexpected number of queued MPDUs");
2232 }
2233 break;
2234 case 1: // second BlockAck frame (STA to AP and AP to STA traffic patterns only)
2235 case 2: // third BlockAck frame (STA to AP and AP to STA traffic patterns only)
2236 NS_TEST_EXPECT_MSG_EQ((m_trafficPattern == WifiTrafficPattern::AP_TO_STA ||
2237 m_trafficPattern == WifiTrafficPattern::STA_TO_AP),
2238 true,
2239 "Did not expect to receive a second BlockAck");
2240 // the second BlockAck is corrupted, but the data frames have been received successfully
2241 std::pair<uint16_t, uint16_t> seqNos;
2242 // if multiple links were setup, the transmission of the second A-MPDU started before
2243 // the end of the first one, so the second A-MPDU includes MPDUs with sequence numbers
2244 // 2 and 3. Otherwise, MPDU with sequence number 1 is retransmitted along with the MPDU
2245 // with sequence number 2.
2246 if (m_staMacs[0]->GetSetupLinkIds().size() > 1)
2247 {
2248 seqNos = {2, 3};
2249 }
2250 else
2251 {
2252 seqNos = {1, 2};
2253 }
2254 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(seqNos.first),
2255 true,
2256 "MPDU " << seqNos.first << " expected to be successfully received");
2257 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(seqNos.second),
2258 true,
2259 "MPDU " << seqNos.second << " expected to be successfully received");
2260 break;
2261 }
2262}
2263
2264void
2266{
2268
2269 if (m_baEnabled)
2270 {
2271 // Enable A-MSDU aggregation. Max A-MSDU size is set such that two MSDUs can be aggregated
2272 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
2273 {
2274 mac->SetAttribute("BE_MaxAmsduSize", UintegerValue(2100));
2275 mac->GetQosTxop(AC_BE)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2277 mac->GetQosTxop(AC_BE)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2278 }
2279 }
2280
2281 // install post reception error model on all devices
2282 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
2283 {
2284 auto errorModel = CreateObject<ListErrorModel>();
2285 m_errorModels[m_apMac->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2286 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2287 }
2288 for (std::size_t i : {0, 1})
2289 {
2290 for (const auto linkId : m_staMacs[i]->GetLinkIds())
2291 {
2292 auto errorModel = CreateObject<ListErrorModel>();
2293 m_errorModels[m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2294 m_staMacs[i]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2295 }
2296 }
2297}
2298
2299void
2301{
2302 Address destAddr;
2303
2304 switch (m_trafficPattern)
2305 {
2306 case WifiTrafficPattern::STA_TO_STA:
2308 destAddr = m_staMacs[1]->GetDevice()->GetAddress();
2309 break;
2310 case WifiTrafficPattern::STA_TO_AP:
2312 destAddr = m_apMac->GetDevice()->GetAddress();
2313 break;
2314 case WifiTrafficPattern::AP_TO_STA:
2316 destAddr = m_staMacs[1]->GetDevice()->GetAddress();
2317 break;
2318 case WifiTrafficPattern::AP_TO_BCAST:
2320 destAddr = Mac48Address::GetBroadcast();
2321 break;
2322 case WifiTrafficPattern::STA_TO_BCAST:
2324 destAddr = Mac48Address::GetBroadcast();
2325 break;
2326 }
2327
2328 PacketSocketAddress sockAddr;
2330 sockAddr.SetPhysicalAddress(destAddr);
2331 sockAddr.SetProtocol(1);
2332
2333 // install first client application generating at most 4 packets
2335 GetApplication(sockAddr, std::min<std::size_t>(m_nPackets, 4), 1000));
2336
2337 if (m_nPackets > 4)
2338 {
2339 // install a second client application generating the remaining packets and
2340 // starting during transmission of first A-MPDU, if multiple links are setup
2342 GetApplication(sockAddr, m_nPackets - 4, 1000, MilliSeconds(4)));
2343 }
2344
2346}
2347
2348void
2350{
2352
2353 // Expected number of packets received by each node (AP, STA 0, STA 1) at application layer
2354 std::array<std::size_t, 3> expectedRxPkts{};
2355
2356 switch (m_trafficPattern)
2357 {
2358 case WifiTrafficPattern::STA_TO_STA:
2359 case WifiTrafficPattern::AP_TO_STA:
2360 // only STA 1 receives the m_nPackets packets that have been transmitted
2361 expectedRxPkts[2] = m_nPackets;
2362 break;
2363 case WifiTrafficPattern::STA_TO_AP:
2364 // only the AP receives the m_nPackets packets that have been transmitted
2365 expectedRxPkts[0] = m_nPackets;
2366 break;
2367 case WifiTrafficPattern::AP_TO_BCAST:
2368 // the AP replicates the broadcast frames on all the links, hence each station
2369 // receives the m_nPackets packets N times, where N is the number of setup link
2370 expectedRxPkts[1] = m_nPackets * m_staMacs[0]->GetSetupLinkIds().size();
2371 expectedRxPkts[2] = m_nPackets * m_staMacs[1]->GetSetupLinkIds().size();
2372 break;
2373 case WifiTrafficPattern::STA_TO_BCAST:
2374 // the AP receives the m_nPackets packets and then replicates them on all the links,
2375 // hence STA 1 receives m_nPackets packets N times, where N is the number of setup link
2376 expectedRxPkts[0] = m_nPackets;
2377 expectedRxPkts[2] = m_nPackets * m_staMacs[1]->GetSetupLinkIds().size();
2378 break;
2379 }
2380
2382 +expectedRxPkts[0],
2383 "Unexpected number of packets received by the AP");
2385 +expectedRxPkts[1],
2386 "Unexpected number of packets received by STA 0");
2388 +expectedRxPkts[2],
2389 "Unexpected number of packets received by STA 1");
2390
2391 // check that the expected number of BlockAck frames are transmitted
2392 if (m_baEnabled && m_nMaxInflight == 1)
2393 {
2394 std::size_t expectedBaCount = 0;
2395 std::size_t expectedBarCount = 0;
2396
2397 switch (m_trafficPattern)
2398 {
2399 case WifiTrafficPattern::STA_TO_AP:
2400 case WifiTrafficPattern::AP_TO_STA:
2401 // two A-MPDUs are transmitted and one BlockAck is corrupted
2402 expectedBaCount = 3;
2403 // one BlockAckReq is sent if m_useBarAfterMissedBa is true
2404 expectedBarCount = m_useBarAfterMissedBa ? 1 : 0;
2405 break;
2406 case WifiTrafficPattern::STA_TO_STA:
2407 case WifiTrafficPattern::STA_TO_BCAST:
2408 // only one A-MPDU is transmitted and the BlockAck is not corrupted
2409 expectedBaCount = 1;
2410 break;
2411 default:;
2412 }
2414 expectedBaCount,
2415 "Unexpected number of BlockAck frames");
2417 expectedBarCount,
2418 "Unexpected number of BlockAckReq frames");
2419 }
2420
2421 // check that setting the QosTxop::NMaxInflights attribute has the expected effect.
2422 // We do not support sending an MPDU multiple times concurrently without Block Ack
2423 // agreement. Also, broadcast frames are already duplicated and sent on all links.
2424 if (m_baEnabled && m_trafficPattern != WifiTrafficPattern::AP_TO_BCAST)
2425 {
2427 m_inflightCount.size(),
2428 m_nPackets / 2,
2429 "Did not collect number of simultaneous transmissions for all data frames");
2430
2431 auto nMaxInflight = std::min(m_nMaxInflight, m_staMacs[0]->GetSetupLinkIds().size());
2432 std::size_t maxCount = 0;
2433 for (const auto& [seqNo, count] : m_inflightCount)
2434 {
2436 count,
2437 nMaxInflight,
2438 "MPDU with seqNo=" << seqNo
2439 << " transmitted simultaneously more times than allowed");
2440 maxCount = std::max(maxCount, count);
2441 }
2442
2444 maxCount,
2445 nMaxInflight,
2446 "Expected that at least one data frame was transmitted simultaneously a number of "
2447 "times equal to the NMaxInflights attribute");
2448 }
2449
2451}
2452
2453/**
2454 * Tested MU traffic patterns.
2455 */
2456enum class WifiMuTrafficPattern : uint8_t
2457{
2461 UL_MU
2462};
2463
2464/**
2465 * \ingroup wifi-test
2466 * \ingroup tests
2467 *
2468 * \brief Test data transmission between MLDs using OFDMA MU transmissions
2469 *
2470 * This test sets up an AP MLD and two non-AP MLDs having a variable number of links.
2471 * The RF channels to set each link to are provided as input parameters through the test
2472 * case constructor, along with the identifiers (starting at 0) of the links that cannot
2473 * switch PHY band (if any). This test aims at veryfing the successful transmission of both
2474 * DL MU and UL MU frames. In the DL MU scenarios, the client applications installed on the
2475 * AP generate 8 packets addressed to each of the stations (plus 3 packets to trigger the
2476 * establishment of BlockAck agreements). In the UL MU scenario, client applications
2477 * installed on the stations generate 4 packets each (plus 3 packets to trigger the
2478 * establishment of BlockAck agreements).
2479 *
2480 * The maximum A-MSDU size is set such that two packets can be aggregated in an A-MSDU.
2481 * The MPDU with sequence number equal to 3 is corrupted (by using a post reception error
2482 * model) once and for a single station, to test its successful re-transmission.
2483 *
2484 * Also, we enable the concurrent transmission of data frames over two links and check that at
2485 * least one MPDU is concurrently transmitted over two links.
2486 */
2488{
2489 public:
2490 /**
2491 * Constructor
2492 *
2493 * \param baseParams common configuration parameters
2494 * \param muTrafficPattern the pattern of traffic to generate
2495 * \param useBarAfterMissedBa whether a BAR or Data frames are sent after missed BlockAck
2496 * \param nMaxInflight the max number of links on which an MPDU can be simultaneously inflight
2497 * (unused if Block Ack agreements are not established)
2498 */
2499 MultiLinkMuTxTest(const BaseParams& baseParams,
2500 WifiMuTrafficPattern muTrafficPattern,
2501 WifiUseBarAfterMissedBa useBarAfterMissedBa,
2502 uint8_t nMaxInflight);
2503 ~MultiLinkMuTxTest() override = default;
2504
2505 protected:
2506 /**
2507 * Check the content of a received BlockAck frame when the max number of links on which
2508 * an MPDU can be inflight is one.
2509 *
2510 * \param psdu the PSDU containing the BlockAck
2511 * \param txVector the TXVECTOR used to transmit the BlockAck
2512 * \param linkId the ID of the link on which the BlockAck was transmitted
2513 */
2514 void CheckBlockAck(Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId);
2515
2516 void Transmit(Ptr<WifiMac> mac,
2517 uint8_t phyId,
2518 WifiConstPsduMap psduMap,
2519 WifiTxVector txVector,
2520 double txPowerW) override;
2521 void DoSetup() override;
2522 void DoRun() override;
2523
2524 private:
2525 void StartTraffic() override;
2526
2527 /// Receiver address-indexed map of list error models
2528 using RxErrorModelMap = std::unordered_map<Mac48Address, Ptr<ListErrorModel>, WifiAddressHash>;
2529
2530 /// A pair of a MAC address (the address of the receiver for DL frames and the address of
2531 /// the sender for UL frames) and a sequence number identifying a transmitted QoS data frame
2532 using AddrSeqNoPair = std::pair<Mac48Address, uint16_t>;
2533
2534 RxErrorModelMap m_errorModels; ///< error rate models to corrupt packets
2535 std::list<uint64_t> m_uidList; ///< list of UIDs of packets to corrupt
2536 std::optional<Mac48Address> m_dataCorruptedSta; ///< MAC address of the station that received
2537 ///< MPDU with SeqNo=2 corrupted
2538 bool m_waitFirstTf{true}; ///< whether we are waiting for the first Basic Trigger Frame
2539 WifiMuTrafficPattern m_muTrafficPattern; ///< the pattern of traffic to generate
2540 bool m_useBarAfterMissedBa; ///< whether to send BAR after missed BlockAck
2541 std::size_t m_nMaxInflight; ///< max number of links on which an MPDU can be inflight
2542 std::vector<PacketSocketAddress> m_sockets; ///< packet socket addresses for STAs
2543 std::size_t m_nPackets; ///< number of application packets to generate
2544 std::size_t m_blockAckCount{0}; ///< transmitted BlockAck counter
2545 std::size_t m_tfCount{0}; ///< transmitted Trigger Frame counter
2546 // std::size_t m_blockAckReqCount{0}; ///< transmitted BlockAckReq counter
2547 std::map<AddrSeqNoPair, std::size_t> m_inflightCount; ///< max number of simultaneous
2548 ///< transmissions of each data frame
2549 Ptr<WifiMac> m_sourceMac; ///< MAC of the node sending application packets
2550};
2551
2553 WifiMuTrafficPattern muTrafficPattern,
2554 WifiUseBarAfterMissedBa useBarAfterMissedBa,
2555 uint8_t nMaxInflight)
2557 std::string("Check MU data transmission between MLDs ") +
2558 (useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES
2559 ? "(send BAR after BlockAck timeout,"
2560 : "(send Data frames after BlockAck timeout,") +
2561 " MU Traffic pattern: " + std::to_string(static_cast<uint8_t>(muTrafficPattern)) +
2562 ", nMaxInflight=" + std::to_string(nMaxInflight) + ")",
2563 2,
2564 baseParams),
2565 m_muTrafficPattern(muTrafficPattern),
2566 m_useBarAfterMissedBa(useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES),
2567 m_nMaxInflight(nMaxInflight),
2568 m_sockets(m_nStations),
2569 m_nPackets(muTrafficPattern == WifiMuTrafficPattern::UL_MU ? 4 : 8)
2570{
2571}
2572
2573void
2575 uint8_t phyId,
2576 WifiConstPsduMap psduMap,
2577 WifiTxVector txVector,
2578 double txPowerW)
2579{
2580 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2581 auto linkId = m_txPsdus.back().linkId;
2582
2583 CtrlTriggerHeader trigger;
2584
2585 for (const auto& [staId, psdu] : psduMap)
2586 {
2587 switch (psdu->GetHeader(0).GetType())
2588 {
2589 case WIFI_MAC_QOSDATA:
2590 CheckAddresses(psdu);
2591 if (psdu->GetHeader(0).HasData())
2592 {
2593 bool isDl = psdu->GetHeader(0).IsFromDs();
2594 auto linkAddress =
2595 isDl ? psdu->GetHeader(0).GetAddr1() : psdu->GetHeader(0).GetAddr2();
2596 auto address = m_apMac->GetMldAddress(linkAddress).value_or(linkAddress);
2597
2598 for (const auto& mpdu : *psdu)
2599 {
2600 // determine the max number of simultaneous transmissions for this MPDU
2601 auto seqNo = mpdu->GetHeader().GetSequenceNumber();
2602 auto [it, success] = m_inflightCount.insert(
2603 {{address, seqNo}, mpdu->GetInFlightLinkIds().size()});
2604 if (!success)
2605 {
2606 it->second = std::max(it->second, mpdu->GetInFlightLinkIds().size());
2607 }
2608 }
2609 for (std::size_t i = 0; i < psdu->GetNMpdus(); i++)
2610 {
2611 // MPDUs with seqNo=2 are always transmitted in an MU PPDU
2612 if (psdu->GetHeader(i).GetSequenceNumber() == 2)
2613 {
2614 if (m_muTrafficPattern == WifiMuTrafficPattern::UL_MU)
2615 {
2616 NS_TEST_EXPECT_MSG_EQ(txVector.IsUlMu(),
2617 true,
2618 "MPDU " << **std::next(psdu->begin(), i)
2619 << " not transmitted in a TB PPDU");
2620 }
2621 else
2622 {
2623 NS_TEST_EXPECT_MSG_EQ(txVector.GetHeMuUserInfoMap().size(),
2624 2,
2625 "MPDU " << **std::next(psdu->begin(), i)
2626 << " not transmitted in a DL MU PPDU");
2627 }
2628 }
2629 // corrupt QoS data frame with sequence number equal to 3 (only once)
2630 if (psdu->GetHeader(i).GetSequenceNumber() != 3)
2631 {
2632 continue;
2633 }
2634 auto uid = psdu->GetPayload(i)->GetUid();
2635 if (!m_dataCorruptedSta)
2636 {
2637 m_uidList.push_front(uid);
2638 m_dataCorruptedSta = isDl ? psdu->GetAddr1() : psdu->GetAddr2();
2639 NS_LOG_INFO("CORRUPTED");
2640 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2641 }
2642 else if ((isDl && m_dataCorruptedSta == psdu->GetAddr1()) ||
2643 (!isDl && m_dataCorruptedSta == psdu->GetAddr2()))
2644 {
2645 // do not corrupt the QoS data frame anymore
2646 if (auto it = std::find(m_uidList.cbegin(), m_uidList.cend(), uid);
2647 it != m_uidList.cend())
2648 {
2649 m_uidList.erase(it);
2650 }
2651 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2652 }
2653 break;
2654 }
2655 }
2656 break;
2658 if (m_nMaxInflight > 1)
2659 {
2660 // we do not check the content of BlockAck when m_nMaxInflight is greater than 1
2661 break;
2662 }
2663 CheckBlockAck(psdu, txVector, linkId);
2665 // to simulate a missed BlockAck, corrupt the fifth BlockAck frame (the first
2666 // two BlockAck frames are sent to acknowledge the QoS data frames that triggered
2667 // the establishment of Block Ack agreements)
2668 if (m_blockAckCount == 5)
2669 {
2670 // corrupt the third BlockAck frame to simulate a missed BlockAck
2671 m_uidList.push_front(psdu->GetPacket()->GetUid());
2672 NS_LOG_INFO("CORRUPTED");
2673 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2674 }
2675 break;
2677 psdu->GetPayload(0)->PeekHeader(trigger);
2678 // the MU scheduler requests channel access on all links but we have to perform the
2679 // following actions only once (hence why we only consider TF transmitted on link 0)
2680 if (trigger.IsBasic() && m_waitFirstTf)
2681 {
2682 m_waitFirstTf = false;
2683 // the AP is starting the transmission of the Basic Trigger frame, so generate
2684 // the configured number of packets at STAs, which are sent in TB PPDUs, when
2685 // transmission of the Trigger Frame ends
2686 auto band = mac->GetWifiPhy(linkId)->GetPhyBand();
2687 Time txDuration = WifiPhy::CalculateTxDuration(psduMap, txVector, band);
2688 for (uint8_t i = 0; i < m_nStations; i++)
2689 {
2690 m_staMacs[i]->GetDevice()->GetNode()->AddApplication(
2691 GetApplication(m_sockets[i], m_nPackets, 450, txDuration, i * 4));
2692 }
2693 }
2694 if (++m_tfCount == m_staMacs[0]->GetSetupLinkIds().size())
2695 {
2696 // a TF has been sent on all the setup links, we can now disable UL OFDMA
2697 auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
2698 NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "Expected an aggregated MU scheduler");
2699 muScheduler->SetAttribute("EnableUlOfdma", BooleanValue(false));
2700 }
2701 break;
2702 default:;
2703 }
2704 }
2705}
2706
2707void
2709 const WifiTxVector& txVector,
2710 uint8_t linkId)
2711{
2712 /*
2713 * Example sequence with DL_MU_BAR_BA_SEQUENCE
2714 * ┌───────┬───────X
2715 * (To:1) │ 2 │ 3 │
2716 * ├───────┼───────┤ ┌───┐ ┌───────┐
2717 * [link 0] (To:0) │ 2 │ 3 │ │BAR│ (To:1) │ 3 │
2718 * ────────────────┴───────┴───────┴┬──┼───┼──┬──────────┴───────┴┬───┬────────
2719 * │BA│ │BA│ │ACK│
2720 * └──┘ └──┘ └───┘
2721 * ┌───────┬───────┐
2722 * (To:1) │ 4 │ 5 │
2723 * ├───────┼───────┤ ┌───┐ ┌───┐
2724 * [link 1] (To:0) │ 4 │ 5 │ │BAR│ │BAR│
2725 * ────────────────────────────┴───────┴───────┴┬──X────┴───┴┬──┼───┼──┬───────
2726 * │BA│ │BA│ │BA│
2727 * └──┘ └──┘ └──┘
2728 *
2729 * Example sequence with UL_MU
2730 *
2731 * ┌──┐ ┌────┐ ┌───┐
2732 * [link 0] │TF│ │M-BA│ │ACK│
2733 * ─────────┴──┴──┬───────┬───────┬──┴────┴────────────┬───────┬─┴───┴─────────
2734 * (From:0) │ 2 │ 3 │ (From:1) │ 3 │
2735 * ├───────┼───────┤ └───────┘
2736 * (From:1) │ 2 │ 3 │
2737 * └───────┴───────X
2738 * ┌──┐
2739 * [link 1] │TF│
2740 * ─────────┴──┴──┬───────────────┬────────────────────────────────────────────
2741 * (From:0) │ QoS Null │
2742 * ├───────────────┤
2743 * (From:1) │ QoS Null │
2744 * └───────────────┘
2745 */
2746 auto mpdu = *psdu->begin();
2747 CtrlBAckResponseHeader blockAck;
2748 mpdu->GetPacket()->PeekHeader(blockAck);
2749 bool isMpdu3corrupted;
2750
2751 switch (m_blockAckCount)
2752 {
2753 case 0:
2754 case 1: // Ignore the first two BlockAck frames that acknowledged frames sent to establish BA
2755 break;
2756 case 2:
2757 if (m_muTrafficPattern == WifiMuTrafficPattern::UL_MU)
2758 {
2759 NS_TEST_EXPECT_MSG_EQ(blockAck.IsMultiSta(), true, "Expected a Multi-STA BlockAck");
2760 for (uint8_t i = 0; i < m_nStations; i++)
2761 {
2762 auto indices = blockAck.FindPerAidTidInfoWithAid(m_staMacs[i]->GetAssociationId());
2763 NS_TEST_ASSERT_MSG_EQ(indices.size(), 1, "Expected one Per AID TID Info per STA");
2764 auto index = indices.front();
2766 true,
2767 "Expected that a QoS data frame was corrupted");
2768 isMpdu3corrupted =
2769 m_staMacs[i]->GetLinkIdByAddress(*m_dataCorruptedSta).has_value();
2770 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(2, index),
2771 true,
2772 "MPDU 2 expected to be successfully received");
2773 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(3, index),
2774 !isMpdu3corrupted,
2775 "Unexpected reception status for MPDU 3");
2776 }
2777
2778 break;
2779 }
2780 case 3:
2781 // BlockAck frames in response to the first DL MU PPDU
2782 isMpdu3corrupted = (mpdu->GetHeader().GetAddr2() == m_dataCorruptedSta);
2784 true,
2785 "MPDU 2 expected to be successfully received");
2787 !isMpdu3corrupted,
2788 "Unexpected reception status for MPDU 3");
2789 // in case of DL MU, if there are at least two links setup, we expect all MPDUs to
2790 // be inflight (on distinct links)
2791 if (m_muTrafficPattern != WifiMuTrafficPattern::UL_MU &&
2792 m_staMacs[0]->GetSetupLinkIds().size() > 1)
2793 {
2794 auto queue = m_apMac->GetTxopQueue(AC_BE);
2795 Ptr<StaWifiMac> rcvMac;
2796 if (m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress() ==
2797 mpdu->GetHeader().GetAddr2())
2798 {
2799 rcvMac = m_staMacs[0];
2800 }
2801 else if (m_staMacs[1]->GetFrameExchangeManager(linkId)->GetAddress() ==
2802 mpdu->GetHeader().GetAddr2())
2803 {
2804 rcvMac = m_staMacs[1];
2805 }
2806 else
2807 {
2808 NS_ABORT_MSG("BlockAck frame not sent by a station in DL scenario");
2809 }
2810 auto item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress());
2811 std::size_t nQueuedPkt = 0;
2812 auto delay = WifiPhy::CalculateTxDuration(psdu,
2813 txVector,
2814 rcvMac->GetWifiPhy(linkId)->GetPhyBand()) +
2815 MicroSeconds(1); // to account for propagation delay
2816
2817 while (item)
2818 {
2819 auto seqNo = item->GetHeader().GetSequenceNumber();
2820 NS_TEST_EXPECT_MSG_EQ(item->IsInFlight(),
2821 true,
2822 "MPDU with seqNo=" << seqNo << " is not in flight");
2823 auto linkIds = item->GetInFlightLinkIds();
2824 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
2825 1,
2826 "MPDU with seqNo=" << seqNo
2827 << " is in flight on multiple links");
2828 // The first two MPDUs are in flight on the same link on which the BlockAck
2829 // is sent. The other two MPDUs (only for AP to STA/STA to AP scenarios) are
2830 // in flight on a different link.
2831 auto srcLinkId = m_apMac->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1());
2832 NS_TEST_ASSERT_MSG_EQ(srcLinkId.has_value(),
2833 true,
2834 "Addr1 of BlockAck is not an originator's link address");
2835 NS_TEST_EXPECT_MSG_EQ((*linkIds.begin() == *srcLinkId),
2836 (seqNo <= 3),
2837 "MPDU with seqNo=" << seqNo
2838 << " in flight on unexpected link");
2839 // check the Retry subfield and whether this MPDU is still queued
2840 // after the originator has processed this BlockAck
2841
2842 // MPDUs acknowledged via this BlockAck are no longer queued
2843 bool isQueued = (seqNo > (isMpdu3corrupted ? 2 : 3));
2844 // The Retry subfield is set if the MPDU has not been acknowledged (i.e., it
2845 // is still queued) and has been transmitted on the same link as the BlockAck
2846 // (i.e., its sequence number is less than or equal to 2)
2847 bool isRetry = isQueued && seqNo <= 3;
2848
2849 Simulator::Schedule(delay, [this, item, isQueued, isRetry]() {
2850 NS_TEST_EXPECT_MSG_EQ(item->IsQueued(),
2851 isQueued,
2852 "MPDU with seqNo="
2853 << item->GetHeader().GetSequenceNumber() << " should "
2854 << (isQueued ? "" : "not") << " be queued");
2856 item->GetHeader().IsRetry(),
2857 isRetry,
2858 "Unexpected value for the Retry subfield of the MPDU with seqNo="
2859 << item->GetHeader().GetSequenceNumber());
2860 });
2861
2862 nQueuedPkt++;
2863 item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress(), item);
2864 }
2865 // Each MPDU contains an A-MSDU consisting of two MSDUs
2866 NS_TEST_EXPECT_MSG_EQ(nQueuedPkt, m_nPackets / 2, "Unexpected number of queued MPDUs");
2867 }
2868 break;
2869 }
2870}
2871
2872void
2874{
2875 switch (m_muTrafficPattern)
2876 {
2877 case WifiMuTrafficPattern::DL_MU_BAR_BA_SEQUENCE:
2878 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2880 break;
2881 case WifiMuTrafficPattern::DL_MU_MU_BAR:
2882 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2884 break;
2885 case WifiMuTrafficPattern::DL_MU_AGGR_MU_BAR:
2886 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2888 break;
2889 default:;
2890 }
2891
2893
2894 // Enable A-MSDU aggregation. Max A-MSDU size is set such that two MSDUs can be aggregated
2895 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
2896 {
2897 mac->SetAttribute("BE_MaxAmsduSize", UintegerValue(1050));
2898 mac->GetQosTxop(AC_BE)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2900 mac->GetQosTxop(AC_BE)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2901
2902 mac->SetAttribute("VI_MaxAmsduSize", UintegerValue(1050));
2903 mac->GetQosTxop(AC_VI)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2905 mac->GetQosTxop(AC_VI)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2906 }
2907
2908 // aggregate MU scheduler
2909 auto muScheduler = CreateObjectWithAttributes<RrMultiUserScheduler>("EnableUlOfdma",
2910 BooleanValue(false),
2911 "EnableBsrp",
2912 BooleanValue(false),
2913 "UlPsduSize",
2914 UintegerValue(2000));
2915 m_apMac->AggregateObject(muScheduler);
2916
2917 // install post reception error model on all devices
2918 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
2919 {
2920 auto errorModel = CreateObject<ListErrorModel>();
2921 m_errorModels[m_apMac->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2922 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2923 }
2924 for (std::size_t i : {0, 1})
2925 {
2926 for (const auto linkId : m_staMacs[i]->GetLinkIds())
2927 {
2928 auto errorModel = CreateObject<ListErrorModel>();
2929 m_errorModels[m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2930 m_staMacs[i]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2931 }
2932 }
2933}
2934
2935void
2937{
2938 if (m_muTrafficPattern < WifiMuTrafficPattern::UL_MU)
2939 {
2940 // DL Traffic
2941 for (uint8_t i = 0; i < m_nStations; i++)
2942 {
2943 PacketSocketAddress sockAddr;
2945 sockAddr.SetPhysicalAddress(m_staMacs[i]->GetDevice()->GetAddress());
2946 sockAddr.SetProtocol(1);
2947
2948 // the first client application generates three packets in order
2949 // to trigger the establishment of a Block Ack agreement
2951 GetApplication(sockAddr, 3, 450, i * MilliSeconds(50)));
2952
2953 // the second client application generates the first half of the selected number
2954 // of packets, which are sent in DL MU PPDUs, and starts after all BA agreements
2955 // are established
2957 GetApplication(sockAddr, m_nPackets / 2, 450, m_nStations * MilliSeconds(50)));
2958
2959 // the third client application generates the second half of the selected number
2960 // of packets, which are sent in DL MU PPDUs, and starts during transmission of
2961 // first A-MPDU, if multiple links are setup
2963 GetApplication(sockAddr,
2964 m_nPackets / 2,
2965 450,
2967 }
2968 }
2969 else
2970 {
2971 // UL Traffic
2972 for (uint8_t i = 0; i < m_nStations; i++)
2973 {
2974 m_sockets[i].SetSingleDevice(m_staMacs[i]->GetDevice()->GetIfIndex());
2975 m_sockets[i].SetPhysicalAddress(m_apMac->GetDevice()->GetAddress());
2976 m_sockets[i].SetProtocol(1);
2977
2978 // the first client application generates three packets in order
2979 // to trigger the establishment of a Block Ack agreement
2980 m_staMacs[i]->GetDevice()->GetNode()->AddApplication(
2981 GetApplication(m_sockets[i], 3, 450, i * MilliSeconds(50), i * 4));
2982
2983 // packets to be included in TB PPDUs are generated (by Transmit()) when
2984 // the first Basic Trigger Frame is sent by the AP
2985 }
2986
2987 // MU scheduler starts requesting channel access when we are done with BA agreements
2989 auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
2990 NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "Expected an aggregated MU scheduler");
2991 muScheduler->SetAttribute("EnableUlOfdma", BooleanValue(true));
2992 muScheduler->SetAccessReqInterval(MilliSeconds(3));
2993 // channel access is requested only once
2994 muScheduler->SetAccessReqInterval(Seconds(0));
2995 });
2996 }
2997
2999}
3000
3001void
3003{
3005
3006 // Expected number of packets received by each node (AP, STA 0, STA 1) at application layer
3007 std::array<std::size_t, 3> expectedRxPkts{};
3008
3009 switch (m_muTrafficPattern)
3010 {
3011 case WifiMuTrafficPattern::DL_MU_BAR_BA_SEQUENCE:
3012 case WifiMuTrafficPattern::DL_MU_MU_BAR:
3013 case WifiMuTrafficPattern::DL_MU_AGGR_MU_BAR:
3014 // both STA 0 and STA 1 receive m_nPackets + 3 (sent to trigger BA establishment) packets
3015 expectedRxPkts[1] = m_nPackets + 3;
3016 expectedRxPkts[2] = m_nPackets + 3;
3017 break;
3018 case WifiMuTrafficPattern::UL_MU:
3019 // AP receives m_nPackets + 3 (sent to trigger BA establishment) packets from each station
3020 expectedRxPkts[0] = 2 * (m_nPackets + 3);
3021 break;
3022 }
3023
3025 +expectedRxPkts[0],
3026 "Unexpected number of packets received by the AP");
3028 +expectedRxPkts[1],
3029 "Unexpected number of packets received by STA 0");
3031 +expectedRxPkts[2],
3032 "Unexpected number of packets received by STA 1");
3033
3034 // check that setting the QosTxop::NMaxInflights attribute has the expected effect.
3035 // For DL, for each station we send 2 MPDUs to trigger BA agreement and m_nPackets / 2 MPDUs
3036 // For UL, each station sends 2 MPDUs to trigger BA agreement and m_nPackets / 2 MPDUs
3038 m_inflightCount.size(),
3039 2 * (2 + m_nPackets / 2),
3040 "Did not collect number of simultaneous transmissions for all data frames");
3041
3042 auto nMaxInflight = std::min(m_nMaxInflight, m_staMacs[0]->GetSetupLinkIds().size());
3043 std::size_t maxCount = 0;
3044 for (const auto& [txSeqNoPair, count] : m_inflightCount)
3045 {
3047 nMaxInflight,
3048 "MPDU with seqNo="
3049 << txSeqNoPair.second
3050 << " transmitted simultaneously more times than allowed");
3051 maxCount = std::max(maxCount, count);
3052 }
3053
3055 maxCount,
3056 nMaxInflight,
3057 "Expected that at least one data frame was transmitted simultaneously a number of "
3058 "times equal to the NMaxInflights attribute");
3059
3061}
3062
3063/**
3064 * \ingroup wifi-test
3065 * \ingroup tests
3066 *
3067 * \brief Test release of sequence numbers upon CTS timeout in multi-link operations
3068 *
3069 * In this test, an AP MLD and a non-AP MLD setup 3 links. Usage of RTS/CTS protection is
3070 * enabled for frames whose length is at least 1000 bytes. The AP MLD receives a first set
3071 * of 4 packets from the upper layer and sends an RTS frame, which is corrupted at the
3072 * receiver, on a first link. When the RTS frame is transmitted, the AP MLD receives another
3073 * set of 4 packets, which are transmitted after a successful RTS/CTS exchange on a second
3074 * link. In the meantime, a new RTS/CTS exchange is successfully carried out (on the first
3075 * link or on the third link) to transmit the first set of 4 packets. When the transmission
3076 * of the first set of 4 packets starts, the AP MLD receives the third set of 4 packets from
3077 * the upper layer, which are transmitted after a successful RTS/CTS exchange.
3078 *
3079 * This test checks that sequence numbers are correctly assigned to all the MPDUs carrying data.
3080 */
3082{
3083 public:
3086
3087 protected:
3088 void DoSetup() override;
3089 void DoRun() override;
3090 void Transmit(Ptr<WifiMac> mac,
3091 uint8_t phyId,
3092 WifiConstPsduMap psduMap,
3093 WifiTxVector txVector,
3094 double txPowerW) override;
3095
3096 private:
3097 void StartTraffic() override;
3098
3099 PacketSocketAddress m_sockAddr; //!< packet socket address
3100 std::size_t m_nQosDataFrames; //!< counter for transmitted QoS data frames
3101 Ptr<ListErrorModel> m_errorModel; //!< error rate model to corrupt first RTS frame
3102 bool m_rtsCorrupted; //!< whether the first RTS frame has been corrupted
3103};
3104
3107 "Check sequence numbers after CTS timeout",
3108 1,
3109 BaseParams{{"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3110 {"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3111 {}}),
3112 m_nQosDataFrames(0),
3113 m_errorModel(CreateObject<ListErrorModel>()),
3114 m_rtsCorrupted(false)
3115{
3116}
3117
3118void
3120{
3121 // Enable RTS/CTS
3122 Config::SetDefault("ns3::WifiRemoteStationManager::RtsCtsThreshold", StringValue("1000"));
3123
3125
3126 // install post reception error model on all STAs affiliated with non-AP MLD
3127 for (const auto linkId : m_staMacs[0]->GetLinkIds())
3128 {
3129 m_staMacs[0]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel);
3130 }
3131}
3132
3133void
3135{
3137 m_sockAddr.SetPhysicalAddress(m_staMacs[0]->GetAddress());
3139
3140 // install client application generating 4 packets
3142}
3143
3144void
3146 uint8_t phyId,
3147 WifiConstPsduMap psduMap,
3148 WifiTxVector txVector,
3149 double txPowerW)
3150{
3151 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
3152
3153 auto psdu = psduMap.begin()->second;
3154
3155 if (psdu->GetHeader(0).IsRts() && !m_rtsCorrupted)
3156 {
3157 m_errorModel->SetList({psdu->GetPacket()->GetUid()});
3158 m_rtsCorrupted = true;
3159 // generate other packets when the first RTS is transmitted
3161 }
3162 else if (psdu->GetHeader(0).IsQosData())
3163 {
3165
3166 if (m_nQosDataFrames == 2)
3167 {
3168 // generate other packets when the second QoS data frame is transmitted
3170 }
3171 }
3172}
3173
3174void
3176{
3179
3180 NS_TEST_EXPECT_MSG_EQ(m_nQosDataFrames, 3, "Unexpected number of transmitted QoS data frames");
3181
3182 std::size_t count{};
3183
3184 for (const auto& txPsdu : m_txPsdus)
3185 {
3186 auto psdu = txPsdu.psduMap.begin()->second;
3187
3188 if (!psdu->GetHeader(0).IsQosData())
3189 {
3190 continue;
3191 }
3192
3193 NS_TEST_EXPECT_MSG_EQ(psdu->GetNMpdus(), 4, "Unexpected number of MPDUs in A-MPDU");
3194
3195 count++;
3196 uint16_t expectedSeqNo{};
3197
3198 switch (count)
3199 {
3200 case 1:
3201 expectedSeqNo = 4;
3202 break;
3203 case 2:
3204 expectedSeqNo = 0;
3205 break;
3206 case 3:
3207 expectedSeqNo = 8;
3208 break;
3209 }
3210
3211 for (const auto& mpdu : *PeekPointer(psdu))
3212 {
3213 NS_TEST_EXPECT_MSG_EQ(mpdu->GetHeader().GetSequenceNumber(),
3214 expectedSeqNo++,
3215 "Unexpected sequence number");
3216 }
3217 }
3218
3220}
3221
3222/**
3223 * \ingroup wifi-test
3224 * \ingroup tests
3225 *
3226 * \brief wifi 11be MLD Test Suite
3227 */
3229{
3230 public:
3232};
3233
3235 : TestSuite("wifi-mlo", Type::UNIT)
3236{
3237 using ParamsTuple = std::tuple<MultiLinkOperationsTestBase::BaseParams, // base config params
3238 std::vector<uint8_t>, // link ID of setup links
3239 WifiTidToLinkMappingNegSupport, // AP negotiation support
3240 std::string, // DL TID-to-Link Mapping
3241 std::string>; // UL TID-to-Link Mapping
3242
3243 AddTestCase(new GetRnrLinkInfoTest(), TestCase::Duration::QUICK);
3244 AddTestCase(new MldSwapLinksTest(), TestCase::Duration::QUICK);
3245
3246 for (const auto& [baseParams,
3247 setupLinks,
3248 apNegSupport,
3249 dlTidLinkMapping,
3250 ulTidLinkMapping] :
3251 {// matching channels: setup all links
3252 ParamsTuple(
3253 {{"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3254 {"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3255 {}},
3256 {0, 1, 2},
3257 WifiTidToLinkMappingNegSupport::NOT_SUPPORTED, // AP MLD does not support TID-to-Link
3258 // Mapping negotiation
3259 "0,1,2,3 0,1,2; 4,5 0,1", // default mapping used instead
3260 "0,1,2,3 1,2; 6,7 0,1" // default mapping used instead
3261 ),
3262 // non-matching channels, matching PHY bands: setup all links
3263 ParamsTuple({{"{108, 0, BAND_5GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3264 {"{36, 0, BAND_5GHZ, 0}", "{120, 0, BAND_5GHZ, 0}", "{5, 0, BAND_6GHZ, 0}"},
3265 {}},
3266 {0, 1, 2},
3267 WifiTidToLinkMappingNegSupport::SAME_LINK_SET, // AP MLD does not support
3268 // distinct link sets for TIDs
3269 "0,1,2,3 0,1,2; 4,5 0,1", // default mapping used instead
3270 ""),
3271 // non-AP MLD switches band on some links to setup 3 links
3272 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3273 {"{36, 0, BAND_5GHZ, 0}", "{9, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3274 {}},
3275 {0, 1, 2},
3276 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3277 "0,1,2,3 0; 4,5,6,7 1,2", // frames of two TIDs are generated
3278 "0,2,3 1,2; 1,4,5,6,7 0" // frames of two TIDs are generated
3279 ),
3280 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3281 // that band, hence only 2 links are setup
3282 ParamsTuple(
3283 {{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{8, 20, BAND_2_4GHZ, 0}"},
3284 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3285 {0}},
3286 {0, 1},
3287 WifiTidToLinkMappingNegSupport::SAME_LINK_SET, // AP MLD does not support distinct
3288 // link sets for TIDs
3289 "0,1,2,3,4,5,6,7 0",
3290 "0,1,2,3,4,5,6,7 0"),
3291 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3292 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3293 // an AP operating on the same channel; hence 2 links are setup
3294 ParamsTuple(
3295 {{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{8, 20, BAND_2_4GHZ, 0}"},
3296 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3297 {0, 1}},
3298 {0, 1},
3299 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3300 "0,1,2,3 1",
3301 "0,1,2,3 1"),
3302 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3303 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3304 // an AP operating on the same channel; the third link of the non-AP MLD cannot
3305 // change PHY band and there is an AP operating on the same band (different channel);
3306 // hence 2 links are setup by switching channel (not band) on the third link
3307 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{60, 0, BAND_5GHZ, 0}"},
3308 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3309 {0, 1, 2}},
3310 {0, 2},
3311 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3312 "",
3313 ""),
3314 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3315 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3316 // an AP operating on the same channel; hence one link only is setup
3317 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3318 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3319 {0, 1}},
3320 {2},
3321 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3322 "",
3323 ""),
3324 // non-AP MLD has only two STAs and setups two links
3325 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3326 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3327 {}},
3328 {1, 0},
3329 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3330 "0,1,2,3 1",
3331 ""),
3332 // single link non-AP STA associates with an AP affiliated with an AP MLD
3333 ParamsTuple({{"{120, 0, BAND_5GHZ, 0}"},
3334 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3335 {}},
3336 {2}, // link ID of AP MLD only (non-AP STA is single link)
3337 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3338 "",
3339 ""),
3340 // a STA affiliated with a non-AP MLD associates with a single link AP
3341 ParamsTuple({{"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3342 {"{120, 0, BAND_5GHZ, 0}"},
3343 {}},
3344 {2}, // link ID of non-AP MLD only (AP is single link)
3345 WifiTidToLinkMappingNegSupport::NOT_SUPPORTED,
3346 "0,1,2,3 0,1; 4,5,6,7 0,1", // ignored by single link AP
3347 "")})
3348 {
3349 AddTestCase(new MultiLinkSetupTest(baseParams,
3350 WifiScanType::PASSIVE,
3351 setupLinks,
3352 apNegSupport,
3353 dlTidLinkMapping,
3354 ulTidLinkMapping),
3355 TestCase::Duration::QUICK);
3356 AddTestCase(new MultiLinkSetupTest(baseParams,
3357 WifiScanType::ACTIVE,
3358 setupLinks,
3359 apNegSupport,
3360 dlTidLinkMapping,
3361 ulTidLinkMapping),
3362 TestCase::Duration::QUICK);
3363
3364 for (const auto& trafficPattern : {WifiTrafficPattern::STA_TO_STA,
3365 WifiTrafficPattern::STA_TO_AP,
3366 WifiTrafficPattern::AP_TO_STA,
3367 WifiTrafficPattern::AP_TO_BCAST,
3368 WifiTrafficPattern::STA_TO_BCAST})
3369 {
3370 // No Block Ack agreement
3371 AddTestCase(new MultiLinkTxTest(baseParams,
3372 trafficPattern,
3373 WifiBaEnabled::NO,
3374 WifiUseBarAfterMissedBa::NO,
3375 1),
3376 TestCase::Duration::QUICK);
3377 for (const auto& useBarAfterMissedBa :
3378 {WifiUseBarAfterMissedBa::YES, WifiUseBarAfterMissedBa::NO})
3379 {
3380 // Block Ack agreement with nMaxInflight=1
3381 AddTestCase(new MultiLinkTxTest(baseParams,
3382 trafficPattern,
3383 WifiBaEnabled::YES,
3384 useBarAfterMissedBa,
3385 1),
3386 TestCase::Duration::QUICK);
3387 // Block Ack agreement with nMaxInflight=2
3388 AddTestCase(new MultiLinkTxTest(baseParams,
3389 trafficPattern,
3390 WifiBaEnabled::YES,
3391 useBarAfterMissedBa,
3392 2),
3393 TestCase::Duration::QUICK);
3394 }
3395 }
3396
3397 for (const auto& muTrafficPattern : {WifiMuTrafficPattern::DL_MU_BAR_BA_SEQUENCE,
3398 WifiMuTrafficPattern::DL_MU_MU_BAR,
3399 WifiMuTrafficPattern::DL_MU_AGGR_MU_BAR,
3400 WifiMuTrafficPattern::UL_MU})
3401 {
3402 for (const auto& useBarAfterMissedBa :
3403 {WifiUseBarAfterMissedBa::YES, WifiUseBarAfterMissedBa::NO})
3404 {
3405 // Block Ack agreement with nMaxInflight=1
3407 new MultiLinkMuTxTest(baseParams, muTrafficPattern, useBarAfterMissedBa, 1),
3408 TestCase::Duration::QUICK);
3409 // Block Ack agreement with nMaxInflight=2
3411 new MultiLinkMuTxTest(baseParams, muTrafficPattern, useBarAfterMissedBa, 2),
3412 TestCase::Duration::QUICK);
3413 }
3414 }
3415 }
3416
3417 AddTestCase(new ReleaseSeqNoAfterCtsTimeoutTest(), TestCase::Duration::QUICK);
3418}
3419
Test release of sequence numbers upon CTS timeout in multi-link operations.
PacketSocketAddress m_sockAddr
packet socket address
Ptr< ListErrorModel > m_errorModel
error rate model to corrupt first RTS frame
std::size_t m_nQosDataFrames
counter for transmitted QoS data frames
void StartTraffic() override
Start the generation of traffic (needs to be overridden)
bool m_rtsCorrupted
whether the first RTS frame has been corrupted
void DoSetup() override
Implementation to do any local setup required for this TestCase.
~ReleaseSeqNoAfterCtsTimeoutTest() override=default
void Transmit(Ptr< WifiMac > mac, uint8_t phyId, WifiConstPsduMap psduMap, WifiTxVector txVector, double txPowerW) override
Callback invoked when a FEM passes PSDUs to the PHY.
void DoRun() override
Implementation to actually run this TestCase.
a polymophic address class
Definition: address.h:101
uint16_t GetAssociationId(Mac48Address addr, uint8_t linkId) const
const std::map< uint16_t, Mac48Address > & GetStaList(uint8_t linkId) const
Get a const reference to the map of associated stations on the given link.
Ptr< WifiMacQueue > GetTxopQueue(AcIndex ac) const override
Get the wifi MAC queue of the (Qos)Txop associated with the given AC, if such (Qos)Txop is installed,...
Definition: ap-wifi-mac.cc:275
Headers for BlockAck response.
Definition: ctrl-headers.h:203
bool IsPacketReceived(uint16_t seq, std::size_t index=0) const
Check if the packet with the given sequence number was acknowledged in this BlockAck response.
std::vector< uint32_t > FindPerAidTidInfoWithAid(uint16_t aid) const
For Multi-STA Block Acks, get the indices of the Per AID TID Info subfields carrying the given AID in...
bool IsMultiSta() const
Check if the BlockAck frame variant is Multi-STA Block Ack.
Headers for Trigger frames.
Definition: ctrl-headers.h:942
bool IsBasic() const
Check if this is a Basic Trigger frame.
Hold variables of type enum.
Definition: enum.h:62
void SetList(const std::list< uint64_t > &packetlist)
Definition: error-model.cc:456
an EUI-48 address
Definition: mac48-address.h:46
static Mac48Address GetBroadcast()
Implement the header for management frames of type association request.
Definition: mgt-headers.h:162
Implement the header for management frames of type association and reassociation response.
Definition: mgt-headers.h:339
Implement the header for management frames of type beacon.
Definition: mgt-headers.h:517
Implement the header for management frames of type probe request.
Definition: mgt-headers.h:437
Implement the header for management frames of type probe response.
Definition: mgt-headers.h:456
Helper class used to assign positions and mobility models to nodes.
MultiUserScheduler is an abstract base class defining the API that APs supporting at least VHT can us...
holds a vector of ns3::NetDevice pointers
keep track of a set of node pointers.
uint32_t AddApplication(Ptr< Application > application)
Associate an Application to this Node.
Definition: node.cc:164
uint32_t GetId() const
Definition: node.cc:117
static Iterator Begin()
Definition: node-list.cc:237
static uint32_t GetNNodes()
Definition: node-list.cc:258
static Iterator End()
Definition: node-list.cc:244
bool TraceConnectWithoutContext(std::string name, const CallbackBase &cb)
Connect a TraceSource to a Callback without a context.
Definition: object-base.cc:322
Ptr< T > GetObject() const
Get a pointer to the requested aggregated Object.
Definition: object.h:522
void AggregateObject(Ptr< Object > other)
Aggregate two Objects together.
Definition: object.cc:309
an address for a packet socket
void SetProtocol(uint16_t protocol)
Set the protocol.
void SetPhysicalAddress(const Address address)
Set the destination address.
void SetSingleDevice(uint32_t device)
Set the address to match only a specified NetDevice.
Give ns3::PacketSocket powers to ns3::Node.
void Install(Ptr< Node > node) const
Aggregate an instance of a ns3::PacketSocketFactory onto the provided node.
Smart pointer class similar to boost::intrusive_ptr.
Definition: ptr.h:77
The Reduced Neighbor Report element.
std::size_t GetNNbrApInfoFields() const
Get the number of Neighbor AP Information fields.
void SetMldParameters(std::size_t nbrApInfoId, std::size_t index, uint8_t mldId, uint8_t linkId, uint8_t changeSequence)
Set the MLD Parameters subfield of the i-th TBTT Information field of the given Neighbor AP Informati...
std::size_t GetNTbttInformationFields(std::size_t nbrApInfoId) const
Get the number of TBTT Information fields included in the TBTT Information Set field of the given Nei...
void AddNbrApInfoField()
Add a Neighbor AP Information field.
void AddTbttInformationField(std::size_t nbrApInfoId)
Add a TBTT Information fields to the TBTT Information Set field of the given Neighbor AP Information ...
static void SetRun(uint64_t run)
Set the run number of simulation.
static void SetSeed(uint32_t seed)
Set the seed.
static EventId Schedule(const Time &delay, FUNC f, Ts &&... args)
Schedule an event to expire after delay.
Definition: simulator.h:571
static void Destroy()
Execute the events scheduled with ScheduleDestroy().
Definition: simulator.cc:142
static Time Now()
Return the current simulation virtual time.
Definition: simulator.cc:208
static void Run()
Run the simulation.
Definition: simulator.cc:178
static void Stop()
Tell the Simulator the calling event should be the last one executed.
Definition: simulator.cc:186
Make it easy to create and manage PHY objects for the spectrum model.
void AddChannel(const Ptr< SpectrumChannel > channel, const FrequencyRange &freqRange=WHOLE_WIFI_SPECTRUM)
The IEEE 802.11 SSID Information Element.
Definition: ssid.h:36
Hold variables of type string.
Definition: string.h:56
encapsulates test code
Definition: test.h:1061
void AddTestCase(TestCase *testCase, Duration duration=Duration::QUICK)
Add an individual child TestCase to this test suite.
Definition: test.cc:302
A suite of tests to run.
Definition: test.h:1273
Type
Type of test.
Definition: test.h:1280
Simulation virtual time values and global simulation resolution.
Definition: nstime.h:105
Hold an unsigned integer type.
Definition: uinteger.h:45
static std::optional< WifiAssocManager::RnrLinkInfo > GetNextAffiliatedAp(const ReducedNeighborReport &rnr, std::size_t nbrApInfoId)
Search the given RNR element for APs affiliated to the same AP MLD as the reporting AP.
static std::list< WifiAssocManager::RnrLinkInfo > GetAllAffiliatedAps(const ReducedNeighborReport &rnr)
Find all the APs affiliated to the same AP MLD as the reporting AP that sent the given RNR element.
helps to create WifiNetDevice objects
Definition: wifi-helper.h:324
create MAC layers for a ns3::WifiNetDevice.
base class for all MAC-level wifi objects.
Definition: wifi-mac.h:99
Ptr< FrameExchangeManager > GetFrameExchangeManager(uint8_t linkId=SINGLE_LINK_OP_ID) const
Get the Frame Exchange Manager associated with the given link.
Definition: wifi-mac.cc:981
std::optional< Mac48Address > GetMldAddress(const Mac48Address &remoteAddr) const
Definition: wifi-mac.cc:1774
const std::map< uint8_t, std::unique_ptr< LinkEntity > > & GetLinks() const
Definition: wifi-mac.cc:1057
Ptr< WifiMacQueueScheduler > GetMacQueueScheduler() const
Get the wifi MAC queue scheduler.
Definition: wifi-mac.cc:670
uint8_t GetNLinks() const
Get the number of links (can be greater than 1 for 11be devices only).
Definition: wifi-mac.cc:1072
void SwapLinks(std::map< uint8_t, uint8_t > links)
Swap the links based on the information included in the given map.
Definition: wifi-mac.cc:1134
Ptr< WifiPhy > GetWifiPhy(uint8_t linkId=SINGLE_LINK_OP_ID) const
Definition: wifi-mac.cc:1315
virtual std::optional< uint8_t > GetLinkIdByAddress(const Mac48Address &address) const
Get the ID of the link having the given MAC address, if any.
Definition: wifi-mac.cc:1100
Ptr< EhtConfiguration > GetEhtConfiguration() const
Definition: wifi-mac.cc:1897
std::optional< std::reference_wrapper< const WifiTidLinkMapping > > GetTidToLinkMapping(Mac48Address mldAddr, WifiDirection dir) const
Get the TID-to-Link Mapping negotiated with the given MLD (if any) for the given direction.
Definition: wifi-mac.cc:1247
Ptr< WifiNetDevice > GetDevice() const
Return the device this PHY is associated with.
Definition: wifi-mac.cc:483
virtual Ptr< WifiMacQueue > GetTxopQueue(AcIndex ac) const
Get the wifi MAC queue of the (Qos)Txop associated with the given AC, if such (Qos)Txop is installed,...
Definition: wifi-mac.cc:639
Ptr< WifiRemoteStationManager > GetWifiRemoteStationManager(uint8_t linkId=0) const
Definition: wifi-mac.cc:1045
const std::set< uint8_t > & GetLinkIds() const
Definition: wifi-mac.cc:1078
Mac48Address GetAddress() const
Definition: wifi-mac.cc:496
uint32_t GetIfIndex() const override
Address GetAddress() const override
Ptr< Node > GetNode() const override
void SetPcapDataLinkType(SupportedPcapDataLinkTypes dlt)
Set the data link type of PCAP traces to be used.
Definition: wifi-helper.cc:543
void Set(std::string name, const AttributeValue &v)
Definition: wifi-helper.cc:163
@ DLT_IEEE802_11_RADIO
Include Radiotap link layer information.
Definition: wifi-helper.h:178
void SetPostReceptionErrorModel(const Ptr< ErrorModel > em)
Attach a receive ErrorModel to the WifiPhy.
Definition: wifi-phy.cc:679
static Time CalculateTxDuration(uint32_t size, const WifiTxVector &txVector, WifiPhyBand band, uint16_t staId=SU_STA_ID)
Definition: wifi-phy.cc:1539
WifiPhyBand GetPhyBand() const
Get the configured Wi-Fi band.
Definition: wifi-phy.cc:1053
const WifiPhyOperatingChannel & GetOperatingChannel() const
Get a const reference to the operating channel.
Definition: wifi-phy.cc:1065
uint8_t GetPrimaryChannelIndex(uint16_t primaryChannelWidth) const
If the operating channel width is a multiple of 20 MHz, return the index of the primary channel of th...
uint16_t GetWidth() const
Return the width of the whole operating channel (in MHz).
WifiPhyBand GetPhyBand() const
Return the PHY band of the operating channel.
uint8_t GetNumber() const
Return the channel number identifying the whole operating channel.
uint16_t GetFrequency() const
Return the center frequency of the operating channel (in MHz).
This class mimics the TXVECTOR which is to be passed to the PHY in order to define the parameters whi...
const HeMuUserInfoMap & GetHeMuUserInfoMap() const
Get a const reference to the map HE MU user-specific transmission information indexed by STA-ID.
bool IsUlMu() const
#define NS_ASSERT(condition)
At runtime, in debugging builds, if this condition is not true, the program prints the source file,...
Definition: assert.h:66
#define NS_ASSERT_MSG(condition, message)
At runtime, in debugging builds, if this condition is not true, the program prints the message to out...
Definition: assert.h:86
void SetDefault(std::string name, const AttributeValue &value)
Definition: config.cc:894
void ConnectWithoutContext(std::string path, const CallbackBase &cb)
Definition: config.cc:954
#define NS_FATAL_ERROR(msg)
Report a fatal error with a message and terminate.
Definition: fatal-error.h:179
#define NS_ABORT_MSG(msg)
Unconditional abnormal program termination with a message.
Definition: abort.h:49
#define NS_ABORT_IF(cond)
Abnormal program termination if a condition is true.
Definition: abort.h:76
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition: log.h:202
#define NS_LOG_INFO(msg)
Use NS_LOG to output a message of level LOG_INFO.
Definition: log.h:275
#define NS_TEST_ASSERT_MSG_LT(actual, limit, msg)
Test that an actual value is less than a limit and report and abort if not.
Definition: test.h:710
#define NS_TEST_ASSERT_MSG_EQ(actual, limit, msg)
Test that an actual and expected (limit) value are equal and report and abort if not.
Definition: test.h:145
#define NS_TEST_EXPECT_MSG_LT_OR_EQ(actual, limit, msg)
Test that an actual value is less than or equal to a limit and report if not.
Definition: test.h:831
#define NS_TEST_EXPECT_MSG_LT(actual, limit, msg)
Test that an actual value is less than a limit and report if not.
Definition: test.h:791
#define NS_TEST_EXPECT_MSG_GT(actual, limit, msg)
Test that an actual value is greater than a limit and report if not.
Definition: test.h:957
#define NS_TEST_EXPECT_MSG_NE(actual, limit, msg)
Test that an actual and expected (limit) value are not equal and report if not.
Definition: test.h:667
#define NS_TEST_EXPECT_MSG_EQ(actual, limit, msg)
Test that an actual and expected (limit) value are equal and report if not.
Definition: test.h:252
#define NS_TEST_ASSERT_MSG_NE(actual, limit, msg)
Test that an actual and expected (limit) value are not equal and report and abort if not.
Definition: test.h:565
Time MicroSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition: nstime.h:1343
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition: nstime.h:1319
Time MilliSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition: nstime.h:1331
WifiScanType
Scan type (active or passive)
Definition: sta-wifi-mac.h:51
AcIndex QosUtilsMapTidToAc(uint8_t tid)
Maps TID (Traffic ID) to Access classes.
Definition: qos-utils.cc:134
@ AP
Definition: wifi-mac.h:69
@ WIFI_STANDARD_80211be
@ WIFI_PHY_BAND_6GHZ
The 6 GHz band.
Definition: wifi-phy-band.h:39
@ WIFI_PHY_BAND_5GHZ
The 5 GHz band.
Definition: wifi-phy-band.h:37
@ AC_BE
Best Effort.
Definition: qos-utils.h:75
@ AC_VO
Voice.
Definition: qos-utils.h:81
@ AC_VI
Video.
Definition: qos-utils.h:79
@ AC_BK
Background.
Definition: qos-utils.h:77
Every class exported by the ns3 library is enclosed in the ns3 namespace.
constexpr FrequencyRange WIFI_SPECTRUM_6_GHZ
Identifier for the frequency range covering the wifi spectrum in the 6 GHz band.
U * PeekPointer(const Ptr< U > &p)
Definition: ptr.h:454
std::unordered_map< uint16_t, Ptr< const WifiPsdu > > WifiConstPsduMap
Map of const PSDUs indexed by STA-ID.
Callback< R, Args... > MakeCallback(R(T::*memPtr)(Args...), OBJ objPtr)
Build Callbacks for class method members which take varying numbers of arguments and potentially retu...
Definition: callback.h:700
WifiTidToLinkMappingNegSupport
TID-to-Link Mapping Negotiation Support.
std::tuple< WifiContainerQueueType, WifiReceiverAddressType, Mac48Address, std::optional< uint8_t > > WifiContainerQueueId
Tuple (queue type, receiver address type, Address, TID) identifying a container queue.
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:193
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:44
bool TidToLinkMappingValidForNegType1(const WifiTidLinkMapping &dlLinkMapping, const WifiTidLinkMapping &ulLinkMapping)
Check if the given TID-to-Link Mappings are valid for a negotiation type of 1.
Definition: wifi-utils.cc:150
std::map< uint8_t, std::set< uint8_t > > WifiTidLinkMapping
TID-indexed map of the link set to which the TID is mapped.
Definition: wifi-utils.h:75
constexpr FrequencyRange WIFI_SPECTRUM_2_4_GHZ
Identifier for the frequency range covering the wifi spectrum in the 2.4 GHz band.
STL namespace.
Function object to compute the hash of a MAC address.
Definition: qos-utils.h:56
uint32_t prev
std::string dir
uint32_t pktSize
packet size used for the simulation (in bytes)
WifiMuTrafficPattern
Tested MU traffic patterns.
WifiTrafficPattern
Tested traffic patterns.
WifiUseBarAfterMissedBa
Whether to send a BlockAckReq after a missed BlockAck.
static WifiMultiLinkOperationsTestSuite g_wifiMultiLinkOperationsTestSuite
the test suite
WifiBaEnabled
Block Ack agreement enabled/disabled.