A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
wifi-emlsr-test.cc
Go to the documentation of this file.
1/*
2 * Copyright (c) 2023 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 "wifi-emlsr-test.h"
21
22#include "ns3/attribute-container.h"
23#include "ns3/boolean.h"
24#include "ns3/config.h"
25#include "ns3/ctrl-headers.h"
26#include "ns3/eht-configuration.h"
27#include "ns3/emlsr-manager.h"
28#include "ns3/he-frame-exchange-manager.h"
29#include "ns3/log.h"
30#include "ns3/mgt-action-headers.h"
31#include "ns3/mobility-helper.h"
32#include "ns3/multi-model-spectrum-channel.h"
33#include "ns3/node-list.h"
34#include "ns3/packet-socket-helper.h"
35#include "ns3/packet-socket-server.h"
36#include "ns3/qos-txop.h"
37#include "ns3/rng-seed-manager.h"
38#include "ns3/rr-multi-user-scheduler.h"
39#include "ns3/simulator.h"
40#include "ns3/spectrum-wifi-helper.h"
41#include "ns3/spectrum-wifi-phy.h"
42#include "ns3/string.h"
43#include "ns3/wifi-net-device.h"
44
45#include <algorithm>
46#include <functional>
47#include <iomanip>
48#include <optional>
49
50using namespace ns3;
51
52NS_LOG_COMPONENT_DEFINE("WifiEmlsrTest");
53
56 "Check serialization and deserialization of the EML Operating Mode Notification frame")
57{
58}
59
60void
62{
63 MgtEmlOmn frame;
64
65 // Both EMLSR Mode and EMLMR Mode subfields set to 0 (no link bitmap);
67
68 frame.m_emlControl.emlsrMode = 1;
69 frame.SetLinkIdInBitmap(0);
70 frame.SetLinkIdInBitmap(5);
71 frame.SetLinkIdInBitmap(15);
72
73 // Adding Link Bitmap
75
76 NS_TEST_EXPECT_MSG_EQ((frame.GetLinkBitmap() == std::list<uint8_t>{0, 5, 15}),
77 true,
78 "Unexpected link bitmap");
79
80 auto padding = MicroSeconds(64);
81 auto transition = MicroSeconds(128);
82
86 frame.m_emlsrParamUpdate->transitionDelay =
88
89 // Adding the EMLSR Parameter Update field
91
94 padding,
95 "Unexpected EMLSR Padding Delay");
98 transition,
99 "Unexpected EMLSR Transition Delay");
100}
101
103 : TestCase(name)
104{
105}
106
107void
109 uint8_t phyId,
110 WifiConstPsduMap psduMap,
111 WifiTxVector txVector,
112 double txPowerW)
113{
114 auto linkId = mac->GetLinkForPhy(phyId);
115 NS_TEST_ASSERT_MSG_EQ(linkId.has_value(), true, "No link found for PHY ID " << +phyId);
116 m_txPsdus.push_back({Simulator::Now(), psduMap, txVector, *linkId, phyId});
117
118 auto txDuration =
119 WifiPhy::CalculateTxDuration(psduMap, txVector, mac->GetWifiPhy(*linkId)->GetPhyBand());
120
121 for (const auto& [aid, psdu] : psduMap)
122 {
123 std::stringstream ss;
124 ss << std::setprecision(10) << "PSDU #" << m_txPsdus.size() << " Link ID "
125 << +linkId.value() << " Phy ID " << +phyId << " " << psdu->GetHeader(0).GetTypeString();
126 if (psdu->GetHeader(0).IsAction())
127 {
128 ss << " ";
129 WifiActionHeader actionHdr;
130 psdu->GetPayload(0)->PeekHeader(actionHdr);
131 actionHdr.Print(ss);
132 }
133 ss << " #MPDUs " << psdu->GetNMpdus() << " duration/ID " << psdu->GetHeader(0).GetDuration()
134 << " RA = " << psdu->GetAddr1() << " TA = " << psdu->GetAddr2()
135 << " ADDR3 = " << psdu->GetHeader(0).GetAddr3()
136 << " ToDS = " << psdu->GetHeader(0).IsToDs()
137 << " FromDS = " << psdu->GetHeader(0).IsFromDs();
138 if (psdu->GetHeader(0).IsQosData())
139 {
140 ss << " seqNo = {";
141 for (auto& mpdu : *PeekPointer(psdu))
142 {
143 ss << mpdu->GetHeader().GetSequenceNumber() << ",";
144 }
145 ss << "} TID = " << +psdu->GetHeader(0).GetQosTid();
146 }
147 NS_LOG_INFO(ss.str());
148 }
149 NS_LOG_INFO("TX duration = " << txDuration.As(Time::MS) << " TXVECTOR = " << txVector << "\n");
150}
151
152void
154{
157 int64_t streamNumber = 100;
158
159 Config::SetDefault("ns3::WifiMac::MpduBufferSize", UintegerValue(64));
160
161 NodeContainer wifiApNode(1);
162 NodeContainer wifiStaNodes(m_nEmlsrStations);
163
164 WifiHelper wifi;
165 // wifi.EnableLogComponents ();
166 wifi.SetStandard(WIFI_STANDARD_80211be);
167 wifi.SetRemoteStationManager("ns3::ConstantRateWifiManager",
168 "DataMode",
169 StringValue("EhtMcs0"),
170 "ControlMode",
171 StringValue("HtMcs0"));
172 wifi.ConfigEhtOptions("EmlsrActivated",
173 BooleanValue(true),
174 "TransitionTimeout",
176
177 // MLDs are configured with three links
178 SpectrumWifiPhyHelper phyHelper(3);
180 phyHelper.Set(0, "ChannelSettings", StringValue("{2, 0, BAND_2_4GHZ, 0}"));
181 phyHelper.Set(1, "ChannelSettings", StringValue("{36, 0, BAND_5GHZ, 0}"));
182 phyHelper.Set(2, "ChannelSettings", StringValue("{1, 0, BAND_6GHZ, 0}"));
183 // Add three spectrum channels to use multi-RF interface
184 phyHelper.AddChannel(CreateObject<MultiModelSpectrumChannel>(), WIFI_SPECTRUM_2_4_GHZ);
185 phyHelper.AddChannel(CreateObject<MultiModelSpectrumChannel>(), WIFI_SPECTRUM_5_GHZ);
186 phyHelper.AddChannel(CreateObject<MultiModelSpectrumChannel>(), WIFI_SPECTRUM_6_GHZ);
187
188 WifiMacHelper mac;
189 mac.SetType("ns3::ApWifiMac",
190 "Ssid",
191 SsidValue(Ssid("ns-3-ssid")),
192 "BeaconGeneration",
193 BooleanValue(true));
194
195 NetDeviceContainer apDevice = wifi.Install(phyHelper, mac, wifiApNode);
196
197 mac.SetType("ns3::StaWifiMac",
198 "Ssid",
199 SsidValue(Ssid("wrong-ssid")),
200 "ActiveProbing",
201 BooleanValue(false));
202 mac.SetEmlsrManager("ns3::DefaultEmlsrManager",
203 "EmlsrLinkSet",
205 "MainPhyId",
207
208 NetDeviceContainer staDevices = wifi.Install(phyHelper, mac, wifiStaNodes);
209
210 m_apMac = DynamicCast<ApWifiMac>(DynamicCast<WifiNetDevice>(apDevice.Get(0))->GetMac());
211
212 for (uint32_t i = 0; i < staDevices.GetN(); i++)
213 {
214 auto device = DynamicCast<WifiNetDevice>(staDevices.Get(i));
215 auto staMac = DynamicCast<StaWifiMac>(device->GetMac());
216 NS_ASSERT_MSG(i < m_paddingDelay.size(), "Not enough padding delay values provided");
217 staMac->GetEmlsrManager()->SetAttribute("EmlsrPaddingDelay",
219 NS_ASSERT_MSG(i < m_transitionDelay.size(), "Not enough transition delay values provided");
220 staMac->GetEmlsrManager()->SetAttribute("EmlsrTransitionDelay",
222 }
223
224 if (m_nNonEmlsrStations > 0)
225 {
226 // create the other non-AP MLDs for which EMLSR is not activated
227 wifi.ConfigEhtOptions("EmlsrActivated", BooleanValue(false));
228 NodeContainer otherStaNodes(m_nNonEmlsrStations);
229 staDevices.Add(wifi.Install(phyHelper, mac, otherStaNodes));
230 wifiStaNodes.Add(otherStaNodes);
231 }
232
233 for (uint32_t i = 0; i < staDevices.GetN(); i++)
234 {
235 auto device = DynamicCast<WifiNetDevice>(staDevices.Get(i));
236 m_staMacs.push_back(DynamicCast<StaWifiMac>(device->GetMac()));
237 }
238
239 // Trace PSDUs passed to the PHY on AP MLD and non-AP MLDs
240 for (uint8_t phyId = 0; phyId < m_apMac->GetDevice()->GetNPhys(); phyId++)
241 {
243 "/NodeList/0/DeviceList/*/$ns3::WifiNetDevice/Phys/" + std::to_string(phyId) +
244 "/PhyTxPsduBegin",
246 }
247 for (std::size_t i = 0; i < m_nEmlsrStations + m_nNonEmlsrStations; i++)
248 {
249 for (uint8_t phyId = 0; phyId < m_staMacs[i]->GetDevice()->GetNPhys(); phyId++)
250 {
252 "/NodeList/" + std::to_string(i + 1) + "/DeviceList/*/$ns3::WifiNetDevice/Phys/" +
253 std::to_string(phyId) + "/PhyTxPsduBegin",
255 }
256 }
257
258 // Uncomment the lines below to write PCAP files
259 // phyHelper.EnablePcap("wifi-emlsr_AP", apDevice);
260 // phyHelper.EnablePcap("wifi-emlsr_STA", staDevices);
261
262 // Assign fixed streams to random variables in use
263 streamNumber += wifi.AssignStreams(apDevice, streamNumber);
264 streamNumber += wifi.AssignStreams(staDevices, streamNumber);
265
266 MobilityHelper mobility;
267 Ptr<ListPositionAllocator> positionAlloc = CreateObject<ListPositionAllocator>();
268
269 for (std::size_t id = 0; id <= m_nEmlsrStations + m_nNonEmlsrStations; id++)
270 {
271 // all non-AP MLDs are co-located
272 positionAlloc->Add(Vector(std::min<double>(id, 1), 0.0, 0.0));
273 }
274 mobility.SetPositionAllocator(positionAlloc);
275
276 mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
277 mobility.Install(wifiApNode);
278 mobility.Install(wifiStaNodes);
279
280 // install packet socket on all nodes
281 PacketSocketHelper packetSocket;
282 packetSocket.Install(wifiApNode);
283 packetSocket.Install(wifiStaNodes);
284
285 // install a packet socket server on all nodes
286 for (auto nodeIt = NodeList::Begin(); nodeIt != NodeList::End(); nodeIt++)
287 {
288 PacketSocketAddress srvAddr;
289 auto device = DynamicCast<WifiNetDevice>((*nodeIt)->GetDevice(0));
290 NS_TEST_ASSERT_MSG_NE(device, nullptr, "Expected a WifiNetDevice");
291 srvAddr.SetSingleDevice(device->GetIfIndex());
292 srvAddr.SetProtocol(1);
293
294 auto server = CreateObject<PacketSocketServer>();
295 server->SetLocal(srvAddr);
296 (*nodeIt)->AddApplication(server);
297 server->SetStartTime(Seconds(0)); // now
298 server->SetStopTime(m_duration);
299 }
300
301 // set DL and UL packet sockets
302 for (const auto& staMac : m_staMacs)
303 {
304 m_dlSockets.emplace_back();
305 m_dlSockets.back().SetSingleDevice(m_apMac->GetDevice()->GetIfIndex());
306 m_dlSockets.back().SetPhysicalAddress(staMac->GetDevice()->GetAddress());
307 m_dlSockets.back().SetProtocol(1);
308
309 m_ulSockets.emplace_back();
310 m_ulSockets.back().SetSingleDevice(staMac->GetDevice()->GetIfIndex());
311 m_ulSockets.back().SetPhysicalAddress(m_apMac->GetDevice()->GetAddress());
312 m_ulSockets.back().SetProtocol(1);
313 }
314
315 // schedule ML setup for one station at a time
316 m_apMac->TraceConnectWithoutContext("AssociatedSta",
318 Simulator::Schedule(Seconds(0), [&]() { m_staMacs[0]->SetSsid(Ssid("ns-3-ssid")); });
319}
320
323 std::size_t staId,
324 std::size_t count,
325 std::size_t pktSize) const
326{
327 auto client = CreateObject<PacketSocketClient>();
328 client->SetAttribute("PacketSize", UintegerValue(pktSize));
329 client->SetAttribute("MaxPackets", UintegerValue(count));
330 client->SetAttribute("Interval", TimeValue(MicroSeconds(0)));
331 client->SetRemote(dir == DOWNLINK ? m_dlSockets.at(staId) : m_ulSockets.at(staId));
332 client->SetStartTime(Seconds(0)); // now
333 client->SetStopTime(m_duration - Simulator::Now());
334
335 return client;
336}
337
338void
340{
341 if (m_lastAid == aid)
342 {
343 // another STA of this non-AP MLD has already fired this callback
344 return;
345 }
346 m_lastAid = aid;
347
348 // wait some time (5ms) to allow the completion of association
349 auto delay = MilliSeconds(5);
350
351 if (m_establishBaDl)
352 {
353 // trigger establishment of BA agreement with AP as originator
354 Simulator::Schedule(delay, [=, this]() {
355 m_apMac->GetDevice()->GetNode()->AddApplication(
356 GetApplication(DOWNLINK, aid - 1, 4, 1000));
357 });
358
359 delay += MilliSeconds(5);
360 }
361
362 if (m_establishBaUl)
363 {
364 // trigger establishment of BA agreement with AP as recipient
365 Simulator::Schedule(delay, [=, this]() {
366 m_staMacs[aid - 1]->GetDevice()->GetNode()->AddApplication(
367 GetApplication(UPLINK, aid - 1, 4, 1000));
368 });
369
370 delay += MilliSeconds(5);
371 }
372
373 Simulator::Schedule(delay, [=, this]() {
375 {
376 // make the next STA start ML discovery & setup
377 m_staMacs[aid]->SetSsid(Ssid("ns-3-ssid"));
378 return;
379 }
380 // all stations associated; start traffic if needed
381 StartTraffic();
382 });
383}
384
385void
387 Mac48Address dest,
388 uint8_t linkId,
390 bool blocked,
391 std::string description,
392 bool testUnblockedForOtherReasons)
393{
395 auto mask = mac->GetMacQueueScheduler()->GetQueueLinkMask(AC_BE, queueId, linkId);
396 NS_TEST_EXPECT_MSG_EQ(mask.has_value(),
397 true,
398 description << ": Expected to find a mask for EMLSR link " << +linkId);
399 if (blocked)
400 {
401 NS_TEST_EXPECT_MSG_EQ(mask->test(static_cast<std::size_t>(reason)),
402 true,
403 description << ": Expected EMLSR link " << +linkId
404 << " to be blocked for reason " << reason);
405 if (testUnblockedForOtherReasons)
406 {
407 NS_TEST_EXPECT_MSG_EQ(mask->count(),
408 1,
409 description << ": Expected EMLSR link " << +linkId
410 << " to be blocked for one reason only");
411 }
412 }
413 else if (testUnblockedForOtherReasons)
414 {
415 NS_TEST_EXPECT_MSG_EQ(mask->none(),
416 true,
417 description << ": Expected EMLSR link " << +linkId
418 << " to be unblocked");
419 }
420 else
421 {
422 NS_TEST_EXPECT_MSG_EQ(mask->test(static_cast<std::size_t>(reason)),
423 false,
424 description << ": Expected EMLSR link " << +linkId
425 << " to be unblocked for reason " << reason);
426 }
427}
428
429EmlOmnExchangeTest::EmlOmnExchangeTest(const std::set<uint8_t>& linksToEnableEmlsrOn,
430 Time transitionTimeout)
431 : EmlsrOperationsTestBase("Check EML Notification exchange"),
432 m_checkEmlsrLinksCount(0),
433 m_emlNotificationDroppedCount(0)
434{
435 m_linksToEnableEmlsrOn = linksToEnableEmlsrOn;
438 m_transitionTimeout = transitionTimeout;
439 m_duration = Seconds(0.5);
440}
441
442void
444{
446
447 m_errorModel = CreateObject<ListErrorModel>();
448 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
449 {
451 }
452
453 m_staMacs[0]->TraceConnectWithoutContext("AckedMpdu",
455 m_staMacs[0]->TraceConnectWithoutContext("DroppedMpdu",
457}
458
459void
461 uint8_t phyId,
462 WifiConstPsduMap psduMap,
463 WifiTxVector txVector,
464 double txPowerW)
465{
466 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
467 auto linkId = m_txPsdus.back().linkId;
468
469 auto psdu = psduMap.begin()->second;
470
471 switch (psdu->GetHeader(0).GetType())
472 {
474 NS_TEST_EXPECT_MSG_EQ(+linkId, +m_mainPhyId, "AssocReq not sent by the main PHY");
475 CheckEmlCapabilitiesInAssocReq(*psdu->begin(), txVector, linkId);
476 break;
477
479 CheckEmlCapabilitiesInAssocResp(*psdu->begin(), txVector, linkId);
480 break;
481
483 if (auto [category, action] = WifiActionHeader::Peek(psdu->GetPayload(0));
485 action.protectedEhtAction ==
487 {
488 CheckEmlNotification(psdu, txVector, linkId);
489
491 m_staMacs[0]->GetLinkIdByAddress(psdu->GetAddr2()) == linkId)
492 {
493 // transmitted by non-AP MLD, we need to corrupt it
494 m_uidList.push_front(psdu->GetPacket()->GetUid());
496 }
497 break;
498 }
499
500 default:;
501 }
502}
503
504void
506 const WifiTxVector& txVector,
507 uint8_t linkId)
508{
510 mpdu->GetPacket()->PeekHeader(frame);
511
512 const auto& mle = frame.Get<MultiLinkElement>();
513 NS_TEST_ASSERT_MSG_EQ(mle.has_value(), true, "Multi-Link Element must be present in AssocReq");
514
515 NS_TEST_ASSERT_MSG_EQ(mle->HasEmlCapabilities(),
516 true,
517 "Multi-Link Element in AssocReq must have EML Capabilities");
518 NS_TEST_ASSERT_MSG_EQ(mle->IsEmlsrSupported(),
519 true,
520 "EML Support subfield of EML Capabilities in AssocReq must be set to 1");
521 NS_TEST_ASSERT_MSG_EQ(mle->GetEmlsrPaddingDelay(),
522 m_paddingDelay.at(0),
523 "Unexpected Padding Delay in EML Capabilities included in AssocReq");
524 NS_TEST_ASSERT_MSG_EQ(mle->GetEmlsrTransitionDelay(),
525 m_transitionDelay.at(0),
526 "Unexpected Transition Delay in EML Capabilities included in AssocReq");
527}
528
529void
531 const WifiTxVector& txVector,
532 uint8_t linkId)
533{
534 bool sentToEmlsrClient =
535 (m_staMacs[0]->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1()) == linkId);
536
537 if (!sentToEmlsrClient)
538 {
539 // nothing to check
540 return;
541 }
542
544 mpdu->GetPacket()->PeekHeader(frame);
545
546 const auto& mle = frame.Get<MultiLinkElement>();
547 NS_TEST_ASSERT_MSG_EQ(mle.has_value(), true, "Multi-Link Element must be present in AssocResp");
548
549 NS_TEST_ASSERT_MSG_EQ(mle->HasEmlCapabilities(),
550 true,
551 "Multi-Link Element in AssocResp must have EML Capabilities");
552 NS_TEST_ASSERT_MSG_EQ(mle->IsEmlsrSupported(),
553 true,
554 "EML Support subfield of EML Capabilities in AssocResp must be set to 1");
556 mle->GetTransitionTimeout(),
558 "Unexpected Transition Timeout in EML Capabilities included in AssocResp");
559}
560
561void
563 const WifiTxVector& txVector,
564 uint8_t linkId)
565{
566 MgtEmlOmn frame;
567 auto mpdu = *psdu->begin();
568 auto pkt = mpdu->GetPacket()->Copy();
570 pkt->RemoveHeader(frame);
571 NS_LOG_DEBUG(frame);
572
573 bool sentbyNonApMld = m_staMacs[0]->GetLinkIdByAddress(mpdu->GetHeader().GetAddr2()) == linkId;
574
576 1,
577 "EMLSR Mode subfield should be set to 1 (frame sent by non-AP MLD: "
578 << std::boolalpha << sentbyNonApMld << ")");
579
581 0,
582 "EMLMR Mode subfield should be set to 0 (frame sent by non-AP MLD: "
583 << std::boolalpha << sentbyNonApMld << ")");
584
586 true,
587 "Link Bitmap subfield should be present (frame sent by non-AP MLD: "
588 << std::boolalpha << sentbyNonApMld << ")");
589
590 auto setupLinks = m_staMacs[0]->GetSetupLinkIds();
591 std::list<uint8_t> expectedEmlsrLinks;
592 std::set_intersection(setupLinks.begin(),
593 setupLinks.end(),
596 std::back_inserter(expectedEmlsrLinks));
597
598 NS_TEST_EXPECT_MSG_EQ((expectedEmlsrLinks == frame.GetLinkBitmap()),
599 true,
600 "Unexpected Link Bitmap subfield (frame sent by non-AP MLD: "
601 << std::boolalpha << sentbyNonApMld << ")");
602
603 if (!sentbyNonApMld)
604 {
605 // the frame has been sent by the AP MLD
608 0,
609 "EMLSR Parameter Update Control should be set to 0 in frames sent by the AP MLD");
610
611 // as soon as the non-AP MLD receives this frame, it sets the EMLSR links
612 auto delay = WifiPhy::CalculateTxDuration(psdu,
613 txVector,
614 m_staMacs[0]->GetWifiPhy(linkId)->GetPhyBand()) +
615 MicroSeconds(1); // to account for propagation delay
617 }
618
620 +linkId,
621 "EML Notification received on unexpected link (frame sent by non-AP MLD: "
622 << std::boolalpha << sentbyNonApMld << ")");
623}
624
625void
627{
628 const auto& hdr = mpdu->GetHeader();
629
630 if (hdr.IsMgt() && hdr.IsAction())
631 {
632 if (auto [category, action] = WifiActionHeader::Peek(mpdu->GetPacket());
634 action.protectedEhtAction ==
636 {
637 // the EML Operating Mode Notification frame that the non-AP MLD sent has been
638 // acknowledged; after the transition timeout, the EMLSR links have been set
641 this);
642 }
643 }
644}
645
646void
648{
649 const auto& hdr = mpdu->GetHeader();
650
651 if (hdr.IsMgt() && hdr.IsAction())
652 {
653 if (auto [category, action] = WifiActionHeader::Peek(mpdu->GetPacket());
655 action.protectedEhtAction ==
657 {
658 // the EML Operating Mode Notification frame has been dropped. Don't
659 // corrupt it anymore
661 }
662 }
663}
664
665void
667{
669
670 auto setupLinks = m_staMacs[0]->GetSetupLinkIds();
671 std::set<uint8_t> expectedEmlsrLinks;
672 std::set_intersection(setupLinks.begin(),
673 setupLinks.end(),
676 std::inserter(expectedEmlsrLinks, expectedEmlsrLinks.end()));
677
678 NS_TEST_EXPECT_MSG_EQ((expectedEmlsrLinks == m_staMacs[0]->GetEmlsrManager()->GetEmlsrLinks()),
679 true,
680 "Unexpected set of EMLSR links)");
681}
682
683void
685{
688
690 2,
691 "Unexpected number of times CheckEmlsrLinks() is called");
694 1,
695 "Unexpected number of times the EML Notification frame is dropped due to max retry limit");
696
698}
699
701 : EmlsrOperationsTestBase("Check EML DL TXOP transmissions (" +
702 std::to_string(params.nEmlsrStations) + "," +
703 std::to_string(params.nNonEmlsrStations) + ")"),
704 m_emlsrLinks(params.linksToEnableEmlsrOn),
705 m_emlsrEnabledTime(0),
706 m_fe2to3delay(MilliSeconds(20)),
707 m_countQoSframes(0),
708 m_countBlockAck(0)
709{
710 m_nEmlsrStations = params.nEmlsrStations;
711 m_nNonEmlsrStations = params.nNonEmlsrStations;
712 m_linksToEnableEmlsrOn = {}; // do not enable EMLSR right after association
713 m_mainPhyId = 1;
714 m_paddingDelay = params.paddingDelay;
715 m_transitionDelay = params.transitionDelay;
716 m_transitionTimeout = params.transitionTimeout;
717 m_establishBaDl = true;
718 m_duration = Seconds(1.5);
719
720 NS_ABORT_MSG_IF(params.linksToEnableEmlsrOn.size() < 2,
721 "This test requires at least two links to be configured as EMLSR links");
722}
723
724void
726 uint8_t phyId,
727 WifiConstPsduMap psduMap,
728 WifiTxVector txVector,
729 double txPowerW)
730{
731 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
732 auto linkId = m_txPsdus.back().linkId;
733
734 auto psdu = psduMap.begin()->second;
735 auto nodeId = mac->GetDevice()->GetNode()->GetId();
736
737 switch (psdu->GetHeader(0).GetType())
738 {
740 NS_ASSERT_MSG(nodeId > 0, "APs do not send AssocReq frames");
741 if (nodeId <= m_nEmlsrStations)
742 {
743 NS_TEST_EXPECT_MSG_EQ(+linkId, +m_mainPhyId, "AssocReq not sent by the main PHY");
744 // this AssocReq is being sent by an EMLSR client. The other EMLSR links should be
745 // in powersave mode after association; we let the non-EMLSR links transition to
746 // active mode (by sending data null frames) after association
747 for (const auto id : m_staMacs.at(nodeId - 1)->GetLinkIds())
748 {
749 if (id != linkId && m_emlsrLinks.count(id) == 1)
750 {
751 m_staMacs[nodeId - 1]->SetPowerSaveMode({true, id});
752 }
753 }
754 }
755 break;
756
757 case WIFI_MAC_MGT_ACTION: {
758 auto [category, action] = WifiActionHeader::Peek(psdu->GetPayload(0));
759
760 if (nodeId == 0 && category == WifiActionHeader::PROTECTED_EHT &&
761 action.protectedEhtAction ==
763 {
764 CheckEmlNotificationFrame(*psdu->begin(), txVector, linkId);
765 }
766 else if (category == WifiActionHeader::BLOCK_ACK &&
768 {
769 CheckPmModeAfterAssociation(psdu->GetAddr1());
770 }
771 }
772 break;
773
775 CheckInitialControlFrame(*psdu->begin(), txVector, linkId);
776 break;
777
778 case WIFI_MAC_QOSDATA:
779 CheckQosFrames(psduMap, txVector, linkId);
780 break;
781
783 CheckBlockAck(psduMap, txVector, phyId);
784 break;
785
786 default:;
787 }
788}
789
790void
792{
794
795 m_errorModel = CreateObject<ListErrorModel>();
796 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
797 {
799 }
800
802 {MicroSeconds(3200), MicroSeconds(3200), MicroSeconds(3200)});
803
805 {
806 auto muScheduler =
807 CreateObjectWithAttributes<RrMultiUserScheduler>("EnableUlOfdma", BooleanValue(false));
808 m_apMac->AggregateObject(muScheduler);
809 for (uint8_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
810 {
811 m_apMac->GetFrameExchangeManager(linkId)->GetAckManager()->SetAttribute(
812 "DlMuAckSequenceType",
814 }
815 }
816}
817
818void
820{
822 {
823 // we are done with association and Block Ack agreement; we can now enable EMLSR mode
824 m_lastAid = 0;
826 return;
827 }
828
829 // we are done with sending EML Operating Mode Notification frames. We can now generate
830 // packets for all non-AP MLDs
831 for (std::size_t i = 0; i < m_nEmlsrStations + m_nNonEmlsrStations; i++)
832 {
833 // when multiple non-AP MLDs are present, MU transmission are used. Given that the
834 // available bandwidth decreases as the number of non-AP MLDs increases, compute the
835 // number of packets to generate so that we always have two A-MPDUs per non-AP MLD
836 std::size_t count = 8 / (m_nEmlsrStations + m_nNonEmlsrStations);
838 }
839
840 // in case of 2 EMLSR clients using no non-EMLSR link, generate one additional short
841 // packet to each EMLSR client to test transition delay
842 if (m_nEmlsrStations == 2 && m_apMac->GetNLinks() == m_emlsrLinks.size())
843 {
845 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(DOWNLINK, 0, 1, 40));
846 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(DOWNLINK, 1, 1, 40));
847 });
848 }
849
850 // schedule the transmission of EML Operating Mode Notification frames to disable EMLSR mode
851 // and the generation of other packets destined to the EMLSR clients
852 for (std::size_t id = 0; id < m_nEmlsrStations; id++)
853 {
854 Simulator::Schedule(m_fe2to3delay + MilliSeconds(5 * (id + 1)), [=, this]() {
855 m_staMacs.at(id)->GetEmlsrManager()->SetAttribute(
856 "EmlsrLinkSet",
858 });
859
861 m_apMac->GetDevice()->GetNode()->AddApplication(
863 });
864 }
865}
866
867void
869{
870 m_staMacs.at(m_lastAid)->GetEmlsrManager()->SetAttribute(
871 "EmlsrLinkSet",
873 m_lastAid++;
874 Simulator::Schedule(MilliSeconds(5), [=, this]() {
876 {
877 // make the next STA send EML Notification frame
879 return;
880 }
881 // all stations enabled EMLSR mode; start traffic
883 StartTraffic();
884 });
885}
886
887void
889{
890 auto psduIt = m_txPsdus.cbegin();
891
892 // lambda to jump to the next QoS data frame or MU-RTS Trigger Frame transmitted
893 // to an EMLSR client
894 auto jumpToQosDataOrMuRts = [&]() {
895 while (psduIt != m_txPsdus.cend() &&
896 !psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData())
897 {
898 auto psdu = psduIt->psduMap.cbegin()->second;
899 if (psdu->GetHeader(0).IsTrigger())
900 {
901 CtrlTriggerHeader trigger;
902 psdu->GetPayload(0)->PeekHeader(trigger);
903 if (trigger.IsMuRts())
904 {
905 break;
906 }
907 }
908 psduIt++;
909 }
910 };
911
912 /**
913 * Before enabling EMLSR mode, no MU-RTS TF should be sent. Four packets are generated
914 * after association to trigger the establishment of a Block Ack agreement. The TXOP Limit
915 * and the MCS are set such that two packets can be transmitted in a TXOP, hence we expect
916 * that the AP MLD sends two A-MPDUs to each non-AP MLD.
917 *
918 * EMLSR client with EMLSR mode to be enabled on all links: after ML setup, all other links
919 * stay in power save mode, hence BA establishment occurs on the same link.
920 *
921 * [link 0]
922 * ───────────────────────────────────────────────────────────────────────────
923 * | power save mode
924 *
925 * ┌─────┐ ┌─────┐ ┌───┬───┐ ┌───┬───┐
926 * ┌───┐ │Assoc│ │ADDBA│ ┌───┐ │QoS│QoS│ │QoS│QoS│
927 * [link 1] │ACK│ │Resp │ │ Req │ │ACK│ │ 0 │ 1 │ │ 2 │ 3 │
928 * ───┬─────┬┴───┴──┴─────┴┬───┬─┴─────┴┬───┬─┬─────┬┴───┴─┴───┴───┴┬──┬─┴───┴───┴┬──┬───
929 * │Assoc│ │ACK│ │ACK│ │ADDBA│ │BA│ │BA│
930 * │ Req │ └───┘ └───┘ │Resp │ └──┘ └──┘
931 * └─────┘ └─────┘
932 *
933 * [link 2]
934 * ───────────────────────────────────────────────────────────────────────────
935 * | power save mode
936 *
937 *
938 * EMLSR client with EMLSR mode to be enabled on not all the links: after ML setup,
939 * the other EMLSR links stay in power save mode, the non-EMLSR link (link 1) transitions
940 * to active mode.
941 *
942 * ┌─────┐ ┌───┬───┐
943 * ┌───┐ │ADDBA│ ┌───┐ │QoS│QoS│
944 * [link 0 - non EMLSR] │ACK│ │ Req │ │ACK│ │ 2 │ 3 │
945 * ──────────────────────────────┬────┬┴───┴──┴─────┴┬───┬─┬─────┬┴───┴─┴───┴───┴┬──┬─
946 * │Data│ │ACK│ │ADDBA│ │BA│
947 * │Null│ └───┘ │Resp │ └──┘
948 * └────┘ └─────┘
949 * ┌─────┐ ┌───┬───┐
950 * ┌───┐ │Assoc│ │QoS│QoS│
951 * [link 1] │ACK│ │Resp │ │ 0 │ 1 │
952 * ───┬─────┬┴───┴──┴─────┴┬───┬──────────────────────────────────┴───┴───┴┬──┬───────
953 * │Assoc│ │ACK│ │BA│
954 * │ Req │ └───┘ └──┘
955 * └─────┘
956 *
957 * [link 2]
958 * ───────────────────────────────────────────────────────────────────────────
959 * | power save mode
960 *
961 * Non-EMLSR client (not shown): after ML setup, all other links transition to active mode
962 * by sending a Data Null frame; QoS data frame exchanges occur on two links simultaneously.
963 */
964 for (std::size_t i = 0; i < m_nEmlsrStations + m_nNonEmlsrStations; i++)
965 {
966 std::set<uint8_t> linkIds;
967
968 jumpToQosDataOrMuRts();
969 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend() &&
970 psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData()),
971 true,
972 "Expected at least one QoS data frame before enabling EMLSR mode");
973 linkIds.insert(psduIt->linkId);
974 const auto firstAmpduTxEnd =
975 psduIt->startTx +
976 WifiPhy::CalculateTxDuration(psduIt->psduMap,
977 psduIt->txVector,
978 m_staMacs[i]->GetWifiPhy(psduIt->linkId)->GetPhyBand());
979 psduIt++;
980
981 jumpToQosDataOrMuRts();
982 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend() &&
983 psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData()),
984 true,
985 "Expected at least two QoS data frames before enabling EMLSR mode");
986 linkIds.insert(psduIt->linkId);
987 const auto secondAmpduTxStart = psduIt->startTx;
988 psduIt++;
989
990 /**
991 * If this is an EMLSR client and there is no setup link other than the one used to
992 * establish association that is not an EMLSR link, then the two A-MPDUs are sent one
993 * after another on the link used to establish association.
994 */
995 auto setupLinks = m_staMacs[i]->GetSetupLinkIds();
996 if (i < m_nEmlsrStations &&
997 std::none_of(setupLinks.begin(), setupLinks.end(), [&](auto&& linkId) {
998 return linkId != m_mainPhyId && m_emlsrLinks.count(linkId) == 0;
999 }))
1000 {
1001 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
1002 1,
1003 "Expected both A-MPDUs to be sent on the same link");
1004 NS_TEST_EXPECT_MSG_EQ(*linkIds.begin(), +m_mainPhyId, "A-MPDUs sent on incorrect link");
1005 NS_TEST_EXPECT_MSG_LT(firstAmpduTxEnd,
1006 secondAmpduTxStart,
1007 "A-MPDUs are not sent one after another");
1008 }
1009 /**
1010 * Otherwise, the two A-MPDUs can be sent concurrently on two distinct links (may be
1011 * the link used to establish association and a non-EMLSR link).
1012 */
1013 else
1014 {
1015 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
1016 2,
1017 "Expected A-MPDUs to be sent on distinct links");
1018 NS_TEST_EXPECT_MSG_GT(firstAmpduTxEnd,
1019 secondAmpduTxStart,
1020 "A-MPDUs are not sent concurrently");
1021 }
1022 }
1023
1024 /**
1025 * After enabling EMLSR mode, MU-RTS TF should only be sent on EMLSR links. After the exchange
1026 * of EML Operating Mode Notification frames, a number of packets are generated at the AP MLD
1027 * to prepare two A-MPDUs for each non-AP MLD.
1028 *
1029 * EMLSR client with EMLSR mode to be enabled on all links (A is the EMLSR client, B is the
1030 * non-EMLSR client):
1031 * ┌─────┬─────┐
1032 * │QoS 4│QoS 5│
1033 * │ to A│ to A│
1034 * ┌───┐ ├─────┼─────┤
1035 * │MU │ │QoS 4│QoS 5│
1036 * [link 0] │RTS│ │ to B│ to B│
1037 * ──────────────────────────┴───┴┬───┬┴─────┴─────┴┬──┬────────────
1038 * │CTS│ │BA│
1039 * ├───┤ ├──┤
1040 * │CTS│ │BA│
1041 * └───┘ └──┘
1042 * ┌───┐ ┌─────┬─────┐
1043 * ┌───┐ │EML│ │QoS 6│QoS 7│
1044 * [link 1] │ACK│ │OM │ │ to B│ to B│
1045 * ────┬───┬┴───┴──┴───┴┬───┬─┴─────┴─────┴┬──┬────────────────────────────────────
1046 * │EML│ │ACK│ │BA│
1047 * │OM │ └───┘ └──┘
1048 * └───┘
1049 * ┌───┐ ┌─────┬─────┐
1050 * │MU │ │QoS 6│QoS 7│
1051 * [link 2] │RTS│ │ to A│ to A│
1052 * ─────────────────────────────────────────────────────────┴───┴┬───┬┴─────┴─────┴┬──┬─
1053 * │CTS│ │BA│
1054 * └───┘ └──┘
1055 *
1056 * EMLSR client with EMLSR mode to be enabled on not all the links (A is the EMLSR client,
1057 * B is the non-EMLSR client):
1058 * ┌─────┬─────┐
1059 * │QoS 4│QoS 5│
1060 * │ to A│ to A│
1061 * ├─────┼─────┤
1062 * │QoS 4│QoS 5│
1063 * [link 0 - non EMLSR] │ to B│ to B│
1064 * ───────────────────────────┴─────┴─────┴┬──┬───────────────────────────
1065 * │BA│
1066 * ├──┤
1067 * │BA│
1068 * └──┘
1069 * ┌─────┬─────┐
1070 * │QoS 6│QoS 7│
1071 * │ to A│ to A│
1072 * ┌───┐ ┌───┐ ├─────┼─────┤
1073 * ┌───┐ │EML│ │MU │ │QoS 6│QoS 7│
1074 * [link 1] │ACK│ │OM │ │RTS│ │ to B│ to B│
1075 * ────┬───┬┴───┴──┴───┴┬───┬─┴───┴┬───┬┴─────┴─────┴┬──┬────────────
1076 * │EML│ │ACK│ │CTS│ │BA│
1077 * │OM │ └───┘ ├───┤ ├──┤
1078 * └───┘ │CTS│ │BA│
1079 * └───┘ └──┘
1080 *
1081 * [link 2]
1082 * ────────────────────────────────────────────────────────────────────────────────
1083 */
1084
1085 /// Store a QoS data frame or an MU-RTS TF followed by a QoS data frame
1086 using FrameExchange = std::list<decltype(psduIt)>;
1087
1088 std::vector<std::list<FrameExchange>> frameExchanges(m_nEmlsrStations);
1089
1090 // compute all frame exchanges involving EMLSR clients
1091 while (psduIt != m_txPsdus.cend())
1092 {
1093 jumpToQosDataOrMuRts();
1094 if (psduIt == m_txPsdus.cend())
1095 {
1096 break;
1097 }
1098
1099 if (IsTrigger(psduIt->psduMap))
1100 {
1101 CtrlTriggerHeader trigger;
1102 psduIt->psduMap.cbegin()->second->GetPayload(0)->PeekHeader(trigger);
1103 // this is an MU-RTS TF starting a new frame exchange sequence; add it to all
1104 // the addressed EMLSR clients
1106 true,
1107 "jumpToQosDataOrMuRts does not return TFs other than MU-RTS");
1108 for (const auto& userInfo : trigger)
1109 {
1110 for (std::size_t i = 0; i < m_nEmlsrStations; i++)
1111 {
1112 if (m_staMacs.at(i)->GetAssociationId() == userInfo.GetAid12())
1113 {
1114 frameExchanges.at(i).emplace_back(FrameExchange{psduIt});
1115 break;
1116 }
1117 }
1118 }
1119 psduIt++;
1120 continue;
1121 }
1122
1123 // we get here if psduIt points to a psduMap containing QoS data frame(s); find (if any)
1124 // the QoS data frame(s) addressed to EMLSR clients and add them to the appropriate
1125 // frame exchange sequence
1126 for (const auto& staIdPsduPair : psduIt->psduMap)
1127 {
1128 std::for_each_n(m_staMacs.cbegin(), m_nEmlsrStations, [&](auto&& staMac) {
1129 if (!staMac->GetLinkIdByAddress(staIdPsduPair.second->GetAddr1()))
1130 {
1131 // not addressed to this non-AP MLD
1132 return;
1133 }
1134 // a QoS data frame starts a new frame exchange sequence if there is no previous
1135 // MU-RTS TF that has been sent on the same link and is not already followed by
1136 // a QoS data frame
1137 std::size_t id = staMac->GetDevice()->GetNode()->GetId() - 1;
1138 for (auto& frameExchange : frameExchanges.at(id))
1139 {
1140 if (IsTrigger(frameExchange.front()->psduMap) &&
1141 frameExchange.front()->linkId == psduIt->linkId &&
1142 frameExchange.size() == 1)
1143 {
1144 auto it = std::next(frameExchange.front());
1145 while (it != m_txPsdus.end())
1146 {
1147 // stop at the first frame other than CTS sent on this link
1148 if (it->linkId == psduIt->linkId &&
1149 !it->psduMap.begin()->second->GetHeader(0).IsCts())
1150 {
1151 break;
1152 }
1153 ++it;
1154 }
1155 if (it == psduIt)
1156 {
1157 // the QoS data frame actually followed the MU-RTS TF
1158 frameExchange.emplace_back(psduIt);
1159 return;
1160 }
1161 }
1162 }
1163 frameExchanges.at(id).emplace_back(FrameExchange{psduIt});
1164 });
1165 }
1166 psduIt++;
1167 }
1168
1169 /**
1170 * Let's focus on the first two frame exchanges for each EMLSR clients. If all setup links are
1171 * EMLSR links, both frame exchanges are protected by MU-RTS TF and occur one after another.
1172 * Otherwise, one frame exchange occurs on the non-EMLSR link and is not protected by
1173 * MU-RTS TF; the other frame exchange occurs on an EMLSR link and is protected by MU-RTS TF.
1174 */
1175 for (std::size_t i = 0; i < m_nEmlsrStations; i++)
1176 {
1177 NS_TEST_EXPECT_MSG_GT_OR_EQ(frameExchanges.at(i).size(),
1178 2,
1179 "Expected at least 2 frame exchange sequences "
1180 << "involving EMLSR client " << i);
1181
1182 auto firstExchangeIt = frameExchanges.at(i).begin();
1183 auto secondExchangeIt = std::next(firstExchangeIt);
1184
1185 const auto firstAmpduTxEnd =
1186 firstExchangeIt->back()->startTx +
1188 firstExchangeIt->back()->psduMap,
1189 firstExchangeIt->back()->txVector,
1190 m_staMacs[i]->GetWifiPhy(firstExchangeIt->back()->linkId)->GetPhyBand());
1191 const auto secondAmpduTxStart = secondExchangeIt->front()->startTx;
1192
1193 if (m_staMacs[i]->GetNLinks() == m_emlsrLinks.size())
1194 {
1195 // all links are EMLSR links
1196 NS_TEST_EXPECT_MSG_EQ(IsTrigger(firstExchangeIt->front()->psduMap),
1197 true,
1198 "Expected an MU-RTS TF as ICF of first frame exchange sequence");
1200 firstExchangeIt->back()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
1201 true,
1202 "Expected a QoS data frame in the first frame exchange sequence");
1203
1204 NS_TEST_EXPECT_MSG_EQ(IsTrigger(secondExchangeIt->front()->psduMap),
1205 true,
1206 "Expected an MU-RTS TF as ICF of second frame exchange sequence");
1208 secondExchangeIt->back()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
1209 true,
1210 "Expected a QoS data frame in the second frame exchange sequence");
1211
1212 NS_TEST_EXPECT_MSG_LT(firstAmpduTxEnd,
1213 secondAmpduTxStart,
1214 "A-MPDUs are not sent one after another");
1215 }
1216 else
1217 {
1218 std::vector<uint8_t> nonEmlsrIds;
1219 auto setupLinks = m_staMacs[i]->GetSetupLinkIds();
1220 std::set_difference(setupLinks.begin(),
1221 setupLinks.end(),
1222 m_emlsrLinks.begin(),
1223 m_emlsrLinks.end(),
1224 std::back_inserter(nonEmlsrIds));
1225 NS_TEST_ASSERT_MSG_EQ(nonEmlsrIds.size(), 1, "Unexpected number of non-EMLSR links");
1226
1227 auto nonEmlsrLinkExchangeIt = firstExchangeIt->front()->linkId == nonEmlsrIds[0]
1228 ? firstExchangeIt
1229 : secondExchangeIt;
1230 NS_TEST_EXPECT_MSG_EQ(IsTrigger(nonEmlsrLinkExchangeIt->front()->psduMap),
1231 false,
1232 "Did not expect an MU-RTS TF as ICF on non-EMLSR link");
1234 nonEmlsrLinkExchangeIt->front()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
1235 true,
1236 "Expected a QoS data frame on the non-EMLSR link");
1237
1238 auto emlsrLinkExchangeIt =
1239 nonEmlsrLinkExchangeIt == firstExchangeIt ? secondExchangeIt : firstExchangeIt;
1240 NS_TEST_EXPECT_MSG_NE(+emlsrLinkExchangeIt->front()->linkId,
1241 +nonEmlsrIds[0],
1242 "Expected this exchange not to occur on non-EMLSR link");
1243 NS_TEST_EXPECT_MSG_EQ(IsTrigger(emlsrLinkExchangeIt->front()->psduMap),
1244 true,
1245 "Expected an MU-RTS TF as ICF on the EMLSR link");
1247 emlsrLinkExchangeIt->back()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
1248 true,
1249 "Expected a QoS data frame on the EMLSR link");
1250
1251 NS_TEST_EXPECT_MSG_GT(firstAmpduTxEnd,
1252 secondAmpduTxStart,
1253 "A-MPDUs are not sent concurrently");
1254 }
1255
1256 // we are done with processing the first two frame exchanges, remove them
1257 frameExchanges.at(i).erase(firstExchangeIt);
1258 frameExchanges.at(i).erase(secondExchangeIt);
1259 }
1260
1261 /**
1262 * A and B are two EMLSR clients. No ICF before the second QoS data frame because B
1263 * has not switched to listening mode. ICF is sent before the third QoS data frame because
1264 * A has switched to listening mode. C is a non-EMLSR client.
1265 *
1266 * ┌─────┐ A switches to listening
1267 * │QoS x│ after transition delay
1268 * │ to A│ |
1269 * ┌───┐ ├─────┤ ┌─────┐
1270 * │MU │ │QoS x│ │QoS y│
1271 * [link 0] │RTS│ │ to B│ │ to B│
1272 * ────────────┴───┴┬───┬┴─────┴┬──┬┴─────┴┬──┬────────────
1273 * │CTS│ │BA│ │BA│
1274 * ├───┤ ├──┤ └──┘
1275 * │CTS│ │BA│
1276 * └───┘ └──┘ AP continues the TXOP A switches to listening
1277 * after PIFS recovery after transition delay
1278 * │ │
1279 * ┌─────┐ ┌───┐ ┌─────┐ │┌───┐ ┌───┐
1280 * │QoS z│ │MU │ │QoS x│ ││MU │ ┌───┐ │CF-│
1281 * [link 1] │ to C│ │RTS│ │ to A│ ││RTS│ │BAR│ │End│
1282 * ───────────────────────────────┴─────┴┬──┬┴───┴┬───┬┴─────┴┬──┬┴───┴┬───┬┴───┴┬──┬┴───┴─
1283 * │BA│ │CTS│ │BA│ │CTS│ │BA│
1284 * └──┘ └───┘ └──x └───┘ └──┘
1285 */
1286 if (m_nEmlsrStations == 2 && m_apMac->GetNLinks() == m_emlsrLinks.size())
1287 {
1288 // the following checks are only done with 2 EMLSR clients having no non-EMLSR link
1289 for (std::size_t i = 0; i < m_nEmlsrStations; i++)
1290 {
1291 NS_TEST_EXPECT_MSG_GT_OR_EQ(frameExchanges.at(i).size(),
1292 2,
1293 "Expected at least 2 frame exchange sequences "
1294 << "involving EMLSR client " << i);
1295 // the first frame exchange must start with an ICF
1296 auto firstExchangeIt = frameExchanges.at(i).begin();
1297
1298 NS_TEST_EXPECT_MSG_EQ(IsTrigger(firstExchangeIt->front()->psduMap),
1299 true,
1300 "Expected an MU-RTS TF as ICF of first frame exchange sequence");
1302 firstExchangeIt->back()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
1303 true,
1304 "Expected a QoS data frame in the first frame exchange sequence");
1305 }
1306
1307 // the second frame exchange is the one that starts first
1308 auto secondExchangeIt = std::next(frameExchanges.at(0).begin())->front()->startTx <
1309 std::next(frameExchanges.at(1).begin())->front()->startTx
1310 ? std::next(frameExchanges.at(0).begin())
1311 : std::next(frameExchanges.at(1).begin());
1312 decltype(secondExchangeIt) thirdExchangeIt;
1313 std::size_t thirdExchangeStaId;
1314
1315 if (secondExchangeIt == std::next(frameExchanges.at(0).begin()))
1316 {
1317 thirdExchangeIt = std::next(frameExchanges.at(1).begin());
1318 thirdExchangeStaId = 1;
1319 }
1320 else
1321 {
1322 thirdExchangeIt = std::next(frameExchanges.at(0).begin());
1323 thirdExchangeStaId = 0;
1324 }
1325
1326 // the second frame exchange is not protected by the ICF and starts a SIFS after the end
1327 // of the previous one
1328 NS_TEST_EXPECT_MSG_EQ(IsTrigger(secondExchangeIt->front()->psduMap),
1329 false,
1330 "Expected no ICF for the second frame exchange sequence");
1332 secondExchangeIt->front()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
1333 true,
1334 "Expected a QoS data frame in the second frame exchange sequence");
1335
1336 // the first two frame exchanges occur on the same link
1337 NS_TEST_EXPECT_MSG_EQ(+secondExchangeIt->front()->linkId,
1338 +frameExchanges.at(0).begin()->front()->linkId,
1339 "Expected the first two frame exchanges to occur on the same link");
1340
1341 auto bAckRespIt = std::prev(secondExchangeIt->front());
1342 NS_TEST_EXPECT_MSG_EQ(bAckRespIt->psduMap.cbegin()->second->GetHeader(0).IsBlockAck(),
1343 true,
1344 "Expected a BlockAck response before the second frame exchange");
1345 auto bAckRespTxEnd =
1346 bAckRespIt->startTx +
1347 WifiPhy::CalculateTxDuration(bAckRespIt->psduMap,
1348 bAckRespIt->txVector,
1349 m_apMac->GetWifiPhy(bAckRespIt->linkId)->GetPhyBand());
1350
1351 // the second frame exchange starts a SIFS after the previous one
1353 bAckRespTxEnd + m_apMac->GetWifiPhy(bAckRespIt->linkId)->GetSifs(),
1354 secondExchangeIt->front()->startTx,
1355 "Expected the second frame exchange to start a SIFS after the first one");
1356
1357 // the third frame exchange is protected by MU-RTS and occurs on a different link
1358 NS_TEST_EXPECT_MSG_EQ(IsTrigger(thirdExchangeIt->front()->psduMap),
1359 true,
1360 "Expected an MU-RTS as ICF for the third frame exchange sequence");
1362 thirdExchangeIt->back()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
1363 true,
1364 "Expected a QoS data frame in the third frame exchange sequence");
1365
1367 +secondExchangeIt->front()->linkId,
1368 +thirdExchangeIt->front()->linkId,
1369 "Expected the second and third frame exchanges to occur on distinct links");
1370
1371 auto secondQosIt = secondExchangeIt->front();
1372 auto secondQosTxEnd =
1373 secondQosIt->startTx +
1374 WifiPhy::CalculateTxDuration(secondQosIt->psduMap,
1375 secondQosIt->txVector,
1376 m_apMac->GetWifiPhy(secondQosIt->linkId)->GetPhyBand());
1377
1378 NS_TEST_EXPECT_MSG_GT_OR_EQ(thirdExchangeIt->front()->startTx,
1379 secondQosTxEnd + m_transitionDelay.at(thirdExchangeStaId),
1380 "Transmission started before transition delay");
1381
1382 // the BlockAck of the third frame exchange is not received correctly, so there should be
1383 // another frame exchange
1384 NS_TEST_EXPECT_MSG_EQ((thirdExchangeIt != frameExchanges.at(thirdExchangeStaId).end()),
1385 true,
1386 "Expected a fourth frame exchange");
1387 auto fourthExchangeIt = std::next(thirdExchangeIt);
1388
1389 // the fourth frame exchange is protected by MU-RTS
1390 NS_TEST_EXPECT_MSG_EQ(IsTrigger(fourthExchangeIt->front()->psduMap),
1391 true,
1392 "Expected an MU-RTS as ICF for the fourth frame exchange sequence");
1393
1394 bAckRespIt = std::prev(fourthExchangeIt->front());
1395 NS_TEST_EXPECT_MSG_EQ(bAckRespIt->psduMap.cbegin()->second->GetHeader(0).IsBlockAck(),
1396 true,
1397 "Expected a BlockAck response before the fourth frame exchange");
1398 auto phy = m_apMac->GetWifiPhy(bAckRespIt->linkId);
1399 bAckRespTxEnd = bAckRespIt->startTx + WifiPhy::CalculateTxDuration(bAckRespIt->psduMap,
1400 bAckRespIt->txVector,
1401 phy->GetPhyBand());
1402 auto timeout = phy->GetSifs() + phy->GetSlot() + MicroSeconds(20);
1403
1404 // the fourth frame exchange starts a PIFS after the previous one because the AP
1405 // performs PIFS recovery (the initial frame in the TXOP was successfully received by
1406 // a non-EMLSR client)
1407 NS_TEST_EXPECT_MSG_GT_OR_EQ(fourthExchangeIt->front()->startTx,
1408 bAckRespTxEnd + phy->GetPifs(),
1409 "Transmission started less than a PIFS after BlockAck");
1410 NS_TEST_EXPECT_MSG_LT(fourthExchangeIt->front()->startTx,
1411 bAckRespTxEnd + phy->GetPifs() +
1412 MicroSeconds(1) /* propagation delay upper bound */,
1413 "Transmission started too much time after BlockAck");
1414
1415 auto bAckReqIt = std::next(fourthExchangeIt->front(), 2);
1416 NS_TEST_EXPECT_MSG_EQ(bAckReqIt->psduMap.cbegin()->second->GetHeader(0).IsBlockAckReq(),
1417 true,
1418 "Expected a BlockAck request in the fourth frame exchange");
1419
1420 // we are done with processing the frame exchanges, remove them (two frame exchanges
1421 // per EMLSR client, plus the last one)
1422 frameExchanges.at(0).pop_front();
1423 frameExchanges.at(0).pop_front();
1424 frameExchanges.at(1).pop_front();
1425 frameExchanges.at(1).pop_front();
1426 frameExchanges.at(thirdExchangeStaId).pop_front();
1427 }
1428
1429 /**
1430 * After disabling EMLSR mode, no MU-RTS TF should be sent. After the exchange of
1431 * EML Operating Mode Notification frames, a number of packets are generated at the AP MLD
1432 * to prepare two A-MPDUs for each EMLSR client.
1433 *
1434 * EMLSR client with EMLSR mode to be enabled on all links (A is the EMLSR client, B is the
1435 * non-EMLSR client):
1436 *
1437 * [link 0] | power save mode
1438 * ────────────────────────────────────────────────────────
1439 * ┌─────┬─────┐ ┌──────┬──────┐
1440 * │QoS 8│QoS 9│ │QoS 10│QoS 11│
1441 * │ to A│ to A│ │ to A │ to A │
1442 * ┌───┐ ┌───┐ ├─────┼─────┤ ├──────┼──────┤
1443 * ┌───┐ │MU │ │EML│ │QoS 8│QoS 9│ │QoS 10│QoS 11│
1444 * [link 1] │ACK│ │RTS│ │OM │ │ to B│ to B│ │ to B │ to B │
1445 * ────┬───┬┴───┴──┴───┴┬───┬┴───┴┬───┬──┴─────┴─────┴┬──┬────┴──────┴──────┴┬──┬─────
1446 * │EML│ │CTS│ │ACK│ │BA│ │BA│
1447 * │OM │ └───┘ └───┘ ├──┤ ├──┤
1448 * └───┘ │BA│ │BA│
1449 * └──┘ └──┘
1450 *
1451 * [link 2] | power save mode
1452 * ────────────────────────────────────────────────────────────────────────────
1453 *
1454 *
1455 * EMLSR client with EMLSR mode to be enabled on not all the links (A is the EMLSR client,
1456 * B is the non-EMLSR client):
1457 * ┌─────┬─────┐
1458 * │QoS 8│QoS 9│
1459 * │ to A│ to A│
1460 * ├─────┼─────┤
1461 * │QoS 8│QoS 9│
1462 * [link 0 - non EMLSR] │ to B│ to B│
1463 * ─────────────────────────────────────────┴─────┴─────┴┬──┬─────────────
1464 * │BA│
1465 * ├──┤
1466 * │BA│
1467 * └──┘
1468 * ┌──────┬──────┐
1469 * │QoS 10│QoS 11│
1470 * │ to A │ to A │
1471 * ┌───┐ ┌───┐ ├──────┼──────┤
1472 * ┌───┐ │MU │ │EML│ │QoS 10│QoS 11│
1473 * [link 1] │ACK│ │RTS│ │OM │ │ to B │ to B │
1474 * ────┬───┬┴───┴──┴───┴┬───┬┴───┴┬───┬──┴──────┴──────┴┬──┬─────
1475 * │EML│ │CTS│ │ACK│ │BA│
1476 * │OM │ └───┘ └───┘ ├──┤
1477 * └───┘ │BA│
1478 * └──┘
1479 *
1480 * [link 2] | power save mode
1481 * ────────────────────────────────────────────────────────────────────────────
1482 *
1483 */
1484
1485 // for each EMLSR client, there should be a frame exchange with ICF and no data frame
1486 // (ICF protects the EML Notification response) and two frame exchanges with data frames
1487 for (std::size_t i = 0; i < m_nEmlsrStations; i++)
1488 {
1489 // the default EMLSR Manager requests to send EML Notification frames on the link where
1490 // the main PHY is operating, hence this link is an EMLSR link and the EML Notification
1491 // frame is protected by an ICF
1492 auto exchangeIt = frameExchanges.at(i).cbegin();
1493
1494 auto linkIdOpt = m_staMacs[i]->GetLinkForPhy(m_mainPhyId);
1495 NS_TEST_ASSERT_MSG_EQ(linkIdOpt.has_value(),
1496 true,
1497 "Didn't find a link on which the main PHY is operating");
1498
1499 NS_TEST_EXPECT_MSG_EQ(IsTrigger(exchangeIt->front()->psduMap),
1500 true,
1501 "Expected an MU-RTS TF as ICF of first frame exchange sequence");
1502 NS_TEST_EXPECT_MSG_EQ(+exchangeIt->front()->linkId,
1503 +linkIdOpt.value(),
1504 "ICF was not sent on the expected link");
1505 NS_TEST_EXPECT_MSG_EQ(exchangeIt->size(),
1506 1,
1507 "Expected no data frame in the first frame exchange sequence");
1508
1509 frameExchanges.at(i).pop_front();
1510
1511 NS_TEST_EXPECT_MSG_GT_OR_EQ(frameExchanges.at(i).size(),
1512 2,
1513 "Expected at least 2 frame exchange sequences "
1514 << "involving EMLSR client " << i);
1515
1516 auto firstExchangeIt = frameExchanges.at(i).cbegin();
1517 auto secondExchangeIt = std::next(firstExchangeIt);
1518
1519 const auto firstAmpduTxEnd =
1520 firstExchangeIt->back()->startTx +
1522 firstExchangeIt->back()->psduMap,
1523 firstExchangeIt->back()->txVector,
1524 m_staMacs[i]->GetWifiPhy(firstExchangeIt->back()->linkId)->GetPhyBand());
1525 const auto secondAmpduTxStart = secondExchangeIt->front()->startTx;
1526
1528 firstExchangeIt->front()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
1529 true,
1530 "Expected a QoS data frame in the first frame exchange sequence");
1531 NS_TEST_EXPECT_MSG_EQ(firstExchangeIt->size(),
1532 1,
1533 "Expected one frame only in the first frame exchange sequence");
1534
1536 secondExchangeIt->front()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
1537 true,
1538 "Expected a QoS data frame in the second frame exchange sequence");
1539 NS_TEST_EXPECT_MSG_EQ(secondExchangeIt->size(),
1540 1,
1541 "Expected one frame only in the second frame exchange sequence");
1542
1543 if (m_staMacs[i]->GetNLinks() == m_emlsrLinks.size())
1544 {
1545 // all links are EMLSR links: the two QoS data frames are sent one after another on
1546 // the link used for sending EML OMN
1548 +firstExchangeIt->front()->linkId,
1549 +linkIdOpt.value(),
1550 "First frame exchange expected to occur on link used to send EML OMN");
1551
1553 +secondExchangeIt->front()->linkId,
1554 +linkIdOpt.value(),
1555 "Second frame exchange expected to occur on link used to send EML OMN");
1556
1557 NS_TEST_EXPECT_MSG_LT(firstAmpduTxEnd,
1558 secondAmpduTxStart,
1559 "A-MPDUs are not sent one after another");
1560 }
1561 else
1562 {
1563 // the two QoS data frames are sent concurrently on distinct links
1564 NS_TEST_EXPECT_MSG_NE(+firstExchangeIt->front()->linkId,
1565 +secondExchangeIt->front()->linkId,
1566 "Frame exchanges expected to occur on distinct links");
1567
1568 NS_TEST_EXPECT_MSG_GT(firstAmpduTxEnd,
1569 secondAmpduTxStart,
1570 "A-MPDUs are not sent concurrently");
1571 }
1572 }
1573}
1574
1575void
1577{
1578 std::optional<std::size_t> staId;
1579 for (std::size_t id = 0; id < m_nEmlsrStations + m_nNonEmlsrStations; id++)
1580 {
1581 if (m_staMacs.at(id)->GetLinkIdByAddress(address))
1582 {
1583 staId = id;
1584 break;
1585 }
1586 }
1587 NS_TEST_ASSERT_MSG_EQ(staId.has_value(), true, "Not an address of a non-AP MLD " << address);
1588
1589 // check that all EMLSR links (but the link used for ML setup) of the EMLSR clients
1590 // are considered to be in power save mode by the AP MLD; all the other links have
1591 // transitioned to active mode instead
1592 for (uint8_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
1593 {
1594 bool psModeExpected =
1595 *staId < m_nEmlsrStations && linkId != m_mainPhyId && m_emlsrLinks.count(linkId) == 1;
1596 auto addr = m_staMacs.at(*staId)->GetAddress();
1597 auto psMode = m_apMac->GetWifiRemoteStationManager(linkId)->IsInPsMode(addr);
1598 NS_TEST_EXPECT_MSG_EQ(psMode,
1599 psModeExpected,
1600 "EMLSR link " << +linkId << " of EMLSR client " << *staId
1601 << " not in " << (psModeExpected ? "PS" : "active")
1602 << " mode");
1603 // check that AP is blocking transmission of QoS data frames on this link
1605 addr,
1606 linkId,
1607 WifiQueueBlockedReason::POWER_SAVE_MODE,
1608 psModeExpected,
1609 "Checking PM mode after association on AP MLD for EMLSR client " +
1610 std::to_string(*staId),
1611 false);
1612 }
1613}
1614
1615void
1617 const WifiTxVector& txVector,
1618 uint8_t linkId)
1619{
1620 // the AP is replying to a received EMLSR Notification frame
1621 auto pkt = mpdu->GetPacket()->Copy();
1622 const auto& hdr = mpdu->GetHeader();
1624 MgtEmlOmn frame;
1625 pkt->RemoveHeader(frame);
1626
1627 std::optional<std::size_t> staId;
1628 for (std::size_t id = 0; id < m_nEmlsrStations; id++)
1629 {
1630 if (m_staMacs.at(id)->GetFrameExchangeManager(linkId)->GetAddress() == hdr.GetAddr1())
1631 {
1632 staId = id;
1633 break;
1634 }
1635 }
1636 NS_TEST_ASSERT_MSG_EQ(staId.has_value(),
1637 true,
1638 "Not an address of an EMLSR client " << hdr.GetAddr1());
1639
1640 // The EMLSR mode change occurs a Transition Timeout after the end of the PPDU carrying the Ack
1641 auto phy = m_apMac->GetWifiPhy(linkId);
1642 auto txDuration =
1643 WifiPhy::CalculateTxDuration(mpdu->GetSize() + 4, // A-MPDU Subframe header size
1644 txVector,
1645 phy->GetPhyBand());
1646 WifiTxVector ackTxVector =
1647 m_staMacs.at(*staId)->GetWifiRemoteStationManager(linkId)->GetAckTxVector(hdr.GetAddr2(),
1648 txVector);
1649 auto ackDuration = WifiPhy::CalculateTxDuration(GetAckSize() + 4, // A-MPDU Subframe header
1650 ackTxVector,
1651 phy->GetPhyBand());
1652
1653 Simulator::Schedule(txDuration + phy->GetSifs() + ackDuration, [=, this]() {
1654 if (frame.m_emlControl.emlsrMode == 1)
1655 {
1656 // EMLSR mode enabled. Check that all EMLSR links of the EMLSR clients are considered
1657 // to be in active mode by the AP MLD
1658 for (const auto linkId : m_emlsrLinks)
1659 {
1660 auto addr = m_staMacs.at(*staId)->GetAddress();
1661 auto psMode = m_apMac->GetWifiRemoteStationManager(linkId)->IsInPsMode(addr);
1662 NS_TEST_EXPECT_MSG_EQ(psMode,
1663 false,
1664 "EMLSR link " << +linkId << " of EMLSR client " << *staId
1665 << " not in active mode");
1666 // check that AP is not blocking transmission of QoS data frames on this link
1667 CheckBlockedLink(
1668 m_apMac,
1669 addr,
1670 linkId,
1671 WifiQueueBlockedReason::POWER_SAVE_MODE,
1672 false,
1673 "Checking EMLSR links on AP MLD after EMLSR mode is enabled on EMLSR client " +
1674 std::to_string(*staId),
1675 false);
1676 }
1677 }
1678 else
1679 {
1680 // EMLSR mode disabled. Check that all EMLSR links (but the link used to send the
1681 // EML Notification frame) of the EMLSR clients are considered to be in power save
1682 // mode by the AP MLD; the other links are in active mode
1683 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1684 {
1685 bool psModeExpected = id != linkId && m_emlsrLinks.count(id) == 1;
1686 auto addr = m_staMacs.at(*staId)->GetAddress();
1687 auto psMode = m_apMac->GetWifiRemoteStationManager(id)->IsInPsMode(addr);
1688 NS_TEST_EXPECT_MSG_EQ(psMode,
1689 psModeExpected,
1690 "EMLSR link "
1691 << +id << " of EMLSR client " << *staId << " not in "
1692 << (psModeExpected ? "PS" : "active") << " mode");
1693 // check that AP is blocking transmission of QoS data frames on this link
1694 CheckBlockedLink(
1695 m_apMac,
1696 addr,
1697 id,
1698 WifiQueueBlockedReason::POWER_SAVE_MODE,
1699 psModeExpected,
1700 "Checking links on AP MLD after EMLSR mode is disabled on EMLSR client " +
1701 std::to_string(*staId),
1702 false);
1703 }
1704 }
1705 });
1706}
1707
1708void
1710 const WifiTxVector& txVector,
1711 uint8_t linkId)
1712{
1713 CtrlTriggerHeader trigger;
1714 mpdu->GetPacket()->PeekHeader(trigger);
1715 if (!trigger.IsMuRts())
1716 {
1717 return;
1718 }
1719
1721 true,
1722 "Did not expect an ICF before enabling EMLSR mode");
1723
1726 "Unexpected preamble type for the Initial Control frame");
1727 auto rate = txVector.GetMode().GetDataRate(txVector);
1728 NS_TEST_EXPECT_MSG_EQ((rate == 6e6 || rate == 12e6 || rate == 24e6),
1729 true,
1730 "Unexpected rate for the Initial Control frame: " << rate);
1731
1732 bool found = false;
1733 Time maxPaddingDelay{};
1734
1735 for (const auto& userInfo : trigger)
1736 {
1737 auto addr = m_apMac->GetMldOrLinkAddressByAid(userInfo.GetAid12());
1738 NS_TEST_ASSERT_MSG_EQ(addr.has_value(),
1739 true,
1740 "AID " << userInfo.GetAid12() << " not found");
1741
1742 if (m_apMac->GetWifiRemoteStationManager(linkId)->GetEmlsrEnabled(*addr))
1743 {
1744 found = true;
1745
1746 for (std::size_t i = 0; i < m_nEmlsrStations; i++)
1747 {
1748 if (m_staMacs.at(i)->GetAddress() == *addr)
1749 {
1750 maxPaddingDelay = Max(maxPaddingDelay, m_paddingDelay.at(i));
1751 break;
1752 }
1753 }
1754
1755 // check that the AP has blocked transmission on all other EMLSR links
1756 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1757 {
1758 if (!m_apMac->GetWifiRemoteStationManager(id)->GetEmlsrEnabled(*addr))
1759 {
1760 continue;
1761 }
1762
1764 *addr,
1765 id,
1766 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1767 id != linkId,
1768 "Checking that AP blocked transmissions on all other EMLSR "
1769 "links after sending ICF to client with AID=" +
1770 std::to_string(userInfo.GetAid12()),
1771 false);
1772 }
1773 }
1774 }
1775
1776 NS_TEST_EXPECT_MSG_EQ(found, true, "Expected ICF to be addressed to at least an EMLSR client");
1777
1778 auto txDuration = WifiPhy::CalculateTxDuration(mpdu->GetSize(),
1779 txVector,
1780 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
1781
1782 if (maxPaddingDelay.IsStrictlyPositive())
1783 {
1784 // compare the TX duration of this Trigger Frame to that of the Trigger Frame with no
1785 // padding added
1786 trigger.SetPaddingSize(0);
1787 auto pkt = Create<Packet>();
1788 pkt->AddHeader(trigger);
1789 auto txDurationWithout =
1790 WifiPhy::CalculateTxDuration(Create<WifiPsdu>(pkt, mpdu->GetHeader()),
1791 txVector,
1792 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
1793
1794 NS_TEST_EXPECT_MSG_EQ(txDuration,
1795 txDurationWithout + maxPaddingDelay,
1796 "Unexpected TX duration of the MU-RTS TF with padding "
1797 << maxPaddingDelay.As(Time::US));
1798 }
1799
1800 // check that the EMLSR clients have blocked transmissions on other links after
1801 // receiving this ICF
1802 for (const auto& userInfo : trigger)
1803 {
1804 for (std::size_t i = 0; i < m_nEmlsrStations; i++)
1805 {
1806 if (m_staMacs[i]->GetAssociationId() != userInfo.GetAid12())
1807 {
1808 continue;
1809 }
1810
1811 Simulator::Schedule(txDuration + NanoSeconds(5), [=, this]() {
1812 for (uint8_t id = 0; id < m_staMacs[i]->GetNLinks(); id++)
1813 {
1814 // non-EMLSR links or links on which ICF is received are not blocked
1817 id,
1818 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1819 id != linkId && m_staMacs[i]->IsEmlsrLink(id),
1820 "Checking EMLSR links on EMLSR client " + std::to_string(i) +
1821 " after receiving ICF");
1822 }
1823 });
1824
1825 break;
1826 }
1827 }
1828}
1829
1830void
1832 const WifiTxVector& txVector,
1833 uint8_t linkId)
1834{
1835 if (m_nEmlsrStations != 2 || m_apMac->GetNLinks() != m_emlsrLinks.size() ||
1837
1838 {
1839 // we are interested in frames sent to test transition delay
1840 return;
1841 }
1842
1843 std::size_t firstClientId = 0;
1844 std::size_t secondClientId = 1;
1845 auto addr = m_staMacs[secondClientId]->GetAddress();
1846 auto txDuration =
1847 WifiPhy::CalculateTxDuration(psduMap, txVector, m_apMac->GetWifiPhy(linkId)->GetPhyBand());
1848
1850
1851 switch (m_countQoSframes)
1852 {
1853 case 1:
1854 // generate another small packet addressed to the first EMLSR client only
1856 GetApplication(DOWNLINK, firstClientId, 1, 40));
1857 // both EMLSR clients are about to receive a QoS data frame
1858 for (std::size_t clientId : {firstClientId, secondClientId})
1859 {
1860 Simulator::Schedule(txDuration, [=, this]() {
1861 for (uint8_t id = 0; id < m_staMacs[clientId]->GetNLinks(); id++)
1862 {
1863 // link on which QoS data is received is not blocked
1864 CheckBlockedLink(m_staMacs[clientId],
1866 id,
1867 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1868 id != linkId,
1869 "Checking EMLSR links on EMLSR client " +
1870 std::to_string(clientId) +
1871 " after receiving the first QoS data frame");
1872 }
1873 });
1874 }
1875 break;
1876 case 2:
1877 // generate another small packet addressed to the second EMLSR client
1879 GetApplication(DOWNLINK, secondClientId, 1, 40));
1880
1881 // when the transmission of the second QoS data frame starts, both EMLSR clients are
1882 // still blocking all the links but the one used to receive the QoS data frame
1883 for (std::size_t clientId : {firstClientId, secondClientId})
1884 {
1885 for (uint8_t id = 0; id < m_staMacs[clientId]->GetNLinks(); id++)
1886 {
1887 // link on which QoS data is received is not blocked
1888 CheckBlockedLink(m_staMacs[clientId],
1890 id,
1891 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1892 id != linkId,
1893 "Checking EMLSR links on EMLSR client " +
1894 std::to_string(clientId) +
1895 " when starting the reception of the second QoS frame");
1896 }
1897 }
1898
1899 // the EMLSR client that is not the recipient of the QoS frame being transmitted will
1900 // switch back to listening mode after a transition delay starting from the end of
1901 // the PPDU carrying this QoS data frame
1902
1903 // immediately before the end of the PPDU, this link only is not blocked for the EMLSR
1904 // client on the AP MLD
1905 Simulator::Schedule(txDuration - NanoSeconds(1), [=, this]() {
1906 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1907 {
1909 addr,
1910 id,
1911 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1912 id != linkId,
1913 "Checking that links of EMLSR client " +
1914 std::to_string(secondClientId) +
1915 " are blocked on the AP MLD before the end of the PPDU");
1916 }
1917 });
1918 // immediately before the end of the PPDU, all the links on the EMLSR client that is not
1919 // the recipient of the second QoS frame are unblocked (they are unblocked when the
1920 // PHY-RXSTART.indication is not received)
1921 Simulator::Schedule(txDuration - NanoSeconds(1), [=, this]() {
1922 for (uint8_t id = 0; id < m_staMacs[secondClientId]->GetNLinks(); id++)
1923 {
1924 CheckBlockedLink(m_staMacs[secondClientId],
1926 id,
1927 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1928 false,
1929 "Checking that links of EMLSR client " +
1930 std::to_string(secondClientId) +
1931 " are unblocked before the end of the second QoS frame");
1932 }
1933 });
1934 // immediately after the end of the PPDU, all links are blocked for the EMLSR client
1935 Simulator::Schedule(txDuration + NanoSeconds(1), [=, this]() {
1936 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1937 {
1939 addr,
1940 id,
1941 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
1942 true,
1943 "Checking links of EMLSR client " +
1944 std::to_string(secondClientId) +
1945 " are all blocked on the AP MLD after the end of the PPDU");
1946 }
1947 });
1948 // immediately before the transition delay, all links are still blocked for the EMLSR client
1950 txDuration + m_transitionDelay.at(secondClientId) - NanoSeconds(1),
1951 [=, this]() {
1952 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1953 {
1955 m_apMac,
1956 addr,
1957 id,
1958 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
1959 true,
1960 "Checking links of EMLSR client " + std::to_string(secondClientId) +
1961 " are all blocked on the AP MLD before the transition delay",
1962 false);
1963 }
1964 });
1965
1966 // 100 us before the transition delay expires, generate another small packet addressed
1967 // to a non-EMLSR client. The AP will start a TXOP to transmit this frame, while the
1968 // frame addressed to the EMLSR client is still queued because the transition delay has
1969 // not yet elapsed. The transition delay will expire while the AP is transmitting the
1970 // frame to the non-EMLSR client, so that the AP continues the TXOP to transmit the frame
1971 // to the EMLSR client and we can check that the AP performs PIFS recovery after missing
1972 // the BlockAck from the EMLSR client
1973 Simulator::Schedule(txDuration + m_transitionDelay.at(secondClientId) - MicroSeconds(100),
1974 [=, this]() {
1975 m_apMac->GetDevice()->GetNode()->AddApplication(
1977 });
1978
1979 break;
1980 case 3:
1981 // this is the frame addressed to a non-EMLSR client, which is transmitted before the
1982 // frame addressed to the EMLSR client, because the links of the latter are still blocked
1983 // at the AP because the transition delay has not yet elapsed
1985 psduMap.cbegin()->second->GetAddr1(),
1986 m_staMacs[m_nEmlsrStations]->GetFrameExchangeManager(linkId)->GetAddress(),
1987 "QoS frame not addressed to a non-EMLSR client");
1988
1989 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1990 {
1992 addr,
1993 id,
1994 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
1995 true,
1996 "Checking links of EMLSR client " + std::to_string(secondClientId) +
1997 " are all blocked on the AP MLD before the transition delay");
1998 }
1999 // Block transmissions to the EMLSR client on all the links but the one on which this
2000 // frame is sent, so that the AP will continue this TXOP to send the queued frame to the
2001 // EMLSR client once the transition delay elapses
2002 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2003 {
2004 if (id != linkId)
2005 {
2006 m_apMac->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED, addr, {id});
2007 }
2008 }
2009 break;
2010 case 4:
2011 // the AP is continuing the TXOP, no need to block transmissions anymore
2012 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2013 {
2014 m_apMac->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED, addr, {id});
2015 }
2016 // at the end of the fourth QoS frame, this link only is not blocked on the EMLSR
2017 // client receiving the frame
2018 Simulator::Schedule(txDuration, [=, this]() {
2019 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2020 {
2021 CheckBlockedLink(m_staMacs[secondClientId],
2023 id,
2024 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2025 id != linkId,
2026 "Checking EMLSR links on EMLSR client " +
2027 std::to_string(secondClientId) +
2028 " after receiving the fourth QoS data frame");
2029 }
2030 });
2031 break;
2032 }
2033}
2034
2035void
2037 const WifiTxVector& txVector,
2038 uint8_t phyId)
2039{
2040 if (m_nEmlsrStations != 2 || m_apMac->GetNLinks() != m_emlsrLinks.size() ||
2042 {
2043 // we are interested in frames sent to test transition delay
2044 return;
2045 }
2046
2047 if (++m_countBlockAck == 4)
2048 {
2049 // fourth BlockAck is sent by a non-EMLSR client
2050 return;
2051 }
2052
2053 auto taddr = psduMap.cbegin()->second->GetAddr2();
2054 std::size_t clientId;
2055 if (m_staMacs[0]->GetLinkIdByAddress(taddr))
2056 {
2057 clientId = 0;
2058 }
2059 else
2060 {
2061 NS_TEST_ASSERT_MSG_EQ(m_staMacs[1]->GetLinkIdByAddress(taddr).has_value(),
2062 true,
2063 "Unexpected TA for BlockAck: " << taddr);
2064 clientId = 1;
2065 }
2066
2067 // find the link on which the main PHY is operating
2068 auto currMainPhyLinkId = m_staMacs[clientId]->GetLinkForPhy(phyId);
2070 currMainPhyLinkId.has_value(),
2071 true,
2072 "Didn't find the link on which the PHY sending the BlockAck is operating");
2073 auto linkId = *currMainPhyLinkId;
2074
2075 // we need the MLD address to check the status of the container queues
2076 auto addr = m_apMac->GetWifiRemoteStationManager(linkId)->GetMldAddress(taddr);
2077 NS_TEST_ASSERT_MSG_EQ(addr.has_value(), true, "MLD address not found for " << taddr);
2078
2079 auto apPhy = m_apMac->GetWifiPhy(linkId);
2080 auto txDuration = WifiPhy::CalculateTxDuration(psduMap, txVector, apPhy->GetPhyBand());
2081 auto cfEndTxDuration = WifiPhy::CalculateTxDuration(
2082 Create<WifiPsdu>(Create<Packet>(), WifiMacHeader(WIFI_MAC_CTL_END)),
2084 txVector.GetChannelWidth()),
2085 apPhy->GetPhyBand());
2086
2087 switch (m_countBlockAck)
2088 {
2089 case 5:
2090 // the PPDU carrying this BlockAck is corrupted, hence the AP MLD MAC receives the
2091 // PHY-RXSTART indication but it does not receive any frame from the PHY. Therefore,
2092 // at the end of the PPDU transmission, the AP MLD realizes that the EMLSR client has
2093 // not responded and makes an attempt at continuing the TXOP
2094
2095 // at the end of the PPDU, this link only is not blocked on both the EMLSR client and
2096 // the AP MLD
2097 Simulator::Schedule(txDuration, [=, this]() {
2098 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2099 {
2100 CheckBlockedLink(m_staMacs[clientId],
2102 id,
2103 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2104 id != linkId,
2105 "Checking links on EMLSR client " + std::to_string(clientId) +
2106 " at the end of fourth BlockAck");
2108 *addr,
2109 id,
2110 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2111 id != linkId,
2112 "Checking links of EMLSR client " + std::to_string(clientId) +
2113 " on the AP MLD at the end of fourth BlockAck");
2114 }
2115 });
2116 // a SIFS after the end of the PPDU, still this link only is not blocked on both the
2117 // EMLSR client and the AP MLD
2118 Simulator::Schedule(txDuration + apPhy->GetSifs(), [=, this]() {
2119 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2120 {
2121 CheckBlockedLink(m_staMacs[clientId],
2122 m_apMac->GetAddress(),
2123 id,
2124 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2125 id != linkId,
2126 "Checking links on EMLSR client " + std::to_string(clientId) +
2127 " a SIFS after the end of fourth BlockAck");
2128 CheckBlockedLink(m_apMac,
2129 *addr,
2130 id,
2131 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2132 id != linkId,
2133 "Checking links of EMLSR client " + std::to_string(clientId) +
2134 " a SIFS after the end of fourth BlockAck");
2135 }
2136 });
2137 // corrupt this BlockAck so that the AP MLD sends a BlockAckReq later on
2138 {
2139 auto uid = psduMap.cbegin()->second->GetPacket()->GetUid();
2140 m_errorModel->SetList({uid});
2141 }
2142 break;
2143 case 6:
2144 // at the end of the PPDU, this link only is not blocked on both the EMLSR client and
2145 // the AP MLD
2146 Simulator::Schedule(txDuration, [=, this]() {
2147 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2148 {
2149 CheckBlockedLink(m_staMacs[clientId],
2151 id,
2152 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2153 id != linkId,
2154 "Checking links on EMLSR client " + std::to_string(clientId) +
2155 " at the end of fifth BlockAck");
2157 *addr,
2158 id,
2159 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2160 id != linkId,
2161 "Checking links of EMLSR client " + std::to_string(clientId) +
2162 " on the AP MLD at the end of fifth BlockAck");
2163 }
2164 });
2165 // before the end of the CF-End frame, still this link only is not blocked on both the
2166 // EMLSR client and the AP MLD
2168 txDuration + apPhy->GetSifs() + cfEndTxDuration - MicroSeconds(1),
2169 [=, this]() {
2170 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2171 {
2172 CheckBlockedLink(m_staMacs[clientId],
2174 id,
2175 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2176 id != linkId,
2177 "Checking links on EMLSR client " + std::to_string(clientId) +
2178 " before the end of CF-End frame");
2180 *addr,
2181 id,
2182 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2183 id != linkId,
2184 "Checking links of EMLSR client " + std::to_string(clientId) +
2185 " on the AP MLD before the end of CF-End frame");
2186 }
2187 });
2188 // after the end of the CF-End frame, all links for the EMLSR client are blocked on the
2189 // AP MLD
2191 txDuration + apPhy->GetSifs() + cfEndTxDuration + MicroSeconds(1),
2192 [=, this]() {
2193 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2194 {
2196 m_apMac,
2197 *addr,
2198 id,
2199 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
2200 true,
2201 "Checking links of EMLSR client " + std::to_string(clientId) +
2202 " are all blocked on the AP MLD right after the end of CF-End");
2203 }
2204 });
2205 // before the end of the transition delay, all links for the EMLSR client are still
2206 // blocked on the AP MLD
2208 txDuration + apPhy->GetSifs() + cfEndTxDuration + m_transitionDelay.at(clientId) -
2209 MicroSeconds(1),
2210 [=, this]() {
2211 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2212 {
2214 m_apMac,
2215 *addr,
2216 id,
2217 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
2218 true,
2219 "Checking links of EMLSR client " + std::to_string(clientId) +
2220 " are all blocked on the AP MLD before the end of transition delay");
2221 }
2222 });
2223 // immediately after the transition delay, all links for the EMLSR client are unblocked
2225 txDuration + apPhy->GetSifs() + cfEndTxDuration + m_transitionDelay.at(clientId) +
2226 MicroSeconds(1),
2227 [=, this]() {
2228 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2229 {
2231 m_apMac,
2232 *addr,
2233 id,
2234 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
2235 false,
2236 "Checking links of EMLSR client " + std::to_string(clientId) +
2237 " are all unblocked on the AP MLD after the transition delay");
2238 }
2239 });
2240 break;
2241 }
2242}
2243
2244void
2246{
2249
2250 CheckResults();
2251
2253}
2254
2256 : EmlsrOperationsTestBase("Check EML UL TXOP transmissions"),
2257 m_emlsrLinks(params.linksToEnableEmlsrOn),
2258 m_channelWidth(params.channelWidth),
2259 m_auxPhyChannelWidth(params.auxPhyChannelWidth),
2260 m_mediumSyncDuration(params.mediumSyncDuration),
2261 m_msdMaxNTxops(params.msdMaxNTxops),
2262 m_emlsrEnabledTime(0),
2263 m_firstUlPktsGenTime(0),
2264 m_unblockMainPhyLinkDelay(MilliSeconds(20)),
2265 m_checkBackoffStarted(false),
2266 m_countQoSframes(0),
2267 m_countBlockAck(0),
2268 m_countRtsframes(0),
2269 m_genBackoffIfTxopWithoutTx(params.genBackoffIfTxopWithoutTx)
2270{
2271 m_nEmlsrStations = 1;
2273 m_linksToEnableEmlsrOn = params.linksToEnableEmlsrOn;
2274 m_mainPhyId = 1;
2275
2276 // when aux PHYs do not switch link, the main PHY switches back to its previous link after
2277 // a TXOP, hence the transition delay must exceed the channel switch delay (default: 250us)
2279 m_establishBaDl = true;
2280 m_establishBaUl = true;
2281 m_duration = Seconds(1);
2282
2283 NS_ABORT_MSG_IF(params.linksToEnableEmlsrOn.size() < 2,
2284 "This test requires at least two links to be configured as EMLSR links");
2285 for (uint8_t id = 0; id < 3; id++)
2286 {
2287 if (!m_emlsrLinks.contains(id))
2288 {
2289 // non-EMLSR link found
2290 m_nonEmlsrLink = id;
2291 break;
2292 }
2293 }
2294}
2295
2296void
2298{
2299 Config::SetDefault("ns3::EmlsrManager::AuxPhyChannelWidth",
2301 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(false));
2302 Config::SetDefault("ns3::EhtConfiguration::MediumSyncDuration",
2304 Config::SetDefault("ns3::EhtConfiguration::MsdMaxNTxops", UintegerValue(m_msdMaxNTxops));
2305 Config::SetDefault("ns3::ChannelAccessManager::GenerateBackoffIfTxopWithoutTx",
2307 // Channel switch delay should be less than RTS TX time + SIFS + CTS TX time, otherwise
2308 // UL TXOPs cannot be initiated by aux PHYs
2309 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(MicroSeconds(75)));
2310
2312
2313 m_staMacs[0]->GetQosTxop(AC_BE)->TraceConnectWithoutContext(
2314 "BackoffTrace",
2316
2317 uint8_t linkId = 0;
2318 // configure channels of the given width
2320 {
2321 uint16_t bw = 20;
2322 uint8_t number = band == WIFI_PHY_BAND_5GHZ ? 36 : 1;
2323
2324 auto width = std::min<uint16_t>(m_channelWidth, band == WIFI_PHY_BAND_2_4GHZ ? 40 : 160);
2325 while (bw < width)
2326 {
2327 bw *= 2;
2328 number += bw / 20;
2329 }
2330
2331 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0]})
2332 {
2333 mac->GetWifiPhy(linkId)->SetOperatingChannel(
2334 WifiPhy::ChannelTuple{number, width, band, 0});
2335 }
2336 linkId++;
2337 }
2338
2339 // install post reception error model on the AP affiliated with the AP MLD and operating on
2340 // the same link as the main PHY of the EMLSR client
2341 m_errorModel = CreateObject<ListErrorModel>();
2343}
2344
2345void
2347{
2348 NS_LOG_INFO("Backoff value " << backoff << " generated by EMLSR client on link " << +linkId
2349 << "\n");
2350 if (linkId != m_mainPhyId)
2351 {
2352 return; // we are only interested in backoff on main PHY link
2353 }
2354
2355 if (m_backoffEndTime)
2356 {
2358 {
2359 // another backoff value while checkBackoffStarted is true is generated only if
2360 // GenerateBackoffIfTxopWithoutTx is true
2363 true,
2364 "Another backoff value should not be generated while the main PHY link is blocked");
2365
2368 "Backoff generated at unexpected time");
2369 }
2370 else
2371 {
2372 // we are done checking the backoff
2373 m_backoffEndTime.reset();
2374 }
2375 }
2376
2378 {
2380 m_staMacs[0]->GetChannelAccessManager(linkId)->GetSifs() +
2381 (m_staMacs[0]->GetQosTxop(AC_BE)->GetAifsn(linkId) + backoff) *
2382 m_staMacs[0]->GetChannelAccessManager(linkId)->GetSlot();
2383 }
2384}
2385
2386void
2388 uint8_t phyId,
2389 WifiConstPsduMap psduMap,
2390 WifiTxVector txVector,
2391 double txPowerW)
2392{
2393 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2394 auto linkId = m_txPsdus.back().linkId;
2395
2396 auto psdu = psduMap.begin()->second;
2397 auto nodeId = mac->GetDevice()->GetNode()->GetId();
2398
2399 switch (psdu->GetHeader(0).GetType())
2400 {
2402 NS_ASSERT_MSG(nodeId > 0, "APs do not send AssocReq frames");
2403 NS_TEST_EXPECT_MSG_EQ(+linkId, +m_mainPhyId, "AssocReq not sent by the main PHY");
2404 break;
2405
2406 case WIFI_MAC_CTL_RTS:
2407 CheckRtsFrames(*psdu->begin(), txVector, linkId);
2408 break;
2409
2410 case WIFI_MAC_CTL_CTS:
2411 CheckCtsFrames(*psdu->begin(), txVector, linkId);
2412 break;
2413
2414 case WIFI_MAC_QOSDATA:
2415 CheckQosFrames(psduMap, txVector, linkId);
2416 break;
2417
2419 CheckBlockAck(psduMap, txVector, linkId);
2420 break;
2421
2422 default:;
2423 }
2424}
2425
2426void
2428{
2429 // initially, we prevent transmissions on aux PHY links
2430 auto auxPhyLinks = m_staMacs[0]->GetSetupLinkIds();
2431 auxPhyLinks.erase(m_mainPhyId);
2432 if (m_nonEmlsrLink)
2433 {
2434 auxPhyLinks.erase(*m_nonEmlsrLink);
2435 }
2436 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2438 auxPhyLinks);
2439
2440 // Association, Block Ack agreement establishment and enabling EMLSR mode have been done.
2441 // After 50ms, schedule:
2442 // - block of transmissions on the link where the main PHY is operating and on the non-EMLSR
2443 // link (if any)
2444 // - the generation of two UL packets
2445 // - after m_unblockMainPhyLinkDelay, unblock transmissions on the link where the main PHY
2446 // is operating, so that the first data frame is transmitted on that link
2448 std::set<uint8_t> linkIds;
2449 linkIds.insert(*m_staMacs[0]->GetLinkForPhy(m_mainPhyId));
2450 if (m_nonEmlsrLink)
2451 {
2452 linkIds.insert(*m_nonEmlsrLink);
2453 }
2454 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2456 linkIds);
2457
2458 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
2459 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 2, 1000));
2461
2463 m_staMacs[0]->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2465 {*m_staMacs[0]->GetLinkForPhy(m_mainPhyId)});
2466 });
2467 });
2468}
2469
2470void
2472 const WifiTxVector& txVector,
2473 uint8_t linkId)
2474{
2476
2477 auto txDuration =
2478 WifiPhy::CalculateTxDuration(psduMap, txVector, m_apMac->GetWifiPhy(linkId)->GetPhyBand());
2479
2480 switch (m_countQoSframes)
2481 {
2482 case 1:
2483 case 2:
2484 // do nothing, these are the QoS data frames sent to establish BA agreements in DL and UL
2485 // direction
2486 break;
2487 case 3:
2488 // first UL data frame (transmitted by the main PHY)
2489 if (m_nonEmlsrLink)
2490 {
2491 // generate data packets for another UL data frame, which will be sent on the
2492 // non-EMLSR link
2493 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
2494 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
2495 GetApplication(UPLINK, 0, 2, 1000));
2496
2497 // unblock transmissions on the non-EMLSR link once the two packets are queued
2498 Simulator::ScheduleNow([=, this]() {
2499 m_staMacs[0]->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2501 {*m_nonEmlsrLink});
2502 });
2503 }
2504
2505 // check that other EMLSR links are now blocked on the EMLSR client and on the AP MLD
2506 // after this QoS data frame is received
2507 Simulator::ScheduleNow([=, this]() {
2508 for (auto id : m_staMacs[0]->GetLinkIds())
2509 {
2511 m_staMacs[0],
2513 id,
2514 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2515 id != m_staMacs[0]->GetLinkForPhy(m_mainPhyId) && m_staMacs[0]->IsEmlsrLink(id),
2516 "Checking EMLSR links on EMLSR client while sending the first data frame",
2517 false);
2518
2520 txDuration + MicroSeconds(1) /* propagation delay */,
2521 [=, this]() {
2523 m_apMac,
2524 m_staMacs[0]->GetAddress(),
2525 id,
2526 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2527 id != m_staMacs[0]->GetLinkForPhy(m_mainPhyId) &&
2528 m_staMacs[0]->IsEmlsrLink(id),
2529 "Checking EMLSR links on AP MLD while sending the first data frame");
2530 });
2531 }
2532 });
2533
2534 if (m_nonEmlsrLink)
2535 {
2536 break;
2537 }
2538 m_countQoSframes++; // if all EMLSR links, next case is already executed now
2539 [[fallthrough]];
2540 case 4:
2541 // check that other EMLSR links are now blocked on the EMLSR client and on the AP MLD
2542 // after this QoS data frame is received
2543 Simulator::ScheduleNow([=, this]() {
2544 // make aux PHYs capable of transmitting frames
2545 auto auxPhyLinks = m_staMacs[0]->GetSetupLinkIds();
2546 auxPhyLinks.erase(m_mainPhyId);
2547 if (m_nonEmlsrLink)
2548 {
2549 auxPhyLinks.erase(*m_nonEmlsrLink);
2550 }
2551 m_staMacs[0]->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2553 auxPhyLinks);
2554
2555 // block transmissions on the link where the main PHY is operating
2556 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2558 {*m_staMacs[0]->GetLinkForPhy(m_mainPhyId)});
2559
2560 // generate data packets for another UL data frame, which will be sent on a link on
2561 // which an aux PHY is operating
2562 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
2563 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
2564 GetApplication(UPLINK, 0, 2, 1000));
2565 });
2566 break;
2567 case 5:
2568 // check that other EMLSR links are now blocked on both the EMLSR client and the AP MLD
2569 Simulator::ScheduleNow([=, this]() {
2570 for (auto id : m_staMacs[0]->GetLinkIds())
2571 {
2573 m_staMacs[0],
2575 id,
2576 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2577 id != linkId && m_staMacs[0]->IsEmlsrLink(id),
2578 "Checking EMLSR links on EMLSR client while sending the second data frame",
2579 false);
2580
2582 m_apMac,
2583 m_staMacs[0]->GetAddress(),
2584 id,
2585 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2586 id != linkId && m_staMacs[0]->IsEmlsrLink(id),
2587 "Checking EMLSR links on AP MLD while sending the second data frame",
2588 false);
2589 }
2590
2591 // unblock transmission on the link where the main PHY is operating
2592 m_staMacs[0]->GetMacQueueScheduler()->UnblockQueues(
2593 WifiQueueBlockedReason::TID_NOT_MAPPED,
2594 AC_BE,
2597 m_staMacs[0]->GetAddress(),
2598 {0},
2599 {m_mainPhyId});
2600 });
2601 break;
2602 }
2603}
2604
2605void
2607 const WifiTxVector& txVector,
2608 uint8_t linkId)
2609{
2611
2612 auto auxPhyLinks = m_staMacs[0]->GetSetupLinkIds();
2613 auxPhyLinks.erase(m_mainPhyId);
2614 if (m_nonEmlsrLink)
2615 {
2616 auxPhyLinks.erase(*m_nonEmlsrLink);
2617 }
2618
2619 // lambda to check that the MediumSyncDelay timer is correctly running/not running and
2620 // the CCA ED threshold is set to the correct value on all the links
2621 auto checkMediumSyncDelayTimerActive = [=, this]() {
2622 for (auto id : m_staMacs[0]->GetLinkIds())
2623 {
2624 // timer only started on EMLSR links other than the link on which TXOP was carried
2625 // out
2626 auto isTimerActive = m_staMacs[0]->IsEmlsrLink(id) && id != linkId;
2627 auto time = m_staMacs[0]->GetEmlsrManager()->GetElapsedMediumSyncDelayTimer(id);
2628 NS_TEST_EXPECT_MSG_EQ(time.has_value(),
2629 isTimerActive,
2631 << " Unexpected status for MediumSyncDelay timer on link "
2632 << +id << " after terminating a TXOP on link " << +linkId);
2633 auto currThreshold = m_staMacs[0]->GetWifiPhy(id)->GetCcaEdThreshold();
2634 NS_TEST_EXPECT_MSG_EQ((static_cast<int8_t>(currThreshold) ==
2635 m_staMacs[0]->GetEmlsrManager()->GetMediumSyncOfdmEdThreshold()),
2636 isTimerActive,
2638 << " Unexpected value (" << currThreshold
2639 << ") for CCA ED threshold on link " << +id
2640 << " when MediumSyncDelay is "
2641 << (isTimerActive ? "active" : "inactive"));
2642 }
2643 };
2644
2645 auto txDuration =
2646 WifiPhy::CalculateTxDuration(psduMap, txVector, m_apMac->GetWifiPhy(linkId)->GetPhyBand());
2647
2648 switch (m_countBlockAck)
2649 {
2650 case 1:
2651 case 2:
2652 // do nothing, these are BlockAcks in response to the QoS data frames sent to establish
2653 // BA agreements in DL and UL direction
2654 break;
2655 case 3:
2656 if (linkId == m_nonEmlsrLink)
2657 {
2658 // this BlockAck has been sent on the non-EMLSR link, ignore it
2659 break;
2660 }
2661 m_checkBackoffStarted = true;
2662 if (!m_nonEmlsrLink)
2663 {
2664 m_countBlockAck++; // if all EMLSR links, next case is already executed now
2665 }
2666 [[fallthrough]];
2667 case 4:
2668 if (m_nonEmlsrLink && m_countBlockAck == 4)
2669 {
2670 // block transmissions on the non-EMLSR link
2671 Simulator::Schedule(txDuration + NanoSeconds(1), [=, this]() {
2672 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2674 {*m_nonEmlsrLink});
2675 });
2676 }
2677 if (linkId == m_nonEmlsrLink)
2678 {
2679 // this BlockAck has been sent on the non-EMLSR link, ignore it
2680 break;
2681 }
2682 m_checkBackoffStarted = true;
2683 // check MediumSyncDelay timer on the EMLSR client after receiving BlockAck
2684 Simulator::Schedule(txDuration + NanoSeconds(1), checkMediumSyncDelayTimerActive);
2685 break;
2686 case 5:
2687 // Block Ack in response to the second data frame sent by the EMLSR client on EMLSR links.
2688 // Check that MediumSyncDelay timer is running on the link where the main PHY is operating
2689 // and that the number of backoff slots is not changed since the beginning of the TXOP
2690 Simulator::Schedule(txDuration + NanoSeconds(1), [=, this]() {
2691 checkMediumSyncDelayTimerActive();
2692 auto elapsed =
2693 m_staMacs[0]->GetEmlsrManager()->GetElapsedMediumSyncDelayTimer(m_mainPhyId);
2695 elapsed.has_value(),
2696 true,
2697 "MediumSyncDelay timer not running on link where main PHY is operating");
2699 m_staMacs[0]->GetEmlsrManager()->GetMediumSyncDuration() -
2700 *elapsed;
2701 });
2702
2703 Simulator::Schedule(txDuration, [=, this]() {
2704 m_checkBackoffStarted = false;
2706 true,
2707 "Backoff end time should have been calculated");
2708 // when this BlockAck is received, the TXOP ends and the main PHY link is unblocked,
2709 // which causes a new backoff timer to be generated if the backoff timer is not
2710 // already running
2712 });
2713
2714 // make aux PHYs not capable of transmitting frames
2715 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2717 auxPhyLinks);
2718
2719 // generate data packets for another UL data frame, which will be sent on the link where
2720 // the main PHY is operating
2721 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
2722 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 2, 1000));
2723 break;
2724 case 6:
2725 // block transmission on the main PHY link and on the non-EMLSR link (if any), so that
2726 // the next QoS frames are sent on a link where an aux PHY is operating
2727 std::set<uint8_t> linkIds{m_mainPhyId};
2728 if (m_nonEmlsrLink)
2729 {
2730 linkIds.insert(*m_nonEmlsrLink);
2731 }
2732 m_staMacs[0]->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
2733 AC_BE,
2736 m_staMacs[0]->GetAddress(),
2737 {0},
2738 linkIds);
2739 // make sure aux PHYs are capable of transmitting frames
2740 m_staMacs[0]->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2742 auxPhyLinks);
2743
2744 // generate data packets for another UL data frame
2745 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
2746 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 2, 1000));
2747
2748 break;
2749 }
2750}
2751
2752void
2754 const WifiTxVector& txVector,
2755 uint8_t linkId)
2756{
2758 {
2759 // this function only considers RTS frames sent after the first QoS data frame
2760 return;
2761 }
2762
2763 if (linkId != m_mainPhyId)
2764 {
2765 if (m_countRtsframes > 0 && !m_corruptCts.has_value())
2766 {
2767 // we get here for the frame exchange in which the CTS response must be corrupted.
2768 // Install post reception error model on the STA affiliated with the EMLSR client that
2769 // is transmitting this RTS frame
2770 m_errorModel = CreateObject<ListErrorModel>();
2771 m_staMacs[0]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel);
2772 m_corruptCts = true;
2773 }
2774
2775 return;
2776 }
2777
2778 // we get here for RTS frames sent by the main PHY while the MediumSyncDelay timer is running
2780
2782 m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId)->GetChannelWidth(),
2783 "RTS sent by main PHY on an unexpected width");
2784
2785 // corrupt reception at AP MLD
2786 NS_LOG_INFO("CORRUPTED");
2787 m_errorModel->SetList({mpdu->GetPacket()->GetUid()});
2788}
2789
2790void
2792 const WifiTxVector& txVector,
2793 uint8_t linkId)
2794{
2795 if (m_corruptCts.has_value() && *m_corruptCts)
2796 {
2797 // corrupt reception at EMLSR client
2798 NS_LOG_INFO("CORRUPTED");
2799 m_errorModel->SetList({mpdu->GetPacket()->GetUid()});
2800 m_corruptCts = false;
2801
2802 auto txDuration = WifiPhy::CalculateTxDuration(mpdu->GetSize(),
2803 txVector,
2804 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
2805 // main PHY is about to complete channel switch when CTS ends
2806 Simulator::Schedule(txDuration, [=, this]() {
2807 const auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
2808 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(),
2809 true,
2810 "Expecting the main PHY to be switching link");
2811 });
2812 }
2813}
2814
2815void
2817{
2820
2821 CheckResults();
2822
2824}
2825
2826void
2828{
2829 if (m_msdMaxNTxops > 0)
2830 {
2834 "Unexpected number of RTS frames sent while the MediumSyncDelay timer is running");
2835 }
2836
2837 auto psduIt = m_txPsdus.cbegin();
2838
2839 // lambda to jump to the next QoS data frame or MU-RTS Trigger Frame or RTS transmitted
2840 // to/by an EMLSR client
2841 auto jumpToQosDataOrMuRts = [&]() {
2842 while (psduIt != m_txPsdus.cend() &&
2843 !psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData() &&
2844 !psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts())
2845 {
2846 auto psdu = psduIt->psduMap.cbegin()->second;
2847 if (psdu->GetHeader(0).IsTrigger())
2848 {
2849 CtrlTriggerHeader trigger;
2850 psdu->GetPayload(0)->PeekHeader(trigger);
2851 if (trigger.IsMuRts())
2852 {
2853 break;
2854 }
2855 }
2856 psduIt++;
2857 }
2858 };
2859
2860 /**
2861 * EMLSR client with EMLSR mode enabled on all links (main PHY ID = 1).
2862 *
2863 * main PHY│
2864 * blocked,│
2865 * aux PHYs││main PHY blocked│
2866 * cannot │
2867 * transmit│
2868 * │ ┌───┐ ┌──┐
2869 * [link 0] │CTS│ │BA│
2870 * ────────────────────────┬───┬┴───┴┬───┬───┬┴──┴─────────────────────────────────────────
2871 * │RTS│ │QoS│QoS│
2872 * └───┘ │ 6 │ 7 │
2873 * └───┴───┘
2874 * gen backoff gen backoff if MediumSyncDelay
2875 * ┌──┐ (also many times) not running timer expired ┌──┐
2876 * [link 1] │BA│ │ if allowed │ │ │BA│
2877 * ─────────┬───┬───┬┴──┴───────────────────────────┬───┬─────┬───┬────┬───┬───┬┴──┴───────
2878 * │QoS│QoS│ │RTS│ ... │RTS│ │QoS│QoS│
2879 * │ 4 │ 5 │ └───┘ └───┘ │ 8 │ 9 │
2880 * └───┴───┘ └───┴───┘
2881 *
2882 * [link 2]
2883 * ───────────────────────────────────────────────────────────────────────────
2884 *
2885 *
2886 *
2887 * EMLSR client with EMLSR mode enabled on links 0 and 1 (main PHY ID = 1).
2888 *
2889 * main PHY │
2890 * and │
2891 * non-EMLSR│
2892 * link │
2893 * blocked,│
2894 * aux PHYs││main PHY blocked│
2895 * cannot │
2896 * transmit│
2897 * │ ┌───┐ ┌──┐
2898 * [link 0] │CTS│ │BA│
2899 * ────────────────────────┬───┬┴───┴┬───┬───┬┴──┴─────────────────────────────────────────
2900 * │RTS│ │QoS│QoS│
2901 * └───┘ │ 8 │ 9 │
2902 * └───┴───┘
2903 * gen backoff gen backoff if MediumSyncDelay
2904 * ┌──┐ (also many times) not running timer expired ┌──┐
2905 * [link 1] │BA│ │ if allowed │ │ │BA│
2906 * ─────────┬───┬───┬┴──┴───────────────────────────┬───┬─────┬───┬────┬───┬───┬┴──┴───────
2907 * │QoS│QoS│ │RTS│ ... │RTS│ │QoS│QoS│
2908 * │ 4 │ 5 │ └───┘ └───┘ │ 10│ 11│
2909 * └───┴───┘ └───┴───┘
2910 * ┌──┐
2911 * [link 2] │BA│
2912 * ──────────┬───┬───┬┴──┴────────────────────────────────────────────────────────────
2913 * │QoS│QoS│
2914 * │ 6 │ 7 │
2915 * └───┴───┘
2916 *
2917 * For both scenarios, after the last frame exchange on the main PHY link, we have the
2918 * following frame exchange on an EMLSR link where an aux PHY is operating on:
2919 *
2920 * ┌───┐ ┌───┐ ┌──┐
2921 * │CTS│ │CTS│ │BA│
2922 * ──────┬───┬┴───X─────────┬───┬┴───┴┬───┬───┬┴──┴─────────────────────────────────────
2923 * │RTS│ │RTS│ │QoS│QoS│
2924 * └───┘ └───┘ │ X │ Y │
2925 * └───┴───┘
2926 * For all EMLSR links scenario, X=10, Y=11; otherwise, X=12, Y=13.
2927 */
2928
2929 // jump to the first (non-Beacon) frame transmitted after establishing BA agreements and
2930 // enabling EMLSR mode
2931 while (psduIt != m_txPsdus.cend() &&
2932 (psduIt->startTx < m_firstUlPktsGenTime ||
2933 psduIt->psduMap.cbegin()->second->GetHeader(0).IsBeacon()))
2934 {
2935 ++psduIt;
2936 }
2937
2938 // the first QoS data frame is transmitted by the main PHY without RTS protection as soon
2939 // as transmissions on the link where the main PHY is operating are unblocked (at this
2940 // moment, aux PHYs cannot transmit)
2941 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
2942 true,
2943 "First QoS data frame has not been transmitted");
2944 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
2945 true,
2946 "First QoS data frame should be transmitted without protection");
2947 NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId,
2948 +m_mainPhyId,
2949 "First QoS data frame should be transmitted by the main PHY");
2950 NS_TEST_EXPECT_MSG_GT_OR_EQ(psduIt->startTx,
2952 "First QoS data frame sent too early");
2953
2954 auto prevPsduIt = psduIt++;
2955 jumpToQosDataOrMuRts();
2956
2957 if (m_nonEmlsrLink)
2958 {
2959 // an additional data frame is sent concurrently on the non-EMLSR link
2961 (psduIt != m_txPsdus.cend()),
2962 true,
2963 "Expected another QoS data frame sent concurrently with the first frame");
2965 psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
2966 true,
2967 "First data frame on non-EMLSR link should be transmitted without protection");
2968 NS_TEST_EXPECT_MSG_EQ(+psduIt->linkId,
2969 +m_nonEmlsrLink.value(),
2970 "First data frame expected to be transmitted on the non-EMLSR link");
2971 const auto txDuration =
2972 WifiPhy::CalculateTxDuration(prevPsduIt->psduMap,
2973 prevPsduIt->txVector,
2974 m_staMacs[0]->GetWifiPhy(prevPsduIt->phyId)->GetPhyBand());
2975 NS_TEST_EXPECT_MSG_LT(psduIt->startTx,
2976 prevPsduIt->startTx + txDuration,
2977 "First data frame on the non-EMLSR link not sent concurrently");
2978 psduIt++;
2979 jumpToQosDataOrMuRts();
2980 }
2981
2982 // the second QoS data frame is transmitted by the main PHY after that the aux PHY has
2983 // obtained a TXOP and sent an RTS
2984 // RTS
2985 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
2986 true,
2987 "RTS before second QoS data frame has not been transmitted");
2988 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts(),
2989 true,
2990 "Second QoS data frame should be transmitted with protection");
2992 +psduIt->phyId,
2993 +m_mainPhyId,
2994 "RTS before second QoS data frame should not be transmitted by the main PHY");
2995 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
2997 "RTS before second data frame transmitted on an unexpected width");
2998 psduIt++;
2999 // CTS
3000 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3001 true,
3002 "CTS before second QoS data frame has not been transmitted");
3003 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsCts(),
3004 true,
3005 "CTS before second QoS data frame has not been transmitted");
3006 psduIt++;
3007 // QoS Data
3008 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3009 true,
3010 "Second QoS data frame has not been transmitted");
3011 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
3012 true,
3013 "Second QoS data frame has not been transmitted");
3014 NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId,
3015 +m_mainPhyId,
3016 "Second QoS data frame should be transmitted by the main PHY");
3017 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
3019 "Second data frame not transmitted on the same width as RTS");
3020
3021 bool moreQosDataFound = false;
3022
3023 while (++psduIt != m_txPsdus.cend())
3024 {
3025 jumpToQosDataOrMuRts();
3026 if (psduIt != m_txPsdus.cend() &&
3027 psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData())
3028 {
3029 moreQosDataFound = true;
3030
3031 NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId,
3032 +m_mainPhyId,
3033 "Last QoS data frame should be transmitted by the main PHY");
3034 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
3035 m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId)->GetChannelWidth(),
3036 "Expecting TX width of last data frame to equal the channel "
3037 "width used by the main PHY");
3039 psduIt->startTx,
3041 "Last QoS data frame sent before MediumSyncDelay timer expired");
3042
3043 break;
3044 }
3045 }
3046
3047 NS_TEST_EXPECT_MSG_EQ(moreQosDataFound,
3048 true,
3049 "Last QoS data frame transmitted by the main PHY not found");
3050
3051 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()), true, "Expected more frames");
3052 ++psduIt;
3053 jumpToQosDataOrMuRts();
3054
3055 // the first attempt at transmitting the last QoS data frame fails because CTS is corrupted
3056 // RTS
3057 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3058 true,
3059 "RTS before last QoS data frame has not been transmitted");
3060 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts(),
3061 true,
3062 "Last QoS data frame should be transmitted with protection");
3064 +psduIt->phyId,
3065 +m_mainPhyId,
3066 "RTS before last QoS data frame should not be transmitted by the main PHY");
3067 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
3069 "RTS before last data frame transmitted on an unexpected width");
3070 psduIt++;
3071 // CTS
3072 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3073 true,
3074 "CTS before last QoS data frame has not been transmitted");
3075 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsCts(),
3076 true,
3077 "CTS before last QoS data frame has not been transmitted");
3078 psduIt++;
3079 jumpToQosDataOrMuRts();
3080
3081 // the last QoS data frame is transmitted by an aux PHY after that the aux PHY has
3082 // obtained a TXOP and sent an RTS
3083 // RTS
3084 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3085 true,
3086 "RTS before last QoS data frame has not been transmitted");
3087 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts(),
3088 true,
3089 "Last QoS data frame should be transmitted with protection");
3091 +psduIt->phyId,
3092 +m_mainPhyId,
3093 "RTS before last QoS data frame should not be transmitted by the main PHY");
3094 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
3096 "RTS before last data frame transmitted on an unexpected width");
3097 psduIt++;
3098 // CTS
3099 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3100 true,
3101 "CTS before last QoS data frame has not been transmitted");
3102 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsCts(),
3103 true,
3104 "CTS before last QoS data frame has not been transmitted");
3105 psduIt++;
3106 // QoS Data
3107 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3108 true,
3109 "Last QoS data frame has not been transmitted");
3110 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
3111 true,
3112 "Last QoS data frame has not been transmitted");
3113 NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId,
3114 +m_mainPhyId,
3115 "Last QoS data frame should be transmitted by the main PHY");
3116 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
3118 "Last data frame not transmitted on the same width as RTS");
3119}
3120
3122 : EmlsrOperationsTestBase(std::string("Check EMLSR link switching (switchAuxPhy=") +
3123 std::to_string(params.switchAuxPhy) +
3124 ", resetCamState=" + std::to_string(params.resetCamState) +
3125 ", auxPhyMaxChWidth=" + std::to_string(params.auxPhyMaxChWidth) +
3126 "MHz )"),
3127 m_switchAuxPhy(params.switchAuxPhy),
3128 m_resetCamState(params.resetCamState),
3129 m_auxPhyMaxChWidth(params.auxPhyMaxChWidth),
3130 m_countQoSframes(0),
3131 m_txPsdusPos(0)
3132{
3133 m_nEmlsrStations = 1;
3135 m_linksToEnableEmlsrOn = {0, 1, 2}; // enable EMLSR on all links right after association
3136 m_mainPhyId = 1;
3137 m_establishBaDl = true;
3138 m_duration = Seconds(1.0);
3139 // when aux PHYs do not switch link, the main PHY switches back to its previous link after
3140 // a TXOP, hence the transition delay must exceed the channel switch delay (default: 250us)
3142}
3143
3144void
3146 uint8_t phyId,
3147 WifiConstPsduMap psduMap,
3148 WifiTxVector txVector,
3149 double txPowerW)
3150{
3151 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
3152 auto linkId = m_txPsdus.back().linkId;
3153
3154 auto psdu = psduMap.begin()->second;
3155 auto nodeId = mac->GetDevice()->GetNode()->GetId();
3156
3157 switch (psdu->GetHeader(0).GetType())
3158 {
3160 NS_ASSERT_MSG(nodeId > 0, "APs do not send AssocReq frames");
3161 NS_TEST_EXPECT_MSG_EQ(+linkId, +m_mainPhyId, "AssocReq not sent by the main PHY");
3162 break;
3163
3164 case WIFI_MAC_MGT_ACTION: {
3165 auto [category, action] = WifiActionHeader::Peek(psdu->GetPayload(0));
3166
3167 if (nodeId == 1 && category == WifiActionHeader::PROTECTED_EHT &&
3168 action.protectedEhtAction ==
3170 {
3171 // the EMLSR client is starting the transmission of the EML OMN frame;
3172 // temporarily block transmissions of QoS data frames from the AP MLD to the
3173 // non-AP MLD on all the links but the one used for ML setup, so that we know
3174 // that the first QoS data frame is sent on the link of the main PHY
3175 std::set<uint8_t> linksToBlock;
3176 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
3177 {
3178 if (id != m_mainPhyId)
3179 {
3180 linksToBlock.insert(id);
3181 }
3182 }
3183 m_apMac->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
3184 AC_BE,
3186 m_staMacs[0]->GetAddress(),
3188 {0},
3189 linksToBlock);
3190 }
3191 else if (category == WifiActionHeader::BLOCK_ACK &&
3193 {
3194 // store the current number of transmitted frame
3195 m_txPsdusPos = m_txPsdus.size() - 1;
3196 }
3197 }
3198 break;
3199
3201 CheckInitialControlFrame(psduMap, txVector, linkId);
3202 break;
3203
3204 case WIFI_MAC_QOSDATA:
3205 CheckQosFrames(psduMap, txVector, linkId);
3206 break;
3207
3208 default:;
3209 }
3210}
3211
3212void
3214{
3215 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(m_switchAuxPhy));
3216 Config::SetDefault("ns3::EmlsrManager::ResetCamState", BooleanValue(m_resetCamState));
3217 Config::SetDefault("ns3::EmlsrManager::AuxPhyChannelWidth", UintegerValue(m_auxPhyMaxChWidth));
3218
3220
3221 // use channels of different widths
3222 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0]})
3223 {
3224 mac->GetWifiPhy(0)->SetOperatingChannel(
3226 mac->GetWifiPhy(1)->SetOperatingChannel(
3228 mac->GetWifiPhy(2)->SetOperatingChannel(
3230 }
3231}
3232
3233void
3235{
3238
3239 CheckResults();
3240
3242}
3243
3244void
3246 const WifiTxVector& txVector,
3247 uint8_t linkId)
3248{
3250
3251 switch (m_countQoSframes)
3252 {
3253 case 1:
3254 // unblock transmissions on all links
3255 m_apMac->GetMacQueueScheduler()->UnblockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
3256 AC_BE,
3258 m_staMacs[0]->GetAddress(),
3260 {0},
3261 {0, 1, 2});
3262 // block transmissions on the link used for ML setup
3263 m_apMac->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
3264 AC_BE,
3266 m_staMacs[0]->GetAddress(),
3268 {0},
3269 {m_mainPhyId});
3270 // generate a new data packet, which will be sent on a link other than the one
3271 // used for ML setup, hence triggering a link switching on the EMLSR client
3273 break;
3274 case 2:
3275 // block transmission on the link used to send this QoS data frame
3276 m_apMac->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
3277 AC_BE,
3279 m_staMacs[0]->GetAddress(),
3281 {0},
3282 {linkId});
3283 // generate a new data packet, which will be sent on the link that has not been used
3284 // so far, hence triggering another link switching on the EMLSR client
3286 break;
3287 case 3:
3288 // block transmission on the link used to send this QoS data frame
3289 m_apMac->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
3290 AC_BE,
3292 m_staMacs[0]->GetAddress(),
3294 {0},
3295 {linkId});
3296 // unblock transmissions on the link used for ML setup
3297 m_apMac->GetMacQueueScheduler()->UnblockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
3298 AC_BE,
3300 m_staMacs[0]->GetAddress(),
3302 {0},
3303 {m_mainPhyId});
3304 // generate a new data packet, which will be sent again on the link used for ML setup,
3305 // hence triggering yet another link switching on the EMLSR client
3307 break;
3308 }
3309}
3310
3311/**
3312 * AUX PHY switching enabled
3313 *
3314 * |--------- aux PHY A ---------|------ main PHY ------|-------------- aux PHY B -------------
3315 * ┌───┐ ┌───┐
3316 * │ICF│ │QoS│
3317 * ──────────────────────────┴───┴┬───┬┴───┴┬──┬────────────────────────────────────────────────
3318 * [link 0] │CTS│ │BA│
3319 * └───┘ └──┘
3320 *
3321 *
3322 * |--------- main PHY ----------|------------------ aux PHY A ----------------|--- main PHY ---
3323 * ┌───┐ ┌───┐ ┌───┐ ┌───┐
3324 * │ICF│ │QoS│ │ICF│ │QoS│
3325 * ───┴───┴┬───┬┴───┴┬──┬──────────────────────────────────────────────────┴───┴┬───┬┴───┴┬──┬──
3326 * [link 1]│CTS│ │BA│ │CTS│ │BA│
3327 * └───┘ └──┘ └───┘ └──┘
3328 *
3329 *
3330 * |--------------------- aux PHY B --------------------|------ main PHY ------|-- aux PHY A ---
3331 * ┌───┐ ┌───┐
3332 * │ICF│ │QoS│
3333 * ─────────────────────────────────────────────────┴───┴┬───┬┴───┴┬──┬─────────────────────────
3334 * [link 2] │CTS│ │BA│
3335 * └───┘ └──┘
3336 *
3337 *
3338 * AUX PHY switching disabled (X = main PHY channel switch delay)
3339 *
3340 * |------------------------------------------ aux PHY A ---------------------------------------
3341 * |-- main PHY --|X|
3342 * ┌───┐ ┌───┐
3343 * │ICF│ │QoS│
3344 * ──────────────────────────┴───┴┬───┬┴───┴┬──┬────────────────────────────────────────────────
3345 * [link 0] │CTS│ │BA│
3346 * └───┘ └──┘
3347 *
3348 * |-main|
3349 * |--------- main PHY ----------| |-PHY-| |------ main PHY ------
3350 * ┌───┐ ┌───┐ ┌───┐ ┌───┐
3351 * │ICF│ │QoS│ │ICF│ │QoS│
3352 * ───┴───┴┬───┬┴───┴┬──┬──────────────────────────────────────────────────┴───┴┬───┬┴───┴┬──┬──
3353 * [link 1]│CTS│ │BA│ │CTS│ │BA│
3354 * └───┘ └──┘ └───┘ └──┘
3355 *
3356 *
3357 * |------------------------------------------ aux PHY B ---------------------------------------
3358 * |-- main PHY --|X|
3359 * ┌───┐ ┌───┐
3360 * │ICF│ │QoS│
3361 * ─────────────────────────────────────────────────┴───┴┬───┬┴───┴┬──┬─────────────────────────
3362 * [link 2] │CTS│ │BA│
3363 * └───┘ └──┘
3364 */
3365
3366void
3368 const WifiTxVector& txVector,
3369 uint8_t linkId)
3370{
3371 if (m_txPsdusPos == 0)
3372 {
3373 // the iterator has not been set yet, thus we are not done with the establishment of
3374 // the BA agreement
3375 return;
3376 }
3377
3378 NS_TEST_EXPECT_MSG_LT(m_countQoSframes, 4, "Unexpected number of ICFs");
3379
3380 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
3381 auto phyRecvIcf = m_staMacs[0]->GetWifiPhy(linkId); // PHY receiving the ICF
3382
3383 auto currMainPhyLinkId = m_staMacs[0]->GetLinkForPhy(mainPhy);
3384 NS_TEST_ASSERT_MSG_EQ(currMainPhyLinkId.has_value(),
3385 true,
3386 "Didn't find the link on which the Main PHY is operating");
3387 NS_TEST_ASSERT_MSG_NE(phyRecvIcf,
3388 nullptr,
3389 "No PHY on the link where ICF " << m_countQoSframes << " was sent");
3390
3391 if (phyRecvIcf != mainPhy)
3392 {
3394 phyRecvIcf->GetChannelWidth(),
3396 "Aux PHY that received ICF "
3397 << m_countQoSframes << " is operating on a channel whose width exceeds the limit");
3398 }
3399
3400 // if aux PHYs switch links, only the first ICF is received by the main PHY; otherwise,
3401 // the first ICF and the last ICF are received by the main PHY
3402 NS_TEST_EXPECT_MSG_EQ((phyRecvIcf == mainPhy),
3404 "Expecting that the ICF was received by the main PHY");
3405
3406 // if aux PHYs do not switch links, the main PHY is operating on its original link when
3407 // the transmission of an ICF starts
3408 NS_TEST_EXPECT_MSG_EQ(m_switchAuxPhy || currMainPhyLinkId == m_mainPhyId,
3409 true,
3410 "Main PHY is operating on an unexpected link ("
3411 << +currMainPhyLinkId.value() << ", expected " << +m_mainPhyId
3412 << ")");
3413
3414 auto txDuration =
3415 WifiPhy::CalculateTxDuration(psduMap, txVector, m_apMac->GetWifiPhy(linkId)->GetPhyBand());
3416
3417 // check that PHYs are operating on the expected link after the reception of the ICF
3418 Simulator::Schedule(txDuration + NanoSeconds(1), [=, this]() {
3419 // the main PHY must be operating on the link where ICF was sent
3420 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId),
3421 mainPhy,
3422 "PHY operating on link where ICF was sent is not the main PHY");
3423
3424 // the behavior of Aux PHYs depends on whether they switch channel or not
3425 if (m_switchAuxPhy)
3426 {
3427 if (mainPhy != phyRecvIcf)
3428 {
3429 NS_TEST_EXPECT_MSG_EQ(phyRecvIcf->IsStateSwitching(),
3430 true,
3431 "Aux PHY expected to switch channel");
3432 }
3433 Simulator::Schedule(phyRecvIcf->GetChannelSwitchDelay(), [=, this]() {
3434 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(*currMainPhyLinkId),
3435 phyRecvIcf,
3436 "The Aux PHY that received the ICF is expected to operate "
3437 "on the link where Main PHY was before switching channel");
3438 });
3439 }
3440 else
3441 {
3442 NS_TEST_EXPECT_MSG_EQ(phyRecvIcf->IsStateSwitching(),
3443 false,
3444 "Aux PHY is not expected to switch channel");
3445 NS_TEST_EXPECT_MSG_EQ(phyRecvIcf->GetPhyBand(),
3446 mainPhy->GetPhyBand(),
3447 "The Aux PHY that received the ICF is expected to operate "
3448 "on the same band as the Main PHY");
3449 }
3450 });
3451}
3452
3453void
3455{
3456 NS_TEST_ASSERT_MSG_NE(m_txPsdusPos, 0, "BA agreement establishment not completed");
3457
3458 const std::size_t nRxOk = 4; // successfully received ICFs
3459
3461 m_txPsdusPos + 3 + nRxOk * 4,
3462 "Insufficient number of TX PSDUs");
3463
3464 // m_txPsdusPos points to ADDBA_RESPONSE, then ACK and then ICF
3465 auto psduIt = std::next(m_txPsdus.cbegin(), m_txPsdusPos + 2);
3466
3467 for (std::size_t i = 0; i < nRxOk; i++)
3468 {
3469 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
3470 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsTrigger()),
3471 true,
3472 "Expected a Trigger Frame (ICF)");
3473 psduIt++;
3475 (psduIt->psduMap.size() == 1 && psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsCts()),
3476 true,
3477 "Expected a CTS");
3478 psduIt++;
3479 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
3480 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsQosData()),
3481 true,
3482 "Expected a QoS Data frame");
3483 psduIt++;
3484 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
3485 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsBlockAck()),
3486 true,
3487 "Expected a BlockAck");
3488 psduIt++;
3489 }
3490}
3491
3493 : TestSuite("wifi-emlsr", Type::UNIT)
3494{
3495 AddTestCase(new EmlOperatingModeNotificationTest(), TestCase::Duration::QUICK);
3496 AddTestCase(new EmlOmnExchangeTest({1, 2}, MicroSeconds(0)), TestCase::Duration::QUICK);
3497 AddTestCase(new EmlOmnExchangeTest({1, 2}, MicroSeconds(2048)), TestCase::Duration::QUICK);
3498 AddTestCase(new EmlOmnExchangeTest({0, 1, 2, 3}, MicroSeconds(0)), TestCase::Duration::QUICK);
3499 AddTestCase(new EmlOmnExchangeTest({0, 1, 2, 3}, MicroSeconds(2048)),
3500 TestCase::Duration::QUICK);
3501 for (const auto& emlsrLinks :
3502 {std::set<uint8_t>{0, 1, 2}, std::set<uint8_t>{1, 2}, std::set<uint8_t>{0, 1}})
3503 {
3505 new EmlsrDlTxopTest(
3506 {1, 0, emlsrLinks, {MicroSeconds(32)}, {MicroSeconds(32)}, MicroSeconds(512)}),
3507 TestCase::Duration::QUICK);
3509 new EmlsrDlTxopTest(
3510 {1, 1, emlsrLinks, {MicroSeconds(64)}, {MicroSeconds(64)}, MicroSeconds(512)}),
3511 TestCase::Duration::QUICK);
3513 2,
3514 emlsrLinks,
3515 {MicroSeconds(128), MicroSeconds(256)},
3516 {MicroSeconds(128), MicroSeconds(256)},
3517 MicroSeconds(512)}),
3518 TestCase::Duration::QUICK);
3519 }
3520
3521 for (auto genBackoffIfTxopWithoutTx : {true, false})
3522 {
3524 {{0, 1, 2}, 40, 20, MicroSeconds(5504), 3, genBackoffIfTxopWithoutTx}),
3525 TestCase::Duration::QUICK);
3527 new EmlsrUlTxopTest({{0, 1}, 40, 20, MicroSeconds(5504), 1, genBackoffIfTxopWithoutTx}),
3528 TestCase::Duration::QUICK);
3529 }
3530
3531 for (bool switchAuxPhy : {true, false})
3532 {
3533 for (bool resetCamState : {true, false})
3534 {
3535 for (uint16_t auxPhyMaxChWidth : {20, 40, 80, 160})
3536 {
3538 new EmlsrLinkSwitchTest({switchAuxPhy, resetCamState, auxPhyMaxChWidth}),
3539 TestCase::Duration::QUICK);
3540 }
3541 }
3542 }
3543}
3544
3545static WifiEmlsrTestSuite g_wifiEmlsrTestSuite; ///< the test suite
#define Max(a, b)
Test the exchange of EML Operating Mode Notification frames.
void CheckEmlCapabilitiesInAssocResp(Ptr< const WifiMpdu > mpdu, const WifiTxVector &txVector, uint8_t linkId)
Check the content of the EML Capabilities subfield of the Multi-Link Element included in the Associat...
void TxOk(Ptr< const WifiMpdu > mpdu)
Callback invoked when the non-AP MLD receives the acknowledgment for a transmitted MPDU.
std::list< uint64_t > m_uidList
list of UIDs of packets to corrupt
std::size_t m_checkEmlsrLinksCount
counter for the number of times CheckEmlsrLinks is called (should be two: when the transition timeout...
Ptr< ListErrorModel > m_errorModel
error rate model to corrupt packets at AP MLD
void DoSetup() override
Implementation to do any local setup required for this TestCase.
EmlOmnExchangeTest(const std::set< uint8_t > &linksToEnableEmlsrOn, Time transitionTimeout)
Constructor.
std::size_t m_emlNotificationDroppedCount
counter for the number of times the EML Notification frame sent by the non-AP MLD has been dropped du...
void CheckEmlsrLinks()
Check that the EMLSR mode has been enabled on the expected EMLSR links.
void CheckEmlCapabilitiesInAssocReq(Ptr< const WifiMpdu > mpdu, const WifiTxVector &txVector, uint8_t linkId)
Check the content of the EML Capabilities subfield of the Multi-Link Element included in the Associat...
void TxDropped(WifiMacDropReason reason, Ptr< const WifiMpdu > mpdu)
Callback invoked when the non-AP MLD drops the given MPDU for the given reason.
void CheckEmlNotification(Ptr< const WifiPsdu > psdu, const WifiTxVector &txVector, uint8_t linkId)
Check the content of a received EML Operating Mode Notification frame.
void Transmit(Ptr< WifiMac > mac, uint8_t phyId, WifiConstPsduMap psduMap, WifiTxVector txVector, double txPowerW) override
Callback invoked when a FEM passes PSDUs to the PHY.
void DoRun() override
Implementation to actually run this TestCase.
Test EML Operating Mode Notification frame serialization and deserialization.
EmlOperatingModeNotificationTest()
Constructor.
void DoRun() override
Implementation to actually run this TestCase.
Test the transmission of DL frames to EMLSR clients.
void CheckInitialControlFrame(Ptr< const WifiMpdu > mpdu, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken by the AP MLD transmitting an initial Control frame to an EM...
const Time m_fe2to3delay
time interval between 2nd and 3rd frame exchange sequences after the enablement of EMLSR mode
void CheckResults()
Check that the simulation produced the expected results.
void CheckPmModeAfterAssociation(const Mac48Address &address)
Check that the AP MLD considers the correct Power Management mode for the links setup with the given ...
EmlsrDlTxopTest(const Params &params)
Constructor.
void StartTraffic() override
Start the generation of traffic (needs to be overridden)
Ptr< ListErrorModel > m_errorModel
error rate model to corrupt BlockAck at AP MLD
std::size_t m_countQoSframes
counter for QoS frames (transition delay test)
Time m_emlsrEnabledTime
when EMLSR mode has been enabled on all EMLSR clients
std::set< uint8_t > m_emlsrLinks
IDs of the links on which EMLSR mode has to be enabled.
void DoSetup() override
Implementation to do any local setup required for this TestCase.
void CheckQosFrames(const WifiConstPsduMap &psduMap, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken by the AP MLD transmitting a PPDU containing QoS data frames...
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 EnableEmlsrMode()
Enable EMLSR mode on the next EMLSR client.
void CheckBlockAck(const WifiConstPsduMap &psduMap, const WifiTxVector &txVector, uint8_t phyId)
Check that appropriate actions are taken by the AP MLD receiving a PPDU containing BlockAck frames fr...
void DoRun() override
Implementation to actually run this TestCase.
void CheckEmlNotificationFrame(Ptr< const WifiMpdu > mpdu, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken by the AP MLD transmitting an EML Operating Mode Notificatio...
std::size_t m_countBlockAck
counter for BlockAck frames (transition delay test)
Base class for EMLSR Operations tests.
std::size_t m_nNonEmlsrStations
number of stations to create that do not activate EMLSR
void SetSsid(uint16_t aid, Mac48Address)
Set the SSID on the next station that needs to start the association procedure.
bool m_establishBaDl
whether BA needs to be established (for TID 0) with the AP as originator
void CheckBlockedLink(Ptr< WifiMac > mac, Mac48Address dest, uint8_t linkId, WifiQueueBlockedReason reason, bool blocked, std::string description, bool testUnblockedForOtherReasons=true)
Check whether QoS data unicast transmissions addressed to the given destination on the given link are...
std::size_t m_nEmlsrStations
number of stations to create that activate EMLSR
std::vector< PacketSocketAddress > m_dlSockets
packet socket address for DL traffic
std::vector< Time > m_paddingDelay
Padding Delay advertised by the non-AP MLD.
std::set< uint8_t > m_linksToEnableEmlsrOn
IDs of the links on which EMLSR mode has to be enabled.
Ptr< ApWifiMac > m_apMac
AP wifi MAC.
TrafficDirection
Enumeration for traffic directions.
void DoSetup() override
Implementation to do any local setup required for this TestCase.
uint8_t m_mainPhyId
ID of the main PHY.
Time m_duration
simulation duration
std::vector< FrameInfo > m_txPsdus
transmitted PSDUs
Ptr< PacketSocketClient > GetApplication(TrafficDirection dir, std::size_t staId, std::size_t count, std::size_t pktSize) const
virtual void Transmit(Ptr< WifiMac > mac, uint8_t phyId, WifiConstPsduMap psduMap, WifiTxVector txVector, double txPowerW)
Callback invoked when a FEM passes PSDUs to the PHY.
bool m_establishBaUl
whether BA needs to be established (for TID 0) with the AP as recipient
uint16_t m_lastAid
AID of last associated station.
std::vector< Time > m_transitionDelay
Transition Delay advertised by the non-AP MLD.
Time m_transitionTimeout
Transition Timeout advertised by the AP MLD.
std::vector< PacketSocketAddress > m_ulSockets
packet socket address for UL traffic
std::vector< Ptr< StaWifiMac > > m_staMacs
MACs of the non-AP MLDs.
virtual void StartTraffic()
Start the generation of traffic (needs to be overridden)
EmlsrOperationsTestBase(const std::string &name)
Constructor.
Test the transmission of UL frames from EMLSR clients.
std::size_t m_countQoSframes
counter for QoS frames
const Time m_unblockMainPhyLinkDelay
delay between the time the first two UL packets are generated and the time transmissions are unblocke...
Ptr< ListErrorModel > m_errorModel
error rate model to corrupt packets
uint16_t m_channelWidth
width (MHz) of the channels used by MLDs
void BackoffGenerated(uint32_t backoff, uint8_t linkId)
Callback invoked when a new backoff value is generated by the EMLSR client.
std::optional< uint8_t > m_nonEmlsrLink
ID of the non-EMLSR link (if any)
Time m_lastMsdExpiryTime
expiry time of the last MediumSyncDelay timer
void DoSetup() override
Implementation to do any local setup required for this TestCase.
std::size_t m_countRtsframes
counter for RTS frames
void CheckCtsFrames(Ptr< const WifiMpdu > mpdu, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken by the EMLSR client when receiving a CTS frame on the given ...
void DoRun() override
Implementation to actually run this TestCase.
Time m_firstUlPktsGenTime
generation time of the first two UL packets
std::optional< bool > m_corruptCts
whether the transmitted CTS must be corrupted
void CheckBlockAck(const WifiConstPsduMap &psduMap, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken when an MLD transmits a PPDU containing BlockAck frames on t...
void StartTraffic() override
Start the generation of traffic (needs to be overridden)
std::optional< Time > m_backoffEndTime
expected backoff end time on main PHY link
std::set< uint8_t > m_emlsrLinks
IDs of the links on which EMLSR mode has to be enabled.
Time m_mediumSyncDuration
duration of the MediumSyncDelay timer
void CheckRtsFrames(Ptr< const WifiMpdu > mpdu, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken by the EMLSR client when transmitting an RTS frame on the gi...
void CheckQosFrames(const WifiConstPsduMap &psduMap, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken when an MLD transmits a PPDU containing QoS data frames on t...
bool m_genBackoffIfTxopWithoutTx
whether the backoff should be invoked when the AC gains the right to start a TXOP but it does not tra...
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.
bool m_checkBackoffStarted
whether we are checking the generated backoff values
std::size_t m_countBlockAck
counter for BlockAck frames
void CheckResults()
Check that the simulation produced the expected results.
uint8_t m_msdMaxNTxops
Max number of TXOPs that an EMLSR client is allowed to attempt to initiate while the MediumSyncDelay ...
EmlsrUlTxopTest(const Params &params)
Constructor.
uint16_t m_auxPhyChannelWidth
max width (MHz) supported by aux PHYs
wifi EMLSR Test Suite
std::optional< Mac48Address > GetMldOrLinkAddressByAid(uint16_t aid) const
A container for one type of attribute.
AttributeValue implementation for Boolean.
Definition: boolean.h:37
Headers for Trigger frames.
Definition: ctrl-headers.h:942
bool IsMuRts() const
Check if this is a MU-RTS Trigger frame.
void SetPaddingSize(std::size_t size)
Set the size in bytes of the Padding field.
Hold variables of type enum.
Definition: enum.h:62
uint32_t GetUid() const
Definition: event-id.cc:104
Subclass of TestCase class adding the ability to test the serialization and deserialization of a Head...
void TestHeaderSerialization(const T &hdr, Args &&... args)
Serialize the given header in a buffer, then create a new header by deserializing from the buffer and...
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:157
Implement the header for management frames of type association and reassociation response.
Definition: mgt-headers.h:334
Implement the header for Action frames of type EML Operating Mode Notification.
void SetLinkIdInBitmap(uint8_t linkId)
Set the bit position in the link bitmap corresponding to the given link.
EmlControl m_emlControl
EML Control field.
std::optional< EmlsrParamUpdate > m_emlsrParamUpdate
EMLSR Parameter Update field.
std::list< uint8_t > GetLinkBitmap() const
Helper class used to assign positions and mobility models to nodes.
holds a vector of ns3::NetDevice pointers
Ptr< NetDevice > Get(uint32_t i) const
Get the Ptr<NetDevice> stored in this container at a given index.
keep track of a set of node pointers.
uint32_t AddApplication(Ptr< Application > application)
Associate an Application to this Node.
Definition: node.cc:164
static Iterator Begin()
Definition: node-list.cc:237
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
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 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
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 EventId ScheduleNow(FUNC f, Ts &&... args)
Schedule an event to expire Now.
Definition: simulator.h:605
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:301
A suite of tests to run.
Definition: test.h:1268
Type
Type of test.
Definition: test.h:1275
Simulation virtual time values and global simulation resolution.
Definition: nstime.h:105
TimeWithUnit As(const Unit unit=Time::AUTO) const
Attach a unit to a Time, to facilitate output in a specific unit.
Definition: time.cc:415
bool IsStrictlyPositive() const
Exactly equivalent to t > 0.
Definition: nstime.h:351
@ US
microsecond
Definition: nstime.h:118
@ MS
millisecond
Definition: nstime.h:117
bool IsZero() const
Exactly equivalent to t == 0.
Definition: nstime.h:315
AttributeValue implementation for Time.
Definition: nstime.h:1406
void SetTxopLimits(const std::vector< Time > &txopLimits)
Set the TXOP limit for each link.
Definition: txop.cc:394
Hold an unsigned integer type.
Definition: uinteger.h:45
See IEEE 802.11 chapter 7.3.1.11 Header format: | category: 1 | action value: 1 |.
static std::pair< CategoryValue, ActionValue > Peek(Ptr< const Packet > pkt)
Peek an Action header from the given packet.
void Print(std::ostream &os) const override
static std::pair< CategoryValue, ActionValue > Remove(Ptr< Packet > pkt)
Remove an Action header from the given packet.
helps to create WifiNetDevice objects
Definition: wifi-helper.h:324
Implements the IEEE 802.11 MAC header.
create MAC layers for a ns3::WifiNetDevice.
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:878
Ptr< WifiMacQueueScheduler > GetMacQueueScheduler() const
Get the wifi MAC queue scheduler.
Definition: wifi-mac.cc:590
uint8_t GetNLinks() const
Get the number of links (can be greater than 1 for 11be devices only).
Definition: wifi-mac.cc:947
void UnblockUnicastTxOnLinks(WifiQueueBlockedReason reason, const Mac48Address &address, const std::set< uint8_t > &linkIds)
Unblock the transmission on the given links of all unicast frames addressed to the station with the g...
Definition: wifi-mac.cc:1447
Ptr< WifiPhy > GetWifiPhy(uint8_t linkId=SINGLE_LINK_OP_ID) const
Definition: wifi-mac.cc:1185
void BlockUnicastTxOnLinks(WifiQueueBlockedReason reason, const Mac48Address &address, const std::set< uint8_t > &linkIds)
Block the transmission on the given links of all unicast frames addressed to the station with the giv...
Definition: wifi-mac.cc:1401
Ptr< WifiNetDevice > GetDevice() const
Return the device this PHY is associated with.
Definition: wifi-mac.cc:453
Ptr< WifiRemoteStationManager > GetWifiRemoteStationManager(uint8_t linkId=0) const
Definition: wifi-mac.cc:920
Mac48Address GetAddress() const
Definition: wifi-mac.cc:466
Ptr< QosTxop > GetQosTxop(AcIndex ac) const
Accessor for a specified EDCA object.
Definition: wifi-mac.cc:513
uint64_t GetDataRate(uint16_t channelWidth, uint16_t guardInterval, uint8_t nss) const
Definition: wifi-mode.cc:122
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:668
static Time CalculateTxDuration(uint32_t size, const WifiTxVector &txVector, WifiPhyBand band, uint16_t staId=SU_STA_ID)
Definition: wifi-phy.cc:1528
WifiPhyBand GetPhyBand() const
Get the configured Wi-Fi band.
Definition: wifi-phy.cc:1042
std::tuple< uint8_t, uint16_t, WifiPhyBand, uint8_t > ChannelTuple
Tuple identifying an operating channel.
Definition: wifi-phy.h:903
This class mimics the TXVECTOR which is to be passed to the PHY in order to define the parameters whi...
WifiMode GetMode(uint16_t staId=SU_STA_ID) const
If this TX vector is associated with an SU PPDU, return the selected payload transmission mode.
WifiPreamble GetPreambleType() const
uint16_t GetChannelWidth() const
#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_ABORT_MSG_IF(cond, msg)
Abnormal program termination if a condition is true, with a message.
Definition: abort.h:108
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition: log.h:202
#define NS_LOG_DEBUG(msg)
Use NS_LOG to output a message of level LOG_DEBUG.
Definition: log.h:268
#define NS_LOG_INFO(msg)
Use NS_LOG to output a message of level LOG_INFO.
Definition: log.h:275
#define NS_TEST_EXPECT_MSG_GT_OR_EQ(actual, limit, msg)
Test that an actual value is greater than or equal to limit and report if not.
Definition: test.h:997
#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
#define NS_TEST_ASSERT_MSG_GT_OR_EQ(actual, limit, msg)
Test that an actual value is greater than or equal to a limit and report and abort if not.
Definition: test.h:916
Time MicroSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition: nstime.h:1343
Time NanoSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition: nstime.h:1355
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
WifiMacDropReason
The reason why an MPDU was dropped.
Definition: wifi-mac.h:80
WifiQueueBlockedReason
Enumeration of the reasons to block container queues.
@ WIFI_STANDARD_80211be
@ WIFI_PREAMBLE_HT_MF
@ WIFI_PHY_BAND_6GHZ
The 6 GHz band.
Definition: wifi-phy-band.h:39
@ WIFI_PHY_BAND_2_4GHZ
The 2.4 GHz band.
Definition: wifi-phy-band.h:35
@ WIFI_PHY_BAND_5GHZ
The 5 GHz band.
Definition: wifi-phy-band.h:37
@ AC_BE
Best Effort.
Definition: qos-utils.h:75
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:706
std::tuple< WifiContainerQueueType, WifiReceiverAddressType, Mac48Address, std::optional< uint8_t > > WifiContainerQueueId
Tuple (queue type, receiver address type, Address, TID) identifying a container queue.
bool IsTrigger(const WifiPsduMap &psduMap)
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_CTL_RTS
@ WIFI_MAC_CTL_CTS
@ WIFI_MAC_MGT_ACTION
@ WIFI_MAC_MGT_ASSOCIATION_RESPONSE
@ WIFI_MAC_MGT_ASSOCIATION_REQUEST
@ WIFI_MAC_CTL_BACKRESP
@ WIFI_MAC_CTL_END
@ WIFI_MAC_QOSDATA
uint32_t GetAckSize()
Return the total Ack size (including FCS trailer).
Definition: wifi-utils.cc:58
static constexpr uint16_t SU_STA_ID
STA_ID to identify a single user (SU)
Definition: wifi-mode.h:35
constexpr FrequencyRange WIFI_SPECTRUM_2_4_GHZ
Identifier for the frequency range covering the wifi spectrum in the 2.4 GHz band.
STL namespace.
ns phy
Definition: third.py:89
ns3::Time timeout
Parameters for the EMLSR DL TXOP test.
Parameters for the EMLSR UL TXOP test.
static uint8_t EncodeEmlsrTransitionDelay(Time delay)
static Time DecodeEmlsrTransitionDelay(uint8_t value)
static Time DecodeEmlsrPaddingDelay(uint8_t value)
static uint8_t EncodeEmlsrPaddingDelay(Time delay)
uint8_t emlsrParamUpdateCtrl
EMLSR Parameter Update Control.
std::optional< uint16_t > linkBitmap
EMLSR/EMLMR Link Bitmap.
EMLSR Parameter Update field.
std::string dir
uint32_t pktSize
packet size used for the simulation (in bytes)
static WifiEmlsrTestSuite g_wifiEmlsrTestSuite
the test suite