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.
945 */
946 void CheckDisabledLinks();
947
948 /**
949 * Check correctness of the given Beacon frame.
950 *
951 * \param mpdu the given Beacon frame
952 * \param linkId the ID of the link on which the Beacon frame was transmitted
953 */
954 void CheckBeacon(Ptr<WifiMpdu> mpdu, uint8_t linkId);
955
956 /**
957 * Check correctness of the given Probe Response frame.
958 *
959 * \param mpdu the given Probe Response frame
960 * \param linkId the ID of the link on which the Probe Response frame was transmitted
961 */
962 void CheckProbeResponse(Ptr<WifiMpdu> mpdu, uint8_t linkId);
963
964 /**
965 * Check correctness of the given Association Request frame.
966 *
967 * \param mpdu the given Association Request frame
968 * \param linkId the ID of the link on which the Association Request frame was transmitted
969 */
970 void CheckAssocRequest(Ptr<WifiMpdu> mpdu, uint8_t linkId);
971
972 /**
973 * Check correctness of the given Association Response frame.
974 *
975 * \param mpdu the given Association Response frame
976 * \param linkId the ID of the link on which the Association Response frame was transmitted
977 */
978 void CheckAssocResponse(Ptr<WifiMpdu> mpdu, uint8_t linkId);
979
980 /**
981 * Check that QoS data frames are sent on links their TID is mapped to.
982 *
983 * \param mpdu the given QoS data frame
984 * \param linkId the ID of the link on which the QoS data frame was transmitted
985 * \param index index of the QoS data frame in the vector of transmitted PSDUs
986 */
987 void CheckQosData(Ptr<WifiMpdu> mpdu, uint8_t linkId, std::size_t index);
988
989 const std::vector<uint8_t> m_setupLinks; //!< IDs of the expected links to setup
990 WifiScanType m_scanType; //!< the scan type (active or passive)
991 std::size_t m_nProbeResp; //!< number of Probe Responses received by the non-AP MLD
993 m_apNegSupport; //!< TID-to-Link Mapping negotiation supported by the AP MLD
994 std::string m_dlTidLinkMappingStr; //!< DL TID-to-Link Mapping for non-AP MLD EHT configuration
995 std::string m_ulTidLinkMappingStr; //!< UL TID-to-Link Mapping for non-AP MLD EHT configuration
996 WifiTidLinkMapping m_dlTidLinkMapping; //!< expected DL TID-to-Link Mapping requested by non-AP
997 //!< MLD and accepted by AP MLD
998 WifiTidLinkMapping m_ulTidLinkMapping; //!< expected UL TID-to-Link Mapping requested by non-AP
999 //!< MLD and accepted by AP MLD
1000 uint8_t m_dlTid1; //!< the TID of the first set of DL QoS data frames
1001 uint8_t m_ulTid1; //!< the TID of the first set of UL QoS data frames
1002 std::optional<uint8_t> m_dlTid2; //!< the TID of the optional set of DL QoS data frames
1003 std::optional<uint8_t> m_ulTid2; //!< the TID of the optional set of UL QoS data frames
1004 std::vector<std::size_t>
1005 m_qosFrames1; //!< indices of QoS frames of the first set in the vector of TX PSDUs
1006 std::vector<std::size_t>
1007 m_qosFrames2; //!< indices of QoS frames of the optional set in the vector of TX PSDUs
1008};
1009
1011 WifiScanType scanType,
1012 const std::vector<uint8_t>& setupLinks,
1013 WifiTidToLinkMappingNegSupport apNegSupport,
1014 const std::string& dlTidToLinkMapping,
1015 const std::string& ulTidToLinkMapping)
1016 : MultiLinkOperationsTestBase("Check correctness of Multi-Link Setup", 1, baseParams),
1017 m_setupLinks(setupLinks),
1018 m_scanType(scanType),
1019 m_nProbeResp(0),
1020 m_apNegSupport(apNegSupport),
1021 m_dlTidLinkMappingStr(dlTidToLinkMapping),
1022 m_ulTidLinkMappingStr(ulTidToLinkMapping)
1023{
1024}
1025
1026void
1028{
1030
1031 m_staMacs[0]->SetAttribute("ActiveProbing", BooleanValue(m_scanType == WifiScanType::ACTIVE));
1032 m_apMac->GetEhtConfiguration()->SetAttribute("TidToLinkMappingNegSupport",
1034 // For non-AP MLD, it does not make sense to set the negotiation type to 0 (unless the AP MLD
1035 // also advertises 0) or 1 (the AP MLD is discarded if it advertises a support of 3)
1036 auto staEhtConfig = m_staMacs[0]->GetEhtConfiguration();
1037 staEhtConfig->SetAttribute("TidToLinkMappingNegSupport",
1038 EnumValue(WifiTidToLinkMappingNegSupport::ANY_LINK_SET));
1039 staEhtConfig->SetAttribute("TidToLinkMappingDl", StringValue(m_dlTidLinkMappingStr));
1040 staEhtConfig->SetAttribute("TidToLinkMappingUl", StringValue(m_ulTidLinkMappingStr));
1041
1042 // the negotiated link mapping matches the one configured in EHT configuration, unless
1043 // the AP MLD does not support TID-to-link mapping negotiation or the AP MLD supports
1044 // the negotiation type 1 and the non-AP MLD is configured with a link mapping that
1045 // maps distinct link sets to the TIDs, in which case the default link mapping is used
1046 m_dlTidLinkMapping = staEhtConfig->GetTidLinkMapping(WifiDirection::DOWNLINK);
1047 m_ulTidLinkMapping = staEhtConfig->GetTidLinkMapping(WifiDirection::UPLINK);
1048
1049 if (m_apNegSupport == WifiTidToLinkMappingNegSupport::NOT_SUPPORTED ||
1050 (m_apNegSupport == WifiTidToLinkMappingNegSupport::SAME_LINK_SET &&
1052 {
1053 m_dlTidLinkMapping.clear(); // default link mapping
1054 m_ulTidLinkMapping.clear(); // default link mapping
1055 }
1056
1057 // find (if any) a TID that is not mapped to all setup links
1058 using TupleRefs = std::tuple<std::reference_wrapper<const WifiTidLinkMapping>,
1059 std::reference_wrapper<uint8_t>,
1060 std::reference_wrapper<std::optional<uint8_t>>,
1061 Ptr<WifiMac>>;
1062 for (auto& [mappingRef, tid1Ref, tid2Ref, mac] :
1065 {
1066 tid1Ref.get() = 0;
1067 for (uint8_t tid1 = 0; tid1 < 8; tid1++)
1068 {
1069 if (auto it1 = mappingRef.get().find(tid1);
1070 it1 != mappingRef.get().cend() && it1->second.size() != m_setupLinks.size())
1071 {
1072 // found. Now search for another TID with a disjoint mapped link set
1073 for (uint8_t tid2 = tid1 + 1; tid2 < 8; tid2++)
1074 {
1075 if (auto it2 = mappingRef.get().find(tid2);
1076 it2 != mappingRef.get().cend() && it2->second.size() != m_setupLinks.size())
1077 {
1078 std::list<uint8_t> intersection;
1079 std::set_intersection(it1->second.cbegin(),
1080 it1->second.cend(),
1081 it2->second.cbegin(),
1082 it2->second.cend(),
1083 std::back_inserter(intersection));
1084 if (intersection.empty())
1085 {
1086 // found a second TID
1087 tid2Ref.get() = tid2;
1088 break;
1089 }
1090 }
1091 }
1092 tid1Ref.get() = tid1;
1093 break;
1094 }
1095 }
1096
1097 std::list<uint8_t> tids = {tid1Ref.get()};
1098 if (tid2Ref.get())
1099 {
1100 tids.emplace_back(*tid2Ref.get());
1101 }
1102
1103 // prevent aggregation of MPDUs
1104 for (auto tid : tids)
1105 {
1106 std::string attrName;
1107 switch (QosUtilsMapTidToAc(tid))
1108 {
1109 case AC_VI:
1110 attrName = "VI_MaxAmpduSize";
1111 break;
1112 case AC_VO:
1113 attrName = "VO_MaxAmpduSize";
1114 break;
1115 case AC_BE:
1116 attrName = "BE_MaxAmpduSize";
1117 break;
1118 case AC_BK:
1119 attrName = "BK_MaxAmpduSize";
1120 break;
1121 default:
1122 NS_FATAL_ERROR("Invalid TID " << +tid);
1123 }
1124
1125 mac->SetAttribute(attrName, UintegerValue(100));
1126 }
1127 }
1128}
1129
1130void
1132{
1133 // DL traffic
1134 {
1135 PacketSocketAddress sockAddr;
1137 sockAddr.SetPhysicalAddress(m_staMacs[0]->GetDevice()->GetAddress());
1138 sockAddr.SetProtocol(1);
1139
1141 GetApplication(sockAddr, m_setupLinks.size(), 500, Seconds(0), m_dlTid1));
1142 if (m_dlTid2)
1143 {
1145 GetApplication(sockAddr, m_setupLinks.size(), 500, Seconds(0), *m_dlTid2));
1146 }
1147 }
1148
1149 // UL Traffic
1150 {
1151 PacketSocketAddress sockAddr;
1152 sockAddr.SetSingleDevice(m_staMacs[0]->GetDevice()->GetIfIndex());
1154 sockAddr.SetProtocol(1);
1155
1156 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
1157 GetApplication(sockAddr, m_setupLinks.size(), 500, MilliSeconds(500), m_ulTid1));
1158 if (m_ulTid2)
1159 {
1160 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
1161 GetApplication(sockAddr, m_setupLinks.size(), 500, MilliSeconds(500), *m_ulTid2));
1162 }
1163 }
1164}
1165
1166void
1168{
1170
1173
1174 /**
1175 * Check content of management frames
1176 */
1177 std::size_t index = 0;
1178
1179 for (const auto& frameInfo : m_txPsdus)
1180 {
1181 const auto& mpdu = *frameInfo.psduMap.begin()->second->begin();
1182 const auto& linkId = frameInfo.linkId;
1183
1184 switch (mpdu->GetHeader().GetType())
1185 {
1187 CheckBeacon(mpdu, linkId);
1188 break;
1189
1191 CheckProbeResponse(mpdu, linkId);
1192 m_nProbeResp++;
1193 break;
1194
1196 CheckAssocRequest(mpdu, linkId);
1197 break;
1198
1200 CheckAssocResponse(mpdu, linkId);
1201 break;
1202
1203 case WIFI_MAC_QOSDATA:
1204 CheckQosData(mpdu, linkId, index);
1205 break;
1206
1207 default:
1208 break;
1209 }
1210
1211 index++;
1212 }
1213
1215
1216 std::size_t expectedProbeResp = 0;
1217 if (m_scanType == WifiScanType::ACTIVE)
1218 {
1219 // the number of Probe Response frames that we expect to receive in active mode equals
1220 // the number of channels in common between AP MLD and non-AP MLD at initialization
1221 for (const auto& staChannel : m_staChannels)
1222 {
1223 for (const auto& apChannel : m_apChannels)
1224 {
1225 if (staChannel == apChannel)
1226 {
1227 expectedProbeResp++;
1228 break;
1229 }
1230 }
1231 }
1232 }
1233
1234 NS_TEST_EXPECT_MSG_EQ(m_nProbeResp, expectedProbeResp, "Unexpected number of Probe Responses");
1235
1236 std::size_t expectedRxDlPkts = m_setupLinks.size();
1237 if (m_dlTid2)
1238 {
1239 expectedRxDlPkts *= 2;
1240 }
1241 NS_TEST_EXPECT_MSG_EQ(m_rxPkts[m_staMacs[0]->GetDevice()->GetNode()->GetId()],
1242 expectedRxDlPkts,
1243 "Unexpected number of DL packets received");
1244
1245 std::size_t expectedRxUlPkts = m_setupLinks.size();
1246 if (m_ulTid2)
1247 {
1248 expectedRxUlPkts *= 2;
1249 }
1251 expectedRxUlPkts,
1252 "Unexpected number of UL packets received");
1253
1255}
1256
1257void
1259{
1260 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_BEACON);
1261
1262 CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::DL);
1263
1265 mpdu->GetHeader().GetAddr2(),
1266 "TA of Beacon frame is not the address of the link it is transmitted on");
1267 MgtBeaconHeader beacon;
1268 mpdu->GetPacket()->PeekHeader(beacon);
1269 const auto& rnr = beacon.Get<ReducedNeighborReport>();
1270 const auto& mle = beacon.Get<MultiLinkElement>();
1271
1272 if (m_apMac->GetNLinks() == 1)
1273 {
1274 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(),
1275 false,
1276 "RNR Element in Beacon frame from single link AP");
1277 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1278 false,
1279 "Multi-Link Element in Beacon frame from single link AP");
1280 return;
1281 }
1282
1283 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(), true, "No RNR Element in Beacon frame");
1284 // All the other APs affiliated with the same AP MLD as the AP sending
1285 // the Beacon frame must be reported in a separate Neighbor AP Info field
1286 NS_TEST_EXPECT_MSG_EQ(rnr->GetNNbrApInfoFields(),
1287 static_cast<std::size_t>(m_apMac->GetNLinks() - 1),
1288 "Unexpected number of Neighbor AP Info fields in RNR");
1289 for (std::size_t nbrApInfoId = 0; nbrApInfoId < rnr->GetNNbrApInfoFields(); nbrApInfoId++)
1290 {
1291 NS_TEST_EXPECT_MSG_EQ(rnr->HasMldParameters(nbrApInfoId),
1292 true,
1293 "MLD Parameters not present");
1294 NS_TEST_EXPECT_MSG_EQ(rnr->GetNTbttInformationFields(nbrApInfoId),
1295 1,
1296 "Expected only one TBTT Info subfield per Neighbor AP Info");
1297 uint8_t nbrLinkId = rnr->GetLinkId(nbrApInfoId, 0);
1298 NS_TEST_EXPECT_MSG_EQ(rnr->GetBssid(nbrApInfoId, 0),
1299 m_apMac->GetFrameExchangeManager(nbrLinkId)->GetAddress(),
1300 "BSSID advertised in Neighbor AP Info field "
1301 << nbrApInfoId
1302 << " does not match the address configured on the link "
1303 "advertised in the same field");
1304 }
1305
1306 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Beacon frame");
1307 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1309 "Incorrect MLD address advertised in Multi-Link Element");
1310 NS_TEST_EXPECT_MSG_EQ(mle->GetLinkIdInfo(),
1311 +linkId,
1312 "Incorrect Link ID advertised in Multi-Link Element");
1313}
1314
1315void
1317{
1318 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_PROBE_RESPONSE);
1319
1320 CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::DL);
1321
1323 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1324 mpdu->GetHeader().GetAddr2(),
1325 "TA of Probe Response is not the address of the link it is transmitted on");
1326 MgtProbeResponseHeader probeResp;
1327 mpdu->GetPacket()->PeekHeader(probeResp);
1328 const auto& rnr = probeResp.Get<ReducedNeighborReport>();
1329 const auto& mle = probeResp.Get<MultiLinkElement>();
1330
1331 if (m_apMac->GetNLinks() == 1)
1332 {
1333 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(),
1334 false,
1335 "RNR Element in Probe Response frame from single link AP");
1336 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1337 false,
1338 "Multi-Link Element in Probe Response frame from single link AP");
1339 return;
1340 }
1341
1342 NS_TEST_EXPECT_MSG_EQ(rnr.has_value(), true, "No RNR Element in Probe Response frame");
1343 // All the other APs affiliated with the same AP MLD as the AP sending
1344 // the Probe Response frame must be reported in a separate Neighbor AP Info field
1345 NS_TEST_EXPECT_MSG_EQ(rnr->GetNNbrApInfoFields(),
1346 static_cast<std::size_t>(m_apMac->GetNLinks() - 1),
1347 "Unexpected number of Neighbor AP Info fields in RNR");
1348 for (std::size_t nbrApInfoId = 0; nbrApInfoId < rnr->GetNNbrApInfoFields(); nbrApInfoId++)
1349 {
1350 NS_TEST_EXPECT_MSG_EQ(rnr->HasMldParameters(nbrApInfoId),
1351 true,
1352 "MLD Parameters not present");
1353 NS_TEST_EXPECT_MSG_EQ(rnr->GetNTbttInformationFields(nbrApInfoId),
1354 1,
1355 "Expected only one TBTT Info subfield per Neighbor AP Info");
1356 uint8_t nbrLinkId = rnr->GetLinkId(nbrApInfoId, 0);
1357 NS_TEST_EXPECT_MSG_EQ(rnr->GetBssid(nbrApInfoId, 0),
1358 m_apMac->GetFrameExchangeManager(nbrLinkId)->GetAddress(),
1359 "BSSID advertised in Neighbor AP Info field "
1360 << nbrApInfoId
1361 << " does not match the address configured on the link "
1362 "advertised in the same field");
1363 }
1364
1365 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Probe Response frame");
1366 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1368 "Incorrect MLD address advertised in Multi-Link Element");
1369 NS_TEST_EXPECT_MSG_EQ(mle->GetLinkIdInfo(),
1370 +linkId,
1371 "Incorrect Link ID advertised in Multi-Link Element");
1372}
1373
1374void
1376{
1377 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_ASSOCIATION_REQUEST);
1378
1379 CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::UL);
1380
1382 m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress(),
1383 mpdu->GetHeader().GetAddr2(),
1384 "TA of Assoc Request frame is not the address of the link it is transmitted on");
1386 mpdu->GetPacket()->PeekHeader(assoc);
1387 const auto& mle = assoc.Get<MultiLinkElement>();
1388
1389 if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetNLinks() == 1)
1390 {
1391 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1392 false,
1393 "Multi-Link Element in Assoc Request frame from single link STA");
1394 }
1395 else
1396 {
1397 NS_TEST_EXPECT_MSG_EQ(mle.has_value(),
1398 true,
1399 "No Multi-Link Element in Assoc Request frame");
1400 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1401 m_staMacs[0]->GetAddress(),
1402 "Incorrect MLD Address advertised in Multi-Link Element");
1404 mle->GetNPerStaProfileSubelements(),
1405 m_setupLinks.size() - 1,
1406 "Incorrect number of Per-STA Profile subelements in Multi-Link Element");
1407 for (std::size_t i = 0; i < mle->GetNPerStaProfileSubelements(); i++)
1408 {
1409 auto& perStaProfile = mle->GetPerStaProfile(i);
1410 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasStaMacAddress(),
1411 true,
1412 "Per-STA Profile must contain STA MAC address");
1413 // find ID of the local link corresponding to this subelement
1414 auto staLinkId = m_staMacs[0]->GetLinkIdByAddress(perStaProfile.GetStaMacAddress());
1416 staLinkId.has_value(),
1417 true,
1418 "No link found with the STA MAC address advertised in Per-STA Profile");
1420 +staLinkId.value(),
1421 +linkId,
1422 "The STA that sent the Assoc Request should not be included in a Per-STA Profile");
1423 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), staLinkId.value());
1424 NS_TEST_EXPECT_MSG_EQ((it != m_setupLinks.end()),
1425 true,
1426 "Not expecting to setup STA link ID " << +staLinkId.value());
1428 +staLinkId.value(),
1429 +perStaProfile.GetLinkId(),
1430 "Not expecting to request association to AP Link ID in Per-STA Profile");
1431 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasAssocRequest(),
1432 true,
1433 "Missing Association Request in Per-STA Profile");
1434 }
1435 }
1436
1437 const auto& tlm = assoc.Get<TidToLinkMapping>();
1438
1439 // A TID-to-Link Mapping IE is included in the Association Request if and only if the AP MLD
1440 // and the non-AP MLD are performing ML setup (i.e., they both have multiple links) and the
1441 // AP MLD advertises a non-null negotiation support type
1442 if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetNLinks() == 1 ||
1443 m_apNegSupport == WifiTidToLinkMappingNegSupport::NOT_SUPPORTED)
1444 {
1445 NS_TEST_EXPECT_MSG_EQ(tlm.empty(),
1446 true,
1447 "Didn't expect a TID-to-Link Mapping IE in Assoc Request frame");
1448 }
1449 else
1450 {
1451 std::size_t expectedNTlm = (m_dlTidLinkMapping == m_ulTidLinkMapping ? 1 : 2);
1452
1453 NS_TEST_ASSERT_MSG_EQ(tlm.size(),
1454 expectedNTlm,
1455 "Unexpected number of TID-to-Link Mapping IE in Assoc Request");
1456
1457 // lambda to check content of TID-to-Link Mapping IE(s)
1458 auto checkTlm = [&](std::size_t tlmId, WifiDirection dir) {
1459 NS_TEST_EXPECT_MSG_EQ(+static_cast<uint8_t>(tlm[tlmId].m_control.direction),
1460 +static_cast<uint8_t>(dir),
1461 "Unexpected direction in TID-to-Link Mapping IE " << tlmId);
1462 auto& expectedMapping =
1463 (dir == WifiDirection::UPLINK ? m_ulTidLinkMapping : m_dlTidLinkMapping);
1464
1465 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].m_control.defaultMapping,
1466 expectedMapping.empty(),
1467 "Default Link Mapping bit not set correctly");
1468 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].m_linkMapping.size(),
1469 expectedMapping.size(),
1470 "Unexpected number of Link Mapping Of TID n fields");
1471 for (uint8_t tid = 0; tid < 8; tid++)
1472 {
1473 if (auto it = expectedMapping.find(tid); it != expectedMapping.cend())
1474 {
1475 NS_TEST_EXPECT_MSG_EQ((tlm[tlmId].GetLinkMappingOfTid(tid) == it->second),
1476 true,
1477 "Unexpected link mapping for TID "
1478 << +tid << " direction " << dir);
1479 }
1480 else
1481 {
1482 NS_TEST_EXPECT_MSG_EQ(tlm[tlmId].GetLinkMappingOfTid(tid).empty(),
1483 true,
1484 "Expecting no Link Mapping Of TID n field for TID "
1485 << +tid << " direction " << dir);
1486 }
1487 }
1488 };
1489
1490 if (tlm.size() == 1)
1491 {
1492 checkTlm(0, WifiDirection::BOTH_DIRECTIONS);
1493 }
1494 else
1495 {
1496 std::size_t dlId = (tlm[0].m_control.direction == WifiDirection::DOWNLINK ? 0 : 1);
1497 std::size_t ulId = (dlId == 0 ? 1 : 0);
1498
1499 checkTlm(dlId, WifiDirection::DOWNLINK);
1500 checkTlm(ulId, WifiDirection::UPLINK);
1501 }
1502 }
1503}
1504
1505void
1507{
1508 NS_ABORT_IF(mpdu->GetHeader().GetType() != WIFI_MAC_MGT_ASSOCIATION_RESPONSE);
1509
1510 CheckAddresses(Create<WifiPsdu>(mpdu, false), MultiLinkOperationsTestBase::DL);
1511
1513 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1514 mpdu->GetHeader().GetAddr2(),
1515 "TA of Assoc Response frame is not the address of the link it is transmitted on");
1517 mpdu->GetPacket()->PeekHeader(assoc);
1518 const auto& mle = assoc.Get<MultiLinkElement>();
1519
1520 if (m_apMac->GetNLinks() == 1 || m_staMacs[0]->GetNLinks() == 1)
1521 {
1523 mle.has_value(),
1524 false,
1525 "Multi-Link Element in Assoc Response frame with single link AP or single link STA");
1526 return;
1527 }
1528
1529 NS_TEST_EXPECT_MSG_EQ(mle.has_value(), true, "No Multi-Link Element in Assoc Request frame");
1530 NS_TEST_EXPECT_MSG_EQ(mle->GetMldMacAddress(),
1532 "Incorrect MLD Address advertised in Multi-Link Element");
1533 NS_TEST_EXPECT_MSG_EQ(mle->GetNPerStaProfileSubelements(),
1534 m_setupLinks.size() - 1,
1535 "Incorrect number of Per-STA Profile subelements in Multi-Link Element");
1536 for (std::size_t i = 0; i < mle->GetNPerStaProfileSubelements(); i++)
1537 {
1538 auto& perStaProfile = mle->GetPerStaProfile(i);
1539 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasStaMacAddress(),
1540 true,
1541 "Per-STA Profile must contain STA MAC address");
1542 // find ID of the local link corresponding to this subelement
1543 auto apLinkId = m_apMac->GetLinkIdByAddress(perStaProfile.GetStaMacAddress());
1545 apLinkId.has_value(),
1546 true,
1547 "No link found with the STA MAC address advertised in Per-STA Profile");
1548 NS_TEST_EXPECT_MSG_EQ(+apLinkId.value(),
1549 +perStaProfile.GetLinkId(),
1550 "Link ID and MAC address advertised in Per-STA Profile do not match");
1552 +apLinkId.value(),
1553 +linkId,
1554 "The AP that sent the Assoc Response should not be included in a Per-STA Profile");
1555 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), apLinkId.value());
1556 NS_TEST_EXPECT_MSG_EQ((it != m_setupLinks.end()),
1557 true,
1558 "Not expecting to setup AP link ID " << +apLinkId.value());
1559 NS_TEST_EXPECT_MSG_EQ(perStaProfile.HasAssocResponse(),
1560 true,
1561 "Missing Association Response in Per-STA Profile");
1562 }
1563
1564 // For the moment, the AP MLD always accepts a valid TID-to-Link Mapping request, hence
1565 // in every case there is no TID-to-Link Mapping IE in the Association Response
1566 NS_TEST_EXPECT_MSG_EQ(assoc.Get<TidToLinkMapping>().empty(),
1567 true,
1568 "Didn't expect to find a TID-to-Link Mapping IE in Association Response");
1569}
1570
1571void
1573{
1574 /**
1575 * Check outcome of Multi-Link Setup
1576 */
1577 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->IsAssociated(), true, "Expected the STA to be associated");
1578
1579 for (const auto linkId : m_setupLinks)
1580 {
1581 auto staLinkId = (m_staMacs[0]->GetNLinks() > 1 ? linkId : SINGLE_LINK_OP_ID);
1582 auto apLinkId = (m_apMac->GetNLinks() > 1 ? linkId : SINGLE_LINK_OP_ID);
1583
1584 auto staAddr = m_staMacs[0]->GetFrameExchangeManager(staLinkId)->GetAddress();
1585 auto apAddr = m_apMac->GetFrameExchangeManager(apLinkId)->GetAddress();
1586
1587 auto staRemoteMgr = m_staMacs[0]->GetWifiRemoteStationManager(staLinkId);
1588 auto apRemoteMgr = m_apMac->GetWifiRemoteStationManager(apLinkId);
1589
1590 // STA side
1591 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetFrameExchangeManager(staLinkId)->GetBssid(),
1592 apAddr,
1593 "Unexpected BSSID for STA link ID " << +staLinkId);
1594 if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1595 {
1596 NS_TEST_EXPECT_MSG_EQ((staRemoteMgr->GetMldAddress(apAddr) == m_apMac->GetAddress()),
1597 true,
1598 "Incorrect MLD address stored by STA on link ID " << +staLinkId);
1600 (staRemoteMgr->GetAffiliatedStaAddress(m_apMac->GetAddress()) == apAddr),
1601 true,
1602 "Incorrect affiliated address stored by STA on link ID " << +staLinkId);
1603 }
1604
1605 // AP side
1606 NS_TEST_EXPECT_MSG_EQ(apRemoteMgr->IsAssociated(staAddr),
1607 true,
1608 "Expecting STA " << staAddr << " to be associated on link "
1609 << +apLinkId);
1610 if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1611 {
1613 (apRemoteMgr->GetMldAddress(staAddr) == m_staMacs[0]->GetAddress()),
1614 true,
1615 "Incorrect MLD address stored by AP on link ID " << +apLinkId);
1617 (apRemoteMgr->GetAffiliatedStaAddress(m_staMacs[0]->GetAddress()) == staAddr),
1618 true,
1619 "Incorrect affiliated address stored by AP on link ID " << +apLinkId);
1620 }
1621 auto aid = m_apMac->GetAssociationId(staAddr, apLinkId);
1622 const auto& staList = m_apMac->GetStaList(apLinkId);
1623 NS_TEST_EXPECT_MSG_EQ((staList.find(aid) != staList.end()),
1624 true,
1625 "STA " << staAddr << " not found in list of associated STAs");
1626
1627 // STA of non-AP MLD operate on the same channel as the AP
1629 +m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetNumber(),
1631 "Incorrect operating channel number for STA on link " << +staLinkId);
1633 m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetFrequency(),
1635 "Incorrect operating channel frequency for STA on link " << +staLinkId);
1636 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetWidth(),
1638 "Incorrect operating channel width for STA on link " << +staLinkId);
1640 +m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetPhyBand(),
1642 "Incorrect operating PHY band for STA on link " << +staLinkId);
1644 +m_staMacs[0]->GetWifiPhy(staLinkId)->GetOperatingChannel().GetPrimaryChannelIndex(20),
1646 "Incorrect operating primary channel index for STA on link " << +staLinkId);
1647 }
1648
1649 // lambda to check the link mapping stored at wifi MAC
1650 auto checkStoredMapping =
1651 [this](Ptr<WifiMac> mac, Ptr<WifiMac> dest, WifiDirection dir, bool present) {
1652 NS_TEST_ASSERT_MSG_EQ(mac->GetTidToLinkMapping(dest->GetAddress(), dir).has_value(),
1653 present,
1654 "Link mapping stored by "
1655 << (mac->GetTypeOfStation() == AP ? "AP" : "non-AP")
1656 << " MLD for " << dir << " direction "
1657 << (present ? "expected" : "not expected"));
1658 if (present)
1659 {
1660 const auto& mapping =
1661 (dir == WifiDirection::DOWNLINK ? m_dlTidLinkMapping : m_ulTidLinkMapping);
1663 (mac->GetTidToLinkMapping(dest->GetAddress(), dir)->get() == mapping),
1664 true,
1665 "Incorrect link mapping stored by "
1666 << (mac->GetTypeOfStation() == AP ? "AP" : "non-AP") << " MLD for " << dir
1667 << " direction");
1668 }
1669 };
1670
1671 auto storedMapping = m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1 &&
1672 m_apNegSupport > WifiTidToLinkMappingNegSupport::NOT_SUPPORTED;
1673 checkStoredMapping(m_apMac, m_staMacs[0], WifiDirection::DOWNLINK, storedMapping);
1674 checkStoredMapping(m_apMac, m_staMacs[0], WifiDirection::UPLINK, storedMapping);
1675 checkStoredMapping(m_staMacs[0], m_apMac, WifiDirection::DOWNLINK, storedMapping);
1676 checkStoredMapping(m_staMacs[0], m_apMac, WifiDirection::UPLINK, storedMapping);
1677}
1678
1679void
1681{
1682 if (m_staMacs[0]->GetNLinks() == 1)
1683 {
1684 // no link is disabled on a single link device
1685 return;
1686 }
1687
1688 for (const auto& linkId : m_staMacs[0]->GetLinkIds())
1689 {
1690 auto it = std::find(m_setupLinks.begin(), m_setupLinks.end(), linkId);
1691 if (it == m_setupLinks.end())
1692 {
1693 // the link has not been setup
1694 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId)->GetState()->IsStateOff(),
1695 true,
1696 "Link " << +linkId << " has not been setup but is not disabled");
1697 continue;
1698 }
1699
1700 // the link has been setup and must be active
1701 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId)->GetState()->IsStateOff(),
1702 false,
1703 "Expecting link " << +linkId << " to be active");
1704 }
1705}
1706
1707void
1708MultiLinkSetupTest::CheckQosData(Ptr<WifiMpdu> mpdu, uint8_t linkId, std::size_t index)
1709{
1711 const auto& hdr = mpdu->GetHeader();
1712
1713 NS_TEST_ASSERT_MSG_EQ(hdr.IsQosData(), true, "Expected a QoS data frame");
1714
1715 if (!hdr.IsToDs() && hdr.IsFromDs())
1716 {
1717 dir = WifiDirection::DOWNLINK;
1718 }
1719 else if (hdr.IsToDs() && !hdr.IsFromDs())
1720 {
1721 dir = WifiDirection::UPLINK;
1722 }
1723 else
1724 {
1725 NS_ABORT_MSG("Invalid combination for QoS data frame: ToDS(" << hdr.IsToDs() << ") FromDS("
1726 << hdr.IsFromDs() << ")");
1727 }
1728
1729 const auto& tid1 = (dir == WifiDirection::DOWNLINK) ? m_dlTid1 : m_ulTid1;
1730 const auto& tid2 = (dir == WifiDirection::DOWNLINK) ? m_dlTid2 : m_ulTid2;
1731 uint8_t tid = hdr.GetQosTid();
1732
1733 NS_TEST_ASSERT_MSG_NE((tid == tid1),
1734 (tid2.has_value() && tid == *tid2),
1735 "QoS frame with unexpected TID " << +tid);
1736
1737 // lambda to find the link set the given TID is mapped to
1738 auto findLinkSet = [this, dir](uint8_t tid) -> std::set<uint8_t> {
1739 std::set<uint8_t> linkSet(m_setupLinks.cbegin(), m_setupLinks.cend());
1740 if (auto mappingOptRef = m_apMac->GetTidToLinkMapping(m_staMacs[0]->GetAddress(), dir))
1741 {
1742 // if the TID is not present in the mapping, it is mapped to all setup links
1743 if (auto it = mappingOptRef->get().find(tid); it != mappingOptRef->get().cend())
1744 {
1745 linkSet = it->second;
1746 NS_ASSERT_MSG(!linkSet.empty(), "TID " << +tid << " mapped to no link");
1747 }
1748 }
1749 return linkSet;
1750 };
1751
1752 auto linkSet = findLinkSet(tid);
1753 auto& qosFrames = (tid == tid1) ? m_qosFrames1 : m_qosFrames2;
1754
1755 // Let N the size of the link set, the first N QoS data frames are sent simultaneously
1756 // on the links of the set, the others (if any) will be sent afterwards on such links
1757
1758 // number of concurrent frames of the same TID transmitted so far (excluding current frame)
1759 std::size_t nConcurFrames = std::min(qosFrames.size(), linkSet.size());
1760
1761 // iterate over the concurrent frames of the same TID transmitted so far
1762 for (std::size_t i = 0; i < nConcurFrames; i++)
1763 {
1764 auto prev = qosFrames[i];
1765
1766 // TX duration of i-th frame
1767 auto band = m_apMac->GetWifiPhy(m_txPsdus[prev].linkId)->GetPhyBand();
1768 Time txDuration =
1769 WifiPhy::CalculateTxDuration(m_txPsdus[prev].psduMap, m_txPsdus[prev].txVector, band);
1770
1771 // the current frame is transmitted concurrently with this previous frame if it is
1772 // within the first N (size of the link set) frames, otherwise it is transmitted after
1773 // this previous frame if they have been transmitted on the same link
1774 if (qosFrames.size() < linkSet.size())
1775 {
1776 // the current frame can be sent concurrently with this previous frame
1777 NS_TEST_EXPECT_MSG_LT(m_txPsdus[index].startTx,
1778 m_txPsdus[prev].startTx + txDuration,
1779 "The " << dir << " QoS frame number " << qosFrames.size()
1780 << " was not sent concurrently with others on link "
1781 << +linkId << " which TID " << +tid << " is mapped to");
1782 }
1783 else if (m_txPsdus[prev].linkId == linkId)
1784 {
1785 // the current frame is sent afterwards
1786 NS_TEST_EXPECT_MSG_GT(m_txPsdus[index].startTx,
1787 m_txPsdus[prev].startTx + txDuration,
1788 "The " << dir << " QoS frame number " << qosFrames.size()
1789 << " was sent concurrently with others on a link "
1790 << +linkId << " which TID " << +tid << " is mapped to");
1791 }
1792 }
1793
1794 if (m_apMac->GetNLinks() > 1 && m_staMacs[0]->GetNLinks() > 1)
1795 {
1796 NS_TEST_EXPECT_MSG_EQ(std::count(linkSet.cbegin(), linkSet.cend(), linkId),
1797 1,
1798 "QoS frame sent on Link ID "
1799 << +linkId << " that does not belong to the link set of TID "
1800 << +tid);
1801 }
1802
1803 if (tid2)
1804 {
1805 // QoS frames of two distinct TIDs are sent.
1806 auto otherTid = (tid == tid1) ? *tid2 : tid1;
1807 const auto& otherQosFrames = (tid == tid1) ? m_qosFrames2 : m_qosFrames1;
1808 auto otherLinkSet = findLinkSet(otherTid);
1809
1810 // number of concurrent frames of the other TID transmitted so far
1811 std::size_t nOtherConcurFrames = std::min(otherQosFrames.size(), otherLinkSet.size());
1812
1813 // iterate over the concurrent frames of the other TID
1814 for (std::size_t i = 0; i < nOtherConcurFrames; i++)
1815 {
1816 auto prev = otherQosFrames[i];
1817
1818 // TX duration of i-th frame
1819 auto band = m_apMac->GetWifiPhy(m_txPsdus[prev].linkId)->GetPhyBand();
1820 Time txDuration = WifiPhy::CalculateTxDuration(m_txPsdus[prev].psduMap,
1821 m_txPsdus[prev].txVector,
1822 band);
1823
1824 // the current frame is transmitted concurrently with this previous frame of the
1825 // other TID if it is within the first N (size of the link set) frames of its TID
1826 if (qosFrames.size() < linkSet.size())
1827 {
1828 // the current frame can be sent concurrently with this previous frame
1829 NS_TEST_EXPECT_MSG_LT(m_txPsdus[index].startTx,
1830 m_txPsdus[prev].startTx + txDuration,
1831 "The " << dir << " QoS frame number " << qosFrames.size()
1832 << " was not sent concurrently with others with TID "
1833 << +otherTid);
1834 }
1835 }
1836 }
1837
1838 // insert the frame
1839 qosFrames.emplace_back(index);
1840
1841 if (qosFrames.size() == m_setupLinks.size())
1842 {
1843 qosFrames.clear();
1844 }
1845}
1846
1847/**
1848 * Tested traffic patterns.
1849 */
1850enum class WifiTrafficPattern : uint8_t
1851{
1852 STA_TO_STA = 0,
1853 STA_TO_AP,
1854 AP_TO_STA,
1857};
1858
1859/**
1860 * Block Ack agreement enabled/disabled
1861 */
1862enum class WifiBaEnabled : uint8_t
1863{
1864 NO = 0,
1865 YES
1866};
1867
1868/**
1869 * Whether to send a BlockAckReq after a missed BlockAck
1870 */
1871enum class WifiUseBarAfterMissedBa : uint8_t
1872{
1873 NO = 0,
1874 YES
1875};
1876
1877/**
1878 * \ingroup wifi-test
1879 * \ingroup tests
1880 *
1881 * \brief Test data transmission between two MLDs.
1882 *
1883 * This test sets up an AP MLD and two non-AP MLDs having a variable number of links.
1884 * The RF channels to set each link to are provided as input parameters through the test
1885 * case constructor, along with the identifiers (starting at 0) of the links that cannot
1886 * switch PHY band (if any). This test aims at veryfing the successful transmission of both
1887 * unicast QoS data frames (from one station to another, from one station to the AP, from
1888 * the AP to the station) and broadcast QoS data frames (from the AP or from one station).
1889 * In the scenarios in which the AP forwards frames (i.e., from one station to another and
1890 * from one station to broadcast) the client application generates only 4 packets, in order
1891 * to limit the probability of collisions. In the other scenarios, 8 packets are generated.
1892 * When BlockAck agreements are enabled, the maximum A-MSDU size is set such that two
1893 * packets can be aggregated in an A-MSDU. The MPDU with sequence number equal to 1 is
1894 * corrupted (once, by using a post reception error model) to test its successful
1895 * re-transmission, unless the traffic scenario is from the AP to broadcast (broadcast frames
1896 * are not retransmitted) or is a scenario where the AP forwards frame (to limit the
1897 * probability of collisions).
1898 *
1899 * When BlockAck agreements are enabled, we also corrupt a BlockAck frame, so as to simulate
1900 * the case of BlockAck timeout. Both the case where a BlockAckReq is sent and the case where
1901 * data frame are retransmitted are tested. Finally, when BlockAck agreements are enabled, we
1902 * also enable the concurrent transmission of data frames over two links and check that at
1903 * least one MPDU is concurrently transmitted over two links.
1904 */
1906{
1907 public:
1908 /**
1909 * Constructor
1910 *
1911 * \param baseParams common configuration parameters
1912 * \param trafficPattern the pattern of traffic to generate
1913 * \param baEnabled whether BA agreement is enabled or disabled
1914 * \param useBarAfterMissedBa whether a BAR or Data frames are sent after missed BlockAck
1915 * \param nMaxInflight the max number of links on which an MPDU can be simultaneously inflight
1916 * (unused if Block Ack agreements are not established)
1917 */
1918 MultiLinkTxTest(const BaseParams& baseParams,
1919 WifiTrafficPattern trafficPattern,
1920 WifiBaEnabled baEnabled,
1921 WifiUseBarAfterMissedBa useBarAfterMissedBa,
1922 uint8_t nMaxInflight);
1923 ~MultiLinkTxTest() override = default;
1924
1925 protected:
1926 /**
1927 * Check the content of a received BlockAck frame when the max number of links on which
1928 * an MPDU can be inflight is one.
1929 *
1930 * \param psdu the PSDU containing the BlockAck
1931 * \param txVector the TXVECTOR used to transmit the BlockAck
1932 * \param linkId the ID of the link on which the BlockAck was transmitted
1933 */
1934 void CheckBlockAck(Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId);
1935
1936 void Transmit(Ptr<WifiMac> mac,
1937 uint8_t phyId,
1938 WifiConstPsduMap psduMap,
1939 WifiTxVector txVector,
1940 double txPowerW) override;
1941 void DoSetup() override;
1942 void DoRun() override;
1943
1944 private:
1945 void StartTraffic() override;
1946
1947 /// Receiver address-indexed map of list error models
1948 using RxErrorModelMap = std::unordered_map<Mac48Address, Ptr<ListErrorModel>, WifiAddressHash>;
1949
1950 RxErrorModelMap m_errorModels; ///< error rate models to corrupt packets
1951 std::list<uint64_t> m_uidList; ///< list of UIDs of packets to corrupt
1952 bool m_dataCorrupted{false}; ///< whether second data frame has been already corrupted
1953 WifiTrafficPattern m_trafficPattern; ///< the pattern of traffic to generate
1954 bool m_baEnabled; ///< whether BA agreement is enabled or disabled
1955 bool m_useBarAfterMissedBa; ///< whether to send BAR after missed BlockAck
1956 std::size_t m_nMaxInflight; ///< max number of links on which an MPDU can be inflight
1957 std::size_t m_nPackets; ///< number of application packets to generate
1958 std::size_t m_blockAckCount{0}; ///< transmitted BlockAck counter
1959 std::size_t m_blockAckReqCount{0}; ///< transmitted BlockAckReq counter
1960 std::map<uint16_t, std::size_t> m_inflightCount; ///< seqNo-indexed max number of simultaneous
1961 ///< transmissions of a data frame
1962 Ptr<WifiMac> m_sourceMac; ///< MAC of the node sending application packets
1963};
1964
1966 WifiTrafficPattern trafficPattern,
1967 WifiBaEnabled baEnabled,
1968 WifiUseBarAfterMissedBa useBarAfterMissedBa,
1969 uint8_t nMaxInflight)
1971 std::string("Check data transmission between MLDs ") +
1972 (baEnabled == WifiBaEnabled::YES
1973 ? (useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES
1974 ? "with BA agreement, send BAR after BlockAck timeout"
1975 : "with BA agreement, send Data frames after BlockAck timeout")
1976 : "without BA agreement") +
1977 " (Traffic pattern: " + std::to_string(static_cast<uint8_t>(trafficPattern)) +
1978 (baEnabled == WifiBaEnabled::YES ? ", nMaxInflight=" + std::to_string(nMaxInflight)
1979 : "") +
1980 ")",
1981 2,
1982 baseParams),
1983 m_trafficPattern(trafficPattern),
1984 m_baEnabled(baEnabled == WifiBaEnabled::YES),
1985 m_useBarAfterMissedBa(useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES),
1986 m_nMaxInflight(nMaxInflight),
1987 m_nPackets(trafficPattern == WifiTrafficPattern::STA_TO_BCAST ||
1988 trafficPattern == WifiTrafficPattern::STA_TO_STA
1989 ? 4
1990 : 8)
1991{
1992}
1993
1994void
1996 uint8_t phyId,
1997 WifiConstPsduMap psduMap,
1998 WifiTxVector txVector,
1999 double txPowerW)
2000{
2001 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2002 auto linkId = m_txPsdus.back().linkId;
2003
2004 auto psdu = psduMap.begin()->second;
2005
2006 switch (psdu->GetHeader(0).GetType())
2007 {
2009 // a management frame is a DL frame if TA equals BSSID
2010 CheckAddresses(psdu,
2011 psdu->GetHeader(0).GetAddr2() == psdu->GetHeader(0).GetAddr3() ? DL : UL);
2012 if (!m_baEnabled)
2013 {
2014 // corrupt all management action frames (ADDBA Request frames) to prevent
2015 // the establishment of a BA agreement
2016 m_uidList.push_front(psdu->GetPacket()->GetUid());
2017 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2018 NS_LOG_INFO("CORRUPTED");
2019 }
2020 break;
2021 case WIFI_MAC_QOSDATA:
2022 CheckAddresses(psdu);
2023
2024 for (const auto& mpdu : *psdu)
2025 {
2026 // determine the max number of simultaneous transmissions for this MPDU
2027 // (only if sent by the traffic source and this is not a broadcast frame)
2028 if (m_baEnabled && m_sourceMac->GetLinkIds().count(linkId) == 1 &&
2029 m_sourceMac->GetFrameExchangeManager(linkId)->GetAddress() ==
2030 mpdu->GetHeader().GetAddr2() &&
2031 !mpdu->GetHeader().GetAddr1().IsGroup())
2032 {
2033 auto seqNo = mpdu->GetHeader().GetSequenceNumber();
2034 auto [it, success] =
2035 m_inflightCount.insert({seqNo, mpdu->GetInFlightLinkIds().size()});
2036 if (!success)
2037 {
2038 it->second = std::max(it->second, mpdu->GetInFlightLinkIds().size());
2039 }
2040 }
2041 }
2042 for (std::size_t i = 0; i < psdu->GetNMpdus(); i++)
2043 {
2044 // corrupt QoS data frame with sequence number equal to 1 (only once) if we are
2045 // not in the AP to broadcast traffic pattern (broadcast frames are not retransmitted)
2046 // nor in the STA to broadcast or STA to STA traffic patterns (retransmissions from
2047 // STA 1 could collide with frames forwarded by the AP)
2048 if (psdu->GetHeader(i).GetSequenceNumber() != 1 ||
2049 m_trafficPattern == WifiTrafficPattern::AP_TO_BCAST ||
2050 m_trafficPattern == WifiTrafficPattern::STA_TO_BCAST ||
2051 m_trafficPattern == WifiTrafficPattern::STA_TO_STA)
2052 {
2053 continue;
2054 }
2055 auto uid = psdu->GetPayload(i)->GetUid();
2056 if (!m_dataCorrupted)
2057 {
2058 m_uidList.push_front(uid);
2059 m_dataCorrupted = true;
2060 NS_LOG_INFO("CORRUPTED");
2061 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2062 }
2063 else
2064 {
2065 // do not corrupt the QoS data frame anymore
2066 if (auto it = std::find(m_uidList.cbegin(), m_uidList.cend(), uid);
2067 it != m_uidList.cend())
2068 {
2069 m_uidList.erase(it);
2070 }
2071 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2072 }
2073 break;
2074 }
2075 break;
2076 case WIFI_MAC_CTL_BACKRESP: {
2077 // ignore BlockAck frames not addressed to the source of the application packets
2078 if (!m_sourceMac->GetLinkIdByAddress(psdu->GetHeader(0).GetAddr1()))
2079 {
2080 break;
2081 }
2082 if (m_nMaxInflight > 1)
2083 {
2084 // we do not check the content of BlockAck when m_nMaxInflight is greater than 1
2085 break;
2086 }
2087 CheckBlockAck(psdu, txVector, linkId);
2089 if (m_blockAckCount == 2)
2090 {
2091 // corrupt the second BlockAck frame to simulate a missed BlockAck
2092 m_uidList.push_front(psdu->GetPacket()->GetUid());
2093 NS_LOG_INFO("CORRUPTED");
2094 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2095 }
2096 break;
2098 // ignore BlockAckReq frames not transmitted by the source of the application packets
2099 if (m_sourceMac->GetLinkIdByAddress(psdu->GetHeader(0).GetAddr2()))
2100 {
2102 }
2103 break;
2104 }
2105 default:;
2106 }
2107}
2108
2109void
2111 const WifiTxVector& txVector,
2112 uint8_t linkId)
2113{
2114 NS_TEST_ASSERT_MSG_EQ(m_baEnabled, true, "No BlockAck expected without BA agreement");
2115 NS_TEST_ASSERT_MSG_EQ((m_trafficPattern != WifiTrafficPattern::AP_TO_BCAST),
2116 true,
2117 "No BlockAck expected in AP to broadcast traffic pattern");
2118
2119 /*
2120 * ┌───────┬───────X ┌───────┐
2121 * link 0 │ 0 │ 1 │ │ 1 │
2122 * ───────┴───────┴───────┴┬──┬────┴───────┴┬───┬────────────────────────
2123 * │BA│ │ACK│
2124 * └──┘ └───┘
2125 * ┌───────┬───────┐ ┌───────┬───────┐
2126 * link 1 │ 2 │ 3 │ │ 2 │ 3 │
2127 * ────────────────────┴───────┴───────┴┬──X───┴───────┴───────┴┬──┬─────
2128 * │BA│ │BA│
2129 * └──┘ └──┘
2130 */
2131 auto mpdu = *psdu->begin();
2132 CtrlBAckResponseHeader blockAck;
2133 mpdu->GetPacket()->PeekHeader(blockAck);
2134 bool isMpdu1corrupted = (m_trafficPattern == WifiTrafficPattern::STA_TO_AP ||
2135 m_trafficPattern == WifiTrafficPattern::AP_TO_STA);
2136
2137 switch (m_blockAckCount)
2138 {
2139 case 0: // first BlockAck frame (all traffic patterns)
2141 true,
2142 "MPDU 0 expected to be successfully received");
2144 blockAck.IsPacketReceived(1),
2145 !isMpdu1corrupted,
2146 "MPDU 1 expected to be received only in STA_TO_STA/STA_TO_BCAST scenarios");
2147 // if there are at least two links setup, we expect all MPDUs to be inflight
2148 // (on distinct links)
2149 if (m_staMacs[0]->GetSetupLinkIds().size() > 1)
2150 {
2151 auto queue = m_sourceMac->GetTxopQueue(AC_BE);
2152 auto rcvMac = m_sourceMac == m_staMacs[0] ? StaticCast<WifiMac>(m_apMac)
2153 : StaticCast<WifiMac>(m_staMacs[1]);
2154 auto item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress());
2155 std::size_t nQueuedPkt = 0;
2156 auto delay = WifiPhy::CalculateTxDuration(psdu,
2157 txVector,
2158 rcvMac->GetWifiPhy(linkId)->GetPhyBand()) +
2159 MicroSeconds(1); // to account for propagation delay
2160
2161 while (item)
2162 {
2163 auto seqNo = item->GetHeader().GetSequenceNumber();
2164 NS_TEST_EXPECT_MSG_EQ(item->IsInFlight(),
2165 true,
2166 "MPDU with seqNo=" << seqNo << " is not in flight");
2167 auto linkIds = item->GetInFlightLinkIds();
2168 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
2169 1,
2170 "MPDU with seqNo=" << seqNo
2171 << " is in flight on multiple links");
2172 // The first two MPDUs are in flight on the same link on which the BlockAck
2173 // is sent. The other two MPDUs (only for AP to STA/STA to AP scenarios) are
2174 // in flight on a different link.
2175 auto srcLinkId = m_sourceMac->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1());
2176 NS_TEST_ASSERT_MSG_EQ(srcLinkId.has_value(),
2177 true,
2178 "Addr1 of BlockAck is not an originator's link address");
2179 NS_TEST_EXPECT_MSG_EQ((*linkIds.begin() == *srcLinkId),
2180 (seqNo <= 1),
2181 "MPDU with seqNo=" << seqNo
2182 << " in flight on unexpected link");
2183 // check the Retry subfield and whether this MPDU is still queued
2184 // after the originator has processed this BlockAck
2185
2186 // MPDUs acknowledged via this BlockAck are no longer queued
2187 bool isQueued = (seqNo > (isMpdu1corrupted ? 0 : 1));
2188 // The Retry subfield is set if the MPDU has not been acknowledged (i.e., it
2189 // is still queued) and has been transmitted on the same link as the BlockAck
2190 // (i.e., its sequence number is less than or equal to 1)
2191 bool isRetry = isQueued && seqNo <= 1;
2192
2193 Simulator::Schedule(delay, [this, item, isQueued, isRetry]() {
2194 NS_TEST_EXPECT_MSG_EQ(item->IsQueued(),
2195 isQueued,
2196 "MPDU with seqNo="
2197 << item->GetHeader().GetSequenceNumber() << " should "
2198 << (isQueued ? "" : "not") << " be queued");
2200 item->GetHeader().IsRetry(),
2201 isRetry,
2202 "Unexpected value for the Retry subfield of the MPDU with seqNo="
2203 << item->GetHeader().GetSequenceNumber());
2204 });
2205
2206 nQueuedPkt++;
2207 item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress(), item);
2208 }
2209 // Each MPDU contains an A-MSDU consisting of two MSDUs
2210 NS_TEST_EXPECT_MSG_EQ(nQueuedPkt, m_nPackets / 2, "Unexpected number of queued MPDUs");
2211 }
2212 break;
2213 case 1: // second BlockAck frame (STA to AP and AP to STA traffic patterns only)
2214 case 2: // third BlockAck frame (STA to AP and AP to STA traffic patterns only)
2215 NS_TEST_EXPECT_MSG_EQ((m_trafficPattern == WifiTrafficPattern::AP_TO_STA ||
2216 m_trafficPattern == WifiTrafficPattern::STA_TO_AP),
2217 true,
2218 "Did not expect to receive a second BlockAck");
2219 // the second BlockAck is corrupted, but the data frames have been received successfully
2220 std::pair<uint16_t, uint16_t> seqNos;
2221 // if multiple links were setup, the transmission of the second A-MPDU started before
2222 // the end of the first one, so the second A-MPDU includes MPDUs with sequence numbers
2223 // 2 and 3. Otherwise, MPDU with sequence number 1 is retransmitted along with the MPDU
2224 // with sequence number 2.
2225 if (m_staMacs[0]->GetSetupLinkIds().size() > 1)
2226 {
2227 seqNos = {2, 3};
2228 }
2229 else
2230 {
2231 seqNos = {1, 2};
2232 }
2233 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(seqNos.first),
2234 true,
2235 "MPDU " << seqNos.first << " expected to be successfully received");
2236 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(seqNos.second),
2237 true,
2238 "MPDU " << seqNos.second << " expected to be successfully received");
2239 break;
2240 }
2241}
2242
2243void
2245{
2247
2248 if (m_baEnabled)
2249 {
2250 // Enable A-MSDU aggregation. Max A-MSDU size is set such that two MSDUs can be aggregated
2251 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
2252 {
2253 mac->SetAttribute("BE_MaxAmsduSize", UintegerValue(2100));
2254 mac->GetQosTxop(AC_BE)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2256 mac->GetQosTxop(AC_BE)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2257 }
2258 }
2259
2260 // install post reception error model on all devices
2261 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
2262 {
2263 auto errorModel = CreateObject<ListErrorModel>();
2264 m_errorModels[m_apMac->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2265 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2266 }
2267 for (std::size_t i : {0, 1})
2268 {
2269 for (const auto linkId : m_staMacs[i]->GetLinkIds())
2270 {
2271 auto errorModel = CreateObject<ListErrorModel>();
2272 m_errorModels[m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2273 m_staMacs[i]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2274 }
2275 }
2276}
2277
2278void
2280{
2281 Address destAddr;
2282
2283 switch (m_trafficPattern)
2284 {
2285 case WifiTrafficPattern::STA_TO_STA:
2287 destAddr = m_staMacs[1]->GetDevice()->GetAddress();
2288 break;
2289 case WifiTrafficPattern::STA_TO_AP:
2291 destAddr = m_apMac->GetDevice()->GetAddress();
2292 break;
2293 case WifiTrafficPattern::AP_TO_STA:
2295 destAddr = m_staMacs[1]->GetDevice()->GetAddress();
2296 break;
2297 case WifiTrafficPattern::AP_TO_BCAST:
2299 destAddr = Mac48Address::GetBroadcast();
2300 break;
2301 case WifiTrafficPattern::STA_TO_BCAST:
2303 destAddr = Mac48Address::GetBroadcast();
2304 break;
2305 }
2306
2307 PacketSocketAddress sockAddr;
2309 sockAddr.SetPhysicalAddress(destAddr);
2310 sockAddr.SetProtocol(1);
2311
2312 // install first client application generating at most 4 packets
2314 GetApplication(sockAddr, std::min<std::size_t>(m_nPackets, 4), 1000));
2315
2316 if (m_nPackets > 4)
2317 {
2318 // install a second client application generating the remaining packets and
2319 // starting during transmission of first A-MPDU, if multiple links are setup
2321 GetApplication(sockAddr, m_nPackets - 4, 1000, MilliSeconds(4)));
2322 }
2323
2325}
2326
2327void
2329{
2331
2332 // Expected number of packets received by each node (AP, STA 0, STA 1) at application layer
2333 std::array<std::size_t, 3> expectedRxPkts{};
2334
2335 switch (m_trafficPattern)
2336 {
2337 case WifiTrafficPattern::STA_TO_STA:
2338 case WifiTrafficPattern::AP_TO_STA:
2339 // only STA 1 receives the m_nPackets packets that have been transmitted
2340 expectedRxPkts[2] = m_nPackets;
2341 break;
2342 case WifiTrafficPattern::STA_TO_AP:
2343 // only the AP receives the m_nPackets packets that have been transmitted
2344 expectedRxPkts[0] = m_nPackets;
2345 break;
2346 case WifiTrafficPattern::AP_TO_BCAST:
2347 // the AP replicates the broadcast frames on all the links, hence each station
2348 // receives the m_nPackets packets N times, where N is the number of setup link
2349 expectedRxPkts[1] = m_nPackets * m_staMacs[0]->GetSetupLinkIds().size();
2350 expectedRxPkts[2] = m_nPackets * m_staMacs[1]->GetSetupLinkIds().size();
2351 break;
2352 case WifiTrafficPattern::STA_TO_BCAST:
2353 // the AP receives the m_nPackets packets and then replicates them on all the links,
2354 // hence STA 1 receives m_nPackets packets N times, where N is the number of setup link
2355 expectedRxPkts[0] = m_nPackets;
2356 expectedRxPkts[2] = m_nPackets * m_staMacs[1]->GetSetupLinkIds().size();
2357 break;
2358 }
2359
2361 +expectedRxPkts[0],
2362 "Unexpected number of packets received by the AP");
2364 +expectedRxPkts[1],
2365 "Unexpected number of packets received by STA 0");
2367 +expectedRxPkts[2],
2368 "Unexpected number of packets received by STA 1");
2369
2370 // check that the expected number of BlockAck frames are transmitted
2371 if (m_baEnabled && m_nMaxInflight == 1)
2372 {
2373 std::size_t expectedBaCount = 0;
2374 std::size_t expectedBarCount = 0;
2375
2376 switch (m_trafficPattern)
2377 {
2378 case WifiTrafficPattern::STA_TO_AP:
2379 case WifiTrafficPattern::AP_TO_STA:
2380 // two A-MPDUs are transmitted and one BlockAck is corrupted
2381 expectedBaCount = 3;
2382 // one BlockAckReq is sent if m_useBarAfterMissedBa is true
2383 expectedBarCount = m_useBarAfterMissedBa ? 1 : 0;
2384 break;
2385 case WifiTrafficPattern::STA_TO_STA:
2386 case WifiTrafficPattern::STA_TO_BCAST:
2387 // only one A-MPDU is transmitted and the BlockAck is not corrupted
2388 expectedBaCount = 1;
2389 break;
2390 default:;
2391 }
2393 expectedBaCount,
2394 "Unexpected number of BlockAck frames");
2396 expectedBarCount,
2397 "Unexpected number of BlockAckReq frames");
2398 }
2399
2400 // check that setting the QosTxop::NMaxInflights attribute has the expected effect.
2401 // We do not support sending an MPDU multiple times concurrently without Block Ack
2402 // agreement. Also, broadcast frames are already duplicated and sent on all links.
2403 if (m_baEnabled && m_trafficPattern != WifiTrafficPattern::AP_TO_BCAST)
2404 {
2406 m_inflightCount.size(),
2407 m_nPackets / 2,
2408 "Did not collect number of simultaneous transmissions for all data frames");
2409
2410 auto nMaxInflight = std::min(m_nMaxInflight, m_staMacs[0]->GetSetupLinkIds().size());
2411 std::size_t maxCount = 0;
2412 for (const auto& [seqNo, count] : m_inflightCount)
2413 {
2415 count,
2416 nMaxInflight,
2417 "MPDU with seqNo=" << seqNo
2418 << " transmitted simultaneously more times than allowed");
2419 maxCount = std::max(maxCount, count);
2420 }
2421
2423 maxCount,
2424 nMaxInflight,
2425 "Expected that at least one data frame was transmitted simultaneously a number of "
2426 "times equal to the NMaxInflights attribute");
2427 }
2428
2430}
2431
2432/**
2433 * Tested MU traffic patterns.
2434 */
2435enum class WifiMuTrafficPattern : uint8_t
2436{
2440 UL_MU
2441};
2442
2443/**
2444 * \ingroup wifi-test
2445 * \ingroup tests
2446 *
2447 * \brief Test data transmission between MLDs using OFDMA MU transmissions
2448 *
2449 * This test sets up an AP MLD and two non-AP MLDs having a variable number of links.
2450 * The RF channels to set each link to are provided as input parameters through the test
2451 * case constructor, along with the identifiers (starting at 0) of the links that cannot
2452 * switch PHY band (if any). This test aims at veryfing the successful transmission of both
2453 * DL MU and UL MU frames. In the DL MU scenarios, the client applications installed on the
2454 * AP generate 8 packets addressed to each of the stations (plus 3 packets to trigger the
2455 * establishment of BlockAck agreements). In the UL MU scenario, client applications
2456 * installed on the stations generate 4 packets each (plus 3 packets to trigger the
2457 * establishment of BlockAck agreements).
2458 *
2459 * The maximum A-MSDU size is set such that two packets can be aggregated in an A-MSDU.
2460 * The MPDU with sequence number equal to 3 is corrupted (by using a post reception error
2461 * model) once and for a single station, to test its successful re-transmission.
2462 *
2463 * Also, we enable the concurrent transmission of data frames over two links and check that at
2464 * least one MPDU is concurrently transmitted over two links.
2465 */
2467{
2468 public:
2469 /**
2470 * Constructor
2471 *
2472 * \param baseParams common configuration parameters
2473 * \param muTrafficPattern the pattern of traffic to generate
2474 * \param useBarAfterMissedBa whether a BAR or Data frames are sent after missed BlockAck
2475 * \param nMaxInflight the max number of links on which an MPDU can be simultaneously inflight
2476 * (unused if Block Ack agreements are not established)
2477 */
2478 MultiLinkMuTxTest(const BaseParams& baseParams,
2479 WifiMuTrafficPattern muTrafficPattern,
2480 WifiUseBarAfterMissedBa useBarAfterMissedBa,
2481 uint8_t nMaxInflight);
2482 ~MultiLinkMuTxTest() override = default;
2483
2484 protected:
2485 /**
2486 * Check the content of a received BlockAck frame when the max number of links on which
2487 * an MPDU can be inflight is one.
2488 *
2489 * \param psdu the PSDU containing the BlockAck
2490 * \param txVector the TXVECTOR used to transmit the BlockAck
2491 * \param linkId the ID of the link on which the BlockAck was transmitted
2492 */
2493 void CheckBlockAck(Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId);
2494
2495 void Transmit(Ptr<WifiMac> mac,
2496 uint8_t phyId,
2497 WifiConstPsduMap psduMap,
2498 WifiTxVector txVector,
2499 double txPowerW) override;
2500 void DoSetup() override;
2501 void DoRun() override;
2502
2503 private:
2504 void StartTraffic() override;
2505
2506 /// Receiver address-indexed map of list error models
2507 using RxErrorModelMap = std::unordered_map<Mac48Address, Ptr<ListErrorModel>, WifiAddressHash>;
2508
2509 /// A pair of a MAC address (the address of the receiver for DL frames and the address of
2510 /// the sender for UL frames) and a sequence number identifying a transmitted QoS data frame
2511 using AddrSeqNoPair = std::pair<Mac48Address, uint16_t>;
2512
2513 RxErrorModelMap m_errorModels; ///< error rate models to corrupt packets
2514 std::list<uint64_t> m_uidList; ///< list of UIDs of packets to corrupt
2515 std::optional<Mac48Address> m_dataCorruptedSta; ///< MAC address of the station that received
2516 ///< MPDU with SeqNo=2 corrupted
2517 bool m_waitFirstTf{true}; ///< whether we are waiting for the first Basic Trigger Frame
2518 WifiMuTrafficPattern m_muTrafficPattern; ///< the pattern of traffic to generate
2519 bool m_useBarAfterMissedBa; ///< whether to send BAR after missed BlockAck
2520 std::size_t m_nMaxInflight; ///< max number of links on which an MPDU can be inflight
2521 std::vector<PacketSocketAddress> m_sockets; ///< packet socket addresses for STAs
2522 std::size_t m_nPackets; ///< number of application packets to generate
2523 std::size_t m_blockAckCount{0}; ///< transmitted BlockAck counter
2524 std::size_t m_tfCount{0}; ///< transmitted Trigger Frame counter
2525 // std::size_t m_blockAckReqCount{0}; ///< transmitted BlockAckReq counter
2526 std::map<AddrSeqNoPair, std::size_t> m_inflightCount; ///< max number of simultaneous
2527 ///< transmissions of each data frame
2528 Ptr<WifiMac> m_sourceMac; ///< MAC of the node sending application packets
2529};
2530
2532 WifiMuTrafficPattern muTrafficPattern,
2533 WifiUseBarAfterMissedBa useBarAfterMissedBa,
2534 uint8_t nMaxInflight)
2536 std::string("Check MU data transmission between MLDs ") +
2537 (useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES
2538 ? "(send BAR after BlockAck timeout,"
2539 : "(send Data frames after BlockAck timeout,") +
2540 " MU Traffic pattern: " + std::to_string(static_cast<uint8_t>(muTrafficPattern)) +
2541 ", nMaxInflight=" + std::to_string(nMaxInflight) + ")",
2542 2,
2543 baseParams),
2544 m_muTrafficPattern(muTrafficPattern),
2545 m_useBarAfterMissedBa(useBarAfterMissedBa == WifiUseBarAfterMissedBa::YES),
2546 m_nMaxInflight(nMaxInflight),
2547 m_sockets(m_nStations),
2548 m_nPackets(muTrafficPattern == WifiMuTrafficPattern::UL_MU ? 4 : 8)
2549{
2550}
2551
2552void
2554 uint8_t phyId,
2555 WifiConstPsduMap psduMap,
2556 WifiTxVector txVector,
2557 double txPowerW)
2558{
2559 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2560 auto linkId = m_txPsdus.back().linkId;
2561
2562 CtrlTriggerHeader trigger;
2563
2564 for (const auto& [staId, psdu] : psduMap)
2565 {
2566 switch (psdu->GetHeader(0).GetType())
2567 {
2568 case WIFI_MAC_QOSDATA:
2569 CheckAddresses(psdu);
2570 if (psdu->GetHeader(0).HasData())
2571 {
2572 bool isDl = psdu->GetHeader(0).IsFromDs();
2573 auto linkAddress =
2574 isDl ? psdu->GetHeader(0).GetAddr1() : psdu->GetHeader(0).GetAddr2();
2575 auto address = m_apMac->GetMldAddress(linkAddress).value_or(linkAddress);
2576
2577 for (const auto& mpdu : *psdu)
2578 {
2579 // determine the max number of simultaneous transmissions for this MPDU
2580 auto seqNo = mpdu->GetHeader().GetSequenceNumber();
2581 auto [it, success] = m_inflightCount.insert(
2582 {{address, seqNo}, mpdu->GetInFlightLinkIds().size()});
2583 if (!success)
2584 {
2585 it->second = std::max(it->second, mpdu->GetInFlightLinkIds().size());
2586 }
2587 }
2588 for (std::size_t i = 0; i < psdu->GetNMpdus(); i++)
2589 {
2590 // MPDUs with seqNo=2 are always transmitted in an MU PPDU
2591 if (psdu->GetHeader(i).GetSequenceNumber() == 2)
2592 {
2593 if (m_muTrafficPattern == WifiMuTrafficPattern::UL_MU)
2594 {
2595 NS_TEST_EXPECT_MSG_EQ(txVector.IsUlMu(),
2596 true,
2597 "MPDU " << **std::next(psdu->begin(), i)
2598 << " not transmitted in a TB PPDU");
2599 }
2600 else
2601 {
2602 NS_TEST_EXPECT_MSG_EQ(txVector.GetHeMuUserInfoMap().size(),
2603 2,
2604 "MPDU " << **std::next(psdu->begin(), i)
2605 << " not transmitted in a DL MU PPDU");
2606 }
2607 }
2608 // corrupt QoS data frame with sequence number equal to 3 (only once)
2609 if (psdu->GetHeader(i).GetSequenceNumber() != 3)
2610 {
2611 continue;
2612 }
2613 auto uid = psdu->GetPayload(i)->GetUid();
2614 if (!m_dataCorruptedSta)
2615 {
2616 m_uidList.push_front(uid);
2617 m_dataCorruptedSta = isDl ? psdu->GetAddr1() : psdu->GetAddr2();
2618 NS_LOG_INFO("CORRUPTED");
2619 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2620 }
2621 else if ((isDl && m_dataCorruptedSta == psdu->GetAddr1()) ||
2622 (!isDl && m_dataCorruptedSta == psdu->GetAddr2()))
2623 {
2624 // do not corrupt the QoS data frame anymore
2625 if (auto it = std::find(m_uidList.cbegin(), m_uidList.cend(), uid);
2626 it != m_uidList.cend())
2627 {
2628 m_uidList.erase(it);
2629 }
2630 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2631 }
2632 break;
2633 }
2634 }
2635 break;
2637 if (m_nMaxInflight > 1)
2638 {
2639 // we do not check the content of BlockAck when m_nMaxInflight is greater than 1
2640 break;
2641 }
2642 CheckBlockAck(psdu, txVector, linkId);
2644 // to simulate a missed BlockAck, corrupt the fifth BlockAck frame (the first
2645 // two BlockAck frames are sent to acknowledge the QoS data frames that triggered
2646 // the establishment of Block Ack agreements)
2647 if (m_blockAckCount == 5)
2648 {
2649 // corrupt the third BlockAck frame to simulate a missed BlockAck
2650 m_uidList.push_front(psdu->GetPacket()->GetUid());
2651 NS_LOG_INFO("CORRUPTED");
2652 m_errorModels.at(psdu->GetAddr1())->SetList(m_uidList);
2653 }
2654 break;
2656 psdu->GetPayload(0)->PeekHeader(trigger);
2657 // the MU scheduler requests channel access on all links but we have to perform the
2658 // following actions only once (hence why we only consider TF transmitted on link 0)
2659 if (trigger.IsBasic() && m_waitFirstTf)
2660 {
2661 m_waitFirstTf = false;
2662 // the AP is starting the transmission of the Basic Trigger frame, so generate
2663 // the configured number of packets at STAs, which are sent in TB PPDUs, when
2664 // transmission of the Trigger Frame ends
2665 auto band = mac->GetWifiPhy(linkId)->GetPhyBand();
2666 Time txDuration = WifiPhy::CalculateTxDuration(psduMap, txVector, band);
2667 for (uint8_t i = 0; i < m_nStations; i++)
2668 {
2669 m_staMacs[i]->GetDevice()->GetNode()->AddApplication(
2670 GetApplication(m_sockets[i], m_nPackets, 450, txDuration, i * 4));
2671 }
2672 }
2673 if (++m_tfCount == m_staMacs[0]->GetSetupLinkIds().size())
2674 {
2675 // a TF has been sent on all the setup links, we can now disable UL OFDMA
2676 auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
2677 NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "Expected an aggregated MU scheduler");
2678 muScheduler->SetAttribute("EnableUlOfdma", BooleanValue(false));
2679 }
2680 break;
2681 default:;
2682 }
2683 }
2684}
2685
2686void
2688 const WifiTxVector& txVector,
2689 uint8_t linkId)
2690{
2691 /*
2692 * Example sequence with DL_MU_BAR_BA_SEQUENCE
2693 * ┌───────┬───────X
2694 * (To:1) │ 2 │ 3 │
2695 * ├───────┼───────┤ ┌───┐ ┌───────┐
2696 * [link 0] (To:0) │ 2 │ 3 │ │BAR│ (To:1) │ 3 │
2697 * ────────────────┴───────┴───────┴┬──┼───┼──┬──────────┴───────┴┬───┬────────
2698 * │BA│ │BA│ │ACK│
2699 * └──┘ └──┘ └───┘
2700 * ┌───────┬───────┐
2701 * (To:1) │ 4 │ 5 │
2702 * ├───────┼───────┤ ┌───┐ ┌───┐
2703 * [link 1] (To:0) │ 4 │ 5 │ │BAR│ │BAR│
2704 * ────────────────────────────┴───────┴───────┴┬──X────┴───┴┬──┼───┼──┬───────
2705 * │BA│ │BA│ │BA│
2706 * └──┘ └──┘ └──┘
2707 *
2708 * Example sequence with UL_MU
2709 *
2710 * ┌──┐ ┌────┐ ┌───┐
2711 * [link 0] │TF│ │M-BA│ │ACK│
2712 * ─────────┴──┴──┬───────┬───────┬──┴────┴────────────┬───────┬─┴───┴─────────
2713 * (From:0) │ 2 │ 3 │ (From:1) │ 3 │
2714 * ├───────┼───────┤ └───────┘
2715 * (From:1) │ 2 │ 3 │
2716 * └───────┴───────X
2717 * ┌──┐
2718 * [link 1] │TF│
2719 * ─────────┴──┴──┬───────────────┬────────────────────────────────────────────
2720 * (From:0) │ QoS Null │
2721 * ├───────────────┤
2722 * (From:1) │ QoS Null │
2723 * └───────────────┘
2724 */
2725 auto mpdu = *psdu->begin();
2726 CtrlBAckResponseHeader blockAck;
2727 mpdu->GetPacket()->PeekHeader(blockAck);
2728 bool isMpdu3corrupted;
2729
2730 switch (m_blockAckCount)
2731 {
2732 case 0:
2733 case 1: // Ignore the first two BlockAck frames that acknowledged frames sent to establish BA
2734 break;
2735 case 2:
2736 if (m_muTrafficPattern == WifiMuTrafficPattern::UL_MU)
2737 {
2738 NS_TEST_EXPECT_MSG_EQ(blockAck.IsMultiSta(), true, "Expected a Multi-STA BlockAck");
2739 for (uint8_t i = 0; i < m_nStations; i++)
2740 {
2741 auto indices = blockAck.FindPerAidTidInfoWithAid(m_staMacs[i]->GetAssociationId());
2742 NS_TEST_ASSERT_MSG_EQ(indices.size(), 1, "Expected one Per AID TID Info per STA");
2743 auto index = indices.front();
2745 true,
2746 "Expected that a QoS data frame was corrupted");
2747 isMpdu3corrupted =
2748 m_staMacs[i]->GetLinkIdByAddress(*m_dataCorruptedSta).has_value();
2749 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(2, index),
2750 true,
2751 "MPDU 2 expected to be successfully received");
2752 NS_TEST_EXPECT_MSG_EQ(blockAck.IsPacketReceived(3, index),
2753 !isMpdu3corrupted,
2754 "Unexpected reception status for MPDU 3");
2755 }
2756
2757 break;
2758 }
2759 case 3:
2760 // BlockAck frames in response to the first DL MU PPDU
2761 isMpdu3corrupted = (mpdu->GetHeader().GetAddr2() == m_dataCorruptedSta);
2763 true,
2764 "MPDU 2 expected to be successfully received");
2766 !isMpdu3corrupted,
2767 "Unexpected reception status for MPDU 3");
2768 // in case of DL MU, if there are at least two links setup, we expect all MPDUs to
2769 // be inflight (on distinct links)
2770 if (m_muTrafficPattern != WifiMuTrafficPattern::UL_MU &&
2771 m_staMacs[0]->GetSetupLinkIds().size() > 1)
2772 {
2773 auto queue = m_apMac->GetTxopQueue(AC_BE);
2774 Ptr<StaWifiMac> rcvMac;
2775 if (m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress() ==
2776 mpdu->GetHeader().GetAddr2())
2777 {
2778 rcvMac = m_staMacs[0];
2779 }
2780 else if (m_staMacs[1]->GetFrameExchangeManager(linkId)->GetAddress() ==
2781 mpdu->GetHeader().GetAddr2())
2782 {
2783 rcvMac = m_staMacs[1];
2784 }
2785 else
2786 {
2787 NS_ABORT_MSG("BlockAck frame not sent by a station in DL scenario");
2788 }
2789 auto item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress());
2790 std::size_t nQueuedPkt = 0;
2791 auto delay = WifiPhy::CalculateTxDuration(psdu,
2792 txVector,
2793 rcvMac->GetWifiPhy(linkId)->GetPhyBand()) +
2794 MicroSeconds(1); // to account for propagation delay
2795
2796 while (item)
2797 {
2798 auto seqNo = item->GetHeader().GetSequenceNumber();
2799 NS_TEST_EXPECT_MSG_EQ(item->IsInFlight(),
2800 true,
2801 "MPDU with seqNo=" << seqNo << " is not in flight");
2802 auto linkIds = item->GetInFlightLinkIds();
2803 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
2804 1,
2805 "MPDU with seqNo=" << seqNo
2806 << " is in flight on multiple links");
2807 // The first two MPDUs are in flight on the same link on which the BlockAck
2808 // is sent. The other two MPDUs (only for AP to STA/STA to AP scenarios) are
2809 // in flight on a different link.
2810 auto srcLinkId = m_apMac->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1());
2811 NS_TEST_ASSERT_MSG_EQ(srcLinkId.has_value(),
2812 true,
2813 "Addr1 of BlockAck is not an originator's link address");
2814 NS_TEST_EXPECT_MSG_EQ((*linkIds.begin() == *srcLinkId),
2815 (seqNo <= 3),
2816 "MPDU with seqNo=" << seqNo
2817 << " in flight on unexpected link");
2818 // check the Retry subfield and whether this MPDU is still queued
2819 // after the originator has processed this BlockAck
2820
2821 // MPDUs acknowledged via this BlockAck are no longer queued
2822 bool isQueued = (seqNo > (isMpdu3corrupted ? 2 : 3));
2823 // The Retry subfield is set if the MPDU has not been acknowledged (i.e., it
2824 // is still queued) and has been transmitted on the same link as the BlockAck
2825 // (i.e., its sequence number is less than or equal to 2)
2826 bool isRetry = isQueued && seqNo <= 3;
2827
2828 Simulator::Schedule(delay, [this, item, isQueued, isRetry]() {
2829 NS_TEST_EXPECT_MSG_EQ(item->IsQueued(),
2830 isQueued,
2831 "MPDU with seqNo="
2832 << item->GetHeader().GetSequenceNumber() << " should "
2833 << (isQueued ? "" : "not") << " be queued");
2835 item->GetHeader().IsRetry(),
2836 isRetry,
2837 "Unexpected value for the Retry subfield of the MPDU with seqNo="
2838 << item->GetHeader().GetSequenceNumber());
2839 });
2840
2841 nQueuedPkt++;
2842 item = queue->PeekByTidAndAddress(0, rcvMac->GetAddress(), item);
2843 }
2844 // Each MPDU contains an A-MSDU consisting of two MSDUs
2845 NS_TEST_EXPECT_MSG_EQ(nQueuedPkt, m_nPackets / 2, "Unexpected number of queued MPDUs");
2846 }
2847 break;
2848 }
2849}
2850
2851void
2853{
2854 switch (m_muTrafficPattern)
2855 {
2856 case WifiMuTrafficPattern::DL_MU_BAR_BA_SEQUENCE:
2857 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2859 break;
2860 case WifiMuTrafficPattern::DL_MU_MU_BAR:
2861 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2863 break;
2864 case WifiMuTrafficPattern::DL_MU_AGGR_MU_BAR:
2865 Config::SetDefault("ns3::WifiDefaultAckManager::DlMuAckSequenceType",
2867 break;
2868 default:;
2869 }
2870
2872
2873 // Enable A-MSDU aggregation. Max A-MSDU size is set such that two MSDUs can be aggregated
2874 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
2875 {
2876 mac->SetAttribute("BE_MaxAmsduSize", UintegerValue(1050));
2877 mac->GetQosTxop(AC_BE)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2879 mac->GetQosTxop(AC_BE)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2880
2881 mac->SetAttribute("VI_MaxAmsduSize", UintegerValue(1050));
2882 mac->GetQosTxop(AC_VI)->SetAttribute("UseExplicitBarAfterMissedBlockAck",
2884 mac->GetQosTxop(AC_VI)->SetAttribute("NMaxInflights", UintegerValue(m_nMaxInflight));
2885 }
2886
2887 // aggregate MU scheduler
2888 auto muScheduler = CreateObjectWithAttributes<RrMultiUserScheduler>("EnableUlOfdma",
2889 BooleanValue(false),
2890 "EnableBsrp",
2891 BooleanValue(false),
2892 "UlPsduSize",
2893 UintegerValue(2000));
2894 m_apMac->AggregateObject(muScheduler);
2895
2896 // install post reception error model on all devices
2897 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
2898 {
2899 auto errorModel = CreateObject<ListErrorModel>();
2900 m_errorModels[m_apMac->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2901 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2902 }
2903 for (std::size_t i : {0, 1})
2904 {
2905 for (const auto linkId : m_staMacs[i]->GetLinkIds())
2906 {
2907 auto errorModel = CreateObject<ListErrorModel>();
2908 m_errorModels[m_staMacs[i]->GetFrameExchangeManager(linkId)->GetAddress()] = errorModel;
2909 m_staMacs[i]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(errorModel);
2910 }
2911 }
2912}
2913
2914void
2916{
2917 if (m_muTrafficPattern < WifiMuTrafficPattern::UL_MU)
2918 {
2919 // DL Traffic
2920 for (uint8_t i = 0; i < m_nStations; i++)
2921 {
2922 PacketSocketAddress sockAddr;
2924 sockAddr.SetPhysicalAddress(m_staMacs[i]->GetDevice()->GetAddress());
2925 sockAddr.SetProtocol(1);
2926
2927 // the first client application generates three packets in order
2928 // to trigger the establishment of a Block Ack agreement
2930 GetApplication(sockAddr, 3, 450, i * MilliSeconds(50)));
2931
2932 // the second client application generates the first half of the selected number
2933 // of packets, which are sent in DL MU PPDUs, and starts after all BA agreements
2934 // are established
2936 GetApplication(sockAddr, m_nPackets / 2, 450, m_nStations * MilliSeconds(50)));
2937
2938 // the third client application generates the second half of the selected number
2939 // of packets, which are sent in DL MU PPDUs, and starts during transmission of
2940 // first A-MPDU, if multiple links are setup
2942 GetApplication(sockAddr,
2943 m_nPackets / 2,
2944 450,
2946 }
2947 }
2948 else
2949 {
2950 // UL Traffic
2951 for (uint8_t i = 0; i < m_nStations; i++)
2952 {
2953 m_sockets[i].SetSingleDevice(m_staMacs[i]->GetDevice()->GetIfIndex());
2954 m_sockets[i].SetPhysicalAddress(m_apMac->GetDevice()->GetAddress());
2955 m_sockets[i].SetProtocol(1);
2956
2957 // the first client application generates three packets in order
2958 // to trigger the establishment of a Block Ack agreement
2959 m_staMacs[i]->GetDevice()->GetNode()->AddApplication(
2960 GetApplication(m_sockets[i], 3, 450, i * MilliSeconds(50), i * 4));
2961
2962 // packets to be included in TB PPDUs are generated (by Transmit()) when
2963 // the first Basic Trigger Frame is sent by the AP
2964 }
2965
2966 // MU scheduler starts requesting channel access when we are done with BA agreements
2968 auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
2969 NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "Expected an aggregated MU scheduler");
2970 muScheduler->SetAttribute("EnableUlOfdma", BooleanValue(true));
2971 muScheduler->SetAccessReqInterval(MilliSeconds(3));
2972 // channel access is requested only once
2973 muScheduler->SetAccessReqInterval(Seconds(0));
2974 });
2975 }
2976
2978}
2979
2980void
2982{
2984
2985 // Expected number of packets received by each node (AP, STA 0, STA 1) at application layer
2986 std::array<std::size_t, 3> expectedRxPkts{};
2987
2988 switch (m_muTrafficPattern)
2989 {
2990 case WifiMuTrafficPattern::DL_MU_BAR_BA_SEQUENCE:
2991 case WifiMuTrafficPattern::DL_MU_MU_BAR:
2992 case WifiMuTrafficPattern::DL_MU_AGGR_MU_BAR:
2993 // both STA 0 and STA 1 receive m_nPackets + 3 (sent to trigger BA establishment) packets
2994 expectedRxPkts[1] = m_nPackets + 3;
2995 expectedRxPkts[2] = m_nPackets + 3;
2996 break;
2997 case WifiMuTrafficPattern::UL_MU:
2998 // AP receives m_nPackets + 3 (sent to trigger BA establishment) packets from each station
2999 expectedRxPkts[0] = 2 * (m_nPackets + 3);
3000 break;
3001 }
3002
3004 +expectedRxPkts[0],
3005 "Unexpected number of packets received by the AP");
3007 +expectedRxPkts[1],
3008 "Unexpected number of packets received by STA 0");
3010 +expectedRxPkts[2],
3011 "Unexpected number of packets received by STA 1");
3012
3013 // check that setting the QosTxop::NMaxInflights attribute has the expected effect.
3014 // For DL, for each station we send 2 MPDUs to trigger BA agreement and m_nPackets / 2 MPDUs
3015 // For UL, each station sends 2 MPDUs to trigger BA agreement and m_nPackets / 2 MPDUs
3017 m_inflightCount.size(),
3018 2 * (2 + m_nPackets / 2),
3019 "Did not collect number of simultaneous transmissions for all data frames");
3020
3021 auto nMaxInflight = std::min(m_nMaxInflight, m_staMacs[0]->GetSetupLinkIds().size());
3022 std::size_t maxCount = 0;
3023 for (const auto& [txSeqNoPair, count] : m_inflightCount)
3024 {
3026 nMaxInflight,
3027 "MPDU with seqNo="
3028 << txSeqNoPair.second
3029 << " transmitted simultaneously more times than allowed");
3030 maxCount = std::max(maxCount, count);
3031 }
3032
3034 maxCount,
3035 nMaxInflight,
3036 "Expected that at least one data frame was transmitted simultaneously a number of "
3037 "times equal to the NMaxInflights attribute");
3038
3040}
3041
3042/**
3043 * \ingroup wifi-test
3044 * \ingroup tests
3045 *
3046 * \brief Test release of sequence numbers upon CTS timeout in multi-link operations
3047 *
3048 * In this test, an AP MLD and a non-AP MLD setup 3 links. Usage of RTS/CTS protection is
3049 * enabled for frames whose length is at least 1000 bytes. The AP MLD receives a first set
3050 * of 4 packets from the upper layer and sends an RTS frame, which is corrupted at the
3051 * receiver, on a first link. When the RTS frame is transmitted, the AP MLD receives another
3052 * set of 4 packets, which are transmitted after a successful RTS/CTS exchange on a second
3053 * link. In the meantime, a new RTS/CTS exchange is successfully carried out (on the first
3054 * link or on the third link) to transmit the first set of 4 packets. When the transmission
3055 * of the first set of 4 packets starts, the AP MLD receives the third set of 4 packets from
3056 * the upper layer, which are transmitted after a successful RTS/CTS exchange.
3057 *
3058 * This test checks that sequence numbers are correctly assigned to all the MPDUs carrying data.
3059 */
3061{
3062 public:
3065
3066 protected:
3067 void DoSetup() override;
3068 void DoRun() override;
3069 void Transmit(Ptr<WifiMac> mac,
3070 uint8_t phyId,
3071 WifiConstPsduMap psduMap,
3072 WifiTxVector txVector,
3073 double txPowerW) override;
3074
3075 private:
3076 void StartTraffic() override;
3077
3078 PacketSocketAddress m_sockAddr; //!< packet socket address
3079 std::size_t m_nQosDataFrames; //!< counter for transmitted QoS data frames
3080 Ptr<ListErrorModel> m_errorModel; //!< error rate model to corrupt first RTS frame
3081 bool m_rtsCorrupted; //!< whether the first RTS frame has been corrupted
3082};
3083
3086 "Check sequence numbers after CTS timeout",
3087 1,
3088 BaseParams{{"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3089 {"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3090 {}}),
3091 m_nQosDataFrames(0),
3092 m_errorModel(CreateObject<ListErrorModel>()),
3093 m_rtsCorrupted(false)
3094{
3095}
3096
3097void
3099{
3100 // Enable RTS/CTS
3101 Config::SetDefault("ns3::WifiRemoteStationManager::RtsCtsThreshold", StringValue("1000"));
3102
3104
3105 // install post reception error model on all STAs affiliated with non-AP MLD
3106 for (const auto linkId : m_staMacs[0]->GetLinkIds())
3107 {
3108 m_staMacs[0]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel);
3109 }
3110}
3111
3112void
3114{
3116 m_sockAddr.SetPhysicalAddress(m_staMacs[0]->GetAddress());
3118
3119 // install client application generating 4 packets
3121}
3122
3123void
3125 uint8_t phyId,
3126 WifiConstPsduMap psduMap,
3127 WifiTxVector txVector,
3128 double txPowerW)
3129{
3130 MultiLinkOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
3131
3132 auto psdu = psduMap.begin()->second;
3133
3134 if (psdu->GetHeader(0).IsRts() && !m_rtsCorrupted)
3135 {
3136 m_errorModel->SetList({psdu->GetPacket()->GetUid()});
3137 m_rtsCorrupted = true;
3138 // generate other packets when the first RTS is transmitted
3140 }
3141 else if (psdu->GetHeader(0).IsQosData())
3142 {
3144
3145 if (m_nQosDataFrames == 2)
3146 {
3147 // generate other packets when the second QoS data frame is transmitted
3149 }
3150 }
3151}
3152
3153void
3155{
3158
3159 NS_TEST_EXPECT_MSG_EQ(m_nQosDataFrames, 3, "Unexpected number of transmitted QoS data frames");
3160
3161 std::size_t count{};
3162
3163 for (const auto& txPsdu : m_txPsdus)
3164 {
3165 auto psdu = txPsdu.psduMap.begin()->second;
3166
3167 if (!psdu->GetHeader(0).IsQosData())
3168 {
3169 continue;
3170 }
3171
3172 NS_TEST_EXPECT_MSG_EQ(psdu->GetNMpdus(), 4, "Unexpected number of MPDUs in A-MPDU");
3173
3174 count++;
3175 uint16_t expectedSeqNo{};
3176
3177 switch (count)
3178 {
3179 case 1:
3180 expectedSeqNo = 4;
3181 break;
3182 case 2:
3183 expectedSeqNo = 0;
3184 break;
3185 case 3:
3186 expectedSeqNo = 8;
3187 break;
3188 }
3189
3190 for (const auto& mpdu : *PeekPointer(psdu))
3191 {
3192 NS_TEST_EXPECT_MSG_EQ(mpdu->GetHeader().GetSequenceNumber(),
3193 expectedSeqNo++,
3194 "Unexpected sequence number");
3195 }
3196 }
3197
3199}
3200
3201/**
3202 * \ingroup wifi-test
3203 * \ingroup tests
3204 *
3205 * \brief wifi 11be MLD Test Suite
3206 */
3208{
3209 public:
3211};
3212
3214 : TestSuite("wifi-mlo", Type::UNIT)
3215{
3216 using ParamsTuple = std::tuple<MultiLinkOperationsTestBase::BaseParams, // base config params
3217 std::vector<uint8_t>, // link ID of setup links
3218 WifiTidToLinkMappingNegSupport, // AP negotiation support
3219 std::string, // DL TID-to-Link Mapping
3220 std::string>; // UL TID-to-Link Mapping
3221
3222 AddTestCase(new GetRnrLinkInfoTest(), TestCase::Duration::QUICK);
3223 AddTestCase(new MldSwapLinksTest(), TestCase::Duration::QUICK);
3224
3225 for (const auto& [baseParams,
3226 setupLinks,
3227 apNegSupport,
3228 dlTidLinkMapping,
3229 ulTidLinkMapping] :
3230 {// matching channels: setup all links
3231 ParamsTuple(
3232 {{"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3233 {"{36, 0, BAND_5GHZ, 0}", "{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3234 {}},
3235 {0, 1, 2},
3236 WifiTidToLinkMappingNegSupport::NOT_SUPPORTED, // AP MLD does not support TID-to-Link
3237 // Mapping negotiation
3238 "0,1,2,3 0,1,2; 4,5 0,1", // default mapping used instead
3239 "0,1,2,3 1,2; 6,7 0,1" // default mapping used instead
3240 ),
3241 // non-matching channels, matching PHY bands: setup all links
3242 ParamsTuple({{"{108, 0, BAND_5GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}"},
3243 {"{36, 0, BAND_5GHZ, 0}", "{120, 0, BAND_5GHZ, 0}", "{5, 0, BAND_6GHZ, 0}"},
3244 {}},
3245 {0, 1, 2},
3246 WifiTidToLinkMappingNegSupport::SAME_LINK_SET, // AP MLD does not support
3247 // distinct link sets for TIDs
3248 "0,1,2,3 0,1,2; 4,5 0,1", // default mapping used instead
3249 ""),
3250 // non-AP MLD switches band on some links to setup 3 links
3251 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3252 {"{36, 0, BAND_5GHZ, 0}", "{9, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3253 {}},
3254 {0, 1, 2},
3255 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3256 "0,1,2,3 0; 4,5,6,7 1,2", // frames of two TIDs are generated
3257 "0,2,3 1,2; 1,4,5,6,7 0" // frames of two TIDs are generated
3258 ),
3259 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3260 // that band, hence only 2 links are setup
3261 ParamsTuple(
3262 {{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{8, 20, BAND_2_4GHZ, 0}"},
3263 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3264 {0}},
3265 {0, 1},
3266 WifiTidToLinkMappingNegSupport::SAME_LINK_SET, // AP MLD does not support distinct
3267 // link sets for TIDs
3268 "0,1,2,3,4,5,6,7 0",
3269 "0,1,2,3,4,5,6,7 0"),
3270 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3271 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3272 // an AP operating on the same channel; hence 2 links are setup
3273 ParamsTuple(
3274 {{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{8, 20, BAND_2_4GHZ, 0}"},
3275 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3276 {0, 1}},
3277 {0, 1},
3278 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3279 "0,1,2,3 1",
3280 "0,1,2,3 1"),
3281 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3282 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3283 // an AP operating on the same channel; the third link of the non-AP MLD cannot
3284 // change PHY band and there is an AP operating on the same band (different channel);
3285 // hence 2 links are setup by switching channel (not band) on the third link
3286 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}", "{60, 0, BAND_5GHZ, 0}"},
3287 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3288 {0, 1, 2}},
3289 {0, 2},
3290 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3291 "",
3292 ""),
3293 // the first link of the non-AP MLD cannot change PHY band and no AP is operating on
3294 // that band; the second link of the non-AP MLD cannot change PHY band and there is
3295 // an AP operating on the same channel; hence one link only is setup
3296 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3297 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3298 {0, 1}},
3299 {2},
3300 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3301 "",
3302 ""),
3303 // non-AP MLD has only two STAs and setups two links
3304 ParamsTuple({{"{2, 0, BAND_2_4GHZ, 0}", "{36, 0, BAND_5GHZ, 0}"},
3305 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3306 {}},
3307 {1, 0},
3308 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3309 "0,1,2,3 1",
3310 ""),
3311 // single link non-AP STA associates with an AP affiliated with an AP MLD
3312 ParamsTuple({{"{120, 0, BAND_5GHZ, 0}"},
3313 {"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3314 {}},
3315 {2}, // link ID of AP MLD only (non-AP STA is single link)
3316 WifiTidToLinkMappingNegSupport::ANY_LINK_SET,
3317 "",
3318 ""),
3319 // a STA affiliated with a non-AP MLD associates with a single link AP
3320 ParamsTuple({{"{36, 0, BAND_5GHZ, 0}", "{1, 0, BAND_6GHZ, 0}", "{120, 0, BAND_5GHZ, 0}"},
3321 {"{120, 0, BAND_5GHZ, 0}"},
3322 {}},
3323 {2}, // link ID of non-AP MLD only (AP is single link)
3324 WifiTidToLinkMappingNegSupport::NOT_SUPPORTED,
3325 "0,1,2,3 0,1; 4,5,6,7 0,1", // ignored by single link AP
3326 "")})
3327 {
3328 AddTestCase(new MultiLinkSetupTest(baseParams,
3329 WifiScanType::PASSIVE,
3330 setupLinks,
3331 apNegSupport,
3332 dlTidLinkMapping,
3333 ulTidLinkMapping),
3334 TestCase::Duration::QUICK);
3335 AddTestCase(new MultiLinkSetupTest(baseParams,
3336 WifiScanType::ACTIVE,
3337 setupLinks,
3338 apNegSupport,
3339 dlTidLinkMapping,
3340 ulTidLinkMapping),
3341 TestCase::Duration::QUICK);
3342
3343 for (const auto& trafficPattern : {WifiTrafficPattern::STA_TO_STA,
3344 WifiTrafficPattern::STA_TO_AP,
3345 WifiTrafficPattern::AP_TO_STA,
3346 WifiTrafficPattern::AP_TO_BCAST,
3347 WifiTrafficPattern::STA_TO_BCAST})
3348 {
3349 // No Block Ack agreement
3350 AddTestCase(new MultiLinkTxTest(baseParams,
3351 trafficPattern,
3352 WifiBaEnabled::NO,
3353 WifiUseBarAfterMissedBa::NO,
3354 1),
3355 TestCase::Duration::QUICK);
3356 for (const auto& useBarAfterMissedBa :
3357 {WifiUseBarAfterMissedBa::YES, WifiUseBarAfterMissedBa::NO})
3358 {
3359 // Block Ack agreement with nMaxInflight=1
3360 AddTestCase(new MultiLinkTxTest(baseParams,
3361 trafficPattern,
3362 WifiBaEnabled::YES,
3363 useBarAfterMissedBa,
3364 1),
3365 TestCase::Duration::QUICK);
3366 // Block Ack agreement with nMaxInflight=2
3367 AddTestCase(new MultiLinkTxTest(baseParams,
3368 trafficPattern,
3369 WifiBaEnabled::YES,
3370 useBarAfterMissedBa,
3371 2),
3372 TestCase::Duration::QUICK);
3373 }
3374 }
3375
3376 for (const auto& muTrafficPattern : {WifiMuTrafficPattern::DL_MU_BAR_BA_SEQUENCE,
3377 WifiMuTrafficPattern::DL_MU_MU_BAR,
3378 WifiMuTrafficPattern::DL_MU_AGGR_MU_BAR,
3379 WifiMuTrafficPattern::UL_MU})
3380 {
3381 for (const auto& useBarAfterMissedBa :
3382 {WifiUseBarAfterMissedBa::YES, WifiUseBarAfterMissedBa::NO})
3383 {
3384 // Block Ack agreement with nMaxInflight=1
3386 new MultiLinkMuTxTest(baseParams, muTrafficPattern, useBarAfterMissedBa, 1),
3387 TestCase::Duration::QUICK);
3388 // Block Ack agreement with nMaxInflight=2
3390 new MultiLinkMuTxTest(baseParams, muTrafficPattern, useBarAfterMissedBa, 2),
3391 TestCase::Duration::QUICK);
3392 }
3393 }
3394 }
3395
3396 AddTestCase(new ReleaseSeqNoAfterCtsTimeoutTest(), TestCase::Duration::QUICK);
3397}
3398
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:274
AttributeValue implementation for Boolean.
Definition: boolean.h:37
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
AttributeValue implementation for Ssid.
Definition: ssid.h:96
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
AttributeValue implementation for Time.
Definition: nstime.h:1406
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:980
std::optional< Mac48Address > GetMldAddress(const Mac48Address &remoteAddr) const
Definition: wifi-mac.cc:1773
const std::map< uint8_t, std::unique_ptr< LinkEntity > > & GetLinks() const
Definition: wifi-mac.cc:1056
uint8_t GetNLinks() const
Get the number of links (can be greater than 1 for 11be devices only).
Definition: wifi-mac.cc:1071
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:1133
Ptr< WifiPhy > GetWifiPhy(uint8_t linkId=SINGLE_LINK_OP_ID) const
Definition: wifi-mac.cc:1314
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:1099
Ptr< EhtConfiguration > GetEhtConfiguration() const
Definition: wifi-mac.cc:1896
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:1246
Ptr< WifiNetDevice > GetDevice() const
Return the device this PHY is associated with.
Definition: wifi-mac.cc:482
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:638
Ptr< WifiRemoteStationManager > GetWifiRemoteStationManager(uint8_t linkId=0) const
Definition: wifi-mac.cc:1044
const std::set< uint8_t > & GetLinkIds() const
Definition: wifi-mac.cc:1077
Mac48Address GetAddress() const
Definition: wifi-mac.cc:495
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:670
static Time CalculateTxDuration(uint32_t size, const WifiTxVector &txVector, WifiPhyBand band, uint16_t staId=SU_STA_ID)
Definition: wifi-phy.cc:1530
WifiPhyBand GetPhyBand() const
Get the configured Wi-Fi band.
Definition: wifi-phy.cc:1044
const WifiPhyOperatingChannel & GetOperatingChannel() const
Get a const reference to the operating channel.
Definition: wifi-phy.cc:1056
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.
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.